공부/자바

[자바] 자바의 정석 정리(내가 다시 볼 것들만)

ghhong 2022. 5. 4. 09:55

자바의 정석
1. 자바를 시작하기 전에
ㅇ JVM : 자바 가상 머신. 다른 os에서도 같은 자바 프로그램 개발을 위함. wora
ㅇ JRE = JVM + JAVA API
ㅇ JDK = JRE + 개발에 필요한 실행파일
ㅇ Hello.java 작성 -> javac.exe컴파일러 -> Hello.class 생성 -> java.exe인터프리터 -> 실행
ㅇ public static void main(String[] args)는 main메서드의 선언부인데, 프로그램을 실행할 때 java.exe에 의해 호출될 수 있도록 미리 약속된 부분이므로 항상 똑같이 적어주어야 한다.
ㅇ 모든 클래스가 main메서드를 가지고 있어야 하는 것은 아니지만, 하나의 java애플리케이션에는 main메서드를 포함한 클래스가 반드시 하나는 있어야 한다.
ㅇ 작성된 java애플리케이션을 실행할 때는 java.exe 다음에 main메서드를 포함한 클래스의 이름을 적어주어야 한다.
ㅇ 하나의 소스파일에 둘 이상의 public class가 존재할 수 없다.

2. 변수
ㅇ 변수란, 단 하나의 값을 저장할 수 있는 메모리 공간
ㅇ 변수를 선언하면 메모리의 빈 공간에 변수타입에 알맞은 크기의 저장공간이 확보되고, 이 저장공간을 변수이름을 통해 사용할 수 있다.
ㅇ 변수를 선언한 후에 반드시 초기화해야한다. 메모리는 여러 프로그램이 공유하는 자원이므로 전에 다른 프로그램에 의해 저장된 쓰레기값이 남이있을 수 있기 때문이다.
ㅇ 덧셈연산자 + 는 문자열과 숫자를 문자열로 결합한다.
ㅇ 클래스 이름의 첫 글자는 대문자, 변수와 메서드의 이름의 첫 글자는 소문자로 한다.
ㅇ camelCase 사용
ㅇ 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우 _로 구분한다. PI, MAX_NUMBER
ㅇ 기본형(primitive type)변수는 실제 값을 저장한다. 참조형(reference type)변수는 주소를 저장한다.
ㅇ int타입의 변수는 대략 10자리 수의 값을 저장할 수 있다. long은 19자리 수.
ㅇ float의 정밀도는 7자리, double는 15자리이다.
ㅇ 상수(constant)는 변수와 달리 한 번 저장한 값은 다른 값으로 변경할 수 없다. final int MAX_SPEED = 10;과 같이 final을 붙여서 선언 및 초기화까지 해야한다.
ㅇ 리터럴 : 그 자체로 값을 의미하는 것.
ㅇ 상수가 필요한 이유 : 상수를 이용해서 코드를 명확하게 한다. 10 * 20 : 면적을 구하는 공식 -> WIDTH * HEIGHT // 상수는 리터럴에 의미있는 이름을 붙여서 코드의 이해와 수정을 쉽게 만든다.
ㅇ 리터럴의 타입은 저장될 변수의 타입과 일치하는 것이 보통이지만, 타입이 달라도 저장범위가 넓은 타입에 좁은 타입의 값을 저장하는 것은 허용된다.
ㅇ 화면 입력 : Scanner scanner = new Scanner(System.in); String input = scanner.nextLine();
ㅇ 컴퓨터는 2진 체계이기 때문에 int age=25 를 작성하면 00000000000000000000000000011001이 저장된다.
ㅇ char a = 'A'; 하면 실제로는 문자가 아닌 문자의 유니코드가 저장된다. 컴퓨터는 숫자밖에 모르기 때문에 모든 데이터를 숫자로 변환하여 저장한다.
ㅇ 문자 A를 유니코드로 인코딩하면 65가 되는 것이고, 65를 유니코드로 디코딩하면 A가 된다.
ㅇ boolean을 제외한 나머지 7개의 기본형은 서로 형변환이 가능하다.
ㅇ 기본형과 참조형은 서로 형변환할 수 없다.
ㅇ 서로 다른 타입의 변수간의 연산은 형변환을 하는 것이 원칙이지만, 값의 범위가 작은 타입에서 큰 타입으로의 형변환은 생략할 수 있다.

3. 연산자
ㅇ 산술변환 : 이항 연산자는 두 피연산자의 타입이 일치해야 연산이 가능하므로, 피연산자의 타입이 서로 다르다면 연산 전에 형변환 연산자로 타입을 일치시켜야 한다. 대부분의 경우, 두 피연산자의 타입 중에서 더 큰 타입으로 일치시키는데, 그 이유는 작은 타입으로 형변환하면 원래의 값이 손실될 가능성이 있기 때문이다. 
ㅇ 피연산자의 타입이 int보다 작은 타입이면 int로 변환한다. byte + short -> int + int. int가 가장 효율적으로 처리할 수 있고 char나 short는 표현범위가 좁아서 연산중에 overflow가 발생할 가능성이 높기 때문이다.
ㅇ 하나의 식에서 증감연산자의 사용을 최소화하고, 식에 두 번 이상 포함된 변수에 증감연산자를 사용하는 것은 피해야 한다.
ㅇ 문자열의 비교 : == 가 아닌 equals를 사용해야 한다.
ㅇ 효율적인 연산(short circuit evaluation) : OR연산의 경우 두 피연산자 중 어느 한 쪽만 참이어도 전체 연산결과가 참이므로 좌측 피연산자가 참이면 우측 피연산자의 값은 평가하지 않는다.

4. 조건문과 반복문
ㅇ 1과 3 사이의 정수 구하기 : (int)(Math.random() * 3 )+1
ㅇ 향상된 for문(enhanced for statement) : for( 타입 변수명 : 배열 또는 컬렉션) { 반복할 문장 }
ㅇ while문에서 조건식은 생략 불가능하다. while() { } //X
ㅇ String -> int : int num = Integer.parseInt("123");
ㅇ do-while문 끝에 ;를 붙인다.
ㅇ break문은 자신이 포함된 가장 가까운 반복문을 벗어난다.
ㅇ continue문은 반복이 진행되는 도중에 continue문을 만나면 반복문의 끝으로 이동하여 다음 반복으로 넘어간다. 반복문 전체를 벗어나지 않고 다음 반복은 수행한다는 점이 break문과 다른다.

5. 배열
ㅇ 같은 타입의 여러 변수를 하나의 묶음으로 다루는 것을 배열(array)이라고 한다. 변수와 달리 배열은 각 저장공간이 연속적으로 배치되어 있다.
ㅇ int[] score = new int[5]; // 값을 저장할 수 있는 공간은 score[0] 부터 score[4] 까지 5개이며, 변수 score는 배열을 다루는데 필요한 참조변수일 뿐 값을 저장하기 위한 공간은 아니다.
ㅇ 배열을 선언하는 것은 단지 생성된 배열을 다루기 위한 참조변수를 위한 공간이 만들어질 뿐이고, 배열을 생성해야만 비로소 값을 저장할 수 있는 공간이 만들어지는 것이다.
ㅇ 배열을 생성하기 위해서는 연산자 new와 함께 배열의 타입과 길이를 지정해 주어야 한다.
ㅇ int[] score = new int[5]; // 각 배열요소는 자동적으로 int의 기본값인 0으로 초기화 된다.
ㅇ 컴파일러는 ArrayIndexOutOfBoundsException을 체크하지 않는다. 왜냐하면 배열의 인덱스로 변수를 많이 사용하는데, 변수의 값은 실행 시(런타임)에 대입되므로 컴파일러는 이 값의 범위를 확인할 수 없다. 그래서 유효한 범위의 값을 인덱스로 사용하는 것은 전적으로 프로그래머의 책임이다.
ㅇ arr.length // 배열의 길이. 배열은 한 번 생성하면 길이를 변경할 수 없기 때문에, 이미 생성된 배열의 길이는 변하지 않는다. 따라서 arr.length 는 상수다.
ㅇ 배열의 길이를 변경하려면 1. 더 큰 배열을 새로 생성한다. 2. 기존 배열의 내용을 새로운 배열에 복사한다.
ㅇ int[] score = new int[]{}; new int[0]; {}; //길이가 0인 배열
ㅇ score를 출력하면 타입@주소의 형식으로 출력된다. @뒤에 나오는 16진수는 배열의 주소인데 실제 주소가 아닌 내부 주소이다. //Arrays.toString(score)를 사용하여 내용을 확인할 수 있다.
ㅇ 예외적으로 char배열의 경우 println을 사용하면 구분자 없이 출력된다.
ㅇ 배열은 참조변수를 통해서만 접근할 수 있기 때문에, 자신을 가리키는 참조변수가 없는 배열은 사용할 수 없다. 이렇게 쓸모없게 된 배열은 JVM의 가비지 컬렉터에 의해 자동적으로 메모리에서 제거된다.
ㅇ System.arraycopy() 는 지정된 범위의 값들을 한 번에 통째로 복사한다. 각 요소들이 연속적으로 저장되어 있다는 배열의 특성때문에 가능하다. System.arraycopy(num, 0, newNum, 0, num.length); // num[0] 에서 newNum[0]으로 num.length개의 데이터를 복사.
ㅇ 변수의 타입에 따른 기본값 : boolean:false , char:\u0000 , byte/short/int:0 , long:0L , fload:0.0f , double:0.0d 또는 0.0 , 참조형 변수:null
ㅇ 참조형 배열의 경우 배열에 저장되는 것은 객체의 주소이다. 참조형 배열을 객체 배열이라고도 한다. 참조형 변수를 참조변수라고도 하며, 모든 참조형 변수에는 객체가 메모리에 저장된 주소인 4byte의 정수값 또는 null이 저장된다.
ㅇ String클래스는 char배열에 기능(메서드)를 추가한 것이다. 
ㅇ 객체지향개념이 나오기 이전의 언어들은 데이터와 기능을 따로 다루었지만, 객체지향언어에서는 데이터와 그에 관련된 기능을 하나의 클래스에 묶어서 다룰 수 있게 한다.
ㅇ char배열과 String클래스의 큰 차이는 String객체(문자열)은 읽을 수만 있을 뿐 내용을 변경할 수 없다. 변경 가능한 문자열을 다루려면 StringBuffer클래스를 사용하면 된다.
ㅇ String클래스의 주요 메서드 : charAt(int index), length(), substring(int from, int to), equals(Object obj), toCharArray()
ㅇ 대소문자를 구분하지 않고 비교하려면 equals대신 equalsIgnoreCase()를 사용한다.
ㅇ char배열과 String클래스의 변환 : char[] chArr = {'A','B','C'}; Stirng str = new String(chArr); char[] tmp = str.toCharArray(); 
ㅇ 2차원 배열의 초기화 : int[][] arr = { {1,2,3} , {4,5,6} };
ㅇ 2차원 이상의 다차원 배열을 생성할 때 전체 배열 차수 중 마지막 차수의 길이를 지정하지 않고, 추후에 각기 다른 길이의 배열을 생성함으로써 고정된 형태가 아닌 보다 유동적인 가변 배열을 구성할 수 있다.

