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 |