IT 강좌(IT Lectures)/Java

6강. 고급 객체 지향 기법

소울입니다 2024. 6. 23. 08:30
728x90
반응형

챕터 6: 고급 객체 지향 기법


6.1 내부 클래스와 익명 클래스

내부 클래스와 익명 클래스는 클래스 내에 다른 클래스를 정의하여 코드의 캡슐화를 강화하고, 외부 클래스와 밀접하게 관련된 기능을 그룹화할 수 있습니다.

6.1.1 내부 클래스의 종류

  • 인스턴스 내부 클래스
    • 외부 클래스의 인스턴스와 연관된 내부 클래스입니다. 외부 클래스의 멤버(필드 및 메서드)에 접근할 수 있습니다.
      public class OuterClass {
          private String outerField = "Outer Field";
          
          // 인스턴스 내부 클래스 
          class InnerClass {
              public void printOuterField() {
                  // 외부 클래스의 필드에 접근 가능
                  System.out.println(outerField);
              }
          }
          
          public static void main(String[] args) {
              // 외부 클래스의 인스턴스를 통해 내부 클래스의 인스턴스를 생성
              OuterClass outer = new OuterClass();
              OuterClass.InnerClass inner = outer.new InnerClass();
              inner.printOuterField(); // 출력: Outer Field 
          }
      }
  • 정적 내부 클래스
    • 외부 클래스의 인스턴스와 독립적으로 사용될 수 있는 내부 클래스입니다. 외부 클래스의 정적 멤버만 접근할 수 있습니다.
      public class OuterClass {
          private static String staticOuterField = "Static Outer Field";
          
          // 정적 내부 클래스 
          static class StaticInnerClass {
              public void printStaticOuterField() {
                  // 외부 클래스의 정적 필드에 접근 가능
                  System.out.println(staticOuterField);
              }
          }
          
          public static void main(String[] args) {
              // 외부 클래스의 인스턴스 없이 정적 내부 클래스의 인스턴스 생성 
              OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
              inner.printStaticOuterField(); // 출력: Static Outer Field
          }
      }
  • 지역 내부 클래스
    • 메서드 내부에 정의된 내부 클래스입니다. 메서드 내에서만 사용되며, 메서드의 로컬 변수처럼 취급됩니다.
      public class OuterClass {
          public void methodWithAnonymousClass() {
              // 익명 클래스 
              Runnable runnable = new Runnable() {
                  @Override 
                  public void run() {
                      System.out.println("Anonymous Class");
                  }
              };
              
              // 익명 클래스의 인스턴스를 생성하고 메서드를 호출
              runnable.run(); // 출력: Anonymous Class
          }
          
          public static void main(String[] args) {
              OuterClass outer = new OuterClass();
              outer.methodWithAnonymousClass();
          }
      }
  • 익명 클래스
    • 이름이 없는 일회용 클래스로, 주로 인터페이스나 추상 클래스의 구현을 위해 사용됩니다. 익명 클래스는 특정 메서드나 초기화 블록에서만 사용되며, 간결한 코드를 작성하는 데 유용합니다.
      public class OuterClass {
          public void methodWithLocalClass() {
          
              // 지역 내부 클래스 class 
              LocalClass {
                  public void printMessage() {
                      System.out.println("Local Class");
                  }
              }
              
              // 지역 내부 클래스의 인스턴스를 생성하고 메서드를 호출
              LocalClass local = new LocalClass();
              local.printMessage(); // 출력: Local Class
          }
          
          public static void main(String[] args) {
              OuterClass outer = new OuterClass();
              outer.methodWithLocalClass();
          }
      }

 


6.2 제네릭 프로그래밍

제네릭은 클래스나 메서드에서 사용할 데이터 타입을 일반화하여 코드의 재사용성을 높이고, 타입 안전성을 보장합니다. 제네릭을 사용하면 컴파일 시점에 타입 검사를 수행할 수 있습니다.

6.2.1 제네릭 클래스

  • 데이터 타입을 매개변수로 받아서 처리하는 클래스입니다. 다양한 데이터 타입을 지원하여 코드의 중복을 줄이고, 타입 변환 오류를 방지할 수 있습니다.
    // 제네릭 클래스
    public class Box <T> {
        private T item;
        
        public void setItem(T item) {
            this.item = item;
        }
        
        public T getItem() {
            return item;
        }
        public static void main(String[] args) { 
        
        	// 문자열을 담는 Box 
            Box < String > stringBox = new Box <>();
            stringBox.setItem("Hello");
            System.out.println(stringBox.getItem()); // 출력: Hello
            
            // 정수를 담는 Box 
            Box < Integer > intBox = new Box <>();
            intBox.setItem(123);
            System.out.println(intBox.getItem()); // 출력: 123
        }
    }

     

 

6.2.2 제네릭 메서드

  • 메서드 내에서 데이터 타입을 매개변수로 받아서 처리하는 메서드입니다. 제네릭 메서드는 다양한 타입의 데이터를 처리할 수 있어 코드의 재사용성을 높입니다.
    public class GenericMethods { 
    	// 제네릭 메서드 
        public static <T> void printArray(T[] array) {
            for (T element: array) {
                System.out.println(element);
            }
        }
        
        public static void main(String[] args) { 
        	// 정수 배열 
            Integer[] intArray = {1, 2, 3, 4, 5};
            printArray(intArray); // 출력: 1 2 3 4 5 
            
            // 문자열 배열 
            String[] stringArray = {"A", "B", "C"};
            printArray(stringArray); // 출력: A B C
        }
    }

 

