콘텐츠로 이동

2021 02 19

2021-02-19

자바 배경지식

  • 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

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이 메서드나 클래스에 들어오는 것을 막아줌 - 데이터 구조에 무엇이 들었는가에 따라 형변환을 고려할 필요가 없음
  • 생김새
    public class Box<T> {
        List<T> items = new ArrayList<>();
    
        public void add (T item) {
            items.add(item);
        }
    }
    
  • 사용법 - 참조 변수와 생성자에 대입된 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와 그 조상들만 가능 - <?> : 제한 없음 모든 타입이 가능

    ArrayList<? extends Product> list = new ArrayList<Tv>(); // OK!
    ArrayList<? extends Product> list = new ArrayList<Audio>(); // OK!
    

  • 제이슨과 함께하는 와일드 카드 - 클래스 구조 - 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));
        }
    }