맞는데 왜 틀릴까..?

전체 글 306

[Effective Java] Item 29. 이왕이면 제네릭 타입으로 만들라

제공하는 제네릭 타입과 메서드를 사용하는 일은 쉽지만 새로 제네릭 타입을 만드는 일은 조금 어렵다. 제네릭 타입으로 만들기 알맞은 Stack 클래스를 만들어 보자. Object 기반 Stack 클래스 Object를 사용해 구현한 Stack 클래스 스택에서 꺼낸 객체를 형변환 해야 하는데 이때 런타임 오류가 날 위험이 있으므로 제네릭 타입으로 만들자. 배열을 사용한 코드를 제네릭으로 만드는 방법 1. 클래스 선언에 타입 매개 변수를 추가 () 2. 코드에 쓰인 Object를 적절한 타입 매개변수로 변경 하지만 Item28에서 설명한 것처럼 E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다. 해결책 1. Object 배열을 생성한 다음 제네릭 배열로 형변환 오류대신 비검사 형변환 경고가 나타난다. 이 ..

Java/Effective Java 2023.05.09

[Effective Java] Item 28. 배열보다는 리스트를 사용하라

공변 vs 불공변 공변 : Sub가 Super의 하위 타입이라면 Sub []도 Super []의 하위 타입이다. 불공변 : Type1과 Type2가 있을 때 List 과 List 는 서로 하위 타입도 상위 타입도 아니다. 배열 : 공변 제네릭 : 불공변 배열은 공변성을 가지기 때문에 특정 타입의 하위 타입으로 변환될 수 있다. ex) Object[] 배열은 String [] 배열로 변환될 수 있다. 따라서 컴파일타임에 타입 안전하지 않다. 배열 vs 리스트 1. 컴파일 시 오류 확인 가능 둘 다 Long용 저장소에 String을 넣을 수는 없지만 배열은 런타임 환경에서 에러를 던지고, 리스트는 컴파일할 때 바로 알 수 있다. 2. 배열은 실체화된다. 배열 : 런타임에도 자신이 담기로 한 원소의 타입을 인..

Java/Effective Java 2023.05.09

[Effective Java] Item 27. 비검사 경고를 제거하라

제네릭을 사용하면 수많은 비검사 경고를 볼 수 있다. 대부분의 비검사 경고는 쉽게 제거할 수 있지만 제거하기 어려운 경고도 있다. 모든 비검사 경고를 제거한다면 그 코드는 타입 안전성이 보장된다. @SuppressWarnings("unchecked") 경고를 제거할 수는 없지만 타입 안전하다고 확신한다면 해당 애너테이션을 달아 경고를 숨기자. 안전하다고 검증된 비검사 경고를 숨기지 않고 그대로 두면 진짜 문제를 알리는 새로운 경고가 파묻혀 눈치채기 힘들다. 가능한 한 좁은 범위에 적용하자. 한 줄이 넘는 메서드나 생성자에 달린 에너테이션은 지역변수 선언으로 옮기자. 애너테이션은 선언에만 달 수 있기 때문에 return 문에 달 수 없다. public T[] toArray(T[] a){ if(a.lengt..

Java/Effective Java 2023.05.09

[Effective Java] Item 26. 로 타입은 사용하지 말라

용어 정리 제네릭 타입 : 제네릭 클래스와 제네릭 인터페이스 ex) List 매개변수화 타입 : List은 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입 E : 정규 타입 매개변수 String : 실제 타입 매개변수 로 타입(raw type) : 제네릭 타입에서 타입 매개변수를 사용하지 않은 때 ex) List 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작 제네릭이 만들어지기 전 코드와 호환되도록 하기 위해 존재 로 타입을 사용하지 말아야 하는 이유 로 타입을 사용하면 제네릭이 안겨주는 안정성과 표현력을 모두 잃게 된다. 아래와 같은 예를 살펴보자. 만약 실수로 Stamp 인스턴스 대신에 Coin 객체를 넣어도 오류 없이 컴파일이 되지만 실제로 Collection에서 객체를 꺼..

Java/Effective Java 2023.05.09

[Effective Java] Item 25. 톱레벨 클래스는 한 파일에 하나만 담으라

소스 파일 하나에 톱레벨 클래스를 여러 개 선언할 수 있지만 아무런 득이 없을뿐더러 심각한 위험을 감수해야 한다. 어느 소스 파일을 먼저 컴파일하냐에 따라 한 클래스를 여러 가지로 정의할 수 있기 때문이다. 아래에서 그 예를 살펴보자 Main Main 클래스는 다른 톱레벨 클래스 2개(Utensil, Dessert)를 참조 public class Main { public static void main(String[] args) { System.out.println(Utensil.NAME + Dessert.NAME); } } Utensil.java 두 클래스가 Utensil.java 한 파일에 정의 class Utensil { static final String NAME = "pan"; } class De..

