JANGUN


가장 빨리 만나는 코어 자바 9
(Core Java SE 9 for the Impatient)


지음 : Horstmann Cay
옮김 : 신경근
출판사 : 길벗



목차

1장 기본 프로그래밍 구조
2장 객체 지향 프로그래밍
3장 인터페이스와 람다 표현식
4장 상속과 리플렉션
5장 예외, 단정, 로깅
6장 제넥릭 프로그래밍
7장 컬렉션
8장 스트림
9장 입출력 처리
10장 병행 프로그래밍
11장 에너테이션
12장 날짜와 시간 API
13장 국제화
14장 컴파일링과 스크립팅
15장 자바 플랫폼 모듈 시스템


1장 기본 프로그래밍 구조

1.1 첫번째 프로그램



컴파일
$ javac HelloWorld.java

실행
$ java HelloWorld

1.2 기본 타입 : 가장 간단한 데이터 타입

정수 타입

- long 타입으로 충분하지 않을 때는 BigInteger 클래스를 사용한다.
- 숫자 리터럴에는 밑줄을 붙일 수 있다. ex) 1_000_000 = 1,000,000

부동소수점 타입

- 무한대(∞) : Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
- 숫자가 아니라는 것 : Double.NaN
- 모든 '숫자가 아닌' 값은 서로 다른 값으로 간주한다.
- if( x == Double.NaN)을 작성해서는 x가 NaN인지 검사할 수 없다.
- if( Double.isNaN(x) )로 작성해야 검사할 수 있다.
- Double.isInfinite로 ±∞를 테스트하고, Double.isFinite로는 부동소수점 수가 무한대도 아니고 NaN도 아닌 것을 검사할 수 있다.

부동소수점 수는 금융 계산에는 적합하지 않다.
- 부동소수점 수는 반올림 오류가 생긴다. ex) 2.0-1.1 = 0.8999999999
- 2진수로 표현되는 한계때문
- BigDecimal 클래스를 사용한다.

char 타입
- UTF-16 문자 인코딩의 ‘코드 유닛(코드 단위)’ (ex) J : 74(0x4A), U+004A(\u004A)

boolean 타입 : false / true (자바에서는 정수 0, 1과 아무 관련이 없다.)

1.3 변수 : 변수와 상수를 선언하고 초기화
- 자바는 타입 결합이 강한 언어다. (자바스크립트와는 다르다)
- 변수 이름은 반드시 글자로 시작해야 한다.
- 대소문자를 구분한다.
- 라틴 문자를 사용할 수 있다. (ex) π = 0
- 공백이나 기호를 사용할 수 없다. 키워드를 사용할 수 없다.
- 변수와 메서드 이름은 소문자로 시작하고, 클래스 이름은 대문자로 시작하는 관례를 따른다. ex) 낙타표기법
- 변수를 사용하기 위해서는 반드시 초기화하고 사용해야 한다.
- final 변수의 초기화는 나중으로 미룰 수 있다.
- final 변수는 처음 사용하기 전에 딱 한 번만 초기화하면 된다.

int total; total++; // error
int total = 0;
Random generator = new Random();
final int DAYS_PER_WEEK = 7; // 상수

1.4 산술 연산

- 자바는 C언어를 기반으로 한 언어에서 익숙한 연산자를 사용한다.
- 나누기(/) 연산자의 경우, 두 피 연산자가 정수 타입이면 나머지는 버린다.
- 모든 정수를 0으로 나누면 예외가 발생한다.
- 부동소수를 0으로 나누면 예외가 일어나지 않고, 무한대 값이나 NaN 결과로 나온다
- 음수일 수도 있는 피 연산자일 경우, % 연산은 주의를 해야 한다.
- Math.floorMod 메서드 참조

수학 메서드 : Math 클래스
- 정수 계산을 더 안전하게 하는 여러 메서드가 있다.
- 계산이 오버플로우되면 틀린 결과를 반환한다.

자바에서는 정보 손실이 없으면 언제나 합법적인 변환이다.
- 작은 크기 → 큰 크기 변수
- 정수 → 실수

기본 정수나 실수 타입의 정밀도로 충분하지 않을 때는 BigInteger, BigDecimal 클래스를 사용한다.
- 자바는 객체에 연산자를 사용할 수 없다. 메서드 호출을 사용해야 한다.

BigInteger r = BigInteger.valueOf(5).multiply(n.add(k)); // r=5*(n+k)
BigDecimal.valueOf(2, 0).subtract(BigDecimal.valueOf(11, 1); // 2.0 – 1.1
BigDecimal.valueOf(n, e) = n x 10-e // BigDecimal.valueOf(2, 0) = 2x10-0 = 2.0

1.5 문자열 : 문자열은 문자의 시퀀스(연속)이다. 유니코드 문자라면 문자열에 무엇이든 쓸 수 있다.

문자열 연결 : +
- 문자열 연결과 숫자 덧셈을 섞어서 사용하면 예상하지 못한 결과를 얻을 수 있으므로 조심해야 한다.
- 여러 문자열을 구분자로 구분해서 결합하려면, String.join 메서드를 사용한다.
- ex) String.join(“,”, “peter”, “Paul”, “Mary”); // “peter, Paul, Mary”, 첫번째 인자는 분리 문자열
- 최종 결과만 원할 때는 StringBuilder를 사용한다.

부분 문자열 : String.substring, split
- 문자열의 위치는 0부터 시작한다.
String greeting = “Hello, World!”;
String location = greeting.substring(7, 12); // location = “World”, 두번째 인자는 부분 문자열에 포함하지 않을 문자의 첫 번째 위치
String names = “Peter, Paul, Mary”; // 분리자로 어떤 정규 표현식이든 사용할 수 있다.
String[] result = names.slipt(“, “); // [“Peter”, “Paul”, “Mary”]

문자열 비교 : equals 메서드
- 문자열 비교할 때는 절대로 ‘==‘ 연산자를 사용하면 안 된다.
- 객체가 null 인지 검사하려면 ‘==‘ 연산자를 사용한다.
- null은 빈 문자열(“”)과 다르다. null은 문자열이 아니다
- 문자열을 리터럴과 비교할 때는 리터럴 문자열을 앞쪽에 두는 게 좋다. ex) if(“world”.equals(location)) …
- 대소문자를 구별하지 않고 두 문자열을 비교할 때는 equalsIgnoreCase 메서드를 사용한다
- compareTo 메서드는 한 문자열이 또 다른 문자열보다 앞에 오는지(오름차순) 알려준다.
ex) first.compareTo(second);
// first가 seconde보다 앞에 오면 음의 정수(꼭 -1은 아님)를 반환하고, 뒤에 오면 양의 정수(꼭 1은 아님)를, 두 문자열이 같으면 0을 반환한다.
// 이 비교는 문자의 유니코드 값에 의존하므로 사람에게는 직관적이지 않다.

숫자와 문자열 사이의 변환
int n =42;
str = Integer.toString(n); // str = “42”, 간단한 방법으로, str = “”+n; // 개발자 중 일부는 이 방법을 선호하지 않는다.
str = Integer.toString(n, 2); // 2진수, str = “101010”
n = Integer.parseInt( str ); // n = 101010
n = Integer.parseInt( str, 2); // n = 42
- Double.toString과 Double.parseDouble

문자열 API
- 자바에서 String 클래스는 절대 변하지 않는다는 점을 유의해야 한다. 즉 어떤 String 메서드도 적용 대상 문자열을 변경할 수 없다. 새로운 문자열을 반환한다는 뜻이다.
- boolean startsWith(String str), boolean endsWith(String str), boolean contains(CharSequence str)
- int indexOf(String str), int lastIndexOf(String str), int indexOf(String str, int fromIndex), int lastIndexOf(String str, int fromIndex)
- String replace(CharSequence oldString, CharSequence newString)
- String toUpperCase(), String toLowerCase(), String trim()
- 코드에서 문자열을 순회하면서 각 코드 포인트를 차례로 보려면 codePoints 메서드를 사용한다.
- codePoints 메서드는 코드 포인트당 int 값 한 개로 구성된 int 값의 스트림을 반환한다.

1.6 입력과 출력
입력
Scanner in = new Scanner(System.in); // Scanner 클래스는 입력이 터미널에 보임. (비밀번호의 경우는 Console terminal = System.console() 사용)
String name = in.nextLine();
Int age = in.nextInt(); // nextDoulbe(), hasNextLine, hasNext, hasNextInt, hasNextDouble

출력 : print, println, printf (서식 지정 출력)
- System.out.printf(“%8.2f”, 1000.0 / 3.0); // 서식 지정 출력용 변환 문자를 사용하거나 서식 지정 출력용 플래그를 사용할 수 있다.

1.7 제어 흐름
분기 : if, switch
루프 : while, do ~ while, for
중단 : break
계속 : continue

1.8 배열과 배열 리스트
배열은 프로그래밍의 기본 구성 요소이며, 타입이 같은 아이템(항목)을 모으는 데 사용한다.
int [] primes = { 2, 3, 5, 7, 11, 13 };
String [] names = new String[100];
names.length
- 배열의 초기화 : 숫자 타입의 배열은 0, boolean 배열은 false, 객체의 배열은 null 참조로 채워진다.
- 객체의 배열을 생성한 후에는 객체로 채워야 한다. (생성자를 각각 호출해줄까?)
- 배열을 생성하려면 길이를 알아야 한다.
- 배열은 한 번 생성하면 절대로 길이를 변경할 수 없다.

배열 리스트 : 배열의 길이를 변경할 수 있도록 해줌
- 배열과 배열 리스트의 문법은 완전히 다르다.
- 다른 내부 배열을 자동으로 생성해서 원본 배열의 요소를 옮기는 작업을 개발자에게 보이지 않게 진행한다.


향상된 for 루프
- for(int n : numbers) sum += n; // 향상된 for 루프의 순환 변수는 배열의 인덱스 값이 아니라 요소를 순회한다.
- for(String name : friends) System.out.println(name);

- 배열 변수를 또 다른 배열 변수로 복사하면, 두 변수가 같은 배열(주소)를 참조한다.


자바에는 다차원 배열이 없다.
- 자바에서는 다차원 배열을 배열의 배열로 구현한다.
- 행 배열의 길이가 꼭 같아야 하는 것은 아니다. 파스칼 삼각형 배열도 가능

1.9 함수 분해
- main 메서드가 너무 길어지면 프로그램을 여러 클래스로 분해할 수 있다.

- 가변 인자 : 메서드의 인자의 숫자를 가변으로 처리 가능, 가변 인자는 반드시 마지막 파라미터여야 한다.
public double average(double … values) {
double sum = 0;
for( double v : values) sum += v;
return values.length == 0 ? 0 : sum / values.length;
}



2장 객체 지향 프로그래밍

2.1 객체 이용하기
- 오래 전 객체가 발명되기 전에는 함수를 호출하는 방식으로 프로그램을 작성
- 각 객체에는 객체 자체의 상태가 있다. 상태는 메서드를 호출해서 얻는 결과에 영향을 준다.
- 다른 사람이 구현한 객체의 메서드를 호출할 때는 내부에서 무슨 일이 일어나는지 몰라도 된다. (캡슐화)

호출 대상 객체를 변경하는 메서드를 변경자(mutator)라고 한다. ex) ArrayList 클래스의 add 메서드
- 객체를 변경하지 않는 메서드는 접근자(accessor)라고 한다.
- 객체 변경은 위험할 수 있어서(특히 두 연산이 한 객체를 동시에 변경할 때) 갈수록 이런 상황이 보편화되고 있다.
- 요즘은 대부분 컴퓨터의 CPU가 여러 개라서 완전한 동시 접근이 중요한 문제다.
- 이 문제를 해결하는 방법은 접근자 메서드만 제공해서 객체를 변경할 수 없게 만드는 것이다.

자바에서는 변수에는 객체를 담을 수 없고, 오직 객체에 대한 참조만 담을 수 있다. (C++은 객체를 변수에 담을 수도 있다)
- C와 C++에서 참조는 포인터를 수정하고 임의의 메모리 위치를 덮어쓰는 데 사용할 수 있다.
- 자바의 참조는 특정 객체에 접근만 할 수 있다.
- 객체를 공유하면 (객체 변수를 다른 변수에 할당), 효율적이고 편리하지만, 참조를 통해 객체를 변경할 수 있어서 유의해야 한다.
- String이나 LocalDate처럼 클래스에 변경자 메서드가 없으면 걱정할 필요없이, 해당 객체에 대한 참조를 공유할 수 있다.

2.2 클래스 구현하기
인스턴스 변수 : 객체의 상태
- 자바에서 인스턴스 변수는 보통 private으로 선언하여 같은 클래스에 속한 메서드만 변수에 접근할 수 있도록 한다.

인스턴스 메서드는 대부분 public으로 선언한다.
- 때로 헬퍼 메서드를 같은 클래스에 속한 다른 메서드에서만 사용하도록 제한하는 private으로 선언한다.
- 인스턴스 메서드는 클래스 변수만 변경한다.
- 객체에 있는 메서드를 호출할 때는 해당 객체를 this로 접근한다 (지역 변수와 인스턴스 변수를 명확히 구분할 수 있다)

값을 이용한 호출 : 메서드에 객체를 전달하면 해당 메서드가 객체 참조의 사본을 얻는다.
- 이 참조를 통해 메서드는 파라미터 객체에 접근하거나 객체를 변경할 수 있다.
- 기본 타입 파라미터를 업데이트하는 메서드를 작성할 수 없다.
- 객체 참조를 다른 것으로 변경하는 메서드도 작성할 수 없다. (객체의 내용을 변경할 수 있으나, 참조를 변경할수는 없다는 말)

2.3 객체 생성
생성자 : 이름이 클래스 이름과 같고, 반환 타입이 없다.
- 대체로 생성자는 public 접근이다.
- 비공개(private) 생성자 역시 유용하다. 클래스의 사용자는 팩토리 메서드를 이용해서 객체를 얻는다.
- 생성자는 두 가지 이상의 다른 버전으로 제공할 수 있다. (오버로딩)
- 다른 생성자에서 특정 생성자를 호출하여, 코드 중복을 제거할 수 있다.
- 생성자 안에서 인스턴스 변수를 명시적으로 설정하지 않으면 자동으로 기본값으로 설정된다. (숫자는 0, 불 값은 false, 객체 참조는 null)
- 인스턴스 변수를 선언할 때 초기화 블록을 포함시키는 방법도 있다.
- 자바에서는 가비지 컬렉터가 객체를 회수할 때 해당 객체를 ‘마무리하는‘ 메커니즘이 있다. (C++는 객체가 소멸될 때 작업을 지정한다)

2.4 정적 변수와 정적 메서드
클래스 안에 있는 변수를 static으로 선언하면 해당 변수는 클래스당 하나만 있게 된다.
- static 변수는 클래스의 특정 인스턴스가 아니라 클래스 자체에 속한다. 모든 객체가 유일한 값을 갖는다.
- 여러 스레드가 객체를 동시에 생성하면 제대로 동작하지 않는다.
- 변경 가능한 정적 변수는 드물지만, 정적 상수는 아주 일반적인다. ex) Math.PI

정적 메서드는 객체로 동작하지 않는다.
- 정적 메서드를 사용하는 이유는 다른 사람이 만든 클래스에 부가 기능을 제공하는 것이다
- 정적 메서드는 객체에 동작하지 않으므로 인스턴스 변수에 접근할 수 없다. 그 대신 자신이 속한 클래스의 정적 변수에는 접근할 수 있다.

팩토리 메서드 : 클래스의 새로운 인스턴스를 반환하는 정적 메서드
- 정적 메서드는 흔히 팩토리 메서드를 만드는데 사용한다.
- 생성자 대신 팩토리 메서드를 사용할까?
- 생성자를 구별하는 유일한 방법은 생성자의 파라미터 타입이다. 그런데 인자 없는 생성자는 두 개 둘 수 없다.
- 팩토리 메서드는 서브 클래스의 객체를 반환할 수 있다.
- 팩트로 메서드를 사용하면 불필요하게 새 객체를 생성하는 대신 공유 객체를 반환하는 것도 가능하다.

2.5 패키지
- 패키지를 사용하면 작업을 조직화하고 다른 사람이 제공한 코드 라이브러리와 분리하기가 편리하다
- 패키지는 주로 클래스 이름의 유일성을 보장하려고 사용한다.
- 자바에서 패키지는 중첩되지 않는다. java.util과 java.util.regex 패키지는 서로 관련이 없다. 각각 독립적인 클래스 묶음이다.

javac 컴파일러는 항상 현재 디렉터리에서 파일을 찾지만, java 프로그램은 . 디렉터리가 클래스 패스에 있을 때만 현재 디렉터리를 찾는다.
- export CLASSPATH=.:/home/username/project/libs/\* (유닉스)
- SET CLASSPATH=.;C:\Users\username\project\libs\* (윈도우)

import 문을 사용하면 전체 이름 없이도 클래스를 사용할 수 있다.
- import 문은 소스 파일에서 첫 번째 클래스 선언부보다 위에 두고 package 문보다는 아래에 둔다.
- import java.util.*; // 와일드 카드는 오직 클래스만 임포트할 수 있고, 패키지는 임포트할 수 없다.
- import와 C 의 #include 지시문과는 다르다. #include에는 컴파일용 헤더 파일이 포함된다. import는 파일을 다시 컴파일하지 않는다. 그저 이름을 줄여줄 뿐이다.
- import static java.lang.Math.*; // 정적 임포트, Math 클래스의 정적 메서드와 정적 변수를 메서드 이름으로 사용 가능 ex) sqrt(2) + pow(x, 2);

2.6 중첩 클래스 : 클래스를 다른 클래스 내부에 두는 방법
클래스의 가시성을 제한하거나 일반적인 이름을 쓰면서도 정돈된 상태를 유지하는 데 유용하다
정적 중첩 클래스 : 클래스 내부에 static 클래스를 생성
내부 클래스(인너 클래스) : 내부 클래스의 메서드는 외부 클래스(아우터 클래스)의 인스턴스 변수에 접근할 수 있다.
- 내부 클래스의 각 객체는 외부 클래스의 객체에 대한 참조를 포함한다.
- 외부 클래스에 대한 숨은 참조를 나타내는 데 ‘outer’를 사용한다.
- 내부 클래스는 외부 클래스의 인스턴스로 외부 클래스의 메서드를 호출할 수 있다.
- 내부 클래스에는 정적 멤버를 선언할 수 없다.