6. 객체지향 프로그래밍 1
ㅇ 객체지향이론의 기본 개념 : 실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.
ㅇ 객체지향언어의 주요특징 : 
    -코드 재사용성이 높다. -> 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
    -코드의 관리가 용이하다. -> 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
    -신뢰성이 높은 프로그래밍을 가능하게 한다. -> 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며, 코드의 중복을 제거하여 코드의 불일치로 인한 오작동을 방지할 수 있다.
ㅇ 객체지향개념을 학습할 때 재사용성과 유지보수 그리고 중복된 코드의 제거, 이 세 가지 관점에서 보면 쉽게 이해할 수 있다.
ㅇ 클래스의 정의 : 객체를 정의해 놓은 것이다.
ㅇ 클래스의 용도 : 객체를 생성하는데 사용될 뿐 객체 그 자체는 아니다.
ㅇ 객체 : 클래스에 정의된 내용대로 메모리에 생성된 것.
ㅇ 클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 하며, 어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 한다.
ㅇ 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며, 인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고 있다.
ㅇ 객체는 속성과 기능으로 이루어져 있으며, 일반적으로 다수의 속성과 기능을 갖는다. 
ㅇ 속성(property) : 멤버변수, 특성, 필드, 상태 / 기능(function) : 메서드, 함수, 행위
ㅇ 인스턴스의 생성과 사용 : 클래스명 변수명 = new 클래스명(); // 클래스의 객체를 참조하기 위한 참조변수를 선언하고 클래스의 객체를 생성한 후 객체의 주소를 참조변수에 저장
ㅇ 참조변수를 선언하면 메모리에 참조변수를 위한 공간이 마련된다. 연산자 new에 의해 인스턴스가 메모리의 빈 공간에 생성된다. 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다. 대입연산자에 의해 생성된 객체의 주소값이 참조변수에 저장된다. 이제 참조변수를 통해 인스턴스에 접근할 수 있다.
ㅇ 인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야한다.
ㅇ 객체 배열 : 많은 수의 객체를 다뤄야 할 때 배열로 다루면 편리하다. 객체 배열 안에 객체가 저장되는 것은 아니고, 객체의 주소가 저장된다. 즉 객체 배열은 참조변수들을 하나로 묶은 참조변수 배열인 것이다.
ㅇ 객체 배열을 생성하면 각 요소는 참조변수의 기본값인 null로 초기화된다. 이 객체 배열은 객체의 주소를 저장할 수 있다. 객체를 생성해서 객체 배열의 각 요소에 저장을 반드시 해야한다.
ㅇ 클래스는 데이터와 함수의 결합이다. 클래스는 사용자 정의 타입이다.
ㅇ 멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스 변수, 붙지 않은 것은 인스턴스 변수이다.
    클래스영역에 static이 붙음 - 클래스 변수 -> 클래스가 메모리에 올라갈 때 생성된다(제일 먼저). 인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스변수로 선언한다. 클래스변수는 인스턴스변수와 달리 인스턴스를 생성하지 않고도 사용할 수 있다. '클래스이름.클래스변수'와 같은 형식으로 사용한다. 클래스가 메모리에 로딩될 때 생성되어 프로그램이 종료될 때 까지 유지되며, public 을 앞에 붙이면 같은 프로그램 내의 어디서나 접근할 수 있는 전역변수의 성격을 갖는다.
    클래스영역에 static이 안 붙음 - 인스턴스 변수 -> 인스턴스가 생성될 때 생성된다. 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야한다. 인스턴스는 독립적인 공간을 가지므로 서로 다른 값을 가질 수 있다. 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.
    클래스영역 이외의 영역(메서드, 생성자, 초기화 블럭 내부)에 위치 -> 지역변수 -> 변수 선언문이 수행될 때 생성된다. 메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다. 블럭 내에 선언된 지역변수는 블럭{}을 벗어나면 소멸되어 사용할 수 없게 된다.
ㅇ 메서드 : 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것
ㅇ 메서드를 사용하는 이유 : 높은 재사용성, 중복된 코드의 제거, 프로그램의 구조화
ㅇ 메서드의 반환타입이 void가 아닌 경우, 메서드가 작업을 수행하고 반환한 값을 사용하지 않아도 무방하다.
ㅇ 메서드에 매개변수 타입으로 자동형변환 될 수 있는 값이 들어가야 한다.
ㅇ 매개변수의 적절한 유효성 검사가 필요하다. 호출하는 쪽에서 알아서 적절한 값을 넘겨줬다고 생각하면 안 된다.
ㅇ 응용프로그램이 실행되면 JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
    메서드 영역(method area) : 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다. 이 때, 그 클래스의 클래스변수도 이 영역에 함께 저장한다.
    힙(heap) : 인스턴스가 생성되는 공간, 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다. 즉, 인스턴스변수들이 생성되는 공간이다.
    호출스택(call stack) : 호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용된다. 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다.
ㅇ 메서드를 호출할 때 매개변수로 지정한 값을 메서드의 매개변수에 복사해서 넘겨준다. 매개변수의 타입이 기본형이면 기본형 값이 복사되고 참조형이면 인스턴스의 주소가 복사된다. 주소를 알 수 있으면 원본의 값을 읽어오는 것은 물론 값을 변경하는 것도 가능하다.
ㅇ 배열도 객체와 같이 참조변수를 통해 데이터가 저장된 공간에 접근한다.
ㅇ 매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다. -> 객체의 주소를 반환한다.
ㅇ 메서드 앞에 static이 붙어있으면 클래스메서드이고 붙어있지 않으면 인스턴스메서드이다. 클래스메서드도 클래스변수처럼 객체를 생성하지 않고도 클래스이름.클래스메서드(매개변수)와 같은 식으로 호출할 수 있다. 반면에 인스턴스메서드는 반드시 객체를 생성해야만 호출할 수 있다.
ㅇ 인스턴스메서드는 인스턴스변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스변수를 필요로 하는 경우에 선언한다.
ㅇ 클래스메서드는 인스턴스와 관계없는, 즉 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 경우에 클래스메서드를 사용한다.
ㅇ 같은 클래스에 있는 static메서드는 클래스이름을 생략하고 호출할 수 있다.
ㅇ 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
ㅇ 클래스변수는 인스턴스를 생성하지 않아도 사용할 수 있다. -> 클래스가 메모리에 올라갈 때 이미 생성
ㅇ 클래스메서드는 인스턴스 변수를 사용할 수 없다. -> 인스턴스 변수는 인스턴스가 반드시 존재해야만 사용할 수 있다. 클래스메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다.
ㅇ 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려한다. -> 클래스메서드는 메서드 호출시간이 짧아지므로 성능이 향상된다. 인스턴스메서드는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문.
ㅇ 메서드 오버로딩 : 매개변수의 개수 또는 타입을 다르게 하여 같은 이름을 가진 메서드를 정의하는 것.
ㅇ 가변인자와 오버로딩 : jdk 1.5부터 매개변수의 개수를 동적으로 지정할 수 있다. 가변인자는 매개변수 마지막에 선언해야 한다. 가변인자에는 인자가 없어도 되고 자료형이 맞으면 배열도 인자로 사용할 수 있다.
ㅇ 생성자 : 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드. 인스턴스 변수의 초기화 작업 또는 인스턴스 생성 시에 실행되어야 할 작업을 위해 사용된다.
ㅇ 생성자의 이름은 클래스의 이름과 같다. 생성자는 리턴값이 없다(모든 생성자가 리턴값이 없으므로 void도 생략한다).
ㅇ 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아니다. 생성자는 단순히 인스턴스변수들의 초기화에 사용되는 특별한 메서드일 뿐이다. 연산자 new에 의해 힙메모리에 인스턴스가 생성되고 생성자가 호출된다. 
ㅇ 클래스에 생성자를 정의하지 않고도 인스턴스를 생성할 수 있었던 이유는 컴파일러가 제공하는 기본생성자 덕분이다. 클래스 내에 생성자가 있는 경우 컴파일러가 자동적으로 기본생성자를 추가하지 않는다.
ㅇ 생성자에서 다른 생성자 호출하기 : 생성자 내에서 다른 생성자의 이름으로 클래스이름 대신 this를 사용한다. 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다. 왜냐하면 생성자 내에서 초기화 작업 도중에 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 멤버변수들을 초기화할 것이므로 다른 생성자를 호출하기 이전의 초기화 작업이 무의미해질 수 있기 때문이다.
ㅇ 생성자를 포함한 모든 인스턴스메서드에는 자신이 관련된 인스턴스를 가리키는 참조변수 this(인스턴스의 주소가 저장되어 있다)가 지역변수로 숨겨진 채로 존재한다. 일반적으로 인스턴스메서드는 특정 인스턴스와 관련된 작업을 하기 때문에 자신과 관련된 인스턴스의 정보(this를 사용할 일)가 필요하지만, static메서드는 인스턴스와 관련 없는 작업을 하므로 인스턴스에 대한 정보가 필요 없다.
ㅇ 멤버변수는 초기화를 하지 않아도 자동적으로 기본값으로 초기화되지만 지역변수는 사용하기 전에 반드시 초기화해야 한다.
ㅇ 클래스 초기화 블럭 ststic { 클래스 초기화} / { 인스턴스 초기화 }. 초기화 블럭이 생성자보다 먼저 실행된다. 
ㅇ 클래스변수(method area)의 초기화 순서 : 기본값->명시적초기화->클래스초기화블럭
ㅇ 인스턴스변수(heap)의 초기화 순서 : 기본값->명시적초기화->인스턴스초기화블럭->생성자

