자바 팁
포스트
취소

자바 팁

equals와 hashcode 재정의

hash 컬렉션은 hashcode가 다르면 다른 객체로 인식하여 equals만 재정의한다면 동일한 객체로 식별되지 않는다. equals를 재정의할 거라면 hashcode도 함께 재정의하자. hashcode는 필드값 연산으로 깔끔하게 재정의

메소드 내부가 넘치지 않도록

메소드는 단 한가지의 기능만 사용하도록 작성하고 (SRP) 웬만하면 10줄 이상으로 넘어가지 않게 만들자 도트 연산자는 한 줄에 하나만 쓰도록 하자 메소드당 들여쓰기를 한차례만 하자는 말도 있다 그렇다면 기능은 자연스레 분리되겠지만 메소드 개수가 넘쳐날 것이다

모듈을 명확히 구성

인터페이스나 컴포지션으로 기능을 분리(또는 위임)하여 사용자 클래스의 OCP 원칙을 지키자

예외도 던지자

유효성 부적합 판정을 받았을 때 return이 아닌 비검사 예외를 명시하되 표준 예외를 사용하자

상속은 코드 재사용을 위한 게 아니다

상속은 is-a 관계를 완벽히 충족할 때만 사용한다

소켓은 1대1 통신이다

서버 소켓에 클라이언트 소켓이 접근하면 accept 메소드는 클라이언트 소켓과 1대1로 연결시킬 서버 측 소켓을 반환한다 이 소켓은 서버 소켓과 동일한 포트를 갖는다

생성자 매개 변수가 많다면

인스턴스 필드의 개수가 많아 초기화할 매개 변수의 양이 많은 경우에 빌더 패턴을 사용하자. 계층 구조에서도 활용할 수 있으며 현재 필드가 적더라도 늘어날 가능성이 있다면 비용은 뒤로하고 빌더로 설계하자

매력덩어리 Enum

Enum을 유용하게 다뤄야 한다. 정수형 열거는 구식 방법이다 Enum을 이용한 싱글턴, enumMap, enumSet 그리고 방어적 복사를 생략하는 강력한 불변객체에 이용할 수 있다.

향상된 for문

enhanced-for(for-each)문은 iterable 인터페이스를 상속한 클래스만 가능하다

1
for( Iterator<String> str = strings.iterator(); str.hasNext() ){};

로 표현할 수 있다. 또는 선언은 외부에하고 while문에 hasNext를 넣으면 동일한 표현이다 for-each문 내부에서 컬렉션 수정에 대한 실행문은 자제하자

문자열 리터럴과 String 인스턴스

문자열 리터럴은 자바7부터 heap영역의 String constant pool에 저장되며 참조를 잃으면 GC에 의해 제거된다. String 클래스의 hashcode는 재정의하여 문자열이 같다면 동일한 해시코드를 반환한다. (equals와 hashcode가 올바르게 재정의됨) (==) 동등 연산자는 System.identityHashCode의 해시코드를 비교한다. (maybe) 문자열 리터럴의 경우 동등연산자의 SystemHashcode가 동일하게 나타난다. 불변식은 thread-safe를 보증하므로 문자열 리터럴은 스레드 안전하다

메소드를 정의할 때 매개 변수 타입은 최대한 인터페이스로 작성한다



제네릭은 타입 검사기를 지배한다

제네릭이 타입 의존성에서 벗어난다는 건 착각이다 컴파일 환경에서 raw 타입과 달리 타입에 대한 제약이 생긴다 이 때문에 타입 검사가 엄격히 이루어져 컴파일 단계에서 Runtime Exception을 방지할 수 있다. 런타임 환경에서의 제네릭 타입은 호환을 위해 타입 소거가 발생한다. (와일드카드는 런타임 환경에서 타입이 실체화됨)

instanceof 를 이용한 분기문보다 재정의와 다형성을 이용하자


방어적 복사에 방어적이면 안된다

클라이언트로부터 데이터를 보호하기위해 방어적 복사를 지향해야 하며, 방어적 복사는 깊은 복사를 사용하거나 아래의 Collections.unmodifiableXXX 메소드도 이용할 수 있다.

Stack? 이제는 Deque