Java/Effective Java 2023.05.08

[Effective Java] Item 24. 멤버 클래스는 되도록 static으로 만들라

중첩 클래스 (nested class) 중첩 클래스 : 다른 클래스 안에 정의된 클래스 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다. 정적 멤버 클래스, 비정적 멤버 클래스, 익명 클래스, 지역 클래스 4가지로 나뉜다. 첫 번째를 제외한 나머지는 내부 클래스에 해당한다. 정적 멤버 클래스 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 제외하고는 일반 클래스와 동일하다. private으로 선언하면 바깥 클래스에서만 접근할 수 있다. 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다. 계산기가 지원하는 연산 종류를 정의하는 열거 타입을 예로 살펴보자. Operation 열거..

Java/Effective Java 2023.05.05

[Effective Java] Item 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

책에서 두 가지 이상의 의미를 표현할 수 있는 클래스(태그 달린 클래스)를 본 적이 있을 거라 했지만 나는 처음 봤고 보자마자 이런 걸 왜 쓸까라는 생각이 들었기 때문에, 단점은 살펴보지 않고 바로 이러한 태그 달린 클래스를 클래스 계층구조로 바꾼 예시만 살펴보자. 태그 달린 클래스 하나의 클래스 Figure에 Shape에 따라 다른 동작을 수행하는 오류를 내기 쉽고, 비효율적인 클래스다. 클래스 계층구조로 변환 1. 계층 구조의 루트가 될 추상 클래스를 정의 2. 태그 값에 따라 동작이 달라지는 메서드들을 루트 클래스의 추상 메서드로 선언 3. 태그 값에 상관없이 동작이 일정한 메서드들을 루트 클래스에 일반 메서드로 추가 (모든 하위 클래스에서 공통으로 사용하는 데이터 필드들도 전부 루트 클래스로 올린..

Java/Effective Java 2023.05.05

[Effective Java] Item 22. 인터페이스는 타입을 정의하는 용도로만 사용하라

클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해 주는 것이다. 인터페이스는 오직 이 용도로만 사용해야 한다. 이 지침에 맞지 않는 예로 상수 인터페이스라는 것이 있다. 상수 인터페이스 상수 인터페이스 : 메서드 없이, 상수를 뜻하는 static final 필드로만 구성된 인터페이스 정규화된 이름을 쓰는걸 피하고자 아래와 같은 인터페이스를 구현한다. 이 상수 인터페이스는 인터페이스를 잘못 사용한 예다. 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아니라 내부 구현에 해당한다. 따라서 상수 인터페이스를 구현하는 것은 이 내부 구현을 클래스의 API로 노출하는 행위다. 클래스가 어떤 상수 인터페이스를 사용하든 사용자에게는 아무런 의미가 없으며, 오히..

Java/Effective Java 2023.05.03

[Effective Java] Item 21. 인터페이스는 구현하는 쪽을 생각해 설계하라

디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 이처럼 자바 8 이후에 자바에도 기존 인터페이스에 메서드를 추가하는 길이 열렸지만 모든 기존 구현체들과 매끄럽게 연동되리라는 보장은 없다. 자바 8 이전의 모든 클래스는 "현재 인터페이스에 새로운 메서드가 추가될 일이 없다"라고 생각해 설계되었기 때문이다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 '삽입' 될 뿐이다. 따라서 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기는 어렵다. 디폴트 메서드로 인한 피해 예시 아파치의 SynchronizedCollection 클래스는 현재에도 removeIf 메서드를 오버라이딩하..

Java/Effective Java 2023.05.03

[Effective Java] Item 20. 추상 클래스보다는 인터페이스를 우선하라

현직에서 개발팀장을 맡아 신입면접까지 진행하는 분과 얘기할 기회가 생겼었다. 자바 개발자를 지향한다고 하니 현직자분이 자바 개발자를 뽑을 때 무조건 하는 질문을 나에게 해주셨다. "자바에서 인터페이스는 왜 사용하나요?" 나는 불현듯 전공 C++ 강의에서 교수님이 해주신 죽음의 다이아몬드 얘기가 생각났다. 교수님은 C++는 다중상속을 허용하기 때문에 죽음의 다이아몬드라는 현상이 발생하고 이 때문에 자바에서는 다중상속을 금지하고 인터페이스라는 개념이 사용된다고 해주셨었다. 마침 이 얘기가 머리에 강하게 박혀서 현직자 님께 이 얘기를 했었던 기억이 있다. 하지만 내가 정확하게 알고 말한 것이 아니니 제대로 알아볼 기회가 필요했는데 마침 좋은 아이템이 있어서 기분 좋게 공부했다. 그러면 자바의 핵심이라고 할 수..

Java/Effective Java 2023.05.03