7. 객체지향 프로그래밍 2
ㅇ 상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다. 상속을 통해서 클래스를 작성하면 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있고 코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 용이하다. 이러한 특징은 코드의 재사용성을 높이고 코드의 중복을 제거하여 프로그램의 생산성과 유지보수에 크게 기여한다.
ㅇ 조상 클래스가 변경되면 자손 클래스는 자동적으로 영향을 받는다. 자손 클래스는 조상 클래스의 모든 멤버를 상속 받는다. 
ㅇ 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
ㅇ 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
ㅇ 접근 제어자가 private 또는 default인 멤버들은 상속되지 않는다기보다 상속은 받지만 자손 클래스로부터의 접근이 제한되는 것이다.
ㅇ 클래스 간의 관계에서 형제관계는 없다. 부모와 자식 관계(상속관계)만 있다.
ㅇ 포함(composite)관계 : 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것.
ㅇ is a -> 상속(~은 ~이다) , has a -> 포함(~은~을 가지고 있다)
ㅇ 참조변수를 출력하면 toString()이 호출되어 대치한 후 처리된다. System.out.println(c) <=> System.out.println(c.toString())
ㅇ toString()은 모든 클래스의 조상인 Object클래스에 정의된 것으로, 어떤 종류의 객체에 대해서도 호출이 가능하다.
ㅇ 자바에서는 단일 상속만을 허용한다. 클래스간의 관계가 매우 복잡해진다. 서로 다른 클래스로부터 상속받은 멤버간의 이름이 같은 경우 구별할 수 없다.
ㅇ 오버라이딩 : 조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것. 자손 클래스에 맞게 변경해야하는 경우 사용한다.
ㅇ 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와 이름, 매개변수, 반환타입이 같아야 한다.
ㅇ 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
ㅇ 조상 클래스의 메서드에 선언된 예외보다 넓은 범위의 예외를 선언할 수 없다.
ㅇ 자손 클래스에서 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.
ㅇ 조상 클래스의 static메서드와 같은 이름을 자손 클래스에서 정의하면 각각의 static메서드를 정의하는 것이지 오버라이딩이 아니다. 각각은 클래스 이름으로 구별될 수 있다.
ㅇ super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 상속받은 멤버와 자신의 멤버가 이름이 같은 때는 super를 붙여서 구별한다.
ㅇ 조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이므로 super대신 this를 사용할 수 있다. 그래도 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별해야 하는 경우에만 super를 사용하는 것이 좋다.
ㅇ 모든 인스턴스메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조변수인 this와 super의 값이 된다. static메서드는 인스턴스와 관련이 없으므로 this, super을 사용할 수 없다.
ㅇ super() -> this와 마찬가지로 생성자이다. this는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super는 조상 클래스의 생성자를 호출하는데 사용된다.
ㅇ 자손 클래스의 인스턴스를 생성하면 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다. 이때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다. 생성자의 첫 줄에서 조상클래스의 생성자를 호출해야 하는 이유 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다.
ㅇ 상속관계를 거슬러 올라가며 계속 조상 클래스의 생성자가 호출된다. Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가할 것이다.
ㅇ 클래스 파일들을 압축한 것이 jar파일이다. 
ㅇ 패키지명은 소문자로 하는 것이 원칙이다. 패키지를 선언하지 않으면 unnamed package가 기본적으로 제공된다. 소스파일에 패키지를 지정하지 않으면 자동으로 이름 없는 패키지에 속하게 된다.
ㅇ 패키지의 루트 디렉토리를 클래스패스에 포함시켜야 실행 시 JVM이 클래스를 찾을 수 있다.
ㅇ import문으로 사용하고자 하는 클래스의 패키지를 미리 명시해주면 소스코드에 사용되는 클래스 이름에서 패키지명은 생략할 수 있다.
ㅇ static import문을 사용하면 static멤버를 호출할 때 클래스 이름을 생략할 수 있다. import static java.lang.Math.random; System.out.println(random());
ㅇ static + 멤버변수 : 모든 인스턴스에서 공통적으로 사용되는 클래스변수. 인스턴스를 생성하지 않고 사용 가능. 클래스가 메모리에 로드될 때 생성.
ㅇ static + 메서드 : 인스턴스를 생성하지 않고 호출 가능한 static메서드. static메서드 내에서 인스턴스멤버를 직접 사용할 수 없다.
ㅇ final은 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고 클래스에 사용하면 자신을 확장하는 자손클래스를 정의하지 못하게 한다.
ㅇ 인스턴스변수의 경우 생성자에서 초기화되도록하여 각 인스턴스마다 final이 붙은 멤버변수가 다른 값을 갖도록 한다. 이후에는 수정되지 않도록 한다.
ㅇ abstract는 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 구현하는데 사용한다. 
ㅇ 인스턴스를 생성하지 못하게 클래스 앞에 제어자 abstract를 붙이는 경우도 있다. 이 자체로는 쓸모가 없지만, 다른 클래스가 이 클래스를 상속받아서 일부의 원하는 메서드만 오버라이딩해도 된다는 장점이 있다. 
ㅇ 접근제어자를 사용하여 외부로부터 데이터를 보호하고, 외부에는 불필요한 내부적으로만 사용되는 부분을 감춘다. data hiding
ㅇ 상속을 통해 확장될 것이 예상되는 클래스는 멤버에 접근 제한을 주되 자손 클래스에서 접근하는 것이 가능하도록 하기 위해 private 대신 protected를 사용한다.
ㅇ 생성자의 접근제어자를 private로 하면 외부에서 생성자를 접근할 수 없으므로 인스턴스를 생성할 수 없다. 그래도 클래스 내부에서는 인스턴스를 생성할 수 있다. 대신 인스턴스를 생성해서 반환해주는 public메서드를 제공함으로써 외부에서 이 클래스의 인스턴스를 사용하도록 할 수 있다. -> 싱글턴
ㅇ 또한 생성자의 접근제어자를 private로 하는 클래스는 다른 클래스의 조상이 될 수 없다. 왜냐하면 생성자의 접근제어자가 private이므로 자손 클래스에서 호출하는 것이 불가능하기 때문이다. 그래서 클래스 앞에 final을 추가하여 상속할 수 없는 클래스라는 것을 알리는 것이 좋다.
ㅇ 메서드에 static과 abstrcat를 함께 사용할 수 없다. static메서드는 몸통이 있는 메서드에만 사용할 수 있기 때문이다.
ㅇ 클래스에 abstract와 final을 동시에 사용할 수 없다. final은 클래스를 확장할 수 없다는 의미이고, abstract는 상속을 통해 완성되어야 한다는 의미이기 때문에 모순된다.
ㅇ abstract메서드의 접근제어자가 private일 수 없다. 자손클래스에서 구현해줘야 하기 때문이다.
ㅇ 메서드에 private와 final을 같이 사용할 필요는 없다. 접근제어자가 private인 메서드는 오버라이딩 될 수 없기 때문이다.
ㅇ 객체지향개념에서의 다형성은 여러가지 형태를 가질 수 있는 능력, 자바에서 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록 구현됐다. 즉, 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있는 것이다. List list = new ArrayList();
ㅇ 조상클래스 타입의 참조변수로는 인스턴스의 멤버 중에서 조상클래스의 멤버들만 사용할 수 있다. 
ㅇ 자손타입의 참조변수로 조상타입의 인스턴스를 참조하는 것은 컴파일 에러가 발생한다. 
ㅇ 참조변수의 형변환 : 서로 상속관계에 있는 클래스 사이에서만 가능하다. 모든 참조변수는 모든 클래스의 조상인 Object클래스 타입으로 형변환이 가능하다.
ㅇ 자손타입->조상타입:업캐스팅은 생략가능, 조상타입->자손타입:다운캐스팅은 생략불가.
ㅇ 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것은 아니기 때문에 참조변수의 형변환은 인스턴스에 아무런 영향을 끼치지 않는다. 단지 참조변수의 형변환을 통해서, 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 범위가 조절되는 것 뿐이다.
ㅇ 컴파일 시에는 참조변수간의 타입만 체크하기 때문에 실행 시 생성될 인스턴스의 타입에 대해서는 알지 못한다. 조상타입의 인스턴스를 자손타입의 참조변수로 참조하면 런타임 에러가 발생한다.
ㅇ instanceof연산자 : 참조변수가 참조하고 있는 인스턴스의 실제 타입을 알기 위함. instanceof의 왼쪽에는 참조변수를 오른쪽에는 타입(클래스명)이 피연산자로 위치한다. c instanceof car -> boolean반환
ㅇ 조상 클래스에 선언된 멤버변수와 같은 이름의 인스턴스변수를 자손 클래스에 중복으로 정의했을 때 서로 다른 결과를 낸다. 반면 메서드의 경우 조상 클래스의 메서드를 자손 클래스에서 오버라이딩한 경우에 참조변수의 타입에 관계없이 항상 실제 인스턴스의 메서드(오버라이딩된 메서드)가 호출된다.
ㅇ -> this와 super를사용해서 접근한다.
ㅇ 매개변수의 다형성 : 메서드의 매개변수로 조상타입을 받으면 자손타입 참조변수의 어느 것이나 매개변수로 받을 수 있다.
ㅇ 조상타입의 참조변수 배열을 사용하면 공통의 조상을 가진 서로 다른 종류의 객체를 배열로 묶어서 다룰 수 있다. 여러 종류의 객체를 배열을 받을 수 있다.
ㅇ Vector클래스는 내부적으로 Object타입의 배열을 가지고 있어서, 이 배열에 객체를 추가하거나 제거할 수 있게 작성되어 있다. 배열의 크기를 동적으로 관리해준다.
ㅇ 추상 클래스 : 추상 메서드를 포함하고 있는 클래스. 상속을 통해서 자손클래스에 의해서만 완성될 수 있다.
ㅇ 추상메서드를 포함하고 있지 않은 클래스에도 키워드 abstract를 붙여서 추상클래스로 지정할 수 있다. 추상메서드가 없는 완성된 클래스라 할지라도 추상클래스로 지정되면 클래스의 인스턴스를 생성할 수 없다.
ㅇ 추상메서드 : 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 메서드. 즉, 설계만 해놓고 실제 수행될 내용은 작성하지 않은 미완성 메서드이다. 메서드의 내용이 상속받는 클래스에 따라 달라질 수 있기 때문에 조상 클래스에서는 선언부만 작성하고, 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려주고, 실제 내용은 상속받는 클래스에서 구현하도록 비워두는 것이다.
ㅇ 추상클래스로부터 상속받는 자손클래스는 오버라이딩을 통해 조상인 추상클래스의 추상메서드를 모두 구현해주어야 한다. 만일 조상으로부터 상속받은 추상메서드 중 하나라도 구현하지 않는다면, 자손클래스 역시 추상클래스로 지정해야 한다.
ㅇ 메서드를 사용하는 쪽에서는 메서드가 실제로 어떻게 구현되어 있는지 몰라도 메서드의 이름과 매개변수, 리턴타입, 즉 선언부만 알고 있으면 되므로 내용이 없을 지라도 추상메서드를 사용하는 코드를 작성하는 것이 가능하며, 실제로는 자손클래스에 구현된 완성된 메서드가 호출되도록 할 수 있다.
ㅇ 상속이 자손 클래스를 만드는데 조상 클래스를 사용하는 것이라면, 추상화는 기존 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것이다.
ㅇ 클래스만 abstract로 하고 메서드는 {}만 사용해서 자손에서 구현해도 되지만, 자손에서의 구현을 강제하기 위해 추상메서드로 적어준다.
ㅇ 인터페이스 : 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 멤버로 가질 수 없다. 오직 추상메서드와 상수만을 멤버로 가질 수 있다.
ㅇ 추상클래스를 부분적으로만 완성된 미완성 설계도라고 한다면, 인터페이스는 구현된 것은 아무것도 없고 밑그림만 그려져 있는 기본 설계도라고 할 수 있다.
ㅇ 인터페이스에 접근제어자로 public 또는 default를 사용할 수 있다.
ㅇ 인터페이스의 모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.
ㅇ 인터페이스의 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다. 단, static 메서드와 디폴트 메서드는 예외(JDK 1.8부터)
ㅇ 인터페이스에서 멤버변수 앞의 final int, static int, int 모두 public static final int이고, 메서드 앞의 String 은 public abstract String이다.
ㅇ 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속이 가능하다. interface Fightable extends Movalble, Attackable {}. 하지만 인터페이스가 다중상속을 위한 것은 아니다.
ㅇ 인터페이스를 구현하려면 implements 키워드를 사용한다. 만약 구현하는 인터페이스의 일부만 구현한다면 abstract를 붙여서 추상클래스로 선언해야 한다. abstract class Fighter implements Fightable{/*일부만 구현*/}
ㅇ 인터페이스의 이름에는 주로 ~able로 끝나는 것이 많은데, 그 이유는 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위함이다.
ㅇ 인터페이스 역시 이를 구현한 클래스의 조상이라 할 수 있으므로 해당 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로의 형변환도 가능하다.
ㅇ 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
ㅇ 인터페이스를 받으면 인터페이스를 구현한 클래스의 인스턴스에 있는 멤버를 갖고 있지 않을 수 있으므로 다운캐스팅하여 사용한다.
ㅇ 인터페이스의 장점 : 개발시간 단축, 표준화 가능, 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다, 독립적인 프로그래밍 가능
ㅇ static메서드 : JDK1.8부터 인터페이스에 추가 가능하며 접근제어자가 항상 public이며 생략할 수 있다. java.util.Collection인터페이스 관련된 static메서드들이 인터페이스에는 추상메서드만 선언할 수 있다는 원칙 때문에 별도의 클래스 Collections라는 클래스에 들어가게 되었다. 
ㅇ 디폴트 메서드 : 인터페이스에 메서드를 추가한다는 것은, 추상메서드를 추가한다는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야한다는 것이다. 아무리 설계를 잘 해도 언젠가 인터페이스의 변경이 발생할 수 있다. 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스는 변경하지 않아도 된다. default 키워드를 붙이며 몸통이 있어야 한다.
ㅇ 내부 클래스 : 클래스 내에 선언된 클래스. 두 클래스의 멤버들 간에 서로 쉽게 접근할 수 있다는 장점과 외부에는 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.
ㅇ 외부 클래스 A와 내부 클래스 B가 있다면 내부 클래스 B는 외부 클래스 A를 제외하고는 다른 클래스에서 잘 사용되지 않는 것이어야 한다.
ㅇ 내부 클래스의 종류 : 인스턴스 클래스, 스태틱 클래스, 지역 클래스, 익명 클래스
ㅇ 인스턴스 클래스 내에 static변수를 선언할 수 없다. static클래스만 static멤버를 정의할 수 있다. final static은 상수이므로 가능하다.
ㅇ 인스턴스멤버는 같은 클래스에 있는 인스턴스멤버와 static멤버 모두 직접 호출이 가능하지만, static멤버는 인스턴스 멤버를 직접 호출할 수 없는 것처럼, 인스턴스 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 바로 사용할 수 있지만, 스태틱 클래스는 외부 클래스의 인스턴스 멤버를 객체생성 없이 사용할 수 없다.
ㅇ 익명 클래스 : 다른 내부클래스들과는 달리 이름이 없다. 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고 오직 하나의 객체만을 생성할 수 있는 일회용 클래스다.