Stack 대신 Deque, ArrayDeque를 사용하자 (공식 문서 권장) ArrayDeque는 스레드 안전하지 않기 때문에 멀티 스레드 환경에서는 LinkedBlockingDeque, ConcurrentLinkedDeque을 이용하자. 전자는 멀티 스레드 환경의 단일 스레드, 후자는 멀티스레드 권장

반복과 공유가 잦은 객체는 정적으로

정적 멤버는 클래스 소속으로 인스턴스 없이 사용할 수 있다는 관점에서만 바라볼 게 아니라 불필요한 메모리 할당을 방지할 수도 있다 물론 이 때문에 오히려 효율이 떨어질 수도 있으므로 적절히 사용한다 정적 멤버는 공유되고 반복되는 상황에서 효율적이다

VO 관련

직렬화나 DB 데이터 송수신때 불필요한 정보를 제외하거나 중복된 값이 많은 어떤 묶음의 데이터를 처리할 때 VO(값 객체)는 유용하다 동등성을 판별하는 필드로 최적화된 건 Priamary Key 적용 여부에 따를 수 있다

컬렉션 프레임워크 시대

배열보다 컬렉션 사용을 지향하는 이유는 제네릭 타입과 데이터 조작 시에 다양한 api를 사용할 수 있기 때문이다

null 방어의 대안 “Optional”

런타임 NPE 방어 로직은 가독성과 유지 보수성이 떨어진다 null을 다룰 일이 생긴다면 Optional 클래스를 이용해보자

인수에 리터럴을 주는 건 좋지 않다

소스 코드에 데이터를 직접 입력하는 하드 코딩을 피하자 예를 들어 배열 선언 시 길이를 초기화할 때 직접 값을 적기 보단 상수를 미리 정의하고 그 상수를 대입해준다 데이터가 직접 기입되어 있다면 유지보수에 애로사항이 생긴다

재귀라는 게 있긴 있다

웬만하면 재귀보단 반복문을 사용한다 재귀는 stack overflow 발생 염려가 있다

가능한 private하게

자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시 클래스의 멤버변수의 접근제어자는 private이며, 모든 멤버변수에 대해 getter, setter가 존재해야 한다. getter는 매개변수가 없어야 하며, setter는 하나 이상의 매개변수가 있어야 한다. 난 빈 이외에도 습관처럼 하고 있다.

객체 자신의 본분을 다하도록

상태를 가지는 객체를 추가했다면 객체가 제대로 된 역할을 하도록 구현해야 한다. 객체가 로직을 구현하도록 해야한다. 상태 데이터를 꺼내 로직을 처리하도록 구현하지 말고 객체에 메시지를 보내 일을 하도록 리팩토링한다.

데이터베이스는 귀하게 대하기

비즈니스 로직은 DB가 아닌 앱에 넣자 일반적으로 app 서버 리소스가 DB 서버 리소스보다 확장하기 훨씬 더 쉽기 때문에 대용량 데이터 중 소수만 가져오는 류의 로직이 아닌 이상 DB 리소스 사용을 최소화

예외를 책임질 자신 있는가

try catch 처리를 남발하면 오류가 발생했을 때 제어 흐름이이 중단되지 않아 부차적 문제를 발생시킬 수 있고 stack trace를 보존하지 않는 로깅은 디버깅을 어렵게 만드는 일이다. 즉, 책임지지 못할 오류는 잡지 않는다. 그래야 JVM이나 어딘가에서 로그를 남길 것이기 떄문이다.

버킷의 개수를 예측하자

Collection Framework를 사용할때 버킷의 개수가 예상된다면 생성자의 인자로 넣어 불필요하게 버킷의 개수가 늘어나는 과정을 배제시킨다.

모던 api는 기능보단 가독성

Optional과 Collection stream은 가독성을 챙기기 좋은 수단이다. Optional의 작동 방식은 stream과 많이 흡사하다.

임시

함수형 인터페이스는 함수를 1급 객체처럼 다룰 수 있게 한다 단 하나의 추상 메서드를 가지는 인터페이스며 이를 이용하면 행위를 값으로 취급할 수 있다.

1
Consumer<Integer> con = System.out::println;

필드 선언을 위와 같은 형식으로도 할 수 있게 되었다.

임시

Arrays.asList의 반환 타입은 update 관련 메소드를 사용할 수 없다 수정이 필요하다면 new 연산자를 통한 ArrayList를 이용하면 된다 둘다 stream은 이용할 수 있다

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.