2.7 문서화 주석
JDK에는 javadoc이라는 유용한 도구가 있다. javadoc은 소스 파일로 HTML 문서를 만든다.
- 소스 코드에 구분자 /**로 시작하는 주석을 추가하면 온라인 API문서처럼 전문가 수준의 문서를 손쉽게 만들 수 있다.
- Javadoc 유틸리티는 다음 정보를 추출 : 패키지, 공개 클래스와 인터페이스, 공개/보호 변수, 공개/보호 생성자와 메서드
- 클래스 주석 : 반드시 클래스 선언부 바로 앞, @author, @version 태그로 저자와 버전을 문서화
- 메서드 주석 : 해당 메서드 바로 앞. @param (변수 설명), @return (반환값), @throws (예외 클래스 설명)
- 변수 주석 : 공개 변수만 문서화
- 일반 주석 : @since 태그 : 모든 문서화 주석
- 링크 : @see, @link 태그로 자바독 문서의 관련 부분이나 외부 문서에 대한 하이퍼 링크를 추가
- 패키지 주석을 만들려면 각 패키지 디렉터리에 파일을 따로 추가해야 한다. (패키지 디렉터리에 자바 파일 package-info.java를 추가)



3장 인터페이스와 람다 표현식

3.1 인터페이스
서비스 공급자와 자신의 객체가 해당 서비스를 이용할 수 있게 하려는 클래스가 있을 때, 이 두 클래스 사이의 계약을 표현하는 메커니즘이 인터페이스다.
- 추상메서드 : 기본 구현을 작성하지 않고 선언만 한 메서드
- 인터페이스의 모든 메서드는 자동으로 public이 된다.
- 구현하려는 클래스는 인터페이스의 메서드를 반드시 public으로 선언해야 한다.

인터페이스 타입 변수는 인터페이스를 구현한 어떤 클래스의 객체라도 참조할 수 있다.
- 구현한 인터페이스 타입으로 변수를 선언하고, 선언한 변수에 객체를 할당할 수 있다.
- 또한 객체는 해당 인터페이스를 기대하는 메서드에 전달할 수도 있다.
- 서브타입의 모든 값을 변환 없이 수퍼타입 변수에 할당할 수 있으면 타입 S의 타입 T(서브 타입)의 수퍼타입이다.
- 변수를 인터페이스 타입으로 선언할 수 있지만, 타입이 인터페이스 자체인 객체는 만들 수 없다. 모든 객체는 클래스의 인스턴스여야 한다.

객체는 실제 클래스나 해당 클래스의 수퍼타입으로만 타입을 변환할 수 있다.
‘객체 instanceof 타입’ : 객체가 타입을 수퍼타입으로 두고 있는 클래스의 인스턴스일 때 true를 반환한다.
- 객체의 실제 클래스가 타입이거나 타입을 확장 또는 구현한 서브타입이면 true

인터페이스는 또 다른 인터페이스를 확장해서 원래 있던 메서드 외의 추가 메서드를 제공할 수 있다.
클래스는 인터페이스를 몇 개든 구현할 수 있다.
인터페이스에 정의한 변수는 자동으로 public static final이다.
- 인터페이스 안에는 인스턴스 변수를 둘 수 없다.
- 인터페이스는 객체의 상태가 아니라 동작을 명시한다.

3.2 정적 메서드와 기본 메서드
자바 8이전에는 인터페이스의 모든 메서드가 추상 메서드여야 했다. 즉 구현부가 없었어야 했다.
- 자바 8은 실제 구현이 있는 메서드(정적 메서드와 기본 메서드)를 추가할 수 있다.
- 팩토리 메서드는 인터페이스에 아주 잘 맞는다.

기본메서드에는 반드시 default 제어자를 붙어야 한다.
- 구현하는 두 개의 인터페이스에 같은 이름의 기본 메서드가 있을 경우에는, 해당 클래스에서 그 메서드를 작성하여 고유의 메서드를 구현하거나, 충돌한 메서드 중 하나로 위임하면 된다.

3.4 인터페이스의 예
인터페이스는 그저 클래스가 구현을 약속한 메서드의 집합으로, 별로 많은 일을 하는 것 같지 않다.


객체의 배열을 정렬할 때 쓰는 Comparable 인터페이스
- x.compareTo(y); // 양수 값 반환 : x가 y 다음에 온다. 음수 값 : y가 x 다음에 온다, 0 : x와 y가 같다.

String[] friends = { “Peter”, “Paul”, “Mary” };
Arrays.sort( friends ); // 이제 friends가 [“Mary”, “Paul”, “Peter”]로 정렬됨

문자열을 사전 순서가 아닌 증가하는 길이 순서로 비교한다고 할 때,
- String 클래스로는 compareTo 메서드를 다른 방법으로 구현하게 만들 수 없다.
- String 클래스는 우리가 소유한 클래스가 아니므로 수정할 수 없다.


3.4 인터페이스의 예
그래픽 사용자 인터페이스에서는 사용자가 버튼 클릭, 메뉴 옵션 선택, 슬라이더 드래그 등의 액션을 했을 때 수행될 액션(동작)을 지정해야 한다
수행될 액션을 보통 콜백이라고 하는데, 사용자가 어떤 액션을 취했을 때 역으로 일부 코드가 호출되기 때문이다.
자바 기반 GUI 라이브러리에서는 콜백에 인터페이스를 사용한다.


그런 다음 해당 클래스의 객체를 생성해서 버튼에 추가한다.
Button cancelButton = new Button("Cancel");
cancelButton.setOnAction(new CancelAction());

3.4 람다 표현식
람다 표현식은 한 번이든 여러 번이든 나중에 실행할 수 있게 전달하는 코드 블록이다.
- 자바에는 함수 타입이 없다. 대신 함수를 객체로 표현한다. 다시 말해서 특정 인터페이스를 구현하는 클래스의 인스턴스로 표현한다.
- 람다 표현식은 이런 인스턴스를 생성하는 아주 편리한 문법을 제공한다.

람다 표현식 문법
- 자바는 타입 결합이 강한 언어다.
- 논리학자 알론조 처치는 수학 함수로 효과적인 계산을 할 수 있도록 관련 내용을 공식화하려 했다. 알론조는 파라미터를 표기하는 데 그리스 문자 람다(λ)를 사용했다.
- 람다표현식의 구현부에서 표현식을 여러 줄로 작성할 때는 메서드를 작성하는 것처럼 작성한다. 즉 { }로 감싸고 명시적인 return 문을 사용한다.
- 람다 표현식이 파라미터를 받지 않으면 파라미터가 없는 메서드처럼 빈 괄호를 붙인다.
- 람다 표현식의 파라미터 타입을 추론할 수 있다면 파라미터 타입을 생략할 수 있다.
- 메서드에서 추론 대상의 타입 파라미터를 한 개만 받으면 괄호도 생략할 수 있다.
- 람다 표현식의 결과 타입은 명시하지 않는다. 하지만 컴파일러는 구현부로부터 결과 타입을 추론해서 기대하는 타입과 일치하는지 검사한다.
(String first, String second) -> first.length() – second.length();
// 이 표현식은 기대하는 결과가 int 타입(또는 Integer, long, double과 같은 호환 타입)인 문맥에서 사용할 수 있다.


함수형 인터페이스
- 자바에서는 Runnable이나 Comparator처럼 액션을 표현하는 인터페이스가 많다. 람다 표현식은 이러한 인터페이스와 호환된다.
- 람다 표현식은 추상 메서드가 한 개만 포함된 인터페이스에만 사용할 수 있는데, 이러한 인터페이스를 함수형 인터페이스라고 한다.
- Arrays.sort 메서드의 두 번째 파라미터는 Comparator 인터페이스의 인스턴스가 필요하다.
- Comparator 인터페이스에는 메서드가 하나만 있다. 두 번째 파라미터로 다음과 같이 간단하게 람다를 전달해보자
Arrays.sort( words, (first, second) -> first.length() – second.length() );
- 내부에서 Arrays.sort 메서드의 두 번째 파라미터 변수는 Comparator<String>을 구현하는 클래스의 객체를 받는다.
- 이 객체에 compare 메서드를 호출하면 람다 표현식의 구현부를 실행한다.
- 이러한 객체와 클래스를 관리하는 일은 전적으로 구현체의 몫이다. 구현체는 객체와 클래스를 관리하는 일에 최적화되어 있다.

함수 리터럴을 지원하는 대부분의 프로그래밍 언어에서는 (String, String) -> int 처럼 함수 타입을 선언하고, 이 함수 타입으로 변수를 선언한다.
- 그러면 함수를 해당 변수에 저장하고 호출할 수 있다. 그런데 자바에서는 람다 표현식으로 이 중에서 한 가지만 할 수 있다.
- 그 한 가지는 바로 람다 표현식을 함수형 인터페이스 타입 변수에 저장해서 해당 인터페이스의 인스턴스로 변환하는 것이다.
- 람다 표현식은 Object 타입 변수에 저장할 수 없다.
- 표준 라이브러리에는 수많은 함수형 인터페이스가 있다. 그 중 하나가 Predicate 인터페이스다.
public interface Predicate<T> { // Predicate는 람다 표현식을 전달하기 위해 특별히 설계됐다.
boolean test ( T t ); // 추가적인 기본 메서드와 정적 메서드
}

- ArrayList 클래스에는 removeIf 메서드가 있다. removeIf 메서드는 파라미터로 Predicate를 받는다.
list.removeIf( e -> e == null );

3.5 메서드 참조와 생성자 참조
다른 코드에 전달하려는 액션을 수행하는 메서드가 이미 있을 수 있다.
- 이럴 때 사용하는 메서드 참조용 특수 문법이 있는데, 이 특수 문법은 메서드를 호출하는 람다 표현식보다 더 짧다.

메서드 참조
- 대소문자 구분 없이 문자열을 정렬한다고 해보자
Arrays.sort( strings, (x, y) -> x.compareToIgnoreCase(y) );
- 이 코드 대신 다음 메서드 표현식을 전달할 수 있다.
Arrays.sort( strings, String::compareToIgnoreCase );
리스트의 요소를 모두 출력한다고 해보자.
- list.forEach( x -> System.out.println(x) );

println 메서드를 forEach 메서드에 전달하면 더 좋다.
- list.forEach(System.out::println);

:: 연산자는 메서드 이름과 클래스를 분리하거나, 메서드 이름과 객체의 이름을 분리한다.
1. 클래스::인스턴스메서드 // 첫 번째 파라미터가 메서드의 수신자가 되고, 나머지 파라미터는 해당 메서드로 전달된다.
2. 클래스::정적메서드 // 모든 파라미터가 정적 메서드로 전달된다. Objects::isNull은 x -> Objects.isNull(x)
3. 객체::인스턴스메서드 // 주어진 객체에서 메서드가 호출되며, 파라미터는 인스턴스 메서드로 전달된다.

같은 이름으로 오버로드된 메서드가 여러 개일 때 컴파일러는 문맥을 통해 어느 것을 의도했는지 알아내려한다.

메서드 참조에서 this 파라미터를 캡처할 수 있다.
- (this::equals는 x -> this.equals(x)

내부 클래스에서 외부클래스의 this 참조는 외부클래스.this::메서드로 캡처할 수 있다.
- super도 캡처할 수 있다.

생성자 참조
- 생성자 참조는 메서드의 이름이 new라는 점만 제외하면 메서드 참조와 같다.
- Employee::new는 Employee 생성자 참조다.
- 클래스에 생성자가 두 개 이상 있을 때는 문맥을 통해 어느 생성자를 호출할 지 결정한다.

이름별로 하나씩 대응하는 직원의 리스트를 원한다고 해보자.
- 스트림을 사용하면 루프 없이도 이 작업을 처리할 수 있다. 리스트를 스트림으로 전환한 후 map 메서드를 호출하면 된다. (8장 참조)

Stream<Employee> stream = names.stream().map( Employee::new );
- names.stream()에 String 객체가 담겨 있으므로 컴파일러는 Employee::new가 Employee(String) 생성자를 가리킨다는 사실을 알게 된다.
- 배열 타입으로도 생성자 참조를 만들 수 있다. ex) int[]::new는 파라미터 한 개 (배열의 길이)를 받는 생성자 참조. 람다 표현식 n -> new int[n] 과 같다.
- 배열 생성자 참조는 자바의 한계를 극복하는 데 유용하다. 자바에서는 제네릭 타입으로 배열을 생성하는 일이 불가능하기 때문이다.
- 그래서 Stream.toArray와 같은 메서드는 해당 타입에 대한 배열이 아니라 Obejct 배열을 반환한다.
Object[] employee = stream.toArray();
- 이 문제를 해결하기 위해 toArray의 또 다른 번전은 다음과 같이 생성자 참조를 받는다.
Employee[] buttons = stream.toArray( Employee[]::new );
- toArray 메서드는 이 생성자를 호출해서 올바른 타입의 배열을 얻은 다음 해당 배열에 내용을 채워서 반환한다.

3.6 람다 표현식 처리하기
람다 표현식을 만들어서 함수형 인터페이스가 필요한 메서드에 전달하는 방법을 살펴봤다.
여기서는 람다 표현식을 소비할 수 있는 자신만의 메서드 작성 방법을 알아보자.

지연실행 구현하기
- 람다를 사용하는 핵심 목적은 지연 실행이다. 어떤 코드를 지금 당장 실행하고 싶다면 람다를 사용하지 않는다.
- 이유 : 별도의 스레드에서 코드 대행, 코드를 여러 번 실행, 알고리즘의 올바른 지점에서 코드 실행, 어떤 일(클릭, 데이터 수신 등)이 일어날 때 코드 실행, 필요할 때만 실행
예) 액션을 n번 반복하기 위해 repeat 메서드에 카운트와 액션을 전달
- repeat(10, () -> System.out.println(“Hello, World!”) );

람다를 받으려면 함수형 인터페이스를 선택(드물게 구현) 해야 한다.
- public static void repeat(int n, Runnable action) { for(int i=0; i<n; i++) action.run(); }
- 람다 표현식의 구현부는 action.run()이 호출될 때 실행된다는 점을 염두에 두어야 한다.

이번에는 몇 번째 반복을 수행하고 있는지 액션에 알리려고 한다.
- int 파라미터를 받고 함수형 인터페이스를 골라야 하는데, 함수형 인터페이스에는 반환 타입이 void인 메서드가 있어야 한다.
- 직접 만들기보다는 표준 함수형 인터페이스 중 하나를 사용하는 것이 좋다.


함수형 인터페이스 고르기
- 함수형 프로그래밍 언어 대부분은 함수 타입이 구조적이다.
- 문자열 두 개를 정수로 대응시키는 함수를 지정하려면 Function2<String, String, Integer> 또는 (String, String) -> int 와 같은 타입을 사용한다.
- 자바에서는 Comparator<String>과 같은 함수형 인터페이스로 함수의 의도를 선언한다.
- 프로그래밍 언어 이론에서는 이 방식을 명목적 타입 지정이라고 한다.
- 물론 특정한 의미 없이 ‘어떤 함수든‘ 받고 싶은 상황도 많다. 이를 위해 준비된 제네릭 함수 타입이 있으니 가능하면 이들 중 하나를 사용하는 것이 좋다
표) 자주 사용되는 함수형 인터페이스


- 예를 들어 파일을 처리하는 메서드를 작성한다고 해보자. 여기서 파일은 특정 기준을 만족해야 한다.
- 설명적인 java.io.FileFilter 클래스와 Predicate<File> 중 어느 것을 사용해야 할까? 표준 Predicate<File> 을 사용할 것을 권장한다.
- 대부분의 표준 함수형 인터페이스에는 비추상 메서드가 포함되어 있고, 비추상 메서드는 함수를 생성하거나 결합한다.
- 오토 박싱을 줄이려면 기본 타입 특화 버전 함수형 인터페이스를 사용하는 것이 좋다.
ex) Consumer<Integer> 대신 IntConsumer 사용을 권장

표) 기본 타입 int/long/double 용 함수형 인터페이스
* p, q는 int, long, doulbe이다. P, Q는 Int, Long, Double이다


자신만의 함수형 인터페이스 구현하기
- 표준 함수형 인터페이스가 적합하지 않은 상황에는 인터페이스를 직접 구현해야 한다.
- 이미지를 색채 패턴으로 채운다고 해보자
- 사용자가 각 픽셀에 사용할 색을 넘겨주는 함수를 작성해야 한다
- (int, int) -> Color 매핑에 해당하는 표준 타입은 없다.
- BiFunction<Integer, Integer, Color> 를 사용할 수도 있겠지만, 오토박싱을 일으킨다.
- 이럴 때 새로운 인터페이스를 정의한다.

함수형 인퍼페이스에는 @FunctionalInterface 애너테이션을 붙여야 한다.
- 컴파일러가 애너테이션이 붙은 앤티티를 검사하는데, 추상 메서드 하나만 있는 인터페이스인지 검사한다
- 자바독 페이지에 해당 인터페이스가 함수형 인터페이스라는 문장을 둔다

함수를 호출하려면 람다 표현식을 전달해야 한다
- 람다 표현식은 두 정수에 해당하는 색상 값을 준다.



3.7 람다 표현식과 변수 유효 범위
여기서는 람다 표현식 내부에서 변수가 어떻게 작동하는지 알아보자.

람다 표현식의 유효 범위
- 람다 표현식의 구현부는 유효 범위가 중첩 블록과 같다.
- 이름 충돌 규칙과 이름 가리기 규칙이 똑같이 적용된다
- 람다 안에 지역 변수와 이름이 같은 파라미터나 지역 변수와 이름이 같은 지역 변수를 선언하면 안된다.
- 메서드 안에는 이름이 같은 두 지역 변수를 함께 둘 수 없다.
- 람다 표현식 안에 있는 this 키워드는 같은 유효 범위 규칙의 영향을 받는다.
- this 키워드는 람다 자체를 생성하는 메서드의 this 파라미터를 의미한다.

바깥쪽 유효 범위에 속한 변수 접근하기
- 종종 람다 표현식에 자신을 감싼 메서드나 클래스에 속한 변수에 접근할 필요가 있다.
- 람다 표현식을 세가지로 구성 : 코드 블록, 파라미터, 자유 변수들의 값 (text, count)
- 예제 코드에서는 text와 count라는 자유 변수 두 개를 이용한다.
- 람다 표현식을 표현하는 자료 구조는 반드시 이 변수들의 값 (“Hello”, 10)을 저장해야 한다.
- 이를 가리켜 람다 표현식이 이 값들을 캡처했다고 말한다.
- 자유 변수의 값을 사용하는 코드 블록을 클로저(closure)라고 한다.
- 자바에서는 람다 표현식이 클로저다.
- 람다 표현식은 자신을 감싸고 있는 유효 범위에 속한 변수의 값을 캡처할 수 있다.
- 캡처된 값이 잘 정의되었는지 보장하기 위한 제약이 있다.
- 즉, 람다 표현식에서는 값이 변하지 않는 변수만 참조할 수 있다.
- 이런 이유 때문에 람다 표현식은 변수가 아니라 값을 캡처한다고 말하기도 한다.

바깥쪽 유효 범위에 속한 변수 접근하기
- 람다 표현식은 i를 캡처하려고 한다. 하지만 i는 값이 아니라 변수이므로 규칙에 어긋난다.
- 다시 말해서 캡처할 단일 값이 없다.
- 람다 표현식은 자신을 감싸고 있는 유효 범위에 속한 사실상 최종인 지역 변수에만 접근할 수 있다.
- 향상된 for 루프의 변수는 유효 범위가 단일 반복이므로 사실상 최종이다.
- 각 반복마다 새로운 변수인 arg가 생성되고, args 배열에 있는 다음 값을 할당받는다.
- 앞의 예에서는 i 변수의 유효 범위가 전체 루프였다.

‘사실상 최종(effectively final)’ 규칙 때문에 람다 표현식은 캡처한 변수를 어느 것도 변경할 수 없다.
- 예에서 두 스레드가 count를 동시에 업데이트하면 count의 값은 정의되지 않느다.
- 컴파일러가 모든 병행 접근 오류를 잡아내리라고 확신하지 말자.
- 캡처한 변수를 변경할 수 없는 것은 지역변수에만 해당한다.



3.8 고차 함수
함수형 프로그래밍 언어에서는 함수가 일차 구성원(기본 구성 요소)이다.
- 함수를 처리하거나 반환하는 함수를 고차 함수라 한다.

함수를 반환하는 메서드
- 어떨 때는 문자열의 배열을 오름차순으로 정렬하고, 어떨 때는 내림차순으로 정렬한다고 해보자
- 각 상황에 맞는 비교자를 만들자.
- compareInDirection(1) 호출은 오름차순 비교자를, compareInDirction(-1) 호출은 내림차순
- 주저하지 말고 함수를 만들어내는 메서드를 작성하자.
(기술적으로 말하면 함수형 인터페이스를 구현한 클래스의 인스턴스)
- 이런 메서드는 함수형 인터페이스를 받는 메서드에 전달할 사용자 정의함수를 만드는 데 유용하다

함수를 수정하는 메서드
- 비교자를 역으로 만들면 이 발상을 일반화할 수 있다.
- 이 메서드는 함수에도 사용할 수 있다. 함수를 인자로 받아서 수정된 함수를 반환한다.
- 대소문자를 구별하지 않는 내리차순 비교자를 얻으려면 다음과 같이 호출해야 한다.
- Comparator 인터페이스에는 바로 이 방법으로 주어진 비교자의 역을 만들어내는 기본 메서드 reversed가 있다.

Comparator 인터페이스의 메서드
- Comparator 인터페이스에는 비교자를 만들어내는 유용한 고차 함수가 정적 메서드로 많이 정의되어 있다.
- comparing 메서드는 T 타입으로 String 처럼 비교 가능한 타입으로 매핑하는 ‘키 추출‘ 함수를 받는다.
- 비교 대상 객체에 키 추출 함수를 적용한 다음 반환받은 키를 비교한다.
- thenComparing 메서드로 비교자를 연결할 때 비교 대상이 같으면 추가로 비교할 수 있다.
- 이 코드에서 두 사람의 이름이 같으면 두 번째 비교자를 사용한다

Comparator 인터페이스의 메서드
- 다음은 이름의 길이 순서로 사람을 정렬한다.
- comparing과 thenComparing 메서드 둘 다 int, long, double 값의 박싱을 피하는 변형이 있다.
- 키 함수가 null을 반환할 가능성이 있다면 nullsFirst와 nullsLast 어댑터가 마음에 들 것이다.
- nullsFirst 메서드는 비교자를 인자로 받는다. null을 반환할 가능성이 있는 중간 이름으로 정렬한다.
- 정적 메서드 reverseOrder는 자연 순서의 역을 준다


3.9 지역 내부 클래스
인터페이스를 구현하는 클래스를 간결하게 정의하는 메커니즘.

지역 클래스 : 메서드 안에 클래스를 정의
- 흔히 어떤 클래스가 인터페이스 하나를 구현하고, 메서드를 호출하는 쪽이 클래스에는 관심이 없고 인터페이스에만 관심이 있을 때

클래스를 지역 클래스로 만들면 두 가지 이점이 있다.
1. 클래스 이름이 메서드의 유효 범위 안으로 숨는다.
2. 람다 표현식의 변수와 마찬가지로 지역 클래스의 메서드에서 지역 클래스를 감싸고 있는 유효 범위에 속한 변수에 접근할 수 있다.
- next 메서드가 low, high, generator 변수를 캡처한다.
- 인스턴스 변수에 저장하는 명시적인 생성자를 피할 수 있다.


익명 클래스
- 앞의 예에서 RandomSequence라는 이름을 반환 값을 생성할 목적으로 딱 한번 사용했다.
- 이럴 때는 클래스를 익명으로 만들 수 있다.

다음 표현식은 인터페이스와 인터페이스의 메서드를 구현하는 클래스를 정의하고, 이 클래스의 객체를 하나 생성한다

new 인터페이스() { 메서드 구현 }
- new 표현식에서 ()는 생성 인자를 나타낸다. 익명 클래스의 기본 생성자가 호출된다.

- 요즘에는 메서드를 두 개 이상 제공해야 할 때만 익명 내부 클래스가 필요하다.
- IntSequence 인터페이스가 기본 메서드 hasNext를 포함한다면 다음과 같이 간단하게 람다 표현식을 사용할 수 있다.

public static IntSequence randomInts(int low, int high) {
return () -> low + generator.nextInt(high – low + 1);
}



4장 상속과 리플렉션

4.1 클래스 확장하기 슈퍼클래스와 서브클래스 : extends 키워드는 기존 클래스에서 파생한 새로운 클래스를 만든다.

메서드 오버라이딩 : 서브클래스에서 슈퍼클래스 메서드를 수정
- 서브클래스 메서드는 슈퍼클래스의 비공개 인스턴스 변수에 직접 접근할 수 없다.
- 그래서, 슈퍼클래스 생성자로 해당 인스턴스 변수를 초기화 해야 한다.
- 슈퍼클래스 생성자 호출을 생략할 때믄 인자 없는 생성자를 반드시 포함해야 한다.
- 인자 없는 생성자를 포함하면 슈퍼클래스가 암시적으로 호출된다.
- 슈퍼클래스 메서드를 호출할 때는 super 키워드를 사용한다.
- super는 this와 달리 객체에 대한 참조가 아니다. super는 동적 메서드 조회를 우회하는 지시자이며 특정 메서드를 호출한다.
- 슈퍼클래스 메서드를 publi으로 선언했다면 서브클래스 메서드도 반드시 publi으로 선언해야 한다.

슈퍼클래스 할당
- 서브클래스 객체를 슈퍼클래스 타입 변수에 할당할 수 있다.
- 동적 메서드 조회 : 메서드가 호출될 때 가상 머신은 객체의 실제 클래스를 살펴보고 해당 클래스에 맞는 메서드 버전을 찾아서 실행한다.
- Manager 객체를 Employee 변수에 할당하는 이유 : 직원이든 관리자든 문지기든 상관없이 모든 직원에 동작하는 코드를 작성할 수 있다.


- 동적 메서드 조회 덕분에 empl.getSalary() 호출은 empl이 참조하는 객체에 속한 getSalary 메서드를 찾아서 호출한다.
- 이 때, 슈퍼클래스에 있는 메서드만 호출할 수 있다는 제한이 있다.

메서드를 final로 선언하면 어느 서브클래스도 해당 메서드를 오버라이드할 수 없다.
- 클래스 정의에 final 제어자를 사용하면, 이 클래스의 서브클래스를 만들 수 없다. ex) 자바에서 String, LocalTime, URL 같은 최종 클래스

추상메서드 : 클래스는 구현이 없는 메서드를 정의해서 서브클래스가 해당 메서드를 구현하도록 강제할 수 있다.
- 추상 메서드와 추상 클래스에는 abstract 제어자 사용해야 한다.
- 추상 메서드가 포함된 클래스를 추상 클래스라고 한다.
- 추상클래스에는 비추상 메서드를 포함할 수 있다
- 추상클래스의 인스턴스를 생성할 수 없다.

protected : 메서드를 서브클래스 전용으로 제한하고 싶거나, 서브클래스 메서드에서 슈퍼클래스의 인스턴스 변수에 접근하고 싶은 때 사용
- 자바에서 protected 접근 권한은 패키지 수준이다.

익명 서브클래스 : 인터페이스를 구현하는 익명클래스를 만들 수 있는 것처럼 슈퍼클래스를 확장
- 디버깅에 유용하다.
- 슈퍼클래스 이름 뒤에 오며 괄호 안에 있는 인자는 슈퍼클래스의 생성자에 전달된다.
- 예제는 ArrayList<String> 의 익명 서브클래스를 생성해서 add 메서드를 오버라이드 했다.
- 인스턴스의 초기 용량은 100으로 생성된다.


클래스를 상속하고 인터페이스를 구현하는 클래스가 있는데 이름이 같은 메서드가 있다면, 항상 슈퍼클래스 구현이 우선이다.
- 따라서 인터페이스에 기본 메서드를 추가해도 기본 메서드가 생기기 전부터 동작하던 코드에는 영향을 주지 않는다.

super::인스턴스메서드
- this에 주어진 메서드의 슈퍼클래스 버전을 호출한다.

4.2 Object : 보편적 슈퍼클래스
자바에서 모든 클래스는 Object 클래스를 직접적/간접적으로 상속한다.
- 클래스에 명시적인 슈퍼클래스가 없으면 암시적으로 Object를 상속한다.
- 배열은 클래스이므로 Object 타입의 참조로 문제 없이 변환할 수 있다.

표4-1) java.lang.Object 클래스의 메서드


toString 메서드 : 객체의 문자열 표현을 반환
- 클래스 명 뒤에 인스턴스 변수 목록을 대괄호([])로 감싸서 나열하는 방식 사용
- 객체를 문자열과 연결하면 자바 컴파일러가 해당 객체의 toString 메서드 호출

public String toString() {
return getClass().getName() + “[name=“ + name + “, salary=“ + salary + “]”;
}

equals 메서드
- 한 객체를 다른 객체와 같은 지 검사
- 두 객체에 대한 참조가 같은 지 판단한다.
- String 클래스는 두 문자열이 같은 문자들로 구성되어 있는지 검사 (오버라이드)

hashCode 메서드
- 해시 코드는 객체에서 파생되는 정수값이다.
- 해시 코드는 값이 중복될 수 있다.
- hashCode와 equals 메서드는 반드시 호환되어야 한다.

clone 메서드
- 객체의 복사본, 즉 원본과 상태가 같은 객체를 만드는 것이다.
- 두 객체 중 하나의 상태를 변경해도 나머지 하나는 변하지 않는다.
- clone 메서드는 Object 클래스에 protected로 선언되어 있다.
- 개발자가 만드는 클래스의 사용자가 인스턴스를 복제할 수 있게 하려면 반드시 clone 메서드를 오버라이드 해야 한다.
- 클래스 변수에 객체 참조 변수가 있으면 얕은 복사 시, 참조를 공유할 수 있다. (이때는 깊은 복사 필요)

4.3 열거
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

열거타입은 인스턴스들이 고정되어 있으므로 열거 타입 값에 equals 메서드를 사용할 필요가 없다.
- toString 메서드를 구현하지 않아도 된다. 자동으로 주어진다.
- toString 의 역은 각 열거 타입에 맞게 통합된 정적 메서드 valueOf다.
- Size notMySize = Size.valueOf(“SMALL”); // notMySize = Size.SMALL
- valueOf 메서드는 주어진 이름에 해당하는 인스턴스가 없으면 예외를 던진다

- values 메서드 : 모든 인스턴스를 선언한 순서로 정렬한 배열을 반환한다.
Size[] allValues = Size.values();
for( Size s : Size.values() ) { System.out.println( s ); }

- ordinal 메서드 : enum 선언에서 인스턴스의 위치(0부터 시작)를 돌려준다.
ex) Size.MEDIUM.ordinal() // ‘= 1’

모든 열거 타입 E는 자동으로 Comparable<E> 를 구현하므로 자체의 객체만을 대상으로 비교할 수 있다.
- 비교할 때는 순서 값으로 비교한다.

표 4-2 java.lang.Enum<E> 클래스의 메서드


원한다면 열거 타입에 생성자, 메서드, 필드를 추가할 수 있다.
- 열거의 각 인스턴스는 한 번만 생성됨을 보장받는다.
- 열거의 생성자는 반드시 비공개여야 한다.
- private 제어자를 생략할 수 있다. public이나 protected를 사용하면 에러

enum 인스턴스 각각에 메서드를 추가할 수 있지만, 이렇게 하면 열거에 정의된 메서드를 오버라이드 해야 한다.


열거는 정적 멤버를 가질 수 있다.
- 열거 상수가 생성된 다음에 정적 멤버가 생성된다. 그러므로 열거 생성자에서는 정적 멤버를 참조할 수 없다.
- 클래스 내부에 열거 타입을 중첩할 수 있다.

열거를 기준으로 스위치하기
- switch 문에서는 Operation.ADD가 아니라 ADD를 사용해야 한다


4.4 실행 시간 타입 정보와 리소스 : java.lang.Class<T>
자바에서는 실행 시간에 객체가 어느 클래스에 속하는지 알아낼 수 있다.
- equals와 toString 메서드를 구현할 때 유용하다.

Class 클래스 :
- 어떤 객체에 참조를 저장하는 Object 타입 변수가 있는 상태에서 해당 객체의 더 많은 정보를 엎고 싶을 때
Object obj = …;
Class<?> cl = obj.getClass();
- 자바에서 배열은 클래스이지만, 인터페이스, 기본 타입, void는 클래스가 아니다.
- 따라서 Class라는 명칭은 다소 적절하지 않다. Class보다는 Type이 더 정확한 표현이라고 생각한다.
- 가상 머신은 각 타입별로 고유한 Class 객체를 관린한다. 그러므로 == 연산자로 클래스 객체를 비교할 수 있다.
- Class 클래스의 유용한 서비스 중 하나는 설정 파일이나 이미지처럼 프로그램에 필요한 리소스를 찾아오는 일이다.
- 가상 머신에서는 명령어를 클래스 파일에 저장한다. 각 클래스 파일에는 단일 클래스나 인터페이스에 해당하는 명령어를 담는다.
- 가상 머신은 main 메서드가 호출될 메인 클래스부터 시작해서 필요할 때 클래스 파일을 로드한다.
- 자바 프로그램을 실행할 때 최소 세 가지 클래스도 로드한다 : 부트스트랩 클래스 로더, 확장 클래스 로더, 시스템 클래스 로더
- 대부분은 클래스 로딩 과정을 신경 쓰지 않아도 된다.

표4-3 java.lang.Class<T> 클래스의 유용한 메서드


표4-4 java.lang.reflect.Modifier 클래스의 메서드
- static String toString(int modifiers)
- static boolean is(Abstract | Interface | Native | Private | Protected | Public | Static | Strict | Synchronized | Volatile) (int modifiers)

4.5 리플렉션
프로그램에서 리플렉션을 이요하면 실행 시간에 객체의 내용을 조사하고, 해당 객체의 메서드를 호출할 수 있다.

리플렉션은 객체-관계 매퍼나 GUI 빌더 같은 도구를 구현할 때 유용하다.
- 리플렉션은 주로 도구 개발자가 관심을 두는 주제다. 애플리케이션 개발자는 이 절을 건너뛰어도 무방하다

java.lang.reflect 패키지에 속한 Field, Method, Constructor 클래스는 각각 클래스의 필드, 메서드, 생성자를 나타낸다.

클래스를 –parameters 플래그로 컴파일 했을 때만 실행 시간에 파라미터 이름을 얻을 수 있다.
- 지정한 클래스의 모든 메서드를 출력하는 방법
- 프로그램을 컴파일한 시점에 이용할 수 있는 클래스 뿐만 아니라 가상 모신이 로드할 수 있는 모든 클래스를 분석할 수 있다.

- 객체에 속한 필드의 타입과 이름을 나타내는 Field 객체를 얻을 수 있다.
- Field 객체를 이용하면 특정 객체를 들여다보고 필드 값을 추출할 수도 있다.
- Method 객체로 객체의 메서드를 호출할 수 있다.

자바빈(JavaBean)은 인자 없는 생성자, 게터/세터 쌍, 기타 다른 메서드로 구성된 클래스를 의미한다.
- 자바빈즈는 GUI 빌더에서 생겨났다.

Proxy 클래스는 실행 시간에 지정한 인터페이스 한 개 또는 일련의 인터페이스를 구현하는 새로운 클래스를 생성할 수 있다.
- 프록시는 컴파일 시간에 어느 인터페이스를 구현해야 하는 지 모를 때만 필요하다.




5장 예외, 단정, 로깅

5.1 예외 처리
메서드가 해야 할 일을 수행할 수 없는 상황에 부딪히면 어떻게 해야 할까?
- 전통적인 해결 방법은 메서드에서 오류를 반환하는 것이다.
- 자바는 예외 처리를 지원하여, 메서드는 예외를 던지는 방법으로 심각한 문제를 알린다.
- 예외 처리의 장점은 오류를 감지하는 과정과 처리하는 과정을 분리하는 것이다.

throw문으로 클래스의 객체(여기서는 IllegalArgumentException)를 던진다.
- 적절한 예외 클래스를 고르는 방법은?
- throw 문이 실행되면 정상적인 실행 흐름이 즉시 중단된다.


자바의 예외 계층도
- 모든 예외는 Throwable 클래스의 서브클래스다
- Errow의 서브클래스는 예외 상황이 일어날 때 던지는 예외다.
ex) 메모리 고갈 – 프로그램이 처리할 수 없는 상황
- 개발자가 보고하는 예외는 Exception 클래스의 서브클래스다
- 개발자는 ‘검사 예외’를 잡아 내야 한다.
- 검사 예외를 잡아내지 않을 때 메서드 선언부에 선언해야 한다.
- 컴파일러는 이러한 예외를 적절히 처리했는 지 검사한다.


검사 예외는 실패가 예상되는 상황에 사용한다
- 흔히 입력이나 출력 과정에서 발생 : 파일 손상, 네트워크 연결 실패 상황

비검사 예외는 개발자가 만든 논리 오류를 나타낸다.
- 피할 수 없는 외부 위험 요소는 나타내지 않는다.
ex) NullPointerException은 검사 대상이 아니다.
- 개발자는 이 예외를 잡아내는 데 시간을 낭비하면 안된다.

ex) Integer.parseInt( str ) 메서드는 str이 유효한 정수를 담고 있지 않으면 비검사 예외인 NumberFormatException을 던진다.

예외 클래스를 직접 만들 때는 인자 없는 생성자와 메시지 문자열을 받는 생성자를 구현하는 것이 좋다.

검사 예외를 일으킬 수 있는 메서드는 메서드 선언부의 throws 절에 해당 예외를 선언해야 한다
- public void write(Object obj, String filename) throws IOException, ReflectvieOperationException
- 메서드에서 throw 문을 사용하기 위해서든, throws 절을 포함하는 또 다른 메서드를 호출하기 위해서든 해당 메서드가 던질 수 있는 예외를 나열해야 한다.
- throws 절에서는 예외를 공통 슈퍼클래스로 묶을 수 있다.
- 예외의 황금율 ; “빨리 던지고, 늦게 잡아내라”

메서드에서 검사 예외나 비검사 예외를 던질 때는 javadoc @throws 태그를 사용해서 문서화한다
- 개발자 대부분은 문서화할 만한 내용이 있을 대만 이 태그로 문서화한다.
- 람다 표현식의 예외 타입은 절대 명시하지 않는다.
- 하지만, 람다 표현식에서 검사 예외를 던질 수 있다면 해당 예외를 선언한 함수형 인터페이스에만 람다 표현식을 전달할 수 있다.

예외를 잡으려면 try 블록을 사용해야 한다.
- try 블록에 들어 있는 문장이 실행되다가 지정한 클래스의 예외가 일어나면 제어가 핸들러로 이동한다.
- catch 절이 여러 개 있으면, 위에 있는 catch 절부터 일치 항목을 찾고 없으면 아래로 내려온다.
- 가장 상세한 예외 클래스부터 먼저 배치해야 한다.


예외 처리의 문제점은 리소스 관리다.
- 각 리소스는 반드시 AutoCloseable 인터페이스를 구현하는 클래스에 속해야 한다.
- public void close() throws Exception
- 리소스는 초기화 순서의 역순으로 close 된다.

finally 절
- try-with-resources 문은 예외 발생 여부와 상관없이 자동으로 리소스를 닫는다.
- finally 절은 정상적이든 예외 때문으든 try 블록이 끝날 때 실행된다.
- finally 절에서 예외를 던지는 일은 하지 말아야 한다.
- finally 절에 return 문을 작성하면 안된다.

예외 다시 던지기와 예외 연쇄하기
- 예외가 일어날 때 무슨 일을 해야 하는지는 모르더라도 실패를 로그로 기록하고 싶을 수 있다.
- 예외를 다시 던져서 적합한 예외 핸들러가 해당 예외를 다룰 수 있게 해야 한다.
- 일어난 예외의 클래스를 변경하고 싶을 때도 있다.
- 예외 연쇄 기법은 검사 예외를 허용하지 않는 메서드에서 검사 예외가 일어날 때도 유용하다.
해당 검사 예외를 잡아서 비검사 예외로 연쇄하면 된다.

스택 추적
- ‘미처리 예외’가 있으면 스택 추적에 표시된다.
- 스택 추적은 예외가 던져진 지점에서 대기 중인 모든 메서드 호출의 목록이다.
- 스택 추적은 오류 메시지용 스트림 System.err 로 전달된다.
- 미처리 예외는 해당 예외가 일어난 스레드를 종료시킨다.

Objects.requireNonNull 메서드 : Objects 클래스에는 편리한 파라미터 널 검사용 메서드
- 문제의 원인으로 보면 무엇을 실수했는지 바로 알 수 있다.

5.2 단정(assertion)
일반적으로 사용하는 방어적 프로그래밍 방법
ex) double y = Math.sqrt( x ); ⇒ if( x < 0 ) throw new IllegalStateException( x + “ < 0 “ );
// 이 조건문은 테스트를 마친 후에도 프로그램에 남아서 프로그램을 느려지게 만든다.
- 단정 메커니즘을 이용하면 테스트 중에만 검사를 하고, 제품용 코드에서는 자동으로 삭제되게 할 수 있다.
- 자바에서 단정은 계약을 강화하는 메커니즘이 아니라 내부 가정을 검증하는 디버깅 보조 도구로 만들어졌다.


assert 문은 조건을 평가해서 거짓이면, AssertionError를 던진다.
- 표식식 부분이 오류 객체의 메시지가 되는 문자열로 변환된다
ex) x가 음수가 아님을 단정 : assert x >= 0;
ex) x의 실제 값을 AssertionError 객체에 전달하여 나중에 표시 : assert x >= 0 : x;

기본적으로 단정은 비활성화되어 있다.
- -enableassertion / -ea 옵션으로 프로그램을 실행하여 단정을 활성화한다. (클래스 로더에서 처리)

5.3 로깅
자바 개발자 대부분은 문제가 있는 코드에 System.out.println 호출을 집어 넣어 프로그램의 동작을 들여다보는 과정에 익숙하다.
- 물론 문제의 원인을 이해하고 나면 출력문을 제거한다.

로거 사용하기 : Logger.getGlobal()
- Logger.getGlobal().info(“Opening file “ + filename);
// Aug 04, 2014 09:53:34 AM com.mycompany.MyClass read INFO : Opening file data.txt
- 로그를 기록한 시각과 호출 클래스나 메서드의 이름이 자동으로 포함된다.
- 전문적인 애플리케이션에서는 보통 전역 로거 하나로 모든 레코드를 로그로 기록하려고 하지 않는다.
- 대신 로거를 직접 정의해서 사용한다. : Logger logger = Logger.getLogger(“com.mycom.myapp”);

로깅 레벨 : SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST
- 기본 설정 : 상위 세 개 레벨만 로그로 기록
- logger.setLevel( Level.FINE ); // Level.ALL, Level.OFF

실행 흐름을 추정하는 데 유용한 메서드 : void entering(…), void exiting(…)

로깅 설정 파일 : jre/lib/logging.properties

로거는 레코드를 ConsoleHandler로 보낸다.



6장 제넥릭 프로그래밍

다양한 타입에도 동작하는 메서드와 클래스를 구현

6.1 제네릭 클래스
제네릭 클래스는 타입 파라미터를 한 개 이상 받는 클래스다.
- 간단한 예로 키/값 쌍을 저장하는 클래스
- 클래스 이름 뒤에 오는 꺾쇠 괄호(<>) 안에 타입 파라미터 K와 V를 명시
- 타입 파라미터 : 클래스 멤버를 정의할 때 인스턴스 변수, 메서드 파라미터, 반환 값의 타입으로 사용한다.
- 타입 변수를 해당하는 타입으로 교체하여 제네릭 클래스의 인스턴스를 만든다.
- 제네릭 클래스의 객체를 생성할 때 생성자에서 타입 파라미터를 생략할 수 있다.


6.2 제네릭 메서드
제네릭 메서드는 타입 파라미터를 받는 메서드.
- 제네릭 메서드를 선언할 때는 타입 파라미터를 제어자(public or static)와 반환 타입 사이에 둔다.
- 호출할 때는 타입 파라미터를 명시하지 않아도 된다. 컴파일러가 추론할 수 있다.

6.3 타입 경계
제네릭 클래스나 제네릭 메서드가 받는 타입 파라미터의 타입을 제한해야 할 때가 있다.
- 타입 경계를 지정하면 타입이 특정 클래스를 확장하거나 특정 인터페이스를 구현하게 할 수 있다.
- 타입 경계 extends AutoCloseable은 요소 타입이 AutoCloseable의 서브타입임을 보장한다.
- 따라서 elem.close()는 유효하다.

public static <T extends AutoCloseable> void closeAll(ArrayList<T> elems) throws Exception { for (T elem : elems) elem.close(); }
6.4 타입 가변성과 와일드 카드
공변성(use-site variance) : 자바에서는 와일드카드로 메서드의 파라미터와 반환 타입이 변하는 방식을 지정한다.
- 서로 다른 배열 리스트 사이를 변환하는 일은 대부분 안전하다
- 메서드에서 배열 리스트에 쓰기를 전혀 수행하지 않아서 인자로 받은 배열 리스트를 전혀 바꾸지 않을 경우,
- 와일드카드 타입 ? extends Employee는 미지의 Employee 서브타입을 가리킨다.
- ArrayList<Employee> 나 ArrayList<Manager> 같은 서브타입의 배열 리스트로 printNames 메서드를 호출 가능

public static void printNames(ArrayList<? extends Employee> staff) {
for (int i = 0; i < staff.size(); i++) {
Employee e = staff.get(i);
System.out.println(e.getName());
}
}

- ArrayList<? extends Employee> 클래스에 속한 get 메서드의 반환 타입은 ? extends Employee가 된다.
- 따라서 Employee e = staff.get(i);는 올바른 문장이다.
- ? 타입이 무엇을 나타내든 Employee의 서브타입이다.
- staff.add(x); // 에러. add 메서드의 파라미터 타입은 ? extends Employss이므로 이 메서드에 전달할 수 있는 객체는 없다.
- ? extends Employee를 Employee로 변환할 수 있지만, 어떤 것도 절대 ? extends Employss로는 변환할 수 없다.
- 바로 이점이 ArrayList<? extends Employss>에서 읽을 수는 있지만, 쓸 수는 없는 이유다.

슈퍼타입 와일드카드 : ‘? super Employee’
- 슈퍼타입 와일드카드는 보통 함수형 객체의 파라미터로 유용하다.
- Predicatee 인터페이스에는 T 타입 객체에 특정 프로퍼티가 있는지 검사하는 메서드가 있다.
- printAll 메서드는 특정 프로퍼티가 있는 모든 직원의 이름을 호출한다.
- Predicate는 함수형 인터페이스이므로 람다 표현식을 전달해도 된다
printAll( employees, e -> e.getSalary() > 100000 );

- test의 파라미터 타입은 Employee의 슈퍼타입이므로 Employee 객체도 안전하게 전달할 수 있다.
- 함수의 파라미터 타입은 자연히 반공변(contravariant)한다.
- 함수에 직원들을 처리하는 파라미터를 전달해야 할 때는 임의의 객체를 처리하는 파라미터를 전달해도 괜찮다.
- 보통 제네릭 함수형 인터페이스를 메서드 파라미터로 지정할 때는 super 와일드카드를 사용해야 한다.

- 조건을 만족하는 요소를 출력하는 메서드의 일반화 방안
public static <T>
void printAll(T[] elements, Predicate<? super T> filter)
// 이 메서드는 T 타입이나 T의 슈퍼타입 요소용 필터를 파라미터로 받는다.

public void addAll( Collection<? extends E> c)
// 다른 컬렉션에 들어 있는 모든 요소를 추가할 수 있다. 다만 요소 타입이 E 또는 서브타입이어야 한다.
// 이 메서드로는 관리자 컬렉션을 직원 컬렉션에 추가할 수 있지만, 그 반대는 안된다.

경계없는 와일드카드 : 아주 일반적인 작업만 수행하는 상황에서 사용
- ArrayList에 null 요소가 들어 있는지 검사하는 hasNulls 메서드
- hasNulls를 제네릭 메서드로 만들 수도 있다.
public static <T> boolean hasNulls(List<T> elements)
- 하지만, 와일드 카드가 더 이해하기 쉬으므로 와일드카드를 이용한 방법이 더 적절하다

- ?를 타입 인자로 사용할 수 있지만, 타입으로는 사용할 수 없다.
- 헬퍼 메서드를 추가해서 우회해서 해결하는 방법이 있다.
- 컴파일러가 ?가 무엇인지 모르지만, ?는 어떤 타입을 나타내므로 제네릭 메서드를 호출해도 된다.
- swapHelper 메서드의 타입 파라미터 T는 와일드카드 타입을 ‘캡처’한다.
public static void swap(List<?> elements, int i, int j) {
? temp = elements.get(i); // 동작하지 않는다.
elements.set(i, elements.get(j));
elements.set(j, temp);
}



6.5 자바 가상 머신에서의 제네릭
자바 설계자들은 클래스의 제네릭 형태가 기존 버전 클래스와 호환되게 하려고 함

제네릭 타입을 정의하면 해당 타입은 raw 타입으로 컴파일 된다.
- Entry<K, V> 클래스에서 K, V가 모두 Object로 교체되어 컴파일 된다.
- 타입 변수에 경계가 있으면 첫 번째로 경계가 교체된다.
- 타입 소거는 약간 위험해 보이지만, 실제로는 아주 안전하다.

6.6 제네릭의 제약
제네릭의 제약은 대부분 타입 소거 때문에 생긴다.
- 타입 파라미터는 절대로 기본 타입이 될 수 없다.
- 실행 시간에는 모든 타입이 raw 형태다.
- 타입 변수의 인스턴스를 만들 수 없다. ex) 타입 변수는 T(…) 또는 new T[…] 같은 표현식에서 사용될 수 없다.
- 파라미터화된 타입의 배열을 생성할 수 없다.
- 정적 컨텍스트에서는 클래스 타입 변수가 유효하지 않다.
- 메서드가 소거 후 충돌하지 않을 수도 있다.
- 제네릭 클래스의 객체는 예외로 던지거나 잡아낼 수 없다. Throwable의 제네릭 서브클래스 조차 만들 수 없다

6.7 리플렉션과 제네릭
Class 클래스는 Class 객체가 기술하는 클래스를 타입 파라미터로 받는다.

소거는 오직 인스턴스화된 타입 파라미터에만 영향을 준다.



7장 컬렉션

7.1 컬렉션 프레임워크 개요
컬렉션 프레임워크에는 자료 구조의 구현체가 있다.
선택한 자료 구조에 독립적인 코드를 쉽게 작성할 수 있게 하기 위해 컬렉션 프레임워크는 공통 인터페이스를 제공한다.

그림7-1 자바 컬렉션 프레임워크 인터페이스


ArrayList 클래스와 LinkedList 클래스는 둘 다 List 인터페이스를 구현한다.
- 개발자 대부분은 순차 컬렉션이 필요할 때 배열 리스트를 사용한다.

Set은 요소를 특정 위치에 삽입하지 않으며, 중복 요소를 허용하지 않는다.
- SortedSet에는 정렬 순서로 요소를 순회하는 기능이 있으며
- NavigableSet에는 이웃 요소를 찾는 메서드가 있다.

Queue는 삽입 순서를 유지하지만, 사람들이 줄지어 있는 것처럼 요소를 뒤(tail)에서만 삽입하고, 앞(head)에서만 제거할 수 있다.
- Deque는 더블 엔디드 큐로, 양쪽 끝에서 삽입과 제거를 할 수 있다.

컬렉션 인터페이스는 모두 제네릭이며, 요소 타입에 대응하는 타입 파라미터를 받는다 (Collection<E>, List<E> 등)
Map<K, V> 인터페이스는 타입 파라미터로 키 타입에 대응하는 K와 값 타입에 대응하는 V를 받는다

코드를 작성할 때 가능하면 인터페이스를 사용하는 게 좋다.
- 예를 들어 ArrayList를 생성한 후에는 해당 참조를 List 타입 변수에 저장한다.
List<String> words = new ArrayList<>();

컬렉션을 처리하는 메서드를 구현할 때는 가장 덜 제한적인 인터페이스를 파라미터 타입으로 사용하기 바란다.
- 보통은 Collection, List, Map이면 충분하다
- 컬렉션 프레임 워크의 장점은 일반적인 알고리즘에 관해서는 ‘바퀴를 재발명‘할 필요가 없다는 것이다.

표 7-1 Collection<E> 인터페이스의 메서드


표 7-2 List 인터페이스


표 7-3 Collections 클래스의 유용한 메서드



7.2 반복자
각 컬렉션에는 어떤 순서로 요소를 순회하는 메서드가 있다.
- Collection의 슈퍼인터페이스 Iterable<T>는 다음과 같은 메서드를 정의한다
Iterator<T> iterator() // 이 메서드는 모든 요소를 방문하는 데 사용할 수 있는 반복자를 돌려준다.

- 간단하게 향상된 for 루프를 사용해도 된다.
for( String element : coll) { process(element); }
- coll이 Iterable<E> 인터페이스를 구현하는 클래스의 객체라면 무엇이든 향상된 for 루프 사용가능

ListIterator 인터페이스는 Iterator의 서브인터페이스로, 반복자 앞쪽에 요소를 추가하고 방문한 요소를 다른 값으로 설정하며 거꾸로 순회하는 메서드가 포함되어 있다.
- ListIterator 인터페이스는 주로 연결 리스트를 이용할 때 유용하다


7.3 집합
집합은 어떤 값이 요소인지 효율적으로 테스트할 수 있다.
- 집합은 요소를 추가한 순서는 기억하지 않는다.
- HashSet과 TreeSet 클래스는 Set 인터페이스를 구현한다.
- 요소에 적용할 좋은 해시 함수가 있을 때는 해시 집합이 더 효율적이다. (ex) String이나 Path 같은 라이브러리 클래스)
- 정렬된 순서로 집합을 순회하려면 TreeSet을 사용한다. (정렬된 목록을 보여준다)
- TreeSet을 사용하는 집합의 요소 타입은 반드시 Comparable 인터페이스를 구현해야 한다.
- Comparable 인터페이스를 구현하지 않을 때는 생성자에 Comparator를 전달해 주어야 한다.
- TreeSet 클래스는 SortedSet과 NavigableSet 인터페이스를 구현한다.
표 7-4 SortedSet<E> 메서드


표 7-5 NaviableSet<E> 메서드

7.4 맵
맵은 연관된 키와 값을 저장한다.
- 연관된 키와 값을 새로 추가하거나 기존 키의 값을 변경하려면 put을 호출해야 한다.
- 키를 정렬 순서로 방문해야 하는 경우가 아니라면 보통은 HashMap을 선택하는 게 좋다.
- 정렬 순서로 방문하려면 TreeMap을 사용하면 된다.
- get 메서드는 키가 없으면 null을 반환한다. (null이 반환되면, NullPointerException이 일어난다.)
- getOrDefault 메서드는 키가 없을 때는 0을 반환한다.
- 맵에 들어 있는 카운터를 업데이트할 때는 먼저 해당 카운터가 있는지 검사한 후, 있으면 기존 값에 1을 더한다. (merge 메서드)
counts.merge(word, 1, Integer::sum);
// 키가 아직 없으면 word를 1로 설정,
// 있으면 Integer::sum 함수로 기존 값에 1을 더한다.

Set<K> keySet() // 메서드를 호출하면 맵의 키, 값, 엔트리 뷰를 얻을 수 있다.
Set<Map.Entry<K, V>> entrySet()
Collection<K> values()
- 반환된 컬렉션은 맵 데이터의 사본이 아니며, 해당 맵에 연결되어 있다
- 따라서 뷰에서 키나 엔트리를 제거하면 뷰를 뒷받침하는 맵의 해당 엔트리도 제거된다.

맵에 들어 있는 모든 키와 값을 순회하려면, entrySet메서드가 반환하는 집합을 순회하면 된다.
- 혹은 간단하게 forEach 메서드를 사용해도 된다.

일부 맵 구현에서는 키나 값에 null을 허용하지 않는다.
- HashMap은 null 허용하므로 null 값을 사용할 때 매우 주의해야 한다.

LinkedhashMap은 엔트리를 추가한 순서를 기억하고 기억한 순서대로 엔트리를 순회한다.


표7-6 Map<K, V> 메서드


7.5 기타 컬렉션

프로퍼티
Properties 클래스는 텍스트 형식으로 쉽게 저장하고 불러올 수 있는 맵을 구현한다.
- 주로 프로그램의 설정 옵션을 저장하는 용도로 사용한다.
- 프로퍼티 파일은 UTF-u이 아니라 ASCII 로 인코드 된다. 설명문은 #나 !로 시작한다.
System.getProperties 메서드는 시스템 프로퍼티가 담긴 Properties 객체를 돌려준다.
- user.dir, user.home, user.name, java.version, java.home, java.class.path, java.io.tmpdir, os.name, os.arch, os.version, file.separator, path.separator, line.separator

비트 집합
BitSet 클래스는 비트 시퀀스를 저장한다.
- 비트 집합은 비트를 long 값의 배열로 패킹하므로 boolean 값의 배열을 이용할 때보다 효율적이다.
- 비트 집합은 플래그 비트 시퀀스나 양의 정수 집합을 표현하는데 유용하다.
- BitSet 클래스에는 개별 비트를 얻고 설정하는 편리한 메서드가 있다.
- 합집합, 교집합과 같은 집합 연산용으로 모든 비트에 한꺼번에 작용하는 메서드도 있다.
- BitSet 클래스는 컬렉션 클래스가 아니다.

열거 집합과 열거 맵
- 열거 값의 집합을 모으려면 BitSet 대신 EnumSet 클래스를 사용해야 한다.
- EnumSet 클래스에는 공개 생성자가 없다. (정적 팩토리 메서드로 집합을 생성한다.)
- EnumMap은 열거 타입에 속하는 키가 포함된 맵이며, 값의 배열로 구현된다.

스택, 큐, 덱, 우선순위 큐
스택을 이용할 때는 push와 pop 메서드
큐를 이용할 때는 add와 remove 메서드
- 스레드 안전 큐 : 병행 프로그램에서 사용
- 우선순위 큐는 요소를 무작위로 삽입해도 정렬된 순서로 꺼낸다. 즉, remove 메서드를 호출할 때마다 우선순위 큐에서 가장 작은 요소를 얻는다.
- 우선순위 큐는 보통 작업 스케줄링에 사용된다.

약한 해시 맵
WeakHashMap 클래스 : 가비지 컬렉터와 협동해서 키의 유일한 참조가 해시 테이블의 엔트리라면 키/값 쌍을 제거한다.
- 약한 참조로 키를 저장한다.
- WeakReference 객체는 또 다른 객체의 참조를 저장한다.

7.6 뷰
컬렉션 뷰는 컬렉션 인터페이스를 구현하는 경량 객체다.
- 하지만 요소를 저장하지는 않는다. 예를 들어 맵의 keySet과 values 메서드는 해당 맵을 들여다보는 뷰를 돌려준다.
- 보통 뷰는 자신이 구현한 인터페이스의 모든 연산을 지원하지는 않는다.

컬렉션의 콘텐츠를 공유하고 싶지만, 수정되는 건 원하지 않을 때도 있다.
- 새로운 컬렉션으로 값을 복사해 넣어도 되지만, 비용이 많이 든다
- 이럴 때는 수정불가 뷰를 사용하는 게 더 좋다.
- 컬렉션, 리스트, 집합, 정렬 집합, 탐색 가능 집합, 맵, 정렬 맵, 탐색 가능 맵 형태로 수정불가 뷰를 얻을 수 있다.




8장 스트림

8.1 반복에서 스트림 연산으로 전환하기

컬렉션을 처리할 때 보통은 요소를 순회하면서 각 요소를 이용해 원하는 작업을 수행한다.
스트림을 이용하면 필터링과 카운팅을 증명하기 위한 루프를 살펴볼 필요가 없다.
- 메서드 이름을 보면 코드가 무엇을 의도하는지 바로 알 수 있다.
- 루프에서는 연산 순서를 자세히 작성해야 하지만, 스트림은 결과만 맞으면 원하는 방식으로 스케줄링 할 수 있다.
- 단순히 stream만 parallelstream으로 바꿔주면 병렬로 수행한다.

스트림은 ‘어떻게가 아니라 무엇을‘ 이라는 원칙을 따른다.
- 수행해야 하는 일을 기술한다.
- 일을 수행할 순서나 스레드는 명시하지 않는다.

스트림은 데이터를 변환하고 추출하는 기능을 제공하므로 컬렉션과 유사해 보인다.
하지만, 큰 차이점이 있다.
1. 스트림은 요소를 저장하지 않는다. 요소는 스트림을 지원하는 컬렉션에 저장되거나 필요할 때 생성된다.
2. 스트림 연산은 원본을 변경하지 않는다. filter 메서드는 새로운 스트림에서 요소를 지우기보다는 해당 요소가 없는 새로운 스트림을 돌려준다
3. 스트림 연산은 가능하면 지연시켜둔다. 즉, 연산 결과가 필요하기 전까지는 실행되지 않는다.
예를 들어 긴 단어를 모두 요청하는 것이 아니라 처음 다섯 개만 요청했다면, filter 메서드는 다섯 번째 일치 단어를 찾은 후 필터링을 중단한다.
- 결과로 무한 스트림까지도 얻을 수 있다.

세 단계로 연산의 파이프라인을 준비한다.
1. 스트림을 생성한다.
2. 초기 스트림을 다른 스트림으로 변환하는 중간 연산을 지정한다. (여러 단계가 될 수도 있다.) ex) filter 메서드
3. 종료 연산을 적용해서 결과를 산출한다. 종료 연산은 앞에서 지정한 지연 연산이 실행되게 한다.
그 이후로는 더 이상 스트림을 사용할 수 없다. ex) count 메서드



8.2 스트림 생성
Collection 인터페이스의 stream 메서드를 이용하면 어떤 컬렉션이든 스트림으로 변환할 수 있다
배열일 때는 정적 메서드 Stream.of를 사용한다.

List<String> words = Stream.of(contents.split("\\PL+")); // split은 String[] 배열을 반환한다.
Stream<String> song = Stream.of(“gently”, “down”, “the”, “stream”); // of 메서드는 가변 인자를 파라미터로 받는다.

- 배열의 일부에서 스트림을 만들려면 , Arrays.stream(array, from, to)
- 요소가 없는 스트림을 만들려면, Stream<String> silence = Stream.empty()
// 제네릭 타입 <String>이 추론되므로 Stream.<String>empty()와 같다.

Stream 인터페이스에는 무한 스트림을 만드는 정적 메서드 두 개가 있다.
- generate 메서드 : 인자 없는 함수(기술적으로는 Supplier<T> 인터페이스 객체)를 받는다. 스트림 값이 필요할 때마다 지저한 함수를 호출하여 값을 만들어 낸다
Stream<String> echos = Stream.generate( () -> “Echo” );
Stream<Double> randoms = Stream.generate( Math::random ); // 난수의 스트림

- iterate 메서드 : 무한 수열 (0, 1, 2, … )을 만들려면 이 메서드를사용하면 된다.
iterate 메서드는 ‘시드‘ 값과 함수(기술적으로는 UnaryOperator<T>)를 받아서 해당 함수를 이전 결과에 반복하여 적용한다
Stream<BigInteger> integers = Stream.iterate( BigInteger.ZERO, n -> n.add(BigInteger.ONE) );
// 수열의 첫 번째 요소는 시드 값인 BigInteger.ZERO. 두 번째 요소는 f(seed), 즉 1(BigInteger). 그 다음 요소는 f(f(seed) ), 즉 2가 되는 식이다.

8.3 filter, map, flatMap 메서드
스트림 변환은 또 다른 스트림에 들어 있는 요소에서 파생된 요소의 스트림을 만들어낸다.
filter 변환은 새로운 스트림을 돌려주는데, 이 스트림은 특정 조건과 일치하는 요소로 구성된다.

map 메서드 : 종종 스트림에 들어 있는 값을 특정 방식으로 변환하고 싶을 때 사용하고, 해당 변환을 수행하는 함수를 전달하면 된다.
Stream<String> lowercaseWords = words.stream().map( String::toLowerCase ); // 모든 단어를 소문자로 변환, 메서드 참조와 함께 map 메서드 사용
Stream<String> firstLetters = words.stream().map( s -> s.substring(0, 1) ); // 람다 표현식, 결과로 나오는 스트림에는 각 단어의 첫 글자가 포함된다.
- map을 사용하면 새로운 스트림이 만들어지고, 이 스트림에는 지정한 함수를 각 요소에 적용한 결과들이 담긴다.
- letters(“boat”)는 스트림 [“b”, “o”, “a”, “t”]를 만들어 낸다.
- letters 메서드는 문자열 스트림에 매핑한다고 해보자
Stream<Stream<String>> result = words.stream().map( w -> letters(w) );
// 스트림의 스트림을 얻는다. [ … [“y”, “o”, “u”], [“b”, “o”, “a”], … ]

- 이 스트림을 문자열 스트림으로 펼쳐내려면 map 대신 flatMap을 사용해야 한다.
Stream<String> letters = Stream.of(song).flatMap(w -> letters(w));
※ 스트림이 아닌 클래스에서도 flatMap 메서드를 접할 수 있다. flatMap은 컴퓨터 과학에서 일반적인 개념이다.


8.4 서브스트림 추출과 스트림 결합하기
스트림.limit(n) 호출은 요소 n개 이후(또는 원본 스트림이 n보다 짧으면 원보니 끝날 때)에 끝나는 새로운 스트림을 반환한다.
- 이 스트림은 무한 스트림을 원하는 크기로 자를 때 유용하다.
Stream<Double> randoms = Stream.generate( Math::random ).limit( 100 );
// 난수 100개가 포함된 스트림을 돌려준다.

스트림.skip(n) 호출은 앞서 봤던 호출의 반대 작업을 수행한다. 즉, 처음 n개 요소를 버린다.
Stream<String> words = Stream.of( contents.split(“\\PL+”)).skip(1); // 불필요한 첫 번째 요소를 사라지게 한다.

Stream 클래스의 정적 메서드 concat을 이용하면 두 스트림을 연결할 수 있다.
Stream<String> combined = Stream.concat( letters(“He”), letters(“Ho”) );
// 스트림 [“H”, “e”, “H”, “o”]를 돌려준다. 첫 번째 스트림은 무한 스트림이면 안된다

8.5 기타 스트림 변환
distinct 메서드는 원본 스트림에 있는 요소를 같은 순서로 돌려주는 스트림을 반환한다.
- 중복을 제거하는 점을 제외하면 같다. 중복은 꼭 인접해 있지 않아도 된다.
Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct();
show("uniqueWords", uniqueWords);

스트림 정렬용으로 사용하는 sorted 메서드는 여러 변형이 있다.
- Comparable 요소의 스트림에 작업하거나 Comparator 를 받는 것도 있다.
- 원본 스트림의 요소가 정렬된 새로운 스트림을 돌려준다
- 물론 스트림을 이용하지 않아도 컬렉션을 정렬할 수 있다.
- sorted 메서드는 정렬 과정이 스트림 파이프라인의 일부일 때 유용하다
Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());

peek 메서드는 원본과 동일한 요소가 포함된 다른 스트림을 돌려준다
- 다른 스트림을 돌려주는데도 요소를 추출할 때마다 전달받는 함수를 호출한다.
- 디버깅할 때 유용하다
Object[] powers = Stream.iterate(1.0, p -> p * 2).peek(e -> System.out.println("Fetching " + e)).limit(20).toArray();
// 요소에 실제로 접근할 때 메시지를 출력한다.
- 무한 스트림이 지연 처림됨을 확인할 수 있다.
- 무한 스트림은 iterate 메서드가 반환한다
- 디버깅 할 때는 브레이크 포인트를 설정해놓은 메서드를 peek에서 호출되게 할 수 있다.


8.6 단순 리덕션
지금까지 스트림을 생성하고 반환하는 방법을 살펴봤다. 이번 절에서는 스트림 데이터로부터 결과를 얻는 것이다.
- 리덕션(reduction) 메서드라고 한다.
- 리덕션은 종료 연산이다
- 리덕션은 스트림을 프로그램에서 사용할 수 있는 넌스트림 값으로 리듀스한다.
ex) count 메서드

최대값과 최소값을 반환하는 max와 min 메서드
- 주의할 점은 이 메서드들을 Optional<T> 값을 반환한다
- Optional<T> 값을 결과를 감싸고 있거나 결과가 없음을 의미한다 (스트림이 비어 있을 경우)
- 예전에는 null 반환 : 예외를 일으킬 수 있다.
Optional<String> largest = words.stream().max(String::compareToIgnoreCase);
System.out.println("largest: " + largest.orElse(""));



findFirst 메서드는 비어 있지 않은 컬렉션의 첫 번째 값을 반환한다.
- filter와 결합하면 유용하다
- 글자 Q로 시작하는 단어가 있다며, 글자 Q로 시작하는 첫 번째 단어 찾기
boolean aWordStartsWithQ = words.stream().anyMatch(s -> s.startsWith("Q"));
System.out.println("aWordStartsWithQ: " + aWordStartsWithQ);

첫 번째 값은 물로 어떤 일치 결과든 괜찮을 때는 findAny 메서드를 사용한다.
Optional<String> startsWithQ = words.stream().parallel().filter(s -> s.startsWith("Q")).findAny();
System.out.println("startsWithQ: " + startsWithQ.orElse("(None)"));
// Run the program again to see if it finds a different word

단순히 일치하는 요소가 있는지 알고 싶을 때는 anyMatch 메서드를 사용한다
- 이 메서드는 프레디케이트 인자를 받으므로 filter를 사용할 필요가 없다.
boolean aWordStartsWithQ = words.parallel().anyMatch( s -> s.startsWith(“Q”) );

모든 요소가 프레디케이트와 일치하거나 아무 것도 일치하지 않으면 true를 반환하는 allMatch와 noneMatch 메서드도 있다.
- 이 메서드들도 병렬 실행의 이점을 얻을 수 있다.v
8.7 옵션 타입 Optional<T> 객체는 T 타입 객체가 있거나 객체가 없을 때의 래퍼다.
- 객체나 null을 가리키는 T 타입의 참조보다 안전하다.
- 하지만 올바르게 사용할 때만 안전하다
- Optional을 효과적으로 사용하려면 값이 없을 때는 대체 값을 생산하고, 값이 있을 때만 해당 값을 소비하는 메서드를 사용해야 한다

값이 없을 때는 대체 값을 생산하는 방법
- 보통은 일치하는 부분이 없을 때 사용하고 싶은 기본값이 있다.
String result = optionalString.orElse("");
// 옵션 값으로 래핑된 문자열, 문자열이 없으면 “”



result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
// 필요할 때만 함수가 호출된다.

또는 값이 없을 때는 다음과 같이 예외를 던져도 된다.
result = optionalString.orElseThrow(IllegalStateException::new);
// 예외 객체를 돌려주는 메서드를 전달한다.

값이 있을 때만 소비하는 방법
ifPresent 메서드는 함수를 받는다.
- 옵션 값이 있을 때는 해당 함수로 값이 전달된다.
- 반면에 옵션 값이 없을 때는 아무 일도 일어나지 않는다
optionalValue.ifPresent(s -> s를 처리한다);

값이 있을 때 집합에 해당 값을 추가하려면,
optionalValue.ifPresent(s -> results.add(v) );
// = optionalValue.ifPresent(results::add);

ifPresent를 호출하면 함수에서 어떤 값도 반환받을 수 없다.
함수의 결과를 처리하려면 map 메서드를 사용한다.
Optional<Boolean> added = optionalValue.map(results::add);
// 이제 added는 세 가지 값 중 하나가 된다.
// true / false / 빈Optional
- 이 map 메서드는 Stream 인터페이스의 map 메서드에 대응한다.

Optional 값을 올바르게 사용하지 않으면 예전에 사용하던 ‘어떤 것 또는 null’ 접근법보다 나을 게 없다.
get 메서드는 Optional 값에 래핑된 요소가 있으면 얻어오고, 그렇지 않으면 NoSuchElementException을 던진다.
Optional<T> optionalValue = …;
optionalValue.get().someMethod();

isPresent 메서드는 Optional<T> 객체가 값을 담고 있는지 알려준다.
if( optionalValue.isPresent() ) optionalValue.get().someMethod(); // if( value != null ) value.someMethod(); 뭐가 더 쉬운지??

Optional 객체를 생성하는 메서드를 작성하고 싶을 때는 어떻게 해야 할까?
- 정적 메서드 Optional.of( result ) 나 Optional.empty() 를 사용한다.
public static Optional<Double> inverse(Double x) {
return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

ofNullable 메서드는 잠재적인 null 값을 옵션 값으로 이어주는 용도로 만들어졌다.
- Optional.ofNullable( obj )는 obj가 null이 아니면 Optional.of( obj )를, null이면 Optional.empty()를 반환한다.

Optional<T> 를 돌려주는 메서드 f가 있고, 타깃 타입 T에는 Optional<U>를 돌려주는 메서드 g가 포함되어 있다고 해보자.
- 만일 일반 메서드라면, s.f().g() 메서드를 호출하여 메서드 합성을 할 수 있다.
- 하지만 이렇게 호출하면, s.f()는 T가 아닌 Optional<T> 타입이 되므로 합성되지 않는다.
- 해결책
Optional<U> result = s.f().flatMap(T::g); // s.f()가 있으면 g가 적용된다. s.f()가 없으면 비어 있는 Optional<U>가 반환된다.

Optional 값을 돌려주는 다른 메서드나 람다가 있다면 이 과정을 반복할 수 있다.
- 호출을 flatMap으로 연쇄하는 방법을 이용해 단계 파이프라인을 구축할 수 있다.
- 이 파이프 라인은 모든 부분이 성공해야만 전체가 성공한다.
public static Optional<Double> squareRoot(Double x) {
return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
Optional<Double> result = inverse(x).flatMap( MyMath::squareRoot); // inverse의 루트를 계산
Optional<Double> result = Optional.of(-4.0).flatMap(Demo::inverse).flatMap(Demo::squareRoot);
// inverse나 squareRoot 메서드 중 하나가 Optional.empty()를 반환하면 결과는 비어 있게 된다.

Stream의 flatMap 메서드는 스트림을 돌려주는 두 메서드를 합성해, 스트림의 결과로 나오는 스트림을 펼쳐내는 데 사용한다.
- 옵션 값을 크기가 0이나 1인 스트림으로 생각하면 Optional.flatMap 메서드도 같은 방식으로 동작한다.

8.8 결과 모으기
스트림을 이용한 작업을 마치면 보통은 결과를 살펴본다.
- 결과를 살펴볼 때는 iterator 메서드를 호출한다.
- iterator 메서드는 요소를 방문하는 데 사용하는 전통적인 반복자를 돌려준다.


- 다음과 같이 forEach 메서드를 호출해서 각 요소에 함수를 적용하는 방법도 있다.
steam.forEach( System.out::println );
- 병렬 스트림에서 forEach 메서드는 요소를 임의의 순서로 순회한다.
- 스트림 순서로 처리하려면 forEachOrdered 메서드를 호출해야 한다.
(병렬성이 주는 이점을 포기해야 할 수도 있다.)

toArray 메서드를 호출하면 스트림 요소의 배열을 얻을 수 있다.
- 실행 시간에 제네릭 배열을 생성할 수 없으므로 stream.toArray()는 Object[]을 반환한다.
- 올바른 타입 배열이 필요할 때는 배열 생성자를 전달한다.
String[] result = stream.toArray( String[]::new );
// stream.toArray()의 타입은 object[]

스트림 요소를 또 다른 타깃으로 모으는 데는 collect 메서드를 이용하면 편리하다.
- collect 메서드는 Collector 인터페이스의 인스턴스를 받는다.
- Collectors 클래스에는 공통 컬렉터용 팩토리 메서드가 여러 개 있다.
- 스트림을 리스트로 모을 때
List<String> result = stream.collect( Collectors.toList() );
Set<String> result = stream.collect( Collectors.toSet() ); // 집합으로 모을 때
TreeSet<String> result = stream.collect( Collectors.toCollection(TreeSet::new) ); // 어떤 집합 종류를 얻을 지 제어하 고 싶을 때
String result = stream.collet( Collectors.joining() ); // 스트림에 있는 모든 문자열을 서로 연결해서 모을 때
String result = stream.collet( Collectors.joining(“, “) ); // 요소 간에 구분자가 필요할 때
String result = stream.map(Object::toString).collet( Collectors.joining() ); // 문자열 외의 객체가 포함되면, 먼저 해당 객체를 문자열로 변환한다

스트림 결과를 합계, 평균, 최대값, 최소값으로 리듀스 하려면, summarizing(Int | Long | Double) 메서드 중 하나를 사용해야 한다.
- 이 메서드들은 스트림 객체를 숫자로 매핑하는 함수를 받고 합계, 평균, 최대값, 최소값을 동시에 계산해서,
- (Int | Long | Double)SummaryStatistics 타입 결과를 돌려준다.
IntSummaryStatistics summary = noVowels("alice.txt").collect(
Collectors.summarizingInt(String::length));
double averageWordLength = summary.getAverage();
double maxWordLength = summary.getMax();
System.out.println("Average word length: " + averageWordLength);
System.out.println("Max word length: " + maxWordLength);

8.9 맵으로 모으기
Stream<Person> 객체의 요소를 맵으로 모아 나중에 ID로 사람을 조회할 수 있게 한다고 해보자.
- Collectors.toMap 메서드는 두 함수의 인자를 받고, 각각 맵의 키와 값을 만들어낸다.
Map<Integer, String> idToName = people().collect( Collectors.toMap(Person::getId, Person::getName));

- 값이 실제 요소여야 하는 상황에서는 두 번째 함수로 Function.identify()를 이용한다.
Map<Integer, Person> idToPerson = people().collect( Collectors.toMap(Person::getId, Function.identity()));
- 키가 같은 요소가 두 개 이상이면 충돌이 일어난다. (IllegalStateException을 던진다)

8.10 그루핑과 파티셔닝
특성이 같은 값을 그룹으로 만드는 일은 아주 흔한 작업이다.
- groupingBy 메서드가 직접 지원한다.
Map<String, List<Locale>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
// 로케일을 국가별로 그루핑, Locale.getCountry는 그루핑의 분류 함수다.

- 분류 함수가 프레디케이트 함수(즉, boolean을 반환하는 함수)면 스트림 요소가 리스트 두 개로 나뉜다.
- 여기서 리스트 두 개는 각각 함수에서 true와 false를 반환할 때의 리스트를 말한다.
- 이때는 partitioningBy 메서드를 사용하면 효율적이다.
Map<Boolean, List<Locale>> englishAndOtherLocales = locales.collect(Collectors.partitioningBy(l -> l.getLanguage().equals("en")));



8.11 다운스트림 컬렉터
groupingBy 메서드는 값이 리스트인 맵을 돌려준다.
이런 리스트를 특정 방식으로 처리하려면 ‘다운스트림 컬렉터’를 작성해야 한다.
- 예를 들어, 리스트 대신 집합이 필요할 때는 Collectors.toSet 컬렉터를 사용한다
Map<String, Set<Locale>> countryToLocaleSet = locales.collect( groupingBy(Locale::getCountry, toSet()));
// import static java.util.stream.Collectors.*; 표현식을 쉽게 읽기 위해서 정적 임포트함

스트림 라이브러리에는 그룹으로 묶인 요소를 숫자로 리듀스하는 데 사용하는 컬렉터가 몇 가지 있다.
- counting은 모인 요소의 개수를 센다.
- summing(Int|Long|Double)은 함수 인자를 받아, 해당 함수를 다운스트림 요소에 적용하고 합계를 구한다.
- maxBy와 minBy는 비교기 하나를 받아서 다운스트림 요소의 최대값과 최소값을 구한다.
- mapping은 함수를 다운스트림 결과에 적용하며, 이 결과를 처리하는 데 필요한 또 다른 컬렉터를 필요로 한다.

그루핑이나 매핑함수가 int, long, double 타입을 반환하면 요소를 요약 통계 객체로 모을 수 있다.
Map<String, IntSummaryStatistics> stateToCityPopulationSummary
= cities.collect( groupingBy(City::getState, summarizingInt(City::getPopulation)));
- 이 외에도 reducing 메서드는 세 가지 버전이 있는데 범용 리덕션을 적용한다.

컬렉터 합성은 강력하지만 아주 난해한 표현식을 만들 수도 있다.
- 가장 좋은 사용법은 groupingBy나 partitioningBy로 ‘다운 스트림‘ 맵 값을 처리하는 것이다.
- 그렇지 않으면 그냥 스트림에 직접 map, reduce, count, max, min 같은 메서드를 적용하는 게 좋다.

8.12 리덕션 연산


reduce 메서드는 스트림에서 값을 계산하는 메커니즘이다.
- 가장 단순한 형태는 이항 함수를 받아서 처음 두 요소부터 시작해 계속해서 함수를 적용하는 형태다.
ex) 합계 함수 : 스트림이 비어 있으면 유효한 결과가 없으므로 Optional을 반환
- 리덕션 연산자 op를 받으면, 해당 리덕션은 v0 op v1 op v2 op …을 돌려준다. = op(v0, v1, v2, …)
- 연산은 결합법칙을 지원해야 한다. 즉, 요소를 결합하는 순서와는 무관해야 한다.
- 결합 법칙을 지원하면 병렬 스트림을 통한 효율적인 리덕션이 가능하다.
- 합계, 곱셈 문자열 연결, 최대/최소값, 합집합/교집합 등 (단 뺄셈, 나눗셈은 안된다)

- 항등값 (0은 덧셈의 항등값)이 있으면 reduce의 두 번째 형태를 호출한다.
values = Stream.of(digits);
Integer sum2 = values.reduce(0, (x, y) -> x + y); // 0+v0+v1+...
- 스트림이 비어있으면 항등값을 반환한다. 그러므로 Optional 클래스는 사용할 필요가 없다.

객체의 스트림이있고, 문자열 스트림에 들어 있는 모든 길이 같은 특정 프로퍼티의 합계를 구한다고 해보자.
- 인자와 결과의 타입이 같은 (T, T) -> T 함수가 필요하다.
- 먼저, 누산기 함수 (total, word) -> total + word.length()를 전달한다. 이 함수는 반복 호출되어 누적 합계를 만든다.
- 계산을 병렬화하면 이와 같은 계산이 여러 개 생기게 되므로 각각의 결과를 결합해야 한다.

int result = words.stream().reduce( 0, (total, word) -> total + word.length(), (total1, total2) -> total1 + total2);

실무에서는 reduce 메서드를 많이 사용하지 않을 것이다.
- 보통은 숫자 스트림에 매핑한 후 스트림의 합계, 최대/최소값을 계산하는 메서드 중 하나를 사용하는 방법이 더 쉽다.

reduct로 충분하지 않을 때는 collec를 사용해야 한다. collec는 인자 세 가지를 받는다.
- 공급자(supplier) : 타깃 객체의 새로운 인스턴스를 만든다
- 누산기(accumulator) : 요소를 타깃에 추가한다
- 결합기(combiner) : 두 객체를 하나로 병합한다.

8.13 기본 타입 스트림
정수를 래퍼 객체로 감싸는 일은 명백히 비효율적이다.
스트림 라이브러리에는 IntStream, LongStream, DoubleStream이 있는데, 이들은 기본 타입 값들을 직접 저장하는 데 특화된 타입이다.

IntStream을 생성하려면, IntStream.of와 Arrays.stream 메서드를 호출하면 된다
IntStream steam = IntStream.of( 1, 1, 2, 3, 5);
stream = Arrays.stream( values, from, to ); // values는 int[] 배열이다.
IntStream is1 = IntStream.generate(() -> (int) (Math.random() * 100));
IntStream is2 = IntStream.range(5, 10); // 크기 증가 단위가 1인 정수 범위, 상한값 제외
IntStream is3 = IntStream.rangeClosed(5, 10); // 상한 값 포함

CharSequence 인터페이스에는 codePoints와 chars 메서드가 있다.
- 각각 문자의 유니코드와 UTF-16 인코딩의 코드 유닛으로 구성된 IntStream을 돌려준다.
String sentence = "\uD835\uDD46 is the set of octonions.";
IntStream codes = sentence.codePoints();

객체 스트림은 mapToInt, mapToLong, mapToDouble 메서드를 이용해 기본 타입 스트림으로 변환할 수 있다.

Stream<String> words = …;
IntStream lengths = words.mapToInt( String::length ); // 문자열 스트림에서 문자열 요소의 길이를 정수로 처리

Stream<Integer> integers = IntStream.range(0, 100).boxed();
// 기본 타입 스트림을 객체 스트림으로 변환하려면 boxed 메서드 사용

기본 타입 스트림에 동작하는 메서드는 객체 스트림에 동작하는 메서드와 유사하다.
다음은 주목할 만한 차이점이다.
- toArray 메서드는 기본 타입 배열을 반환한다.
- 옵션 결과를 돌려주는 메서드는 OptionalInt, OptionalLong, OptionalDouble을 반환한다.
- 각각 합계, 평균, 최대/최소값을 반환하는 sum, average, max, min 메서드가 있다. (객체 스트림에는 이러한 메서드가 정의되어 있지 않다.)
- summaryStatistics 메서드는 스트림의 합계, 평균, 최대/최소값을 동시에 보고할 수 있는 IntSummaryStatistics, LongSummaryStatistics, DoubleSummaryStatistics 타입 객체를 돌려준다.

Random 클래스에는 ints, longs, doubles 메서드가 있다.
- 이 메서드들은 난수로 구성된 기본타입 스트림을 반환한다.

8.14 병렬 스트림
스트림은 벌크 연산을 병렬화하기 쉽게 해준다.
- 처리 과정은 대부분 자동이지만, 몇몇 규칙을 따라야 한다.
- 먼저 병렬 스트림이 있어야 한다.
- 병렬 스트림은 어떤 컬렉션에서든 Collection.parallelStream() 메서드로 얻을 수 있다.
Steam<String> parallelWords = words.parallelStream();



- parallel() 메서드는 어떤 순차 스트림이든 병렬 스트림으로 변환한다.
Steam<String> parallelWords parallelWords = Stream.of(wordArray).parallel();

스트림이 병렬 모드에 있으면 종료 메서드가 실행될 때 중간 스트림 연산이 모두 병렬화된다.
- 스트림 연산이 병렬로 실행될 때, 목적은 차례로 실행됐을 때와 같은 결과를 반환하는 것이다.
- 따라서 연산은 상태가 없고 임의의 순서로 실행할 수 있는 것이어야 한다.

- forEach에 전달된 함수는 여러 스레드에서 동시에 실행되어 공유 배열을 업데이트 한다. 이 상황은 경쟁 조건이다.
- 이 프로그램을 여러 번 실행하면, 매번 실행할 때마다 다른 개수를 얻고 각각도 잘못된 결과일 가능성이 크다.

병렬 스트림 연산에 전달할 함수를 안전하게 병렬로 실행할 수 있게 만드는 일은 개발자의 몫이다.
- 가장 좋은 방법은 변경 가능한 상태를 멀리하는 것이다.
- 이 예에서는 문자열을 길이로 묶어서 세면 계산을 안전하게 병렬화할 수 있다.

- 기본적으로 순서 유지 컬렉션(배열과 리스트), 범위, 발생기, 반복자, Stream.sorted를 호출해 얻는 스트림은 순서를 유지한다.
- 순서 유지 스트림의 결과는 원본 요소 순서로 쌓이고, 전적으로 예측 가능하게 동작한다.
- 순서 적용이 효율적인 병렬화를 방해하지는 않는다.

- 일부 연산은 순서에 대한 요구 사항을 버리면 더 효과적으로 병렬화할 수 있다.
- Stream.unordered 메서드를 호출하여 순서에는 신경 쓰지 않음을 나타낼 수 있다.
- 이로부터 이점을 얻을 수 있는 연산은 Stream.distinct 다
: 유일한 요소이고 어느 것을 보존해도 괜찮다면 중복을 추적하는 공유 집합을 사용해서 모든 세그먼트를 동시에 처리할 수 있다.
- 순서를 포기하면 limit 메서드를 빠르게 만들 수 있다.
Stream<String> sample = words.parallelStream().unordered().limit( n );

맵을 병합하는 일은 비용이 많이 든다.
- 따라서 Collectors.groupingByConcurrent 메서드는 공유되는 병행 맵을 사용한다.
- 병렬화 이점을 얻으려고 맵 값의 순서가 스트림 순서와 달라질 수 있다.

스트림 연산을 수행하는 동안에는 스레드에 안전한 수정이라도 해당 스트림을 뒷받침하는 컬렉션을 절대 수정하면 안된다.
- 스트림은 직접 데이터를 모으지 않는다는 것을 기억해두자. (데이터는 항상 별도의 컬렉션에 존재한다)
- 만을 해당 컬렉션을 수정하면 스트림 연산들의 결과는 정의되지 않는다. (방해 금지)
- 정확히 말하면, 중간 스트림 연산은 지연 처리되므로 종료 연산이 실행되기 전까지는 컬렉션을 변경할 수 있다. (권장하는 방법은 아니다)



9장 입출력 처리

9.1 입력/출력 스트림, 리더와 라이터
자바 API에서 바이트를 읽어 올 소스(출처)를 입력 스트림이라고 한다.
바이트는 파일, 네트워크 연결, 메모리에 있는 배열에서 읽어올 수 있다. (스트림과 연관은 없다.)
유사하게 바이트의 목적지는 출력 스트림이라고 한다.
reader와 writer 는 문자의 시퀀스를 소비하고 생산한다.

스트림 얻기 : 파일에서 스트림을 얻는 가장 쉬운 방법은 정적 메서드를 사용하는 것이다.
InputStream in = Files.newInputStream( path );
OutputStream out = File.newOutputStram( path ); // path는 Path 클래스의 인스턴스다. Path는 파일 시스템에서 경로를 나타낸다.

URL이 있을 때는 입력 스트림에서 해당 URL의 콘텐츠를 읽을 수 있다.
URL url = new URL(http://horstmann.com/index.html);
InputStream in = url.openStream();

ByteArrayInputStream / ByteArrayOutputStream 클래스를 이용하면 바이트 배열에서 읽고 쓸 수 있다.

InputStream 클래스에는 바이트 한 개를 읽는 메서드가 있다.
InputStream in = …;
int b = in.read(); // byte 타입 : -128 ~ 127
- 바이트를 벌크로 읽을 수도 있고, 입력 스트림에 있는 바이트를 모두 읽어 오는 메서드도 있다.
in.read( bytes );
copy( in, out );

OutputStream의 write 메서드는 개별 바이트와 바이트 배열을 쓸 수 있다.
OutputStream out = …;
int b = ..;
out.write( b );
byte[] bytes = …;
out.write( bytes );
- 스트림에 쓰기를 마친 후에는 반드시 해당 스트림을 당아서 버퍼에 저장된 출력을 커밋(commit)해야 한다.
- 가장 좋은 방법은 try-with-resource 문을 이용하는 것이다.
try (OutputStream out = … ) {
out.write( bytes );
}

입력 스트림을 출력 스트림으로 복사할 때는 헬퍼 메서드를 사용한다.
while( (len = in.read( bytes ) ) != -1 ) out.write( bytes, 0, len );

입력과 출력 스트림은 바이트 시퀀스 용이지만 대부분 텍스트를 다룬다.
- 바이트 시퀀스가 아니라 문자 시퀀스를 다룬다. 텍스트를 다룰 때는 텍스트가 바이트로 인코드되는 방식이 중요하다.
- 자바는 문자에 유니코드 표준을 사용한다. 각 문자나 코드 포인트는 21비트 정수다.
- 문자 인코딩은 21비트 숫자로 바이트로 패키징하는 것이고, 문자 인코딩을 하는 방법은 여러 가지가 있다.
- UTF-8, UTF-16 인코딩
- 바이트 스트림에서 문자 인코딩을 자동으로 감지하는 신뢰할 만한 방법이 없다.
- 그러므로 언제나 명시적으로 인코딩을 지정해야 한다. (ex) 웹 페이지의 Content-Type 선언부 검사

StandardCharsets 클래스에는 Charset 타입 정적 변수가 있는데,
- 이 Charset 타입 정적 변수는 모든 자바 가상 머신에서 반드시 지원해야 하는 문자 인코딩을 나타낸다.

텍스트 입력을 읽을 때는 Reader를 사용한다.
- InputStreamReader 어댑터를 사용하면 어떤 입력 스트림에서든 Reader를 얻을 수 있다.
InputStream instream = …;
Reader in = new InputStreamReader( instream, charset );
int ch = in.read(); // read 메서드는 0 ~ 65536 사이에 있는 코드 유닛을 반환하거나 입력의 끝에 이르면 -1을 반환한다. 아주 불편하다
String content = new String( Files.readAllBytes( path ), charset ); // 짧은 텍스트 파일일 때는 문자열 하나로 읽는다
List<String> lines = Files.readAllLines( path, charset ); // 파일을 일련의 줄로 읽으려면

try( Stream<String> lines = Files.lines( path, charset ) ) { // Stream으로 지연 처리하는 방법이 더 좋다.

} // 스트림이 줄을 가져올 때 IOException이 일어나면 해당 예외가 UncheckedIOException으로 래핑된다.

파일에서 숫자나 단어를 읽으려면 Scanner를 사용해야 한다.
Scanner in = new Scanner( path, “UTF-8” );
while( in.hasNextDouble() ) {
double value = in.nextDouble() ;

}
- 알파벳 단어를 읽으려면 스캐너의 구분자를 정규 표현식으로 설정해야 하는데, 토큰으로 받아들일 단어는 제외한다.
- 파일에서 입력이 오지 않으면 InputStream을 BufferedReader로 래핑한다.

try ( BufferedReader reader = new BufferedReader( new InputStreamReader( url.openStream() ) ) ) {
Stream<String> lines = reader.lines();

}

- BufferedReader는 효율성을 높이려고 입력을 청크(chunk)로 읽는다.
- BufferedReader에는 단일 줄을 읽는 readLine 메서드와 줄들의 스트림을 돌려주는 lines 메서드가 포함되어 있다.
호출할 메서드가 Reader를 요구할 때 해당 메서드를 파일에서 읽어오게 하려면,
- File.newBufferedReader( path, charset )을 호출해야 한다.

텍스트를 쓸 때는 Writer를 사용한다.
- write 메서드로 문자열을 쓸 수 있다.
- 어떤 출력 스트림이든 Writer로 변환할 수 있다.
OutputStream outStream = …;
Writer out = new OutputStreamWriter( outStream, charset );
out.write( str );

Writer out = Files.newBufferedWriter( path, charset ); // 파일에 대응하는 라이터를 얻는다

PrintWriter에는 print, println, printf 메서드가 있는데, System.out을 통해 사용해온 메서드다.
PrintWriter out = new PrintWriter( Files.newBufferedWriter( path, charset ) ); // 파일에 쓸 때
PrintWriter out = new PrintWriter( outStream, “UTF-8” ); // 또 다른 스트림에 쓸 때
- PrintWriter 생성자는 Charset 객체가 아닌 문자 인코딩에 대응하는 문자열을 요구한다
System.out은 PrintWriter가 아닌 PrintStream의 인스턴스다.

String content = …; // 파일에 쓸 텍스트가 이미 문자열에 들어 있을 때,
Files.write( path, content.getBytes( charset ) ); // or Files.write( path, lines, charset );
// lines는 Collection<String> 이나 Iterable<? extends CharSequence>

파일에 텍스트 추가
Files.write ( path, content.getBytes(charset), StandardOpenOption.APPEND );
Files.write( path, lines, charset, StandardOpenOption.APPEND);

DataInput 인터페이스는 다음 메서드들을 선언한다.
- 숫자, 문자, 불 값, 문자열을 바이너리 형식으로 쓰는 데 사용한다
- byte readByte(), int readUnsignedByte(), char readChar(), …
※ DataOutput 인터페이스는 대응하는 write 메서드들을 선언한다.

바이너리 입출력의 장점은 너비가 고정되어 있고 효율적이라는 것이다.
- DataInputStream과 DataOutputStream 어댑터는 어떤 스트림에든 사용할 수 있다.

RandomAccessFile 클래스는 파일의 어디에서든 데이터를 읽거나 쓰는 기능을 제공한다.
- 임의 접근 파일은 읽기 전용이나 쓰기용으로 열 수 있다.
- 임의 접근 파일에는 파일 포인터가 포함되어 있는데, 이는 읽거나 쓸 바이트의 다음 위치를 가리킨다.
- seek 메서드는 파일에서 파일 포인터를 임의의 바이트 위치로 설정한다.
- seek의 인자는 0과 파일 길이 사이의 long 정수이다.
- 파일 길이는 length 메서드로 얻을 수 있다.
- getFilePointer 메서드는 파일 포인터의 현재 위치를 반환한다.
- DataInput과 DataOutput 인터페이스를 구현한다.

메모리 맵 파일은 또 다른 임의 접근 방법을 제공하는데, 큰 파일도 잘 다루고 매우 효율적이다.
- 입출력 스트림과 다르다.
- 먼저 파일에 대한 채널을 얻는다
FileChannel channel = FileChannel.open( path, StandardOpenOption.READ, StandardOpenOption.WRITE )
- 그런다음 파일의 영역을 메모리로 매핑한다.
ByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size() );
- 값을 읽을 때는 get, getInt, getDouble 등의 메서드를 사용하고, 쓸 때는 상응하는 put 메서드들을 사용한다.

동시에 실행 중인 여러 프로그램이 같은 파일을 수정할 때는 프로그램 사이에서 파일 잠금 기능을 이용한다.
- FileChannel 클래스의 lock 메서드나 tryLock 메서드를 호출한다.
FileChannel channel = FileChannel.open( path );
FileLock lock = channel.lock(); // or lock = channel.tryLock();
- 첫번째 잠금을 이용할 수 있을 때까지 블록한다.
- 두 번째 호출은 잠금 또는 null로 즉시 반환한다.
- 파일은 해당 잠금이나 채널이 닫힐 때까지 잠긴다. (try-with-resources 문을 사용해야 한다.)

9.2 경로, 파일, 디렉터리
Path는 디렉터리 이름으로 경우에 따라 파일 이름이 붙는다.
- 정적 메서드 Paths.get은 문자열을 한 개 이상 받아 기본 파일 시스템의 경로 구분자를 이용해 결합한다.
- Paths.get 메서드에 경로 구분자가 포함된 문자열을 전달해도 된다
Path homeDir = Paths.get(“/home/cay”);
- Path 객체는 그저 추상적인 이름을 나타내므로 실제 있는 파일에 대응할 필요가 없다.
- 파일을 생성하려면 우선 경로를 만들고 메서드를 호출하면 된다.

경로를 결합하거나 해석 : p.resolve(q) ~ q가 절대 경로면 결과는 q, q가 상대 경로면 결과는 ‘p 다음 q’
- resvolveSibling 메서드는 경로의 부모를 기준으로 경로를 해석해서 이웃 경로를 돌려준다.
- resolve의 반대는 relative다.
- normalize 메서드
- toAbsolutePath 메서드 : 지정한 경로의 절대 경로를 돌려준다
- Path 인터페이스에는 경로를 분리하고 다른 경로와 결합하는 메서드가 있다.
- Path 인터페이스는 Iterable<Path> 를 확장(상속)한므로 향상된 for 루프로 Path의 이름 구성 요소를 순회할 수 있다.

새 디렉터리를 생성하려면 : Files.createDirectory( path );
빈 파일을 생성 : Files.createFile( path );
Files.exists( path )는 지정한 파일이나 디렉터리가 있는지 검사한다.

파일을 다른 위치로 복사 : Files.copy( fromPath, toPath );
- 파일 이동 : Files.move( fromPath, toPath );

정적 메서드 Files.list는 Stream<Path> 를 반환한다.
- Stream<Path>는 디렉터리의 엔트리(항목)를 읽는다.

9.3 URL 커넥션
URL 객체의 getInputStream 메서드를 호출해서 URL을 읽어올 수 있다.
하지만, 웹 리소스에 대한 추가 정보가 필요하거나 데이터를 써야 한다면 URLConnection 클래스를 사용해야 한다.
1. URLConnection 객체를 얻는다.
2. 필요하면 요청 프로퍼티를 설정한다.
3. 서버로 데이터를 보내려면 다음과 같이 호출해야 한다.
4. 응답 헤더를 읽으려고 하고, getOutputSream을 호출하지 않았다면 다음과 같이 호출해야 한다.
5. 응답을 읽는다.


9.4 정규 표현식
정규 표현식은 문자열 패턴을 지정한다.
- 특정 패턴과 일치하는 문자열을 찾아야 할 때 정규 표현식을 사용한다.

정규표현식 문법
- 정규표현식에서 문자가 예약 문자 중 하나가 아니면 자신을 나타낸다.

정규 표현식을 사용하는 방식은 두 가지가 있다
- 문자열이 정규 표현식을 준수하는 지 찾는 방식 : 정적 메서드 matches 사용
- 문자열에서 정규 표현식의 일치 대상을 모두 찾는 방식 : 루프 사용
Matcher matcher = pattern.matcher( input );
while ( matcher.find() ) {
String match = matcher.group();

}

일치 대상의 구성 요소를 추출할 때는 그룹을 이용한다.

9.5 직렬화
객체 직렬화는 객체를 다른 곳으로 보내거나 디스크에 저장할 수 있는 바이트들의 묶음으로 변환하고, 해당 바이트들로부터 객체를 재구성하는 메커니즘이다.
- 직렬화는 객체를 한 가상 머신에서 다른 가상 머신으로 보내는 분산 처리에서 필수 도구다.
- 직렬화된 객체를 다른 서버로 옮길 수 있을 때는 페일오버와 로드밸런싱에도 이용한다.
- 서버 사이드 소프트웨어를 다룰 때는 종종 클래스에 직렬화를 활성화해야 한다.

Serailizable 인터페이스
- 객체를 직렬화하려면 해당 객체가 Serializable 인터페이스를 구현하는 클래스의 인자여야 한다.
- 메서드가 없는 마커 인터페이스 (ex) 4장의 Cloneable 인터페이스 참조)



10장 병행 프로그래밍

자바는 병행 프로그래밍을 자체적으로 지원하는 첫 번째 주류 프로그래밍 언어 중 하나였다.

10.1 병행 태스크
병행 프로그램을 설계할 때는 병렬로 실행할 수 있는 태스크(작업)을 생각해야 한다.
- 태스크를 동시에 실행하는 방법을 알아보자


자바에서는 보통 다른 태스크와 동시에 Runnable 인터페이스에 실행할 태스크(작업)를 작성한다.
public interface Runnable {
void run();
}
- run 메서드에 들어 있는 코드는 스레드 안에서 실행된다
- 스레드는 일련의 명령어를 실행하는 메커니즘이며, 보통은 운영체제가 제공한다.
- 별도의 프로세스나 같은 프로세서의 서로 다른 타임 슬라이스를 이용해 여러 스레드가 동시에 실행된다.

Runnable용으로 스레드를 만들 수 있다.
- 하지만 실전에서 태스크와 스레드 사이를 일대일 관례로 만드는 것은 바람직한 방법이 아니다
- 짧은 시간 동안 실행되는 태스크일 때는 스레드를 시작하는 데 드는 시간을 낭비하지 말고, 같은 스레드에서 다수를 실행하는 게 좋다
- 강도 높은 계산을 수행하는 태스크일 때는 태스크별로 스레드를 사용하는 대신, 프로세서별로 스레드를 하나씩 사용해서 스레드 사이에서 스위칭하는 오버헤드를 피하는 게 좋다.

자바 병행성 라이브러리에서 실행자는 태스크를 수행할 스레드를 선택해서 태스크를 실행한다.
- Executors 클래스에는 다양한 유형의 실행자를 만들어내는 팩토리 메서드가 있다.
- 각 태스크는 가능하면 유휴 스레드에서 실행되지만, 모든 스레드가 실행 중이면 새로운 스레드가 할당된다.
- 장시간 유휴 상태인 스레드는 종료된다.

exec = Executors.newFixedThreadPool( nthreads ); // 이 호출은 고정 개수 스레드 풀을 결과로 준다.
- 태스크를 제출하면 해당 태스크는 스레드를 이용할 수 있게 될 때까지 순서를 기다린다.
- 고정 개수 스레드 풀은 강도 높은 계산을 수행하는 태스크에 적합하다.

int processors = Runtime.getRuntime().availableProcessor(); // 알아낼 수 있는 가용 프로세서 개수로부터 스레드 개수를 도출한다

서브태스크(하위 작업) 여러 개로 나누는 계산을 생각해보자
- 서브태스크는 각각 부분 결과를 계산한다. 그리고 모든 태스크가 완료되면 결과들을 결합하려고 한다.
- 이러한 서브태스크에 Callable 인터페이스를 사용할 수 있다.
- Callable의 call 메서드는 Runnable 인터페이스의 run 메서드와 달리 값을 반환한다.
public interface Callable<V> {
V call() throws Exception;
}
- call 메서드는 추가로 임의의 예외를 던질 수 있다.

Callable을 실행하려면 Executor의 서브인터페이스인 ExecutorService 인터페이스의 인스턴스가 필요하다.
- Excutors 클래스의 newCachedThreadPool과 newFixedThreadPool 메서드가 ExecutorService 객체를 돌려준다
- 태스크를 제출하면 퓨처 객체를 얻게 되는데, 퓨처 객체는 언젠간 결과를 얻게 되는 계산을 표현한다.
- Future 인터페이스에는 다음과 같은 메서드가 포함되어 있다.
V get() throws InterruptedException, ExecutionException
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutExeption;
boolean cancel(boolean mayInterruptIfRunning)
boolean isCancelled()
boolean isDone()
- get 메서드는 결과를 얻게 되거나 타임아웃에 이를 때까지 블록한다
- 그런다음 계산된 값을 반환하거나,
- call 메서드에서 예외를 던졌을 때 해당 예외를 감싸고 있는 ExecutionException을 던진다
- cancel 메서드는 태스크 취소를 시도한다.
- 태스크가 이미 실행 중인 상태가 아니면 해당 태스크는 스케줄링되지 않는다
- 태스크가 이미 실행중이고, mayInterruptIfRunning이 true면 해당 태스크를 실행하는 스레드가 인터럽트된다.

보통 태스크는 여러 서브태스크의 결과를 기다려야 한다.
- 각 서브태스크를 별도로 제출하는 대신에 Callable 인스턴스의 Collection을 전달하여 invokeAll 메서드를 호출할 수 있다.
- invokeAll : 타임아웃을 파라미터로 받아서 해당 타임아웃에 이르렀을 때 완료되지 않은 태스크를 모두 취소
- invokeAny 메서든 : 제출한 태스크 중 하나가 예외를 던지지 않고 완료하면 즉시 반환한다.
그런 다음 Future의 값을 반환하고, 다른 태스크는 취소한다.
- 이 메서드는 일치하는 대상을 발견한 즉시 결론을 내릴 수 있는 검색에 유용하다
- 예에서 볼 수 있듯이, ExecutorService는 많은 일을 대신해준다.
- 즉, 태스크를 스레드에 매핑할 뿐만 아니라 태스크의 결과, 예외, 취소까지 처리해준다


10.2 스레드 안전성

가시성
- 모던 프로세서를 이용할 때는 변수를 읽고 쓰는 것 같은 간단한 작업조차 엄청나게 복잡해 질 수 있다
- 옆의 예에서, done = true; 문장의 효과가 보이지 않는다.
- 이유는 캐싱과 명령어 재배치와 관련하여 여러 가지를 들 수 있다.
- 프로세서는 필요한 데이터를 레지스터나 보드에 달린 메모리 캐시에 저장하려고 하고, 결국 변경을 다시 메모리에 쓴다.
- 이러한 캐싱은 프로세서 퍼포먼스에서 빠질 수 없는 역할을 한다.



이 예에서 공유 변수 done을 valatile 제어자로 선언하면 문제가 사라진다.
private static volatile boolean done;
- 이렇게 하면 컴파일러는 한 태스크에서 done을 변경했을 때 다른 태스크에도 해당 변경이 보이도록 보장

경쟁 조건
- 예 프로그램에는 스레드 100개가 포함되어 있으며, 각 스레드는 카운터를 1000번 증가시키고 결과 출력
- 의도한 결과는 100,000을 출력해야 하지만….
- 싱글 CPU는 경쟁 조건을 발견하기가 어려웠다.
- 이 문제를 해결하는 방법은 잠금을 이용해서 임계적인 연산을 원자적으로 만든느 것이다.
- 잠금은 적절하게 이용하기 어려워서 퍼포먼스를 현저히 떨어뜨리거나, 교착 상태를 야기하는 실수를 저지르기 쉽다

자바에는 가비지 컬렉터가 있어서, 자바 개발자 소수만이 메모리 관리에 신경을 쓴다
- 불행히도 병행 프로그램에서는 이에 상응하는 공유 데이터 접근 메커니즘이 없다.
- 개발자가 할 수 있는 최선은 내재된 위험을 관리하는 지침을 따르는 것 뿐이다.

지침 1. 가두기(confinement) : 태스크에서 원가를 세야 할 때는 공유 카운터를 업데이트하는 대신, 각 태스크에 비공개 카운터를 제공하고 태스크가 완료될 때는 결과들을 결합는 또 다른 태스크에 각각의 결과를 전달
2. 불변성(immutability) : 불변 객체를 공유하는 일은 안전하다. 불변 컬렉션, 불변 자료 구조
3. 잠금(locking) 설정 : 한 번에 한 태스크에만 자료 구조에 접근할 수 있는 권한을 주는 방법으로 병렬성 기회를 줄이므로 많은 비용이 들 수 있다.

불변 클래스
- 처음에는 불변 클래스로 할 수 있는 일이 많지 않아 보이지만 사실은 그렇지 않다. ex) 날짜와 시간 API
- 주의 사항
1. 인스턴스 변수를 final로 선언해야 한다
2. 어떤 메서드도 변경자가 될 수 없다
3. 가변 상태를 유출하지 않아야 한다
4. 생성자에서 this 참조를 노출하지 않아야 한다.

10.3 병렬 알고리즘
계산을 병렬화하기 앞서 자바 라이브러리에서 해당 병렬화를 제공하는 지 확인해야 한다.
- 스트림 라이브러리나 Arrays 클래스에서 이미 필요한 기능을 제공하고 있을 수도 있기 때문이다

스트림 라이브러리는 거대한 병렬 스트림의 연산을 자동으로 병렬화한다.
long result = coll.parallelStream().filter( s -> s.startsWith(“A”) ).count(); // 컬렉션에서 문자 A로 시작하는 문자열 카운팅
- parallelStream 메서드는 병렬 스트림을 돌려준다. 병렬 스트림은 여러 세그먼트로 나뉜다.
- 그리고 각 세그먼트에서 필터링과 카운팅을 수행하고 결과들을 결합한다
- 개발자는 내부 내용을 신경 쓰지 않아도 된다.

Arrays 클래스는 다수의 병렬화된 연산을 지원한다.
- 스트림 연산과 마찬가지로, 병렬 배열 연산은 배열을 여러 부분으로 나눠서 병렬로 처리한 후 결과들을 결합한다.
- 정적 메서드 Arrays.parallelSetAll는 전달받은 함수에서 계산한 값으로 배열을 채운다.
- 이 메서드가 전달받은 함수는 요소 인덱스를 받고 해당 위치에서 계산한다
Arrays.parallelSetAll( values, i -> i % 10 ); // 0 1 2 3 4 … 9 0 1 2… 으로 값을 채운다

모든 기본 타입 배열과 객체 배열에 사용할 수 있는 버전이 있다.
- parallelSort 메서드는 기본 타입 값이나 객체의 배열을 정렬할 수 있다.
Arrays.parallelSort( words, Comparator.comparing( String::length ) );
Arrays.parallelSort( values, values.length / 2, values.length ); // 상반부를 정렬, 범위의 경계를 전달할 수 있다.

약간 특화된 parallelPrefix가 있다.
다른 병렬 배열 연산이 필요할 때는 스트림으로 변환한다.
long sum = IntStream.of(values).parallel().sum(); // 정수로 구성된 긴 배열의 합계 계산

10.4 스레드 안전 자료 구조
여러 스레드에서 동시에 큐나 해시 테이블과 같은 자료 구조를 수정하면 해당 자료 구조의 내부가 손상되기 쉽다.
잠금을 이용해서 한 시점에 한 스레드에서만 자료 구조에 접근할 수 있게 하고, 다른 스레드는 블로킹하도록 보장할 수 있다. ~ 비효율적이다
반면에 java.util.concurrent 패키지에 있는 컬렉션은 영리하게 구현되어 있다.
- 여러 스레드에서 자료 구조의 각기 다른 부분에 접근하면 서로 블로킹하는 일 없이 동시에 접근할 수 있다.

ConcurrentHashMap은 스레드가 안전한 연산을 할 수 있게 해주는 해시 맵이다.
- 스레드가 동시에 해당 맵에 연산을 수행할 때 아무리 많은 스레드가 연산을 수행해도 내부가 손상되지 않는다
- 물론 일부 스레드가 일시적으로 블록될 수는 있지만, 상당수의 병행 리더와 일정 개수의 병행 writer를 효율적으로 지원한다.

블로킹 큐는 태스크 사이에서 작업을 조율하는 데 사용한다.
- 생산자 태스크는 아이템을 큐에 삽입하고, 소비자 태스크는 아이템을 추출한다.
- 블로킹 큐를 이용하면 한 태스크에서 다른 태스크로 데이터를 안전하게 전달할 수 있다.
- 블로킹 큐 연산 : put, take, add, remove, element, offer, poll, peek
- 블로킹 큐의 변경 : LinkedBlockingQueue(연결 리스트), ArrayBlockingQueue (원형 배열)

10.5 원자값
여러 스레드에서 공유 카운터를 업데이트할 때 해당 업데이트는 스레드에 안전한 방식으로 일어나게 해야 한다.
java.util.concurrent.atomic 패키지에는 이를 위한 클래스가 있다.
- 이 클래스들은 안전하고 효율적인 머신 수준 명령어를 이용해서 정수, long, boolean, 객체 참조, 배열에 작용하는 연산의 원자성을 보장한다.
- 잠금 대신에 원자값을 이용하는 시점을 결정할 수 있으려면 상당한 전문 지식이 필요하다.
- 원자적인 카운터와 누산기를 이용하면 애플리케이션 수준의 프로그래밍이 편리해진다.

incrementAndGet 메서드 : 원자적으로 AtomicLong을 증가시키고 증가한 값을 반환한다.
compareAndSet : 값을 원자적으로 설정하고, 더하고, 빼는 메서드가 있지만, 더 복잡한 업데이트를 수행하는 메서드
accumulateAndGet : 원자값과 제공받은 인자를 결합하는 데 이용하는 이항 연산자
LongAdder와 LongAccumulator 클래스

10.6 잠금
재진입 가능 잠금 :
- 공유 변수가 손상되는 것을 피하려면 한 번에 한 스레드에서만 새로운 값을 계산하고 설정할 수 있게 해야 한다.
- 인터럽션 없이 온전히 실행해야 하는 코드를 임계 영역이라고 한다
- 임계 영역은 잠금을 이용해서 구현할 수 있다.
- lock 메서드
- unlock 메서드 : finally 절 안에 둔 덕분에 임계 영역에서 어떤 예외가 일어나도 잠금이 해제된다


synchronized 키워드
자바의 모든 객체에는 고유의 잠금이 포함되어 있다.
- 그러므로 명시적인 잠금을 사용할 필요가 없다.
- synchronized 키워든느 고유의 잠금을 잠그는 데 사용한다.
synchronized ( obj ) {
임계 영역
}

이 코드는 본질적으로 다음 코드를 의미한다
obj.intrinsicLock.lock();
try {
임계영역
} finally {
obj.intrinsicLock.unlock();
}
- 객체에 실제 고유의 잠금 필드가 포함되는 건 아니다.
- 이 코드는 그저 synchronized 키워드를 사용할 때 일어나는 일을 설명하려고 만든 것이다

메서드를 synchronized로 선언할 수도 있다.
public synchronized void method() {
구현부
}

이 코드는 다음 코드와 같다
public void method() {
this.intrinsicLock.lock();
try {
구현부
} finally {
this.intrinsicLock.unlock();
}
}
- synchronized 키워드를 사용하면 상당히 간결한 코드를 얻는다.
- 잠금은 단순히 잠금 설정 역할만 하는 건 아니다.
- 잠금은 가시성도 보장한다.

조건 대기
wait 메서드는 Object 클래스의 메서드다, 객체의 잠금과 관련이 있다.
- 이 메서드를 호출하면 해당 객체으 대기 집합에 들어가게 된다.
- 해당 스레드는 잠금을 이용할 수 있게 되더라도 실행할 수 있는 상태로 만들어지지 않는다
- 대신 또 다른 스레드에서 같은 객체에 notifyAll을 호출할 때까지 비활성 상태로 머문다.
- 또 다른 스레드에서 요소를 추가하려면 notifyAll 메서드를 호출해야 한다.
- notifyAll을 호출하면 대기 집합에 있는 모든 스레드가 재활성화된다.

10.7 스레드
스레드 시작하기
Runnable task = () -> { for (int i = 1; i <= 100; i++) System.out.print(i + " "); };
Thread thread = new Thread(task);
thread.start();

정적 메서드 sleep은 지정한 시간 동안 현재 스레드를 잠들게 하여 다른 스레드에서 작업할 기회를 얻게 해준다.
Runnable task = () -> {
….
Thread.sleep( millis );
….
}

이 두 메서드는 검사 예외인 InterruptedException을 던진다.

스레드는 run 메서드가 반환될 때 종료된다.
- 정상적이든 예외가 일어나서 반환되든 말이다.
- 예외가 일어나면 해당 스레드의 미처리 예외 핸들러가 호출된다.
- 스레드가 생성될 때 이 예외 핸들러는 궁극적 전역 핸들러인 스레드 그룸의 미처리 예외 핸들러로 설정된다.
- setUncauchtExceptionHandler 메서들 호출해서 스레드의 핸들러를 바꿀 수 있다.

스레드 인터럽션
- 각 스레드에는 인터럽트 상태가 포함된다.
Runnable 은 이 상태를 검사할 수 있다. 보통 루프로 상태를 검사한다.
Runnable task = () -> {
while( 남은 작업이 있으면 ) {
if( Thread.currentThread().isInterrupted() ) return;
남은 작업을 수행한다
}
};
- 이 스레드가 인터럽트 되면 run 메서드가 종료된다.
- 정적 메서드 Thread.interrupted 도 있다. : 현재 스레드의 인터럽트 상태를 얻은 다음, 상태를 지우고 기존 상태를 반환한다.

ThreadLocal 헬퍼 클래스를 이용해 각 스레드에 고유 인스턴스를 부여하는 방법으로 공유를 피할수도 있다.

Thread 클래스는 스레등의 여러 프로퍼티를 노출하지만, 대부분은 애플리케이션 개발자보다는 자격 시험 응시자에게 유용한 것이다.
- 스레드를 그룹으로 모으거나 스레드 그룹을 관리하는 API 메서드도 있다.
- 스레드에 우선순위를 설저할 수 있다
- 스레드에는 상태가 있으며, 개발자는 어떤 스레드가 새로운 것인지, 실행 중인지, 입출력에 블록되어 있는지, 대기 중이거나 종료되었는지 지정할 수 있다.
- 미처리 예외 때문에 스레드가 종료될 때는 예외가 해당 스레드의 미처리 예외 핸들러로 전달된다. 기본적으로 스택 추적은 System.err로 덤프
- 데몬은 다른 스레드에 서비스를 제공하는 역할 외에 다른 역할을 맡지 않는 스레드를 말한다.
- 데몬은 타이머 틱을 보내거나 오래된 캐시 엔트리를 정리하는 스레드에 유용하다.
- 데몬 스레드만 남아 있으면 가상 머신이 종료된다.
- 데몬 스레드를 만들려면 스레드를 시작하기 전에 thread.setDaemon( true )를 호출한다

10.8 비동기 계산
대기 없는 계산, 즉 비동기 계산을 구현하는 방법을 알아보자
스레드를 사용하는 이유 중 하나는 프로그램이 더 잘 반응하게 만드는 것이다.
- 프로그램에서 시간이 많이 소모되는 작업을 해야 할 때는 해당 작업을 사용자 인터페이스 스레드에서 수행하면 안된다.
- 사용자 인터페이스가 멈춰버리기 때문이다.
- 시간이 많이 소모되는 작업은 다른 작업 스레드에서 실행해야 한다.
- 이런 작업 스레드에서 하는 일을 주의해야 한다.

JavaFX, Swing, Android 와 같은 사용자 인터페이스는 스레드에 안전하지 않다.
- 여러 스레드에서 사용자 인터페이스 요소를 조작하면 해당 요소가 손상될 수 있기 때문이다.

넌블로킹 호출을 다루는 전통적인 접근법은 이벤트 핸들러를 이요하는 것이다.
- 개발자는 태스크가 완료된 후에 일어나야 하는 액션에 대응하는 핸들러를 등록한다.

CompletableFuture 클래스
thenApply 메서드는 어느 것도 블록하지 않고 또 다른 퓨처를 반환한다
- 첫 번째 퓨처가 완료되면 그 결과가 getLinks 메서드에 전달되고, 이 메서드의 반환 값이 최종 결과가 된다.
- 수행할 작업과 그 순서만 지정하면 된다. 물론 작업이 바로 일어나지는 않지만, 중요한 건 모든 코드가 한 자리에 있다는 점이다.
- 완료 가능한 퓨처를 합성하는 메서드의 변형이 많다.
- 단일 퓨처를 다루는 메서드 : thenApply, thenCompose, handle, thenAccept, whenComplete, thenRun
- 다수의 합성 객체 결합하기 : thenCombine, thenAcceptBoth, runAfterBoth, applyToEither, accptEither, runAfterEither, 정적 allOf, 정적 anyOf

10.9 프로세스
지금까지는 같은 프로그램 안에 있는 자바 코드를 별도의 스레드에서 실행하는 방법을 살펴봤다.
때로는 다른 프로그램을 실행해야 할 때도 있다.
이 땐, ProcessBuilder와 Process 클래스를 사용하면 된다.
- Process 클래스는 명령을 별도의 운영체제 프로세스에서 실행하고, 표준 입력, 표준 출력, 표준 오류 스트림과 상호 작용할 수 있게 해준다.
- ProcessBuilder 클래스는 Process 객체를 설정하는 기능을 제공한다.
- ProcessBuilder 클래스는 Runtime.exec 호출을 대체하며 이보다 더 유연한 구현이다

프로세스 생성하기
ProcessBuilder builder = new ProcessBuilder(“gcc”, “myapp.c” ); // 실행할 명령을 지정해서 프로세스 생성
- 첫 번째 문자열은 반드시 실행 가능한 명령이어야 하며, 셀 내장 명령이면 안된다.
- 각 프로세스는 작업 디렉터리와 연관되고, 작업 디렉터리는 상대 디렉터리 이름을 해석하는데 사용
- 프로세스는 기본으로 가상 머신과 같은 작업 디렉터리에 연관된다.
builder = builder.director( path.toFile() ); // 작업 디렉터리 변경

빌더를 성정한 후에는 start 메서드를 호출해서 프로세스를 시작한다.
- 프로세스가 완료되기를 기다릴 때는 waitFor() 메서드를 호출하면 된다.
- isAlive 메서드 : 프로세스가 여전히 살아 있는지 확인




11장 에너테이션

애너테이션은 소스 코드에 삽입하는 태그로 일부 도구에서 처리할 수 있다.
여기서 도구는 애너테이션을 소스 수준에서 처리하거나, 컴파일러가 애너테이션을 집어넣은 클래스 파일을 처리할 수 있다.

애너테이션은 프로그램이 컴파일되는 방식을 바꾸지 않는다.
자바 컴파일러는 애너테이션이 있든 없든 같은 가상 머신 명령어를 만들어낸다.

11.1 애너테이션 사용하기
public class CacheTest {

@Test public void checkRandomInsertion();
}

@Test 애너테이션은 checkRandomInsertions 메서드에 설명을 붙인다.
- 자바에서는 애너테피엿을 제어자(public 같은)처럼 사용한다.
- 각 애너테이션 앞에는 @ 기호가 붙는다
- 애너테이션 자체로는 아무것도 하지 않으며, 도구가 있어야 이를 유용하게 사용할 수 있다.
- 예를 들어 Junit 테스팅 도구는 클래스를 테스트할 때 @Test가 붙은 모든 메서드를 호출한다.
- 다른 도구는 클래스 파일에서 모든 테스트 메서드를 제거하여 테스트를 마친 후에는 프로그램에 테스트 메서드가 포함되지 않게 할 것이다.

애너테이션에는 키/값 쌍 요소를 둘 수 있다. ex) @Test (timeout = 10000)
- 허용되는 요소의 이름과 타입은 각 애너테이션에서 정의한다.
- 애너테이션의 요소는 해당 애너테이션을 읽는 도구로 처리할 수 있다.

애너테이션 요소
- 기본 타입 값
- String
- Class 객체
- enum 인스턴스
- 애너테이션
- 위의 항목의 배열(배열의 배열은 안됨)

애너테이션 요소는 기본값을 가질 수 있다.
- 절대로 null 값을 가질 수 없다.
- 요소 이름이 values고 이 요소만 지정할 때는 ‘value=‘를 생략할 수 있다.

아이템 하나에 여러 애너테이션을 둘 수 있다.

매서드 선언부에 적용하는 애너테이션을 살펴봤다.
- 메서드 외에도 애너테이션을 사용할 수 있는 곳은 다양하며, 크게 선언과 타입 사용 카테고리로 나뉜다.
- 선언 애너테이션 : 클래스와 인터페이스, 메서드, 생성자, 인스턴스 변수, 지역변수,
- 파라미터 변수와 catch 절의 파라미터, 타입 파라미터, 패키지



선언 애너테이션은 선언되는 아이템에 관한 정보를 몇 가지 제공한다.
- public User getUser(@NonNull String userID) // 다음 선언에서는 userID 파라미터가 널이 아님을 보장한다.

타입 사용 애너테이션은 다음 위치에 사용할 수 있다.
- 제네릭 타입 인자 : List<@NonNull String>
- 배열의 모든 위치 : @NonNull String[][]
- 슈퍼 클래스와 구현 대상 인터페이스 : class Warning extends @Localized Message
- 생성자 호출 : new @Localized String( … )
- 중첩 타입 : Map.@Localized Entry.
- 타입 변환과 instanceof 검사 : (@Localized String) text, if ( text instanceof @Localized String
- 예외 명세 : public String read() throws @Localized IOException
- 와일드 카드와 타입 경계 : List<@Localized ? extends Message>
- 메서드 참조와 생성자 참조 : @Localized Message::getText

애너테이션을 못 붙이는 타입 위치도 있다
@NonNull String.class // 오류 – 클래스 리터럴에 애너테이션을 붙일 수 없다
import java.lang.@NonNull String; // 오류 – import에 애너테이션을 붙일 수 없다

애너테이션은 private과 static 같은 다른 제어자의 앞이나 뒤에도 붙일 수 있다.
- 필수는 아니지만 타입 사용 애너테이션은 다른 제어자 뒤에 붙이고, 선언 애너테이션은 다른 제어자 앞에 붙이는 게 관례다
private @NonNull String text; // 타입 사용에 애너테이션을 적용한다
@Id private String userId; // 변수에 애너테이션을 적용한다.
- 애너테이션 작성자는 특정 애너테이션을 사용할 수 있는 위치를 명시해야 한다.
- 변수와 타입 사용 모두에 애너테이션을 붙일 수 있을 때 애너테이션을 변수 선언에서 사용하면 해당 변수와 타입 사용 모두에 적용된다

메서드에서 변경되지 않는 파라미터에 애너테이션을 붙인다고 해보자
public class Point {
public boolean equals(@ReadOnly object other) { … }
}
p.equals(q) // 애너테이션을 처리하는 도구에서 다음 호출을 발견하면 q가 변경되지 않았다고 판단
- 그런데 p는 어떨까?
- 위 메서드를 호출할 때 수신자 변수 this는 p에 연결되지만, this는 선언한 적이 없으므로 애너테이션을 붙일 수 없다.
- 실제로는 this를 거의 사용하지 않는 문법 형태를 이용해 선언할 수 있는데, 그저 애너테이션을 붙이는 용도일 뿐이다.
public boolean equals(@ReadOnly Point this, @ReadOnly Object obj) { … }
- 첮 번째 파라미터를 수신자 파라미터라고 한다
- 수신자 파라미터의 이름은 반드시 this여야 하며, 파라미터 타입이 생성된 클래스여야 한다
- 수신자 파라미터는 메서드에만 제공할 수 있으며, 생성자에는 제공할 수 없다.


11.2 애너테이션 정의하기
각 애너테이션은 반드시 @interface 문법을 사용해 애너테이션 인터페이스로 선언해야 한다.
- 인터페이스의 메서드는 애너테이션의 요소에 대응한다.

@interface 선언은 실제 자바 인터페이스를 만들어낸다.
- 애너테이션을처리하는 도구는 애너테이션 인터페이스를 구현하는 객체를 받는다.
- 애너테이션 인터페이스의 요소 선언은 실제로 메서드 선언이다

@Target과 @Retention 애너테이션은 메타애너테이션이다.
- 이 애너테이션들은 Test 애너테이션에 적용되며 각각 Test애너테이션을 적용할 수 있는 위치와 접근할 수 있는 위치를 나타낸다.
- @Target 메타애너테이션의 값은 ElementType 객체의 배열인데, 이는 애너테이션을 적용할 수 있는 아이템을 나타낸다.
- @Target 애너테이션용 요소 타입 : ANNOTATION_TYPE, PACKAGE, TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE

@Retention 메타애너테이션은 애너테이션에 접근할 수 있는 위치를 지정한다.
- RetentionPolicy.SOURCE, RetentionPolicy.CLASS, RetentionPolicy.RUNTIME

11.3 표준 애너테이션
자바 API는 java.lang, java.lang.annotation, javax.annotation 패키지에 애너테이션을 정의해 놓았다.


11.4 실행 시간에 애너테이션 처리하기
실행 중인 프로그램에서 애너테이션을 분석하는 방법
리플렉션 API로 실행 시간에 애너테이션을 처리하는 간단한 예
- toString 메서드를 구현하는 지루함을 줄고자 한다.
- 단순히 모든 인스턴스 변수의 이름과 값이 포함된 리플렉션으로 제네릭 toString 메서드 작성
- 이 서비스를 이요할 모든 클래스에 @ToString 애너페이션을 붙인다.

11.5 소스 수준 애너테이션 처리
소스 코드, 설정 파일, 스크립트 또는 원하는 것을 만들어내기 위해 소스 파일을 자동으로 처리할 때도 애너테이션을 사용한다.
- 애너테이션 처리는 자바 컴파일러에 통합되어 있다.
- 컴파이러가 소스 파일에서 애너테이션을 찾는다.
- 애너테이션 핸들러는 새로운 소스 파일만 생성할 수 있다. (기존 소스 파일은 수정할 수 없다)

소스 수준 애너테이션을 분석하는 데는 언어 모델 API를 사용한다.
- 리플렉션 API는 클래스와 메서드의 가상 머신 표현을 보여준다.
- 언어 모델 API는 자바 언어 규칙에 따라 자바 프로그램을 분석할 수 있게 해준다.




12장 날짜와 시간 API

자바 8에 도입한 java.time API는 이전 시간의 결함을 해결했고, 앞으로도 꽤 오랫동안 제 역할을 할 것이다.

12.1 타임 라인

12.2 지역 날짜

12.3 날짜 조정기

12.4 지역 시간

12.5 구역 시간

12.6 서식 정과 파싱

12.7 레거시 코드와 상호 동작하기



13장 국제화

일부 개발자는 애플리케이션을 국제화하는데 필요한 작업은 유니코드를 지원하고 사용자 인터페이스에서 메시지를 번역하는 게 전부라고 믿는다.
- 세계 곳곳마다 날짜, 시간, 통화를 이용하는 형식이 다르고, 심지어는 숫자를 이용하는 형식도 다르다
- 자바의 국제화 기능으로 프로그램의 정보가 어떤 사용자에게도 의미 있게 표시되고 의미 있게 받아들여지도록 만드는 방법

13.1 로케일

13.2 숫자 형식

13.3 통화

13.4 날짜와 시간 서식 지정

13.5 컬레이션과 정규화

13.6 메시지 서식 지정

13.7 리소스 번들

13.8 문자 인코딩

13.9 프레퍼런스



14장 컴파일링과 스크립팅

14.1 컴파일러 API

14.2 스크립팅 API

14.3 Nashorn 스크립팅 엔진

14.4 Nashorn을 이용한 셸 스크립팅



15장 자바 플랫폼 모듈 시스템

객체 지향 프로그래밍의 중요한 특성은 캡슐화다.
- 클래스 선언은 공개 인터페이스와 비공개 구현으로 구성된다.
- 따라서 클래스 사용자에게는 영향을 주지 않은 채 구현을 변경해 진화할 수 있다.
- 모듈 시스템은 대규모 프로그래밍에서 같은 이점을 제공한다.
- 모듈을 이용하면 클래스와 패키지를 선별적으로 사용할 수 있어 해당 모듈의 진화를 제어할 수 있다.

기존의 여러 자바 모듈 시스템은 클래스 로더에 의존해 클래스들을 분리한다.
- 하지만 자바 9에서는 자바 컴파일러와 가상 머신이 지원하는 자바 플랫폼 시스템이라는 새 시스템을 도입했다.
- 자바 플랫폼 모듈 시스템은 자바 플랫폼의 방대한 코드 기반을 모듈화하도록 설계했다.

손수 만든 애플리케이션에서 자바 플랫폼 모듈을 사용하든 사용하지 않든, 모듈화된 자바 플랫폼의 영향을 받게 될 것이다.
- 이 장에서는 자바 플랫폼 모듈을 선언하고 사용하는 방법을 설명한다.
- 또 모듈화된 자바 플랫폼과 서드파티 모듈을 사용하도록 애플리케이션을 마이그레이션하는 방법도 알아본다.

15.1 모듈 개념 잡기
객체 지향 프로그래밍에서 기본 빌딩 블록은 클래스다. 클래스는 캡슐화를 제공한다.
비공개 기능은 명시적인 권한이 있는 코드(즉, 클래스의 메서드)로만 접근할 수 있다. 이렇게 하면 접근을 추론할 수 있다.
다시 말해, 비공개 변수가 변경되었다면 해당 변경을 일으킬 수 있는 모든 코드를 알아낼 수 있다. 따라서 비공개 표현을 수정해야 할 때 어느 메서드가 여향을 받을 지 파악할 수 있다.

대규모 시스템에서는 접근 제어 수준으로는 충분하지 않다.
공개 기능은 어디에서나 접근할 수 있다.
거의 사용하지 않는 기능을 수정하거나 삭제하려한다고 하자. 기능을 공개한 후에는 해당 변경이 주는 영향을 추론할 방법이 없다.
거대한 코드 더미와 마주한 자바 플랫폼 설계자들은 더 많은 제어 기능을 제공하는 구조화 메커니즘이 필요하다고 판단했다.
그래서 자바 플랫폼과 가상 시스템의 일부가 된 자바 플랫폼 모듈 시스템(JFMS)이라는 새 시스템을 설계했다.

자바 플랫폼 모듈
- 패키지 모음
- 선택적으로 리소스 파일과 네이티브 라이브러리 같은 다른 파일
- 모듈에 있는 접근 가능한 패키지의 목록
- 모듈이 의존하는 모든 모듈의 목록

프로그램에서 클래스 패스에 있는 JAR 파일을 사용하는 전통적인 접근 방법을 따르지 않고 자바 플랫폼 모듈 시스템을 이용하는 장점
- 강력한 캡슐화 : 어느 패키지를 접근할 수 있게 할지 제어할 수 있으므로, 공개적으로 사용하도록 의도하지 않은 코드는 유지 보수할 필요가 없다
- 신뢰할 수 있는 구성 : 클래스 중복이나 누락 같은 흔한 클래스 패스 문제를 피할 수 있다.

15.2 모듈 이름 짓기
모듈은 패키지의 모음이다. 그렇다고 해서 같은 모듈에 속한 패키지들의 이름이 연관되어 있을 필요는 없다.
- 다른 사람이 사용할 모듈을 만들 때 모듈 이름은 전역적으로 유일해야 한다.
- 모듈의 최상위 패키지 이름으로 모듈을 명시하는 것이 가장 쉬운 방법이다.
- 어떠한 패키지든 한 모듈에만 들어간다.

패키지를 포함하는 모듈인 ch15.sec03을 만들려면 모듈 선언을 추가해야 한다.


- 모듈 선언은 베이스 디렉토리(즉, com 디렉터리를 담은 디렉터리)의 module-info.java 파일에 넣는다.


이 모듈 선언이 비어 있는 이유는 모듈이 제공할 것이 없고, 아무것도 필요 없기 때문이다. 이제 평소처럼 컴파일 한다.
$ javac ch15.sec03/module-info.java ch15.sec03/com/horstmann/hello/HelloWorld.java

module-info.java 파일은 자바 소스 파일처럼 보이지 않으며, 클래스 이름에 하이폰을 넣을 수 없으므로 당연히 클래스도 있을 수 없다.
- Module 키워드와 뒤에서 알아볼 requires, exports 같은 키워드는 모듈 선언 안에서만 특별히 의미가 있는 ‘제한된 키워드'이다.
- module-info.java 파일은 모듈 정의를 바이너리 형식으로 저장한 클래스 파일인 module-info.class로 컴파일 된다.

이 프로그램을 모듈식 애플리케이션으로 실행하려면 모듈 패스를 지정해야 한다. (모듈 패스는 클래스 패스와 유사하지만 모듈을 담는다.)
또 메인 클래스를 modulename/classname 형식으로 지정해야 한다.
$ java --module-path ch15.sec03 --module ch15.sec03/com.horstmann.hello.HelloWorld
--module-path와 –module 대신에 단일 문자 옵션 –p와 –m을 사용해도 된다.
$ java –p ch15.sec03 -m ch15.sec03/com.horstmann.hello.HelloWorld

15.4 모듈 요구
모듈 시스템의 설계 목표는 모듈이 요구 사항을 명시적으로 선언하도록 해서, 가상 머신이 프로그램을 시작하기 전에 모든 요구 사항을 충족했는지 확인할 수 있게 하는 것이다.
모듈 그래프에 순환이 생기게 할 수 없다. 다시 말해 모듈은 직간접적으로 자산을 요구할 수 없다.


모듈은 다른 모듈에 대한 접근 권한을 자동으로 넘기지 않는다.
- Java.desktop 모듈은 java.prefs 모듈을 요구하고, java.prefs 모듈은 java.xml을 요구한다고 선언한다고 해서, java.desktop이 java.xml 모듈에 있는 패키지를 사용할 권한을 부여받는 것은 아니다.
- 필요하면 요구 사항을 명시적으로 선언해야 한다. 수학 용어로 전이적(transitive)이지 않다.

15.5 패키지 익스포트
앞절에서 모듈이 다른 모듈에 있는 패키지를 사용하려면 해당 모듈을 요구해야 한다는 것을 배웠다.
하지만 모듈을 요구한다고 해서 필요한 모듈에 있는 모든 패키지를 자동으로 이용할 수 있지는 않다.
모듈은 exports 키워드로 외부에서 접근할 수 있는 패키지를 명시한다.

15.6 모듈과 리플렉션을 이요한 접근
앞 절에서는 모듈 시스템이 캡슐화를 적용하는 것을 알아보았다.
모듈은 다른 모듈이 명시적으로 익스포트한 패키지에만 접근할 수 있다.
자바 9 이전에는 리플렉션으로 성가신 접근 제한을 극복할 수 있었다.
4장 상속과 리플렉션에서 살펴보았듯이 리플렉션을 이용하면 모든 클래스의 비공개 멤버에 접근할 수 있기 때문이다.
하지만 모듈 세계에서는 그렇지 않다. 클래스가 모듈 안에 있으면 공개가 아닌 멤버를 리플렉션으로 접근하지 못한다.

15.7 모듈식 JAR
모듈의 모든 클래스를 JAR 파일에 넣고 JAR 파일의 루트에 module-info.class를 넣어 모듈을 배포하는 방법이 있다.

15.8 자동 모듈과 이름 없는 모듈

15.9 마이그레이션용 명령줄 플래그

15.10 전이적 요구 사항과 정적 요구 사항

15.11 한정된 익스포트와 개방

15.12 서비스 로드

15.13 모듈 작업용 도구
jdeps 도구는 JAR 파일 집합의 의존성을 분석한다.