8. 예외처리
ㅇ 소스코드를 컴파일 하면 컴파일러가 소스코드(*.java)에 대해 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류가 있는지를 알려준다. 컴파일러가 알려준 에러들을 모두 수정해서 컴파일을 성공적으로 마치고 나면, 클래스파일(*.class)이 생성되고, 생성된 클래스파일을 실행할 수 있게 되는 것이다.
ㅇ 컴파일을 성공적으로 마쳤다고 해서 프로그램의 실행 시에도 에러가 발생하지 않는 것은 아니다.
ㅇ 실행 중 발생하는 런타임 에러를 방지하기 위해서는 프로그램의 실행도중 발생할 수 있는 모든 경우의 수를 고려하여 이에 대한 대비를 하는 것이 필요하다. 자바에서는 실행 시(runtime) 발생할 수 있는 프로그램 오류를 에러와 예외 두 가지로 구분하였다.
ㅇ 에러는 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 것이다.
ㅇ 에러가 발생하면 프로그램의 비정상적인 종료를 막을 수 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다.
ㅇ 오류 : Error , 예외 : Exception 모두 Object-Throwable의 자손들이다. Exception은 Checked Exception과 Unchecked Exception으로 구분할 수 있다. RuntimeException을 상속하지 않는 클래스를 Checked Exception, 상속하는 클래스를 Unchecked Exception으로 분류한다. RuntimeException(Unchecked Exception)도 Exception의 일종이지만 명시적으로 예외처리를 하지 않아도 된다.
ㅇ RuntimeException클래스들은 주로 프로그래머의 실수로 발생하는 예외들로 ArrayIndexOutOfBoundsException, NullPointerException, ClassCastException, ArithmeticException등이 있다.
ㅇ Exception(Checked Exception)클래스들은 주로 외부의 영향으로 발생할 수 있는 것들로, 사용자들의 동작에 의해 발생하는 경우가 많다. FileNotFoundException, ClassNotFoundException, DataFormatException등이 있다.
ㅇ 예외처리(exception handling)란 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.
ㅇ 발생한 예외를 처리하지 못하면, 프로그램은 비정상 종료되며, 처리되지 못한 예외(uncaught exception)은 JVM의 예외처리기(UncaughtExceptionHandler)가 받아서 예외의 원인을 화면에 출력한다.
ㅇ 하나의 메서드 내에 여러 개의 try-catch문을 사용할 수 있으며, try븝ㄹ럭 또는 catch블럭에 또 다른 try-catch문이 포함될 수 있다. catch블럭 내에서도 예외가 발생할 수 있기 때문이다. 이때 같은 이름의 참조변수를 catch블럭 내에 여러번 사용할 수 없다.
ㅇ 예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch블럭을 찾는다. catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해서 검사하게 되는데, 검사결과가 true인 catch블럭을 만날 때까지 검사는 계속된다.
ㅇ 첫 번째 catch블럭에서 일치하게 된다면 두 번째 catch블럭은 검사하지 않는다. try-catch문의 마지막에 Exception클래스 타입의 참조변수를 선언한 catch블럭을 사용하면, 어떤 종류의 예외가 발생하더라도 이 catch블럭에 의해 처리되도록 할 수 있다.
ㅇ 예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨 있으며, e.getMessage() : 예외 메시지 반환, e.printStackTrace() : 예외발생 당시의 호출스택(call stack)에 있었떤 메서드의 정보와 예외 메시지를 화면에 출력한다.
ㅇ 멀티catch블럭 : catch(ExceptionA | ExceptionB e){e.printStackTrace();} . 상속관계에 있는 예외들을 함께 사용할 수 없다. 조상 클래스 하나만 써주는 것과 같기 때문이다.
ㅇ 예외 발생시키기 : 키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다. 연산자 new를 사용해서 발생시키려는 예외 클래스의 객체를 만든 후에 throw를 이용해서 예외를 발생시킨다. throw new Exception("고의예외"); //생성자에 String을 넣어주면 메시지로 저장된다.
ㅇ 메서드에 예외 선언하기 : 메서드의 선언부에 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어준다. 이 메서드는 해당 예외가 발생할 수 있다는 뜻이다. 메서드의 선언부를 보았을 때 이 메서드를 사용하기 위해서는 어떤 예외들이 처리되어야 하는지 쉽게 알 수 있다.
ㅇ 사실 예외를 메서드의 throws에 명시하는 것은 예외를 처리하는 것이 아니라, 자신을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.
ㅇ finally블럭 : 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다. try블럭(또는 catch블럭)에서 return문이 실행되는 경우에도 finally블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다. 
ㅇ try-with-resources : try(FileInputStream fis = new FileInputStream("score.dat");){~~} . try 옆 괄호에 생성할 객체를 넣어준다. 이 객체는 따로 close를 호출하지 않아도 try를 벗어나는 순간 자동적으로 close를 호출한다. 이 객체는 AutoCloseable 인터페이스를 구현한 것이어야 한다. 이때 예외가 발생하면 close로 인한 예외는 Suppressed(억제된예외)로 다룬다.
ㅇ 예외 되던지기(exception re-throwing) : 하나의 예외에 대해서 예외가 발생한 메서드와 호출한 메서드 양쪽에서 처리하도록 할 수 있다. 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 사용한다. catch문에서 필요한 작업을 수행한 후에 throw문을 사용해서 예외를 다시 발생시킨다. catch(Exception e) { throw e; }
ㅇ 연결된 예외(chained exception) : 한 예외가 다른 예외를 발생시킬 수도 있다. 예외 A가 예외 B를 발생시켰다면, A를 B의 원인 예외(cause exception)라고 한다.
ㅇ Throwable initCause(Throwable cause) 지정한 예외를 원인 예외로 등록. 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위함. 그리고 checked예외를 unchecked예외로 바꿀 수 있도록 하기 위함(throw new RuntimeException(new MemoryException("checked를 unchecked로"));)