6.2.3 제네릭 타입 제한

  • 특정 타입이나 타입의 상위/하위 클래스만 허용하도록 제네릭 타입을 제한할 수 있습니다. 이를 통해 타입 안전성을 강화하고, 의도하지 않은 타입의 사용을 방지할 수 있습니다.
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List; 
    
    // 상한 제한 와일드카드 (Number 또는 그 하위 타입만 허용) 
    public static void printNumbers(List <? extends Number> list) {
        for (Number number: list) {
            System.out.println(number);
        }
    }
    
    // 하한 제한 와일드카드 (Integer 또는 그 상위 타입만 허용) 
    public static void addNumbers(List <? super Integer> list) {
        list.add(10);
        list.add(20);
    }
    public static void main(String[] args) {
        List <Integer> intList = new ArrayList <> (Arrays.asList(1, 2, 3));
        printNumbers(intList); // 출력: 1 2 3
        List <Number> numList = new ArrayList <>();
        addNumbers(numList);
        printNumbers(numList); // 출력: 10 20 
    }

     

 


6.3 람다 표현식과 함수형 프로그래밍

람다 표현식은 익명 함수(anonymous function)를 나타내며, 코드의 간결성과 가독성을 높입니다. 함수형 프로그래밍은 함수를 일급 객체로 취급하는 프로그래밍 패러다임입니다.

6.3.1 람다 표현식

  • 기본 문법: (parameters) -> expression 또는 (parameters) -> { statements }
    import java.util.Arrays;
    import java.util.List;
    public class Main {
        public static void main(String[] args) { 
        
        	// 람다 표현식을 사용한 리스트 요소 순회
            List <String> list = Arrays.asList("Apple", "Banana", "Cherry");
            list.forEach(fruit -> System.out.println(fruit)); 
            // 출력: Apple Banana Cherry 
            
            // 두 수의 합을 계산하는 람다 표현식 
            MyFunctionalInterface add = (a, b) -> a + b;
            System.out.println("Sum: " + add.operation(5, 3)); // 출력: Sum: 8
        }
    }
    
    // 함수형 인터페이스 (추상 메서드가 하나만 있어야 함) 
    @FunctionalInterface 
    interface MyFunctionalInterface {
        int operation(int a, int b);
    }

6.3.2 함수형 인터페이스

  • 추상 메서드가 하나만 있는 인터페이스입니다. @FunctionalInterface 애노테이션을 사용하여 컴파일러가 함수형 인터페이스로 인식하게 할 수 있습니다. 
     
  • @FunctionalInterface interface MyFunctionalInterface { void myMethod(); } public class Main { public static void main(String[] args) { // 람다 표현식을 사용한 함수형 인터페이스 구현 MyFunctionalInterface myFunc = () -> System.out.println("Hello, World!"); myFunc.myMethod(); // 출력: Hello, World! } }

 

6.3.3 스트림 API

  • Java 8부터 도입된 스트림 API는 컬렉션에 대한 함수형 프로그래밍을 지원합니다. 스트림은 데이터를 처리하는 선언적이고, 체이닝 가능한 방식을 제공합니다. 
  • import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List <String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry"); // 필터링과 매핑을 사용한 스트림 처리 List <String> result = list.stream() .filter(fruit -> fruit.startsWith("A") || fruit.startsWith("E")) .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(result); // 출력: [APPLE, ELDERBERRY] } }
  • 기본 스트림 연산
    • filter: 조건에 맞는 요소만 추출합니다.
    • map: 요소를 변환합니다.
    • collect: 스트림의 결과를 컬렉션으로 수집합니다.
    • forEach: 각 요소에 대해 작업을 수행합니다.
    • reduce: 스트림 요소를 하나의 값으로 줄입니다.

 

6.3.4 메서드 참조

  • 람다 표현식을 간결하게 작성할 수 있는 방법입니다. ClassName::methodName 형식으로 사용합니다.
    import java.util.Arrays;
    import java.util.List;
    public class Main {
        public static void main(String[] args) {
            List <String> list = Arrays.asList("Apple", "Banana", "Cherry"); 
            
            // 메서드 참조를 사용한 리스트 요소 순회 
            list.forEach(System.out::println); 
            // 출력: Apple Banana Cherry
        }
    }
  • 메서드 참조 유형
    • 정적 메서드 참조: ClassName::staticMethod
    • 인스턴스 메서드 참조: instance::method
    • 생성자 참조: ClassName::new

 


이로써 Java의 고급 객체 지향 기법에 대해 자세히 설명하고, 각 개념을 코드와 예시를 통해 설명했습니다. 다음 챕터에서는 Java의 동시성 프로그래밍에 대해 다루겠습니다.

 
반응형

'IT 강좌(IT Lectures) > Java' 카테고리의 다른 글

8강. 스트림과 컬렉션 프레임워크  (0) 2024.06.25
7강. 동시성 프로그래밍  (0) 2024.06.24
5강. Java API 활용  (0) 2024.06.22
4강. 기본 클래스 사용  (0) 2024.06.21
3강. 객체 지향 프로그래밍  (0) 2024.06.20