Java
-
[Effective Java] 스트림은 주의해서 사용하라Java 2023. 2. 2. 11:08
스트림 API는 다량의 데이터 처리 작업(순차적이든 병렬적이든)을 돕고자 자바 8에 추가되었다. 이 API가 제공하는 추상 개념 중 핵심은 두 가지다. 스트림은 데이터 원소의 유한 혹은 무한 시퀀스를 뜻한다. 스트림 파이프라인은 이 원소들로 수행하는 연산 단계를 표현하는 개념이다. 스트림 원소들은 어디로부터든 올 수 있다. 대표적으로는 아래와 같은 것들이 있다. 컬렉션 배열 파일 정규표현식 난수 생성기 다른 스트림 스트림 안의 데이터 원소들은 객체 참조나 기본 타입(int, long, double)을 지원한다. Stream : 객체 참조타입에 대한 Stream IntStream: int 타입에 대한 Stream LongStream: long 타입에 대한 Stream DoubleStrema: double ..
-
[Effective Java] 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라Java 2023. 2. 2. 11:07
열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다. 단, 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그럴 수 없다는 예외가 하나 있다. 확장 불가능한 열거 타입 열거 타입은 열거한 값들을 그대로 가져온 다음 값을 더 추가하여 다른 목적으로 사용할 수 없다. 즉, 확장이 불가능하다. 열거 타입이 확장 불가능하도록 설계한 이유는 다음과 같다. 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대는 성립하지 않는 것은 이상하다. 기반 타입과 확장된 타입들의 원소 모두를 순회할 방법도 마땅치 않다. 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 더 복잡해진다. 인터페이스를 이용해 확장 가능 열거 타입 흉내내기 확장할 수 있는 열거 타입을 활용하기 좋은 연산코드를 구현해보..
-
[Effective Java] ordinal 인덱싱 대신 EnumMap을 사용하라Java 2023. 2. 2. 11:07
이따금 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 코드가 있다. 식물의 생애주기를 열거 타입으로 표현한 예를 살펴보자. public class Plant { enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL } final String name; final LifeCycle lifeCycle; Plant(String name, LifeCycle lifeCycle) { this.name = name; this.lifeCycle = lifeCycle; } @Override public String toString() { return name; } } ordinal 인덱싱의 단점 정원에 심은 식물들을 배열 하나로 관리하고, 이들을 생애주기(한해살이, ..
-
[Effective Java] int 상수 대신 열거 타입을 사용하라Java 2023. 2. 2. 11:06
열거 타입은 일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입이다. 사계절, 태양계의 행성, 카드게임의 카드 종류 등이 좋은 예다. 자바에서 열거 타입을 지원하기 전에는 다음 코드처럼 정수 상수를 한 묶음 선언해서 사용하곤 했다. 정수 열거 패턴 - 상당히 취약하다. public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public static final int ORANGE_NAVEL = 0; public static final int ORANGE_TEMPLE = 1; public static final i..
-
[Effective Java] 한정적 와일드카드를 사용해 API 유연성을 높이라Java 2023. 2. 2. 11:05
매개변수화 타입은 불공변이다. 서로 다른 Type1과 Type2가 있을 때 List는 List의 하위 타입도 상위 타입도 아니다. 즉 List은 List의 하위타입이 아니다. List에는 어떤 객체든지 넣을 수 있지만 List에는 문자열만 넣을 수 있다. 불공변 방식의 문제점1 다음과 같이 Stack 클래스의 public API가 있을 때 public class Stack{ public Stack(); public void psuh(E,e); public E pop(); public boolean isEmpty(); } 여기에 일련의 원소를 스택에 넣는 메서드를 추가해야 한다고 해보자. public void pushAll(Iterable src){ for(E e : src) push(e); } 이 메서드..
-
[Effective Java] 로 타입은 사용하지 말라Java 2023. 2. 2. 11:04
제네릭 제네릭을 지원하기 전에는 컬렉션에서 객체를 꺼낼 때 마다 형변환을 해야 했다. 반면, 제네릭을 사용하면 컬렉션이 담을 수 있는 타입을 컴파일러에게 알려주게 된다. 그래서 컴파일러는 알아서 형변환 코드를 추가할 수 있게 되고, 엉뚱한 타입의 객체를 넣으려는 시도를 컴파일 과정에서 차단하여 더 안전하고 명확한 프로그램을 만들어 준다. 제네릭 클래스, 제네릭 인터페이스 클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다. public class Box { // T는 타입을 의미한다. private T data; public void set(T data) { this.data = data; } public T get() { return data; } } ..
-
[Effective Java] 톱레벨 클래스는 한 파일에 하나만 담으라Java 2023. 2. 2. 11:04
소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다. 하지만 아무런 득이 없을 뿐더러 심각한 위험을 감수해야 하는 행위다. 이렇게 하면 한 클래스를 여러 가지로 정의할 수 있으며, 그 중 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일하냐에 따라 달라지기 때문이다. 톱레벨 클래스를 중복 정의한 경우 Utensil(집기)와 Dessert(디저트)를 참조하는 Main 클래스 public class Main{ public static void main(String[] args){ System.out.println(Utensil.NAME + Dessert.NAME); } } Utensil 파일에 함께 선언된 utensil 클래스와 Dessert 클래스 class Utens..
-
[Effective Java] 추상 클래스보다는 인터페이스를 우선하라Java 2023. 2. 2. 11:03
자바가 제공하는 다중 구현 메커니즘은 인터페이스와 추상 클래스, 이렇게 두 가지다. 자바 8 부터 인터페이스도 디폴트 메서드를 제공할 수 있게 되어, 이제는 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다. 둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 점이다. 자바는 단일 상속만 지원하니, 추상 클래스를 상속받은 클래스는 다른 클래스를 상속받을 수 없게되는 것이다. 반면 인터페이스가 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급된다. 디폴트 메서드(default method) 디폴트 메서드는 인터페이스에 있는 구현 메서드를 의미한다. 기존의 추상 메..