9. java.lang패키지와 유용한 클래스
ㅇ Object클래스: 모든 클래스의 조상이기 때문에 Object클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다. 
ㅇ getClass: 객체 자신의 클래스 정보를 담고 있는 Class인스턴스를 반환한다. 
ㅇ equals: 객체의 참조변수(주소값)를 비교한다. 객체를 생성할 때 메모리의 비어있는 공간을 찾아 생성하므로 서로 다른 두 개의 객체가 같은 주소를 갖는 일은 있을 수 없다. 두 개 이상의 참조변수가 같은 주소값을 갖는 것(한 객체를 참조하는 것)은 가능하다. 오버라이딩하여 객체에 저장된 내용을 비교도록 할 수 있다.
ㅇ hashCode: 객체의 주소값으로 해시코드를 만들어 반환한다. String클래스에서는 문자열의 내용이 같으면 동일한 해시코드를 반환하도록 hashCode메서드가 오버라이딩되어있다.
ㅇ toString: getClass().getName()+"@"+Integer.toHexString(hashCode()); //클래스이름 + 16진수의 해시코드를 반환한다. String클래스의 toString은 String인스턴스가 갖고 있는 문자열을 반환하도록 오버라이딩되어 있다. Object에 정의된 toString의 접근제어자가 public이므로 오버라이딩하는 클래스의 toString은 조상의 접근범위보다 같거나 넓어야 한다.
ㅇ clone: 단순히 인스턴스변수의 값만 복사하기 때문에 참조타입의 인스턴스변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다. 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래 인스턴스에 영향을 미치게 된다. 이런 경우 clone메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야한다. clone메서드를 호출하기 위해서는 Cloneable인터페이스를 구현해야한다.
ㅇ 공변 반환타입(covariant return type): 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것이다. 즉, 오버라이딩 후 반환타입을 바꿔도 된다는 뜻이다.(원래는 안 됐었음)
ㅇ 배열뿐만 아니라 java.util패키지의 Vector, ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap, Calendar, Date와 같은 클래스들은 clone이 재정의되어있어 쉽게 복제가 가능하다.
ㅇ 얕은 복사와 깊은 복사: clone은 단순히 객체에 저장된 값을 그대로 복사할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다. clone으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하는 얕은 복사(shallow copy)가 수행되고, 얕은 복사에서는 원본을 변경하면 복사본까지도 영향을 받는다. 반면에 원본이 참조하고 있는 객체까지 복제하는 것을 깊은 복사(deep copy)라고 하고, 깊은 복사에서는 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.
ㅇ getClass(): 이 메서드는 자신이 속한 클래스의 Class객체를 반환하는 메서드이다. Class객체는 이름이 'Class'인 클래스의 객체이다. 이는 클래스의 모든 정보를 담고 있으며, 클래스당 1개만 존재한다. 그리고 클래스 파일이 클래스로더에 의해 메모리에 올라갈 때 자동으로 생성된다. 클래스로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다. 먼저 기존에 생성된 클래스 객체라 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라서 클래스 파일을 찾는다. 못 찾으면 ClassNotFoundException이 발생하고 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환한다. 즉 파일형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하는 것이다.(클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓는 것.)
ㅇ Class 객체를 얻는 방법 : 1. new Card().getClass(); 2. Card.class; 3. Class.forName("Card");
ㅇ java Reflection API : 구체적인 클래스 타입을 알지 못해도 그 클래스의 메서드, 타입, 변수에 접근할 수 있도록 해주는 자바 API. 런타임중에 실행되고 있는 클래스에 접근해서 동적으로 객체를 생성하고 메서드를 호출할 수 있다. 코드 작성시에는 어떤 타입의 클래스를 사용할 지 모르지만, 런타임 시점에 사용하고자 할 때 적용.
ㅇ String클래스: 변경 불가능한(immutable)클래스이다. 문자열을 저장하기 위해 내부에 문자형 배열 참조변수(char[]) value를 인스턴스 변수로 정의해놓고 있다. 한번 생성된 String인스턴스가 갖고 있는 문자열은 읽어 올 수만 있고, 변경할 수는 없다. 예시로 +연산자로 문자열을 결합하는 경우는 문자열이 바뀌는 것이 아니라 새로운 객체를 생성해서 참조를 바꾸는 것이다. 
ㅇ 문자열간의 결합이나 추출 등 문자열을 다루는 작업이 많이 필요한 경우에는 String클래스 대신 StringBuffer클래스를 사용하는 것이 좋다. StringBuffer인스턴스에 저장된 문자열은 변경이 가능하므로 하나의 StringBuffer인스턴스만으로도 문자열을 다루는 것이 가능하다.
ㅇ String클래스의 생성자를 이용하여 문자열을 만들면 매번 메모리 할당이 발생하고 새로운 인스턴스가 생성된다. 반면에 문자열 리터럴을 사용하면 이미 존재하는 것의 주소가 변수에 저장되는 것이다.
ㅇ 자바 소스파일에 포함된 모든 문자열 리터럴은 컴파일 시에 클래스 파일에 저장된다. 이때 같은 내용의 문자열 리터럴은 한번만 저장된다. 문자열 리터럴도 String인스턴스이고, 한번 생성하면 내용을 변경할 수 없으니 하나의 인스턴스를 공유하면 되기 때문이다.
ㅇ 클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있다. 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 리터럴의 목록에 있는 리터럴들이 JVM내에 있는 상수 저장소(constant pool)에 저장된다.
ㅇ 예전에는 parseInt와 같은 메서드를 많이 썼는데, 메서드의 이름을 통일하기 위해 valueOf가 추가되었다. valueOf내부에서 그저 parseInt를 호출하는 것 뿐이다.
ㅇ String.valueOf(int i) , Integer.parseInt(String s)
ㅇ parseInt나 parseFloat같은 메서드는 문자열에 공백 또는 문자가 포함되어 있는 경우 변환 시 예외(NumberFormatException)이 발생할 수 있으므로 주의해야 한다. 그래서 문자열 양 끝의 공백을 제거해주는 trim()을 습관적으로 같이 사용하기도 한다.
ㅇ StringBuffer의 비교 : StringBuffer클래스는 equals메서드를 오버라이딩하지 않아서 등가비교연산자(==)로 비교한 것과 같은 결과를 얻는다. toString은 오버라이딩 되어있어서 toString호출 뒤에 equals메서드를 사용하여 비교한다.
ㅇ StringBuilder : StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화되어있다. 이 동기화가 StringBuffer의 성능을 떨어뜨린다. 동기화만 뺀 StringBuilder가 추가되었다. 
ㅇ 래퍼클래스(Wrapper Class) : Boolean, Character, Byte, Short, Integer, Long, Float, Double. 래퍼클래스들은 객체생성 시에 생성자의 인자로 주어진 각 자료형에 알맞은 값을 내부적으로 저장하고 있으며(value 변수), 이에 관련된 메서드가 정의되어 있다.
ㅇ Number클래스의 자손중에 BigInteger는 long으로 다룰 수 없는 큰 범위의 정수를, BigDecimal은 double로 다룰 수 없는 큰 범위의 부동 소수점수를 처리하기 위한 것으로 연산자의 역할을 대신하는 다양한 메서드를 제공한다. 
ㅇ 오토박싱 & 언박싱 : 기본형과 참조형 간의 연산이 가능하다. 컴파일러가 자동으로 변환하는 코드를 넣어준다. ArrayList클래스에 기본형 값을 저장하려 할 때 컴파일러가 자동적으로 형변환 코드를 추가해준다. 이를 오토박싱이라 하고 반대로 변환하는 것을 언박싱이라 한다.
ㅇ java.util.Objects클래스 : isNull, nonNull, requireNonNull 과 같은 메서드들이 있다. Object에는 없는 compare메서드가 있다. static int compare(Object a, Object b, Comparator c). Object에 정의된 equals가 Objects에도 있는데 이는 널검사까지 해준다. deepEquals는 객체의 재귀적 비교를 하여 다차원 배열의 비교도 가능하다. 
ㅇ java.util.Random클래스 : Math.random()과의 차이는 종자값을 설정할 수 있어서 종자값이 같은 Random인스턴스들은 항상 같은 난수를 반환한다.
ㅇ 정규식(Regular Expression)-java.util.regex : 정규식이란 텍스트 데이터 중에서 원하는 조건(패턴)과 일치하는 문자열을 찾아내기 위해 사용하는 것으로 미리 정의된 기호와 문자를 이용해서 작성한 문자열을 말한다.
ㅇ java.util.Scanner클래스 : Scanner는 화면, 파일, 문자열과 같은 입력소스로부터 문자데이터를 읽어오는데 도움을 줄 목적으로 JDK1.5부터 추가되었다. 또한 Scanner는 정규식 표현을 이용한 라인단위의 검색을 지원하며 구분자에도 정규식 표현을 사용할 수 있어 복잡한 형태의 구분자도 처리가 가능하다.
ㅇ java.util.StringTokenizer클래스 : StringTokenizer는 긴 문자열을 지정된 구분자를 기준으로 토큰이라는 여러 개의 문자열로 잘라내는 데 사용된다. 다른 방법으로는 String의 split(String regex)이나 Scanner의 useDelimiter(String pattern)을 사용할 수도 있다. split은 빈 문자열도 토큰으로 인식하는 반면 StringTokenizer는 빈 문자열을 토큰으로 인식하지 않기 때문에 인식하는 토큰의 개수가 서로 다르다는 것을 알 수 있다. split은 데이터를 토큰으로 잘라낸 결과를 배열에 담아서 반환하기 때문에 데이터를 토큰으로 바로바로 잘라서 반환하는 StringTokenizer보다 성능이 떨어질 수 밖에 없다.
ㅇ java.math.BigInteger클래스 : 내부적으로 int배열을 사용해서 long보다 훨씬 큰 값을 다룰 수 있는 클래스이다. 
ㅇ java.math.BigDecimal클래스 : double타입으로 표현할 수 있는 값은 상당히 범위가 넓지만, 정밀도가 최대 13자리 밖에 되지 않고 실수형의 특성상 오차를 피할 수 없다. BigDecimal은 실수형과 달리 정수를 이용해서 실수를 표현한다.

