배열과 제네릭의 차이 1) 배열은 공변(함께 변한다)이다. → Sub가 Super의 하위타입이면 Sub[]는 Super[]의 하위타입이 된다. 제네릭은 불공변이다. → 서로 다른 Type1, Type2 가 있으면 List은 List의 하위타입도 상위타입도 아니다. //배열 : 런타임 오류 -> 문제가 있다! Object[]objectArray = new Long[1]; objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException을 던진다. //제네릭 : 컴파일 오류 List ol = new ArrayList(); // 호환되지 않는 타입이다. ol.add("타입이 달라 넣을 수 없다."); 2) 배열은 실체화된다. 배열은 런타임에도 자신이 담기로 한 원소의 타..
제네릭 사용시에 컴파일러 경고를 많이 보게 될 것이다. (비검사 경고) 대부분의 비검사 경고는 쉽게 제거할 수 있다. 할수 있는한 모든 비검사 경고를 제거하라. 그러면 타입 안전성이 보장된다. 비검사 경고 예시 Set set = new HashSet(); 위 코드는 구현체 타입을 명확히 작성하지 않고 HashSet 객체를 생성하고 있다. 이는 컴파일러가 아래와 같이 경고를 한다. Set set = new HashSet(); ^ required: Set found: HashSet 컴파일러가 알려준대로 수정하면 경고가 사라지지만, 자바7부터 다이아몬드 연산자()를 지원하기 때문에 그걸로 해결할 수 있다. 다이아몬드 연산자를 사용하면 컴파일러가 올바른 실제 타입 매개변수를 추론해준다. Set set = new..
클래스와 인터페이스 선언에 타입 매개변수가 쓰이면 제네릭 클래스나 제네릭 인터페이스라 한다. 이들을 제네릭 타입이라고 한다. 로 타입 (raw type) 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type) 도 함께 정의된다. 제네릭 타입에서 타입 매개변수를 사용하지 않을 때를 로 타입이라고 한다. ex) List의 로 타입은 List 로 타입을 사용하면 안되는 이유 로 타입은 사용하지 않는 것이 좋다. //Raw type private final Collection stamps = ...; // Stamp 인스턴스만 취급한다. stamps.add(new Coin(...)) // Coin 인스턴스를 add() 한다면 "unchecked call" 경고를 내뱉는다. 위 코드는 컴파일 오류 없이..
톱레벨 클래스가 소스 파일 하나에 들어있으면 심각한 위험을 감수해야한다. //A.java 파일 class A { static final String NAME = "ab"; } class B{ static final String NAME = "cd"; } //B.java 파일 class A { static final String NAME = "ef"; } class B{ static final String NAME = "gh"; } 만약 다른곳에서 A 클래스와 B 클래스를 참조할때 중복 정의이기 때문에 이와 같이 작성하면 안된다. 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라지므로 이렇게 사용하면 안된다. 해결방안은 단순히 톱레벨 클래스들을 서로 다른 소스파일로 분리하는 것이다. 굳이 한 ..
중첩 클래스 (nested class) 중첩 클래스는 다른 클래스 안에 정의된 클래스를 말한다. 자신을 감싼 바깥 클래스에서만 쓰여야함 정적 멤버 클래스, (비정적) 멤버 클래스, 익명 클래스, 지역 클래스 정적 멤버 클래스 다른 클래스 내부에서 static으로 선언된 클래스 다른 정적 멤버와 똑같은 규칙 적용 흔히 바깥 클래스와 함께 쓰일때만 유용한 public 도우미 클래스로 사용 public class Calculator { public enum Operation { // 열거 타입도 암시적 static 이다. PLUS, MINUS, MULTIPLE, SUBTRACT } } 비정적 멤버 클래스 다른 클래스 내부에서 static 없이 선언된 클래스 비정적 멤버 클래스 인스턴스는 바깥 클래스 인스턴스와..
태그 달린 클래스 두 가지 이상의 의미를 표현할 수 있고 현재 표현하는 의미를 태그 값으로 알려주는 클래스 public class Figure { enum Shape {RECTANGLE, CIRCLE} // 태그 private final Shape shape; // 이 필드들은 사각형일때만 사용 private final double length; private final double width; // 이 필드는 원형일때만 사용 private final double radius; //원용 생성자 public Figure(Shape shape, double radius) { this.shape = shape; this.length = 0; this.width = 0; this.radius = radius; ..
인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 하는데, 인터페이스는 이 용도로만 사용해야 한다. 상수 인터페이스는 메서드 없이 static final 필드로만 가득찬 인터페이스로, 인터페이스를 잘못 사용한 예이다. //상수 인터페이스 안티패턴 - 사용금지 public interface PhysicalConstants { static final double AVOGADROS_NUMBER = 6.022_140_857e23; static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23; static final double ELECTRON_MASS = 9.109_383_56e-31; } 클래스 내부의 상수는 외부 인터페이스가 아니라 내부 구..
디폴트 메서드의 도입 - 자바8부터는 기존 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드를 도입. but 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 되므로 모든 기존 구현체들과 매끄럽게 연동되리라는 보장을 할 수 없음 - 자바8에서는 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가됨 → 람다 활용 위해 추가된 메서드들은 품질이 높고 범용적이어서 대부분 상황에서 잘 작동 but 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하긴 어렵다. default boolean removeIf(Predicate
자바가 제공하는 다중 구현 메커니즘 : 인터페이스, 추상 클래스 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상클래스의 하위 클래스가 되어야 한다. 자바는 단일 상속을 지원하기 때문에 추상 클래스 방식은 새로운 타입을 정의하는데 제약 but 인터페이스를 구현한 클래스는 다른 어떤 클래스를 상속했든 간에 같은 타입으로 취급 인터페이스 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다.but 추상 클래스는 그렇지 않다. 새로 추가된 추상 클래스의 모든 자손이 상속구조를 가지게 되며 혼란을 줌. 인터페이스가 요구하는 메서드를 추가하고 implements 구문만 추가하면 된다. 인터페이스는 믹스인에 적절함 : 원래의 주된 타입 외에 특정 선택적 행위를 제공 효과 but 추상 클래스는 그렇..
상속을 고려한 설계와 문서화 1) 메서드를 재정의할때 어떤일이 일어나는지 정확히 정리하여 문서로 남겨라. 상속용 클래스는 재정의할 수 있는 메서드(public, protected 이면서 final이 아닌 메서드)들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다. 2) 효율적인 하위 클래스를 어려움 없이 만들 수 있게 하기 위해 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다. 3) 상속용으로 설계한 클래스를 시험하는 방법은 직접 하위 클래스를 만들어보는 것이다. 꼭 필요한 protected 멤버를 놓쳤다면 빈 자리가 드러남, 전혀 쓰이지 않는 protected 멤버는 사실 private 임 등을 알 수 있다. 배포전에 반드시 하..