2021 02 19
2021-02-19¶
자바 배경지식¶
- 참고1: https://aljjabaegi.tistory.com/387
- 참고2: https://www.youtube.com/watch?v=f0cAmTYo4tQ
- 참고3: https://www.youtube.com/watch?v=L19wXSpv5cs
- Java 컴파일 과정 - Java 소스 코드(.java) ---Java Compiler(javac)--> Java 바이트 코드(.class) - Java 바이트 코드(.class) ---Class Loader--> JVM - 이제 JVM 안의 실행 엔진에 의해 기계어로 해석 후 메모리 상에 배치 - 실행 엔진에는 Interpreter와 JIT Compiler가 있음 - Interpreter: Java 바이트 코드 한 줄씩 실행, slow - JIT Compiler: 전체 바이트 코드 컴파일, slow but after cached fast
- JVM 메모리 구조 - Stack Area - 클래스 내의 메서드에서 사용되는 정보들의 저장 - 매개변수, 지역변수, 리턴값 등 - Method Area - 클래스, 메서드, 인스턴스 변수, 클래스 변수, 상수 등 저장 - Heap Area - New 명령어를 통해 생성한 인스턴스 - 참조형 변수 정보들이 저장 - GC의 대상 - PC Register Area - Thread 별로 하나씩 생성된 JVM 명령의 주소값이 저장되는 곳 - Native Method Stack Area - Java 외의 다른 언어의 호출 담당 - C/C++을 호출하는 Stack 영역
- JDK, JRE, JVM - JDK > JRE > JVM 관계 - JVM: OS 별로 다름. Bytecode를 검증하고 실행 - JRE: JVM + 시스템 라이브러리들을 가지고 있음 (scanner 등) - JDK: JRE + 개발을 위한 도구 (compiler, debugger 등)
- 빌드 - 정의 - 소스코드 파일을 여러 과정을 거쳐 컴퓨터에서 실행할 수 있는 독립 sw 가공물 (jar, war) 로 변환시키는 과정 - 빌드 단계 - 소스코드 컴파일 - Test 코드 컴파일 - Test 코드 실행 - Test 코드 리포트 작성 - 기타 추가 설정한 작업들 진행 - 패키징 수행 - 독립 sw 가공물 만들기 - 빌드 자동화 - 빌드가 수동이라면, 사람마다 다르게 빌드하고, 큰일 남 - 소스코드의 빌드 과정을 자동으로 처리해주는 프로그램을 사용 - 외부 라이브러리의 자동 추가/관리를 지원 - ANT, Maven, Gradle이 존재 - Maven - 빌드를 위해 xml으로 설정 - 외부 라이브러리 관리 가능 - ANT의 장황한 스크립트 해결 - 하지만 xml의 태생적 한계에 부딪힘... 복잡 - Gradle - 빌드를 위해 JVM 위에서 동작하는 Groovy 언어 사용 - 외부 라이브러리 관리 가능 - 유연한 빌드 스크립트 작성 가능 - build.gradle에 dependencies를 추가해주자
자바 문자열¶
- String vs StringBuffer vs StringBuilder - StringBuffer --> threadSafe?
자바 JCF¶