10. 날짜와 시간 & 형식화
ㅇ Calendar 는 추상클래스이기 때문에 직접 객체를 생성할 수 없고, 메서드를 통해서 완전히 구현된 클래스의 인스턴스를 얻어야 한다. 인스턴스를 직접 생성해서 사용하지 않고 메서드를 통해(Calendar.getInstance()) 인스턴스를 반환받게 하는 이유는 변경에 유리하기 때문이다. getInstance메서드가 static인 이유는 메서드 내의 코드에서 인스턴스 변수를 사용하거나 인스턴스 메서드를 호출하지 않기 때문이며 또 다른 이유는 getInstance가 static이 아니라면 객체를 생성한 다음에 호출해야 하는데 Calendar는 추상클래스이기 때문에 객체를 생성할 수 없다.
ㅇ 형식화 클래스 : java.text패키지에 포함되어 있으며 숫자, 날짜, 텍스트 데이터를 일정한 형식에 맞게 표현할 수 있는 방법을 객체지향적으로 설계하여 표준화하였다.
ㅇ DecimalFormat : 형식화 클래스 중에서 숫자를 형식화 하는데 사용된다. DecimalFormat df = new DecimalFormat("#.#E0"); String result = df.format(12345.67);
ㅇ SimpleDateFormat : 원하는 출력형식의 패턴을 작성하여 SimpleDateFormat인스턴스를 생성한 다음, 출력하고자 하는 Date인스턴스를 가지고 format(Date d)를 호출하면 지정한 출력형식에 맞게 변환된 문자열을 얻는다. 
ㅇ ChoiceFormat : 특정 범위에 속하는 값을 문자열로 반환해준다. 연속적 또는 불연속적인 범위의 값들을 처리할 때 ChoiceFormat을 사용한다. 
ㅇ MessageFormat : MessageFormat은 데이터를 정해진 양식에 맞게 출력할 수 있도록 도와준다. 데이터가 들어갈 자리를 마련해 놓은 양식을 미리 작성하고 프로그램을 이용해서 다수의 데이터를 같은 양식으로 출력할 때 사용하면 좋다.
ㅇ java.time패키지 : java.time패키지에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다. 시간을 표현할 때는 LocalTime클래스를 사용하고, 날짜를 표현할 때는 LocalDate클래스를 사용한다. 그리고 날짜와 시간이 모두 필요할 때는 LocalDateTime클래스를 사용한다.
ㅇ LocalDate, LocalTime, LocalDateTime에는 now(), of()를 사용하여 객체를 생성한다.
ㅇ LocalDate, LocalTime, LocalDateTime, ZonedDateTime등 날짜와 시간을 표현하기 위한 클래스들은 모두 Temporal, TemporalAccessor, TemporalAdjuster인터페이스를 구현했고,
ㅇ Period(날짜 - 날짜), Duration(시간 - 시간)은 TemporalAmount인터페이스를 구현했다.
ㅇ 날짜와 시간의 파싱과 포맷 : DateTimeFormatter를 사용한다. 

11. 컬렉션 프레임웍(Colletion Framework)
ㅇ 컬렉션 프레임웍이란, 데이터 군을 저장하는 클래스들을 표준화한 설계를 뜻한다.
ㅇ 컬렉션 프레임웍은 컬렉션(다수의 데이터)을 다루는 데 필요한 다양하고 풍부한 클래스들을 제공하기 때문에 프로그래머의 짐을 덜어주며, 또한 인터페이스와 다형성을 이용한 객체지향적 설계를 통해 표준화되어 있기 때문에 사용법을 익히기에도 편리하고 재사용성이 높은 코드를 작성할 수 있다는 장점이 있다.
ㅇ 컬렉션 프레임웍에서는 컬렉션 데이터 그룹을 크게 3가지 타입이 존재한다고 인식하고 각 컬렉션을 다루는데 필요한 기능을 가진 3개의 인터페이스를 정의하였다. 그리고 인터페이스 List와 Set의 공통된 부분을 다시 뽑아서 새로운 인터페이스인 Collection을 추가로 정의하였다.
ㅇ List: 순서가 있는 데이터의 집합. 중복을 허용한다. 구현클래스: ArrayList, LinkedList, Stack, Vector 등
ㅇ Set: 순서를 유지하지 않는 데이터의 집합. 중복을 허용하지 않는다. 구현클래스: HashSet, TreeSet 등
ㅇ Map: 키와 값의 쌍으로 이루어진 데이터의 집합. 순서는 유지되지 않으며, 키는 중복을 허용하지 않고, 값은 중복을 허용한다. 기존에 저장된 데이터와 중복된 키와 값을 저장하면 기존의 값은 없어지고 마지막에 저장된 값이 남게 된다. 구현클래스: HashMap, TreeMap, HashTable, Properties 등
ㅇ ArrayList: List인터페이스를 구현하여 데이터의 저장순서가 유지되고 중복을 허용한다. Object배열을 이용하여 데이터를 순차적으로 저장한다. 배열에 더이상 저장할 공간이 없으면 보다 큰 새로운 배열을 생성해서 기존의 배열에 저장된 내용을 새로운 배열로 복사한 다음에 저장된다.
ㅇ Collections.sort(list1); // Collection은 인터페이스고 Collections는 클래스다.
ㅇ LinkedList: 배열은 접근시간이 가장 빠르지만 크기를 변경할 수 없고, 비순차적인 데이터의 추가 또는 삭제에 시간이 많이 걸린다는 단점이 있다. 이러한 배열의 단점을 보완하기 위해 LinkedList라는 자료구조가 고안되었다. 링크드리스트는 불연속적으로 존재하는 데이터를 서로 연결한 형태로 구성되어 있다. 삭제하고자 하는 요소의 이전요소가 삭제하고자 하는 요소의 다음 요소를 참조하도록 변경하면 삭제된다. 
ㅇ 링크드리스트는 다음요소에 대한 접근은 쉽지만 이전요소에 대한 접근은 어렵다. 이 점을 보완한 것이 이중연결리스트(doubly linked list)이다. 단순히 참조변수를 하나 더 추가하여 이전 요소에 대한 참조가 가능하게 했다. 
ㅇ 이중 원형 연결리스트(doubly circular linked list)는 단순히 더블 링크드리스트의 첫번째 요소와 마지막 요소를 연결시킨 것이다.
ㅇ 실제로 LinkedList클래스는 이름과 달리 더블 링크드 리스트로 구현되어 있다. 
ㅇ 컬렉션 프레임웍에 속한 대부분의 컬렉션 클래스들은 이처럼 서로 변환이 가능한 생성자를 제공하므로 이를 이용하면 간단히 다른 컬렉션 클래스로 데이터를 옮길 수 있다.
ㅇ 스택과 큐: 스택에는 ArrayList와 같은 배열기반의 컬렉션 클래스가 적합하지만 큐는 데이터를 꺼낼 때 항상 첫 번째 저장된 데이터를 삭제하므로, LinkedList로 구현하는 것이 적합하다.
ㅇ Stack st = new Stack(); Queue q = new LinkedList();// Queue인터페이스의 구현체인 LinkedList를 사용.
ㅇ PriorityQueue: Queue인터페이스의 구현체 중의 하나로, 저장한 순서에 관계없이 우선순위가 높은 것부터 꺼내게 된다. 그리고 null은 저장할 수 없다. PriorityQueue는 저장공간으로 배열을 사용하며, 각 요소를 힙 자료구조 형태로 저장한다. 
ㅇ 우선순위 큐는 숫자뿐만 아니라 객체를 저장할 수도 있는데 그럴 경우 각 객체의 크기를 비교할 수 있는 방법을 제공해야 한다.
ㅇ Deque(Double-Ended Queue): Queue의 변형으로, 한 쪽 끝으로만 추가/삭제할 수 있는 Queue와 달리, Deque은 양쪽 끝에 추가/삭제가 가능하다. Deque의 조상은 Queue이며, 구현체로는 ArrayDeque과 LinkedList가 있다. 덱은 스택과 큐를 하나로 합쳐놓은 것과 같으며 스택으로 사용할 수도 있고, 큐로 사용할 수도 있다.
ㅇ Iterator, ListIterator, Enumeration은 모두 컬렉션에 저장된 요소를 접근하는데 사용되는 인터페이스다.Enumeration은 Iterator의 구버전이며, ListIterator는 Iterator의 기능을 향상시킨 것이다.
ㅇ 컬렉션 프레임웍에서는 컬렉션에 저장된 요소들을 읽어오는 방법을 표준화하였다. 컬렉션에 저장된 각 요소에 접근하는 기능을 가진 Iterator인터페이스를 정의하고, Collection인터페이스에는 Iterator(Iterator를 구현한 클래스의 인스턴스)를 반환하는 iterator()를 정의하고 있다.
ㅇ boolean hasNext(): 읽어올 요소가 있는지 확인, Object next(): 다음 요소를 읽어온다, void remove(): next()로 읽어온 요소를 삭제한다.
ㅇ 공통 인터페이스를 정의해서 표준을 정의하고 구현하여 표준을 따르도록 함으로써 코드의 일관성을 유지하여 재사용성을 극대화하는 것이 객체지향 프로그래밍의 중요한 목적 중 하나이다.
ㅇ Map인터페이스를 구현한 컬렉션 클래스는 키와 값을 쌍으로 저장하고 있기 때문에 iterator()를 직접 호출할 수 없고, keySet()이나 entrySet()과 같은 메서드를 통해서 키와 값을 각각 따로 Set의 형태로 얻어 온 후에 다시 iterator()를 호출해야한다.
ㅇ Enumeration: iterator의 구버전, ListIterator: Iterator에 양방향 조회기능 추가(List를 구현한 경우만 사용 가능)
ㅇ Arrays: Arrays클래스에는 배열을 다루는데 유용한 메서드가 정의되어 있다.
ㅇ 배열의 복사: Arrays.copyOf(arr,길이); , 배열 채우기: Arrays.fill(arr,값); Arrays.setAll(arr, ()->(int)(Math.random()*5)+1);
ㅇ 배열의 정렬과 검색: Arrays.sort(arr); Arrays.binarySearch(arr,값);//인덱스반환. 배열이 정렬되어있을 경우에 사용
ㅇ 배열의 비교와 출력: Arrays.equals(arr1,arr2);//1차원배열 Arrays.deepEquals(arr2d1,arr2d2);//2차원배열비교 Arrays.toString(arr1d);//1차원배열만가능
ㅇ 배열을 리스트로 변환: Arrays.asList(arr);
ㅇ Comparator와 Comparable 모두 인터페이스로 컬렉션을 정렬하는데 필요한 메서드를 정의하고 있으며, Comparable을 구현하고 있는 클래스들은 같은 타입의 인스턴스끼리 서로 비교할 수 있는 클래스들, 주로 wrapper클래스와 String, Date, File과 같은 것들이며 기본적으로 오름차순으로 정렬되도록 구현되어 있다.
ㅇ Comparable을 구현한 클래스들이 기본적으로 오름차순으로 정렬되어 있지만, 내림차순으로 정렬한다던가 아니면 다른 기준에 의해서 정렬되도록 하고 싶을 때 Comparator를 구현해서 정렬기준을 제공할 수 있다.
ㅇ Comparable: 기본 정렬 기준을 구현하는데 사용, Comparator: 기본 정렬기준 외에 다른 기준으로 정렬하고자할 때 사용
ㅇ HashSet: Set인터페이스를 구현한 컬렉션으로 중복된 요소를 저장하지 않는다. 중복된 요소를 추가하고자 할 때 false를 반환하고 추가에 실패한다. 저장순서를 유지하지 않으므로 저장순서를 유지하고자 한다면 LinkedHashSet을 이용한다.
ㅇ HashSet의 add메서드는 새로운 요소를 추가하기 전에 기존에 저장된 요소와 같은 것인지 판별하기 위해 추가하려는 요소의 equals()와 hashCode()를 호출하기 때문에 목적에 맞게 오버라이딩해야 한다.
    String클래스는 문자열의 내용으로 해시코드를 만들고 Object클래스는 객체의 주소로 해시코드를 만든다.
ㅇ TreeSet: 이진 검색 트리(binary search tree)라는 자료구조 형태로 데이터를 저장하는 컬렉션 클래스이다. 이진 검색 트리는 정렬, 검색, 범위검색에 높은 성능을 보이는 자료구조이며 TreesSet은 이진 검색 트리의 성능을 향상시킨 레드블랙트리로 구현되어 있다.
ㅇ TreeSet은 Set인터페이스를 구현했으므로 중복된 데이터의 저장을 허용하지 않으며 정렬된 위치에 저장하므로 저장순서를 유지하지도 않는다.
ㅇ 트리는 데이터를 순차적으로 저장하는 것이 아니라 저장위치를 찾아서 저장해야하고, 삭제하는 경우 트리의 일부를 재구성해야하므로 링크드 리스트보다 데이터의 추가/삭제시간은 더 걸린다. 대신 배열이나 링크드 리스트에 비해 검색과 정렬기능이 뛰어나다.
ㅇ HashMap과 HashTable의 관계는 ArrayList와 Vector의 관계와 같아서 HashTable보다는 새로운 버전인 HashMap을 사용할 것을 권한다. 
ㅇ HashMap은 Map을 구현했으므로 앞에서 살펴본 Map의 특징, 키와 값을 묶어서 하나의 데이터(entry)로 저장한다는 특징이 있다. 그리고 해싱을 사용하기 때문에 많은 양의 데이터를 검색하는데 있어서 뛰어난 성능을 보인다.
ㅇ HashMap은 Entry라는 내부 클래스를 정의하고, 다시 Entry타입의 배열을 선언하고 있다. 키와 값은 별개의 값이 아니라 서로 관련된 값이기 때문에 각각의 배열로 선언하기보다는 하나의 클래스로 정의해서 하나의 배열로 다루는 것이 데이터의 무결성(integrity)적인 측면에서 더 바람직하기 때문이다. 
ㅇ 해싱과 해싱함수: 해싱이란 해시함수를 이용해서 데이터를 해시테이블에 저장하고 검색하는 기법을 말한다. 해시함수는 데이터가 저장되어 있는 곳을 알려주기 때문에 다량의 데이터 중에서도 원하는 데이터를 빠르게 찾을 수 있다. 검색하고자 하는 값의 키로 해시함수를 호출한다. 해시코드(해시함수의 계산결과)로 해당 값이 저장되어 있는 링크드 리스트를 찾는다. 링크드 리스트에서 검색한 키와 일치하는 데이터를 찾는다.
ㅇ 이때 링크드 리스트는 검색에 불리한 자료구조이기 때문에 링크드 리스트의 크기가 커질수록 검색속도가 떨어지게 된다. 반면에 배열의 크기가 커져도 원하는 요소가 몇 번째에 있는지만 알면 공식(배열의 인덱스가 n인 요소의 주소 = 배열의 시작주소 + type의 size * n)에 의해 빠르게 원하는 값을 찾을 수 있다.
ㅇ 하나의 링크드 리스트에 최소한의 데이터만 저장되려면, 저장될 데이터의 크기를 고려해서 HashMap의 크기를 적절하게 지정해주어야 하고, 해시함수가 서로 다른 키에 대해서 중복된 해시코드의 반환을 최소화해야 한다. 그래야 HashMap에서 빠른 검색시간을 얻을 수 있다.
ㅇ 그래서 해싱을 구현하는 과정에서 제일 중요한 것은 해시함수의 알고리즘이며, 실제로 HashMap과 같이 해싱을 구현한 컬렉션 클래스에서는 Object클래스에 정의된 hashCode()를 해시함수로 사용한다. 이는 객체의 주소를 이용하는 알고리즘으로 해시코드를 만들어 내기 때문에 모든 객체에 대해 hashCode()를 호출한 결과가 서로 유일한 훌륭한 방법이다.
ㅇ TreeMap: TreeMap은 이진 검색 트리의 형태로 키와 값의 쌍으로 이루어진 데이터를 저장한다. 검색에 관한 대부분의 경우에는 HashMap이 TreeMap보다 더 뛰어나고, 범위검색이나 정렬이 필요한 경우에는 TreeMap이 낫다.
ㅇ Properties: HashMap의 구버전인 HashTable을 상속받아 구현한 것으로, (String,String)을 저장하는 단순화된 컬렉션클래스이다. 주로 애플리케이션의 환경설정과 관련된 속성을 저장하는데 사용되며 데이터를 파일로부터 읽고 쓰는 편리한 기능을 제공한다.
ㅇ Collections: 컬렉션과 관련된 메서드를 제공한다. fill, copy, sort, binarySearch 등의 메서드가 있다.
ㅇ 컬렉션의 동기화: 멀티 스레드 프로그래밍에서는 하나의 객체를 여러 스레드가 동시에 접근할 수 있기 때문에 데이터의 일관성을 유지하기 위해서는 공유되는 객체에 동기화가 필요하다. ArrayList와 HashMap과 같은 컬렉션은 동기화를 자체적으로 처리하지 않고 필요한 경우에만 java.util.Collections클래스의 동기화 메서드를 통해서 동기화처리가 가능하도록 변경하였다.(synchronizedCollection, synchronizedList ....)
ㅇ 변경불가 컬렉션 만들기: 컬렉션에 저장된 데이터를 보호하기 위해서 컬렉션을 변경할 수 없게, 즉 읽기 전용으로 만들어야할 때가 있다. 주로 멀티스레드 프로그래밍에서 여러 스레드가 하나의 컬렉션을 공유하다보면 데이터가 손상될 수 있는데, 이를 방지하기 위해 unmodifiableCollection, unmodifiableList ... 를 사용한다.
ㅇ 싱글톤 컬렉션 만들기: 단 하나의 객체만을 저장하는 컬렉션을 만들고 싶을 때 singleonList, singleton, singletonMap과 같은 메서드를 사용한다. 매개변수로 저장할 요소를 지정하면, 해당 요소를 저장하는 컬렉션을 반환한다. 