- 정의 - Java Collections Framework - Collections: 데이터의 집합, 그룹 - JCF는 이러한 데이터/자료구조인 Collection과 이를 구현하는 클래스/인터페이스들을 제공
- 장점 - 코드 재사용 - 속도, 품질 보장
- List
- ArrayList: 배열을 구현한 클래스, 물리적 순서가 동일함
- 삽입: 기본 Capacity 10, 꽉 차면 기존 크기에서 50%의 크기를 더해 새로운 크기를 산정해 새로운 배열에 복사
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //기존 사이즈의 50%를 추가해 새로운 크기 산정 ... }- 삭제: 삭제하는 index 다음부터 복붙, 마지막 데이터 null 처리- LinkedList: 논리적으로 순차적, 물리적으론 안 순차적 - 삽입: 앞과 뒤는 빨리 찾음 (처음, 끝 내부적으로 저장 해둠), 중간 인덱스 오래걸림 - 삭제: 걸리는 시간은 삽입과 같음. 자기자신을 null 처리
- List
integers = new ArrayList<>(); - 왜 List로 초기화를 시키는가? - 우리의 관심사는 데이터 구조 안에 있는 Integer이지 어떻게 저장되어 있는가가 X - 한 단계 더 추상화 시킨 List로 Integer를 관리하는 "무엇"으로 초기화 시키는 것이 변경 및 확장에 유리
- Vector - ArrayList와 같은 구조 - 근데 얘는 동기화된 메서드로 구성되어 있어 MultiThread에서 안전! - ArrayList는 Thread Safe 하지 않아... 개발자의 명시적 동기화 필수
- Stack - LIFO - Vector로 구성되어 있음
- Queue
- FIFO
- 인터페이스라서 객체 생성 불가능... LinkedList or PriorityQueue 사용하자
- Queue
queue = new LinkedList<>(); - Queue queue = new PriorityQueue<>();
- Set
- HashSet
- 특징
- 객체 정렬 순서 유지 X
- Iterator 혹은 stream으로 순회
- HashMap에서 key부분만 활용하여 구현
- Value에는 더미 값 투입
- 저장 방식
- 객체의 hashCode() 호출
- hashCode()가 같은 객체가 있다면
- 객체의 equals() 호출
- equals()도 같다면 동일한 놈이구나 -> 저장 X
- TreeSet - 특징 - 객체 정렬 순서 유지 O - BST (RB Tree로 구현)
- Map
- HashMap
- Key와 Value의 쌍으로 연관지어 저장
- HashSet과 동일한 저장 방식
- LinkedList 구조와 유사
- TreeMap - 객체의 정렬에 사용 - 내부 구조 TreeSet과 동일
- 왜 Map은 컬렉션 인터페이스를 상속 안 받는가? - Set, List, Map 구분하고 나니... - 야, Set이랑 List 겹친다! 추출해! ==> Collection - Map은 컬렉션 인터페이스의 "엘리먼트들의 그룹"이라는 기본 개념에 안 맞음ㅜㅜ
자바 Generics¶
- 정의 - 다양한 Type의 객체를 다루는 메서드나 클래스에 Compile시 Type Check를 지원하는 기능!
- 장점 - 의도하지 않은 Type이 메서드나 클래스에 들어오는 것을 막아줌 - 데이터 구조에 무엇이 들었는가에 따라 형변환을 고려할 필요가 없음
- 생김새
- 사용법
- 참조 변수와 생성자에 대입된 Type이 일치해야 한다!
Box<Apple> appleBox = new Box<Apple>(); // OK Box<Fruit> appleBox = new Box<Apple>(); // Compile Error! Apple이 Fruit을 상속해도 안 됨- 모든 객체에 동일하게 동작하는 static에서 타입 변수 T는 사용할 수 없다! - 대입된 타입의 종류에 관계없이 동일한 것이여야 하기 때문 - 제네릭 타입의 배열 생성, instanceof 연산도 X - new 연산자 때문... new 연산자는 컴파일 시점에 정확히 타입 T가 뭔지 알아야 함 - newInstance()와 같은 동적으로 객체를 생성하는 메서드를 고려해볼 것
- 제한된 제네릭 - 위의 방법으로는 T가 모든 Type을 받아들인다 - 나는 제한하고 싶은데...? - T extends Fruit - Fruit 타입의 자손들만 대입할 수 있음 - 상한선을 정하는 것 - 인터페이스를 구현해야 한다는 제약에도 extends를 사용
- 와일드 카드
- ? 로 표현되며, 어떠한 타입도 받아들임
public static void printList(List<Object> list) { for (Object elem: list) { System.out.println(elem); } } public static void printListWithWildCard(List<? extends Object> list) { for (Object elem: list) { System.out.println(elem); } } public static void main(String[] args) { List<Fruit> fruits = new ArrayList<>(); MyClass.printList(fruits); //에러!!! 정확히 Object형의 요소만 받아요!!! MyClass.printListWithWildCard(fruits); // 정상 작동! }- 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능 - <? extends T> : T와 그 자손들만 가능 - <? super T> : T와 그 조상들만 가능 - <?> : 제한 없음 모든 타입이 가능
- 제이슨과 함께하는 와일드 카드 - 클래스 구조 - Animal(부모) - Dog(자식) - Cat(자식)
// 상한... Animal이거나 Animal을 상속한 List만 매개변수로 받을게요~
private void extends(final List<? extends Animal> animals) {
final Animal animal = animals.get(0); // Animal이라는 보장이 확실!
animals.add(new Dog()); // 에러!! List<Cat> 이 들어왔으면 Dog 못 넣어
}
//하한...
private void super(final List<? super Animal> animals) {
final Object object = animals.get(0); // 어디가 끝인지 안정해져서 Object가 기본형
animals.add(new Dog()); // 여기선 OK! Dog는 확실히 animal이니 넣어줄 수 있음! (animal 형변환 생략됨)
}
- 제네릭 메서드
- 메서드의 선언부에 제네릭 타입이 선언된 메서드
- 메서드에서 어떤 타입이 들어와도 동작하게 하고싶음!
- public
T foo(List list) { } - : 타입 매개변수 -- 이게 새로운 문법! - T: 리턴 타입 - foo: 메서드 명 - List list: 매개변수 - 전달되는 타입 매개변수의 범위를 제한하고 싶다면 앞의 타입 매개변수에서 해주자 - public T foo(List list) {} // X - public T foo(List list) {} // O - 제너릭 메서드의 타입과 제네릭 클래스의 타입은 상관이 없다!
- 제네릭 메서드 vs 와일드카드
- 제네릭: 지금은 이 타입을 모르지만, 이 타입이 정해지면 그 타입 특성에 맞게 사용하겠다!
- 와일드카드: 지금도 이 타입 모르고, 앞으로도 모를 것이다!
- 얘가 좀 더 무책임하다
public static <T extends Fruit> void test2(final List<T> fruits) { final T fruit = fruits.get(0); fruits.add(fruits.get(0)); // OK!! 나는 너의 타입이 정해지면 맞춰줄거야! } public static void test1(final List<? extends Fruit> fruits) { final Fruit fruit = fruits.get(0); fruits.add(fruits.get(0)); // 에러!! List<Apple>이 들어왔다면 못 넣자나! }
자바 Enum¶
- 정의 - 관련한 상수 Singleton 객체의 모음집 - 컴파일 당시, 가능한 모든 값을 알고 있다면 Enum 활용을 고려해보자!
- 장점 - C/C++ 과는 다르게 Java Enum은 클래스 처럼 변수, 메서드, 생성자를 추가할 수 있다 - 상태와 행위를 동시에 관리 가능하다!
- 특징
- 모든 Enum은 클래스를 통해 내부적으로 정의됨
public enum Operator { Plus("+"), // public static final Operator Plus = new Operator("+") 와 동일 Minus("-"); // public static final Operator Minus = new Operator("-") 와 동일 }- 모든 enum은 java.lang.Enum 클래스를 상속받음 - 따라서 또다른 클래스를 enum에 상속할 수는 없다!
- 생성자는 항상 private!
- Setter는 쓰지말자 - 컴파일 시에 필요한 상수를 모두 모았다는 전제하에 enum을 쓰는 것인데, 런타임에 수정할 구멍을 주면 의미가 없어서 그런 듯 하다.
- 제공 API - values(): enum 안에 존재하는 모든 값 반환 - ordinal(): enum 상수 인덱스 값 반환 - valueOf(): 존재한다면 enum 상수의 스트링 값 반환
- Calculator Enum 리팩토링
public enum Operator { PLUS("+") { @Override int calculate(Number a, Number b) { return a.add(b).toInt(); } }, MINUS("-") { @Override int calculate(Number a, Number b) { return a.sub(b).toInt(); } }, MULTIPLY("*") { @Override int calculate(Number a, Number b) { return a.multiply(b).toInt(); } }, DIVISION("/") { @Override int calculate(Number a, Number b) { return a.divide(b).toInt(); } }; private final String operator; private static final Map<String, Operator> OPERATOR_MAP = new HashMap<>(); static { Arrays.stream(values()) .forEach(operator -> OPERATOR_MAP.put(operator.operator, operator)); } Operator(final String operator) { this.operator = operator; } public static Operator from(final String operator) { Operator requestedOperator = OPERATOR_MAP.get(operator); if (Objects.isNull(requestedOperator)) { throw new IllegalArgumentException(); } return requestedOperator; } abstract int calculate(final Number a, final Number b); } public class Calculator { public Calculator() { } public int calculate(final String operator, final int a, final int b) { return Operator.from(operator).calculate(new Number(a), new Number(b)); } }