12. 지네릭스, 열거형, 애너테이션
ㅇ 지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
ㅇ 타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.
ㅇ 지네릭 클래스를 선언하면 클래스 옆에 <T>와 같은 타입변수를 붙여주면 된다. Map<K,V>와 같은 경우 키와 값을 의미한다. 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 모두 같다. 지네릭 클래스 Box<T>의 객체를 생성할 때는 참조변수와 생성자에 타입 T대신 실제로 사용될 타입을 지정해주어야 한다.
ㅇ 타입문자 T는 지네릭 클래스의 타입변수 또는 타입 매개변수라고 한다. 타입 매개변수에 타입을 지정하는 것을 '지네릭 타입 호출'이라고 하고 지정된 타입 예를 들어 String이라면 이를 매개변수화된 타입(parameterized type)이라고 한다.
ㅇ 컴파일 후에 Box<String>의 지네릭은 사라지고 원시타입인 Box로 바뀐다.
ㅇ 모든 객체에서 동일하게 동작해야하는 static멤버에 타입 변수T를 사용할 수 없다. T는 인스턴스변수로 간주되기 때문이다. static멤버는 인스턴스변수를 참조할 수 없다.
ㅇ 지네릭 타입의 배열을 생성할 수 없다. 지네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만, new T[10]과 같이 배열을 생성하는 것은 안 된다. new연산자는 컴파일 시점에 타입 T가 정확히 뭔지 알아야 하기 때문이다.
ㅇ 꼭 지네릭 배열을 생성해야할 필요가 있을 때는, new연산자 대신 Reflection API의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나, Object배열을 생성해서 복사한 다음에 T[]로 형변환하는 방법 등을 사용한다.
ㅇ 제한된 지네릭 클래스: 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한하려면 extends를 사용한다. FruitBox<T extends Fruit>과 같이 사용하면 T는 Fruit타입의 자손들만 대입할 수 있게 제한된다.
ㅇ 만일 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면 이때도 extends를 사용한다.(implements를 사용하지 않는다.) 클래스 Fruit의 자손이면서 Eatable인터페이스도 구현해야 한다면 class FruitBox<T extends Fruit & Eatable>{...} 과 같이 사용한다.
ㅇ static메서드에는 타입 매개변수를 사용할 수 없다. 또한 지네릭을 사용하는 메서드를 오버로딩하면 컴파일 에러가 발생한다. 지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 때문이다. 지네릭 타입은 컴파일러가 컴파일할 때만 사용하고 제거해버린다. 오버로딩이 아니라 메서드 중복 정의가 되는 것이다.
ㅇ 이때 사용할 수 있는 것이 와일드 카드이다. 와일드 카드는 기호 ?로 표현하는데 와일드 카드는 어떤 타입도 될 수 있다.
ㅇ ?만으로는 Object타입과 다를게 없으므로 extends로 상한을, super로 하한을 설정할 수 있다. <? extends T> T와 그 자손들만 가능, <? super T> T와 그 조상들만 가능, <?> 모든 타입 가능 <? extends Object>와 동일.
ㅇ 지네릭 메서드: 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다. 지네릭 타입의 선언 위치는 반환 타입 바로 앞이다. static <T> void sort(List<T> list, Comparator<? super T> c)
ㅇ 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다. 같은 타입 문자 T를 사용해도 같은 것이 아니다. static멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다.
ㅇ 메서드에 선언된 지네릭 타입은 지역 변수를 선언한 것과 같다고 이해하면 쉬운데, 이 타입 매개변수는 메서드 내에서만 지역적으로 사용될 것이므로 메서드가 static이건 아니건 상관이 없다. (그냥 내부에서 타입변수 T를 어떻게 쓰겠다는 것만 정의하는 느낌) 
ㅇ static Juice makeJuice(FruitBox<? extends Fruit> box) ==> static <T extends Fruit> Juice makeJuice(FruitBox<T> box)
ㅇ 지네릭 메서드를 호출할 때는 다음과 같이 타입 변수에 타입을 대입해야 한다. Juicer.<Apple>makeJuice(appleBox). 그러나 대부분 컴파일러가 타입을 추정할 수 있기 때문에 생략해도 된다. 다만 참조변수나 클래스 이름은 생략할 수 없다.
ㅇ 지네릭 타입의 형변환: 지네릭 타입과 넌지네릭 타입간의 형변환은 항상 가능하다. 하지만 대입된 타입이 다른 지네릭 타입 간(Box<Object> objBox = new Box<String>();)에는 형변환이 불가능하다. 
ㅇ Optional<Object>를 Optional<String>으로 직접 형변환하는 것은 불가능하지만, 와일드 카드가 포함된 지네릭 타입으로 형변환하면 가능하다. 대신 확인되지 않은 타입으로의 형변환이라는 경고가 발생한다. Optional<Object> -> Optional<?> -> Optional<T>
ㅇ 지네릭 타입의 제거: 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다. 즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다.
ㅇ 순서 1. 지네릭 타입의 경계(bound)를 제거한다. 지네릭 타입이 <T extends Fruit>라면 클래스 내부의 T는 Fruit으로 치환된다. <T>인 경우는 T는 Object로 치환된다. 
ㅇ 순서 2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다. 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형변환이 추가된다.
ㅇ 열거형(enums): 열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다. 자바의 열거형은 타입에 안전한 열거형(typesafe enum)이라서 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다.
ㅇ 열거형의 정의와 사용: enum 열거형이름 { 상수명1, 상수명2, ...} // '열거형이름.상수명' 형태로 사용한다.
ㅇ 열거형 상수간의 비교에는 '=='를 사용할 수 있다. equals가 아닌 ==로 비교가 가능하다는 것은 그만큼 빠른 성능을 제공한다는 얘기다. 그러나 비교할 땐 <, >를 사용할 수 없고 compareTo()를 사용할 수 있다.
ㅇ 열거형 상수의 값이 불연속적인 경우에는 열거형 상수의 이름 옆에 원하는 값을 괄호와 함께 적어준다. enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10); }. 그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해주어야 한다. 
ㅇ 열거형의 생성자는 외부에서 호출할 수 없다. 열거형의 생성자는 제어자가 묵시적으로 private이기 때문이다. 
ㅇ 열거형의 이해: 사실은 열거형 상수 하나하나가 객체이다.
ㅇ 애너테이션(annotation): 자바를 개발한 사람들은 소스코드와 문서를 하나의 파일로 관리하기로 하였고 주석 '/** ~~~ */'에 소스코드에 대한 정보를 저장하고 주석으로부터 HTML문서를 생성해내는 javadoc.exe를 사용했다. 이 기능을 응용하여 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 애너테이션이다.
ㅇ 예를 들어, @Test라는 애너테이션을 메서드 앞에 분인다. 이는 '이 메서드를 테스트해야 한다.'라는 것을 테스트 프로그램에게 알리는 역할을 할 뿐, 메서드가 포함된 프로그램 자체에는 아무런 영향을 끼치지 않는다.
ㅇ 메타 애너테이션: 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션. @Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface, @Native ...
ㅇ