- 목차
- 9. Equals를 재정의할 때는 일반 규약을 따르라.
- equals 메서드를 재정의 하지 않아도 되는 경우
- equals 메서드를 재정의하는 경우
- equals 메서드는 동치 관계 (equivalence relation)을 구현한다.
- 반사성(reflexive): null이 아닌 참조 x와 y가 있을때, x.equals(y) 는 true 반환한다.
- 대칭성(Symmentry): null이 아닌 참조 x가 있을 때, x.equals(y)는 y.equals(z)가 true 일때만 true를 리턴한다
- 추이성(transitive): null 아닌 참조 z,y,z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 true 이면 x.equals(z)도 true 이다.
- 일관성(consistent): null 아닌 참조 x와 y가 있을 때, equals를 통해 비교되는 정보에 아무 변환가 없다면 x.equals(y) 호출결과는 호출 횟수에 상관없이 같아야한다.
- null 아닌 참조 x에대해서 x.equals(null)은 항상 false 이다
- 정리
- 추가
- example code
- 10. equals를 재정의할 때는 반드시 hashCode도 재정의하라
- 11. 규칙 : toString은 항상 재정의하라
- 13. 클래스와 멤버의 접근 권한은 최소화하라
- 14. public 클래스 안에는 public 필드를 두지 말고 접근자 메서드를 사용하라
- 15. 변경가능성을 최소화하라
- 16. 계승하는 대신 구성하라
- 18. 추상 클래스대신에 인터페이스를 사용하라
- 인터페이스는 믹스인(혼합)을 정의하는 데 이상적이다.
- 인터페이스는 비 계층적인 자료형 프레임워크를 만들 수 있도록 한다.
- 인터페이스를 사용하면 포장 클래스 숙어을 통해 안전하면서도 강력한 기능 개선이 가능하다.
- [추상 골격 구현 클래스를 중요 인터페이스마다 두면, 인터페이스의 장점과 추상 클래스의 장점을 결합 할 수 있다.](#추상-골격-구현-클래스를-중요-인터페이스마다-두면-인터페이스의-장점과-추상-클래스의-장점을-결합-할-수 있다)
- 인터페이스보다 추상 클래스가 발전시키기는 쉽다
- public 인터페이스는 신중하게 설계해야 한다.
- 19. 인터페이스는자료형을 정의할 대만 사용하라
- 20. 태그 달린 클래스 대신 클래스 계층을 활용하라
- 22. 멤버 클래스는 가능한 static으로 선언하라
- 23. 새 코드에는 무인자 제네릭 자료형을 사용하지마라
- 25. 배열 대신 리스트를 써라
- 26. 가능하다면 제네릭 자료형으로 만들것
- 27. 가능하다면 제네릭 메서드로 만들것
- 28. 한정적 와일드카드를 써서 API 유연성을 높여라
- 42. varargs는 신중히사용하라
- 43. null 대신 빈 배열이나 컬렉션을 반환하라
- 45. 지역 뱐수의 유효범위를 최소화하라
- 46. for 문보다 for-each 문을 사용하라
- 48. 정확한 답이 필요하다면 float와 dobule은 피하라
- 49. 객체화 된 기본 자료형 대신 기본 자료형을 이용하라
- 50 제네릭
- 50. 다른 자료형이 적절하다면 문자열 사용은 피하라
- 51. 문자열 연결시 성능에 주의하라
- 52. 객체를 참조할 때는 그 인터페이스르 사용해라
- 53. 리플렉션 대신 인터페이스를 이용하라
- 54. 네이티브 메서드는 신중하게 사용하라
- 55. 신중하게 최적화하라
- 56. 일반적으로 통용되는 작명 관습을 따르라
- 57. 예외는 예외적 상황에만 사용하라
- 58. 복구 가능 상태에는 점검지정 예외를 사용하고, 프로그래밍 오류에는 실행시점 예외를 이용하라
- 59. 불필요한 점검지정 예외 사용은 피하라
- 60. 표준 예외를 사용하라
- 61. 추상화 수준에 맞는 예외를 던져라
- 62. 메서드에서 던져지는 모든 예외 대해 문서를 남겨라
- 63. 어떤 오류인지를 드러내는 정보를 상세한 메시지에 담으라
- 65. 예외를 무시하지 마라
- Equals 메서드는 재정의 하기 쉬워 보이지만 실수할 여지가 많다. 그 결과는 끔찍하다(아래 설명).
- 이러한 문제를 피하는 가장 간단한 방법은 equals 메서드를 재정의하지 않는 것이다.
- 각각의 객체가 고유하다
- 값 대신 활성 개체를 나라태는 Thread 같은 클래스가 이 조건에 부합한다. 이런 클래스는 equals 메서드를 그대로 사용해도 된다.
- 클래스에 논리적 동일성 검사 방법이 있건 없건 상관없다.
- 대표적으로 Random 클래스
- 상위 클래스에서 정의한 equals 메소드가 그대로 하위 클래스에서 사용하기에도 적당하다
- Set 클래스는 AbstractSet의 equals 메서드를 그대로 사용한다
- 클래스가 private 또는 package-private로 선언되었고, equals 메서드를 호출할 일이 없다.
- 논란의 소지가 있음
- 클래스가 공개되지 않을 경우 equals 메서드 호출시 에러를 나게 하는 것
- 객체 동일성이 아닌 논리적 동일성을 개념을 지원하는 클래스일 때
- 상위 클래스의 equals가 하위 클래스의 필요를 충족하지 못할 때
- Value Class가 대표적인 예이다.(데이터베이스에서 조회한 회원 동일한지 PK가 동일하다면 같다고 봐야하는 논리적 동일성)
- 모든 객체는 자기 자신과 같아야 한다는 뜻이다. 일부로 깨뜨리기도 어려운 요구사항이다.
- 이 요구사항을 어기면 컬랙션의 contains 메서드는 방금 추가한 객체가 없다고 할것이다
- contains 메서드는 equals 메서드를 통해서 동작하니
- 두 객체가 서로 같은지 물으면 같은 답이 나와야한다.
- 쉽게 실수할 수 있다.
- 이 것도 마찬가지로 컬렉션의 contains 등 다양한 곳에서 문제가 생긴다.
추이성(transitive): null 아닌 참조 z,y,z가 있을 때, x.equals(y)가 true이고 y.equals(z)가 true 이면 x.equals(z)도 true 이다.
- 첫 번째 객체가 두 번째 객체와 같고, 두 번째 객체가 세 번째 객체아 같다면 첫 번째 객체와 세 번째 객체도 같아야 한다는 뜻이다.
- 쉽게 실수할 수 있다.
일관성(consistent): null 아닌 참조 x와 y가 있을 때, equals를 통해 비교되는 정보에 아무 변환가 없다면 x.equals(y) 호출결과는 호출 횟수에 상관없이 같아야한다.
- 일단 같다고 판정된 객체들은 추후 변경되지 않는 한 계속 같아야 한다는 것이다.
- 변경 가능한 객체들 간의 동치 관계는 시간에 따라 달라질 수 있지만 변경 불가능 객체 사이의 동치 관계는 달라질 수 없다.
- 신뢰성이 보장되지 않는 자원들을 비교하는 equals를 구현하는 것은 삼가하라
- 모든 객체는 null과 동치 관계에 있지 아니한다는 요구조건
- NullPointerException 예외 발생 가능성
- == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라.
- 선능 최적화를 위한것, 가장 앞부분에거 검사하는 것이 좋다.
- instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라
- 그렇지 않다면 false 리턴
- 두 번째 검사로직에 있는 것이 바림직, 컬랙션 equals도 동일하게 구현
- equals의 인자로 정확한 자료형으로 변경하라
- 그 앞에 instnaceof를 사용한 검사코드를 두엇음으로, 형변환은 반드시 성공할것이다.
-
- 중요 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다
- 논리적 동일성을 검사한다
- 기본 타입들은 == 으로 비교한다. (dobule, float, int...)
- 객체 타입은 Compare 메서드, equals 메서드들 통해서 검사한다.
- equals 메서드 구현을 끝냈다면 대창성, 추이성, 일관성의 세 속성의 만족되는지 검토하라
- 테스트 코드를 반드시 작성해서 검사하라
- equals를 구현할 때는 hashCode로 재정의하라
- 너무 머리쓰지마라
- 디비에있는 값일 경우에는 PK만 검사하면 된다 이런 내용인듯
- equals 메서드의 인자 형을 Objext에서 다른 것으로 바꾸지마라
@Override
public boolean equals(Object o) {
if (this == o) return true; // 1. == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라.
if(!(o instanceof MemberId)) return false; //2. instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라, null 체크 까지 함
MemberId memberId = (MemberId) o; // 3. equals의 인자로 정확한 자료형으로 변경하라
return Objects.equals(id, memberId.id); // 4. * **중요** 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다
}
- 많은 버그가 hashCode를 재정의하지 않아서 생긴다.
- equals 메서드를 재정의한 클래스는 반드시 hashCode 메서드도 재정의햐아한다.
- 그렇지 않으면 Obejct.hashCode의 일반 규약을 어기게 되므로, HashMap, HastSet, Hasttable 같은 Hash 기반 컬랙샨과 함께 사용된다면 오동작하게된다.
- 응용프로그램 실행 중에 같은 객체의 hashCode를 여러 번 호출하는 경우, equals가 사용하는 정보들이 변경되지 않았다면, 언제나 동일한 정수가 리턴된다.(프로그램 종료시에는 같은 값이 나올 필요는 없다 )
- equals(Object) 메서드가 같다고 판정한 두 객체의 hashCode 값은 같아야한다
- equals(Object) 메서드가 다르다고 판정한 두 객체의 hashCode 값이 꼭 다를 필요는 없다. 그러나 서로 다른 hashCode 값이 나오면 해시 테이블의 성능이 향상될 수 있다는 점은 이해하고 있어야한다.
- hashCode 재정의하지 않으면 위반되는 핵심 규약은 두 번째다.같은 객체는 같은 해시 코드값을 가 져야한다는 규약이 위반되는 것이다.
- hashCode를 재정의하지 않을 경우는 equals가 true임에도 서로다른 hashCode를 갖게된다.
- 따라서 Map 컬랙션에서 get메서드는 put 메서드가 객체를 저장한 것과 다른 해시버킷을 뒤지게된다.
- 설사 좋아서 같은 버킷을 뒤지게 되더라도 get 메서드는 항상 null을 리턴한다. HashMap은 성능 최적화를 위해 내부에 보관된 코드를 캐시해 두고, 캐시된 해시 코드가 없다면 객체는 동일성 검사조차 하지 않기 때문이다.
- 그렇다고 모든 객체가 같은 해시 코드를 갖게하는 방법으로 구현하면 안된다.
- 해시 테이블에서 검색하는 시간이 끔찍히 오래 걸리니가(뭐 당연한 이야기)
- 이상적인 해시; 함수는 서로 다른 객체들을 모든 가능한 해시에 값에 균등하게 분배햐야 한다.
- 이런 이상적인 해시 함수에 가까운 함수를 만드는 건 별로 어렵지 않다.
- 알맞는 자료형에 따라서 알고리즘이 달라진다. 결과적으로 나름 균등한 해쉬 값을 갖게하는거 같다.
- 내부적인 알고리즘은 좀 어렵다... 실용적인 방법은 아래 sample code 참조
- 주의할 것은, 성능 개선을하려고 객체의 중요 부분을 해시 코드 계산 과정에서 생략하면 안된다는 것이다.
- 해시 만드는 것에 로직이 있어 좀 오려 걸려도 해시 품질이 안좋아지면(값이 균등하지 않다면) 해시 기반으로 검색시 느려지기 때문에 결과적으로 더 느려진다는 내용
@Override
public int hashCode() {
return Objects.hash(id);
}
- Object 클래스가 toString을 제공
- toString 규약 : 사람이 읽기 쉽도록 간략하지만 유용한 정보를 제공해야한다.
- @ + 16진수 표현된 해시코드가 붙은 문자열로 사람이 읽기 쉬운것은 아니다
- 그래서 모든 하위클래스에서 toString을 재정의함이 바람직하다.
- toString을 재정의하지 않으면 진단 메시지를 통해서 얻을수 있는 저옵는 거의 없을 것이다.
- 가능하다면 toString 메서드는 객체 내의 중요 정보를 전부 담아 반한해야한다.
- equals, hashCode 규약을 따는 것보다는 덜 중요하다
- toSring 어떤 의도인지를 주석을 분명하게 남겨야한다
- toString을 통해서 필요한 비즈니스 로직을 수행하기도 하니 말이다.
- 잘 설계된 모듈과 그렇지 못한 모듈을 구별 짓는 가장 중요한 속성 하나는 모듈 내부의 데이터를 비롯한 구현 세부사항을 다른 모듈에 잘 감추냐의 여부다
- 잘 설계된 모듈은 구현 세부사항을 전부 API 뒤쪽으로 감춘다. 모듈들은 이 API를 통해서만 서로 통신하며, 각자 내부적으로 무신 짓을 하는지 신경쓰지 않는다.
- 이 개념은 정보 은닉, 캡슐화 용어로 알ㄹ져 있으며, 소프트웨어 설계의 기본적인 원칙 가운데 하나이다.
-
정보 은닉은 시스템을 구성할는 모듈 사이의 의존성을 낮춰, 각자 개별적으로 개발하고 시험하고, 최적화하고, 이해하고, 벼녁ㅇ 할 수 있도록 한다는 사실에 기초한다.
- 객체의 자율성의 개념으로 이해, 객체는 충분히 자율적이어야 한다는 원칙
-
그렇게 되면 시스템 개발속도가 올라가감
- 각가의 모듈은 병렬적으로 개발할 수 있기 때문
- 모듈 각각을 좀 더 빨리 이해할 수 있을 뿐아니라 유지보수의 부담도 낮아짐
- 정보 은닉 원칙이 좋은 성능을 자동적으로 보장하는 것은 아니지만, 효과적인 성능 튜닝을 하게된다는 사실이다.
-
정보 은닉 원칙은 소프트웨어의 재사용 가능성을 높인다.
- 모듈 간의 의존성이 낮음으로 각 모듈은 다른 소프트웨어 개발에도 유용하게 쓰일 수 있다.
- 설사 전체 시트템은 성공적이지 않더라도, 각가의 모듈은 성공적으로 규현될 수 있기 때문에 더 좋다.
-
자바는 정보 은닉 원칙을실현할 수 있도록 하는 다양한 도구들을 갖추고 있다. 접근 제어 메커니즘은 클래스와, 인터페이스 그리고 멤버 접근 권한을 규정한다.
-
원칙은 단순하다. 각 클래스와 멤버는 가능한 접근 불가능하도록 만들라는 것이다.
- 개발 중인 소프트웨어의 정상적인 동작을 보증하는 한도 내에서 가장 낮은 접근 권하을 설정하라는 것이다.
- public 으로 공개된 메서드는 외부 모듈이 의존하고 있음으로 리팩토링에 취약하다.
- protected 상송관계에 모듈만이 의존 하고 있으니 조금더 안전
- private 내부 동작에만 관여하니 더 안전
- private
- 이렇게 선언된 멤버는 선언된 최상위 레벨 클래스 내부에서만 접근 가능하다.
- package-private
- 이렇게 선언된 멤버는 같은 패키지 내부 아무 클래스나 사용할 수 있다.
- default access 으로 알려져 있다. 아무 것도 붙이지 않으면 이 권한이 주어지기 때문에
- protected
- 이렇게 선언된 멤버는 선언된 클래스 및 하위 클래스에만 사용할 수 있다.
- 같은 패키지 내에서도 사용 가능
- 내 개인적생각 : protected는 동일 패키지에서 접근하는 것은 그렇게 바람직하지 않아보임, protected 자체가 상속을 위한 개념으로 받아 들이기 때문에
- public
- 이렇게 선언된 멤버는 어디서도 사용이 가능하다.
클래스의 API는 주의 깊게 설계한 뒤에 반사적으로 다른 모든 멤버는 private로 선언하게 될것이다. 같은 패키지 내의 다른 클래스가 반드시 사용해야하는 멤버인 경우에는 package-private로 만들어라, 이런 코드가 반복되면 클래스 간 의존관계를 줄일수 있는 방법을 생각 하라
어느 선까지는 괜찮다. 테스트를 위해 public 클래스의 private 멤버를 package-private 으로 만드는 것까지는 괜찮다. 하지만 그 이상은 곤란하다. 다시 말해 테스트를 위한 것이라고 하너라도 클래스나 인터페이스 또는 맴버를 패키지 공개 API로 만드는 것음 곤란하다
필드에 저장될 값을 강제할 수 없다. 예를들어 email 필드에는 반드시 email에 형식에 맞는 값이 들어와야하는데 public으로 들어가면 아무 조건 없이 값을 넣을 수 있다. 이렇게 되면 불변식을 강제 할 수 없다 또 변경 가능한 public 필드를 가진 클래스는 다중 스레드에 안전하지 않다. 이 이야기는 static으로 선언된 필드에도 적용되지만 한 가지 예외가 있다. 어떤 상수들이 클래스로 추상화 된 결과물의 핵심적 부분으로 구성한다고 판단되는 경우이다. 이 해당 상수들은 아래 처럼 사용할 수 있다.
public static final int PI = 3.14
하지만 길아가 0 아닌 배열은 언제나 변경이 가능하므로 public static final 배열 필드를 두거나, 배열 필드를 반환하는 접근을 정의하면 안된다.
public static final Thing[] VALUES = {...} // 보안상 문제를 초래할 수 있는 코드
public 으로 선언되있던 배열은 private로 바꾸고, 변경이 불가능한 public 리스트를 하나 만드는 것
private static final Thing[] PRIVATE_VALUES = {...}
public static final List<Thing> VALUES = collections.unmodifiableLsit(Arrats.asList(PRIVATE_VALUES)
//이런 저급한 클래스는 절대로 public 으로 선헌하지 말것
class Point {
public dobule x;
public double y;
}
- 이런 클래스는 데이터 필드를 직접 조작할 수 있어 캡슐화의 이점을 누릴수가 없다.
- API를 변경하지 안ㄴㅎ고서는 내부 표현을 변경할 수 없고, 불변식도 강제할 수없고, 필드를 사용하는 순간에 어떤 동작이 실행되도록 만들 수도 없다.
- 객체 지향 개념에 충실하가조하는 프로그래머에게 이런 크래스는 저주와도 같다
- 항상 접근 private로 선언하고 Getter, Setter 메서드를 통해야한다
- package-private 클래스나 private 중첩 클래스(nested class)는 데이터 필드를 공개하더라도 잘못이라 할 수없다
- 클래스가 추상화하려는 내용을 제대로 기술하기만 하면말이다.
public final class Time {
...
...
// 변경 불가능 필드를 외부로 공개하는 public 클래스 - 정말 이래야하는 지 의문
public final int hour;
public final int minute;
}
- 비록 final 키워드가 있다곤 하지만 public 으로 굳이 공개가 필요할지 의문이다.
- 내 개인적인 생각은 왠만하면 getter 메서드로 뺴는 것이 올바르다.
- 변경 불가능(inmmutable) 클래스는 그 객체를 수정할 수 없는 클래스다.
- 객체 내부의 저오븐ㄴ 객체가 생성될 때 주어진 것이며, 객체가 살아 있는 동안 그대로 보존된다.
- 우선 변경 불가능 클래스는 변경 가능 클래스보다 설계하기 쉽고, 구현하기 쉬우며, 사용하기도 쉽다. 오류 가능성도 적고, 더안전하다
- 객체 상태를 변경하는 메서드(수정자 메스드 등)를 제공하지 않는다.
- 계승할 수 없도록 한다
-
- 그러면 잘못 작성되거나 악의적인 하위 클래스가 객체 상태가 변경된 것처럼 동작해서 변경 불가능 성을 깨트리는 일을 막을 수 있다.
- 계승을 금지하려면 보통 클래스를 final 선언하면 된다.
-
- 모든 필드를 final로 선언하라
- 그러면 시스템이 강제하는 형태대로 프로그래머의 의도가 분명히 드러난다.
- 이렇게 되면 자바 메모리 모델에 명시된ㅁ바와 같이 새로 생성된 객체에 대한 참조가 동기화 없이 다른 스레드로 절달되어 안전하다.(이해 못했음...)
- 모든 필드를 private로 선언하라
- 그러면 클라이언트가 필드가 참조하는 변경 가능 객체를 직접 수정하는 일을 막을 수 있다.
- 변경 가능 컴포넌에 대한 독점적 접근권을 보장하라
- 클래스에 포함된 변경 가능 객체에 대한 참조를 클라이언트가 획득할 수 없어야 한다.
public final class Complex {
private final dobule re;
private final dobule im;
public Complex(dobule re, dobule im){
this.re = re;
this.im = im;
}
// 대응되는 수정자가 없는 접근자들
public dobule realPart() {return re;}
public dobule imaginaryPart() {return im;}
public Compex add (Complex c){
return new Compex(re+ c.re, im + c.im);
}
}
- 변경 불가능 객체는 단순하다. 생성될 때 부여된 한가지 상태만 갖는다. 따라서 불변식을 확실히 따른다
- 변경 불가능 객체는 스레드에 안전할 수밖에 없다. 어떤 동기화도 필요 없으며, 여러 스레드가 동시에 동시에 사용해도 상태가 휴ㅐ손될 일이 없다.
- 따라서 변경 불가능한 객체는 자유롭게 공유할 수 있다.
- 변경 간으한 클래스로 만들 타당한 이유가 없다면, 반드시 변경 불가능 클래스로 만들어야한다
- 변경 불가능한 클래스로 만들수 없다면, 변경 가능성을 최대한 제한하라.
- 측별한 이유가 없다면 모든 필드는 final로 선언하라
여기서 말하는 계승은 extends 상속을 의미한다.
- 계승은 코드 재사용을 돕는 강력한 도구지만, 항상 최선이라고는 할 수 없다.
- 계승은 상위 클래스와 하위 클래ㅑ스 구현을 같은 프로그래머가 통제하는 단일 패키지 안에서 사용하면 안전하다. 계승을 고려해 설계되고 그에 ㅏㅈ는 문서를 찾춘 클래스에 사용 하는 것도 안전하다.
- 메서드 호출과 달리, 계승은 캡슐화 원칙을 위반한다.
- 하위 클래스가 정상 동작하기 위해서는 상위 클래스의 구현에 의존할 수박에 없다.
- 상위 클래스의 구현은 릴리즈가 거읍되면서 바꿀 수 있는데
- 그러나다 보면 하위 클래스 코드는 수정된 적이 없어도 망가질 수 있다.
- 따라서 상위 클래스가 작성자가 계승을 고려해 클래스를 설계하고 문서화까지 만들어 놓지 않았다면, 하위클래스는 상위 클래스의 변화에 발맞춰 진화해야한다.
- 하위 클래스에서 상위 클래스의 로직을 그대로 쓰면 문제가 생길 염려가 많다는 것이다
- 다행이도 이런 문제를 피할 방법이 있다. 기존 크래스를 계승하는 대신, 새로운 클래스에 기존 클래스 객체를 참조하는 private 필드를 하다 두는 것이다. 이런 설계 기법을 구성(Composition) 이라고 부른다.
- 계승은 강력한 도구이지만 캡슐화 원칙을 침해하므로 문제를 발생 시킬 소지가 있다는 것이다.
- 상위 클래스와 하위 클래스 사이에 IS-A 관계가 있을 대만 사용하는 것이 좋다.
- 설사 IS-A 관계가 성립해도, 하위 클래스가 상위 클래스와 다른 패키지에 있거나 곗응을 고려해 만들어진 상위 클래스가 아니라면, 하위 클래스는 깨지기 쉽다.
- 이런 문제를 피하려면 구성과 전달 기법을 사용하는 것이좋다.
- 메서드를 재정의하면 무슨 일이 생기는지 정확하게 문서로 남겨야 한다.
- 재정의 기능 메서드를 내부적으로 어떻게 사용하느지 반드시 문서에 담겨라
- public, protected로 선언된 모든 메서드와 생성자에 대해, 어떤 재정의 기능 메서드를 어떤 순서로 호출하는지, 그리고 호출 결과가 추후 어떤 영향을 미치는지 문서로 남겨라는 것이다.
- 내용이 너무 심오 하다...이해 못했음
- 자바 언어에는 여러 가지 구현을 허용하는 잘형을 만드는 방법이 두가지 있다, 인터페이스 추상화 클래스가 그것이다.
- 추상화 클래스는 구현된 메서드를 포함할 수 있지만 인터페이스는 아니라는 것이다. 좀 더 중요한 차이는 추상 클래스가 규졍하는 자료형을 구현하기 위해서는 추상 클래스를 반드시 계승해야한다는 것이다. 언터페이스의 경우에는 인터페이스에 포함된 모든 메서드들을 정의하고 인토페이스가 규정하는 일반 규약을 지키기만 하면 되며, 그렇게 만든 클래스는 클래스 계층에 속할 필요가 없다.
- 이미 있는 클래스를 개조해서 새로운 인터페이스를 구현하도록하는 것은 간단하다.
- 간단히 말해서 믹스인은 클래스가 주 자료형 이외에 추가로 구현할 수 있는 자료형으로 어떤 선택적 기능을 제공한다는 사실을 선언하기 위해 쓰인다.
- 추상화 클래스는 믹스인 정의에는 사용할 수 없다. 클래스가 가질수 있는 상위 클래스는 하나뿐이며, 클래스 계층에는 믹스인을 넣기 좋은 곳이 없다.
interface Singer {
void test1();
}
interface Songwriter {
void test();
}
interface SingerSongwriter extends Singer, Songwriter {
void yyyy();
}
class Yun implements SingerSongwriter {
@Override
public void test1() {}
@Override
public void test() {}
@Override
public void yyyy() {}
}
이런식으로 조합해서 사용 가능
- 추상 클래스를 사용해서 자료형을 정의하면 프로그래머는 계승 이외의 수단을 사용 할 수없다.
- 그렇게 해서 만든 클래스는 포장 클래스보다 강력하지 않고, 깨지기도 쉽다
- 다음 릴리즈에 새로운 메서드를 추가하고 싶다면, 적당한 기본구현 코드를 담은 메서드를 언제든지 추가할 수 있다. 하지만 인터페이스는 이런 작업들이 어렵다.
- 인터페이스가 공개되고 널리 구현된 다음에, 인터페이스 수정이 겅의 불가능하기 때문이다.
- 인터페이스를 구현하는 클래스를 만들게 되면, 그 인터페이스는 해당 클래스의 객체를 참조할 수 있는 자료형 역할을 한다.
- 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위다.
public interface Anti{
static final dobule PI = 3.14;
}
- 상수 인터페이스 패턴은 인터페이스를 잘못 사용 한 것이다.
- 클래스가 어떤 상태를 어떻게 사용하느냐 하는 것은 궇ㄴ 세부사항이다. 상수 정의를 인터페이스가 포함시키면 구현 세부사항은 클래스의 공개 API에 스며글게된다.
- 인터페이스를 만드는 것은 일종의 약속이다.
public interface Good{
pubic static final dobule PI = 3.14;
}
이런식으로 사용해라
- 때로는 여러분은 두 가지 이상의 기능을 가지고 있으며, 그중 어떤 기능을 제공하는지 표시하는 태그(플레그)가 달린 클래스를 마날 때가 있다.
- 예로 들어, 아래의 클래스는 원을 표현할 수도 있고 사각형을 표현할 수도 있다.
- 태그 기반 클래스는 지저분한 데다 오류 발생 가능성이 높고 효율 적이지도 않다
- 태그 달린 클래스 대신에 계층을 활용하는 것이 바람짇하다.
- 중첩 클래스는 해당 클래스를 보조하는 용도로만 쓰여야아 한다. 그렇지 않으면 중첩 클래스로 만들면 안된다.
- 정적 멤버 클래스는 가장 단간한 중첩 클래스
- 정적 멤버 클래스는 바깥 클래스의 모든 맴버에(private로 선언된 것까지도) 접근할 수 있다.
- 정적 멤버 클래스는 바깥 클래스의 정적 멤버이며, 다른 정적 멤버와 동일한 접근 권한 규칙을 따른다.
- 정적 멤버 클래스를 private로 선언했다면 해당 중첩 클래스에 접근할 수 있는 것은 바깥 클래스 뿐일것이다.
- 정적 멤버 클래스의 흔항 영례 가운데 하나는 바깥 클래스스와 함께 사용될때 용의하다
- 비-정적 멤버 클래스 안에서 바깥 클래스의 메소드를 호출할 수도 있다.
- 비-정적 멤버 클래스의 객체는 바깥 클래스 객체 앖이는 존재 할 수 없다.
- 자바 언어의 다른 어떤 것과도 다르다. 익명 클래스에 이름이 없다.
- 익명 클래스는 바깥 클래스의 멤보도 아니다.
- 멤버로 선언하지 않아으며, 사용하는 순간에 선언하고 객체를 만든다.
- 익명 클래스는 표현식 문법을 준수하기만 하면 코드 어디서도 사용 할 수 있다.
- 익명 클래스는 비-정적 문맥 안에서 사용될 때만 바깥 객체를 갖는다
- 익명 클래스를 사용하는데 많은 제약이 있다. 선언하는 바로 그 순간에만 객체를 만들수 있다.
- instanceof를 비롯, 클래스 이름이 필요한 곳에 사용할 수 없다
- 여러 인테페이스를 구현하는 익명 클래스는 선언할 수 없으며, 인터페이스를 구현하는 동시에 특정한 클래스를 계승하는 익명 클래도 만들 수 없다.
- 익명 클래스는 표현식 중간에 등장하므로, 10중 이하로 짧게 작성되어야 한다. 아니면 가독성이 떨어질 것이다.
- 익명 클래스는 함수 객체를 정의할 때 널리 쓰인다.
- 지역 클래스는 네 종류 중첩 클래스 가운데 사용 빈도가 가장 낮다.
- 지역 클래스는 지역 변수가 선언될 수 있는 거ㅗㅅ이라면 어디든지 선언할 수 있으며, 지역 변수와 동일한 유혀범위 규칙을 따른다.
- 지역 클래스는 다른 중첩 클래스와 비슷한 특성을 갖고 있다.
중첩 클래스에는 네 가지 종류가 있다. 그리고 그 각각은 어울리는 자리가 다르다. 중첩 클래스를 메서드 밖에서 사용할 수 있어야 하거나, 메스드 안에 놓기에 너무 길 경우에는 멤버 클래스로 정의하라.
멤버 클래스의 객체 각각이 바깥 객체에 대한 참조를 가져야 하는 경우에는 비-정적 멤버 클래스로 만들어라.그렇지 않는 경우에는 정적 멤버 클래스로 만들면 된다.
중첩 클래스가 특정한 메서드에 속해야하고, 오직 한곳에서만 객체를 생성하며, 해당 줓첩 클래스의 특성을 규정하는 자료형이 이미 있다면 익명 클래스로 만들어라. 그렇지 않을 때는 지역 클래스로 만들면 된다
- 선언부에 형인자가 포함된 클래스나 인터페이스는 제네릭 클래스나, 제네릭 인터페이스라고 부란다.
- 제네릭 클래스와 인터페이스는 제네릭 자료형이라고 부른다.
- 무인자 자료형을 정의하는데, 무인자 자료형은 실 형인자 없이 사용되는 제네릭 자료형이다.
- 예를들어 List의 무인자 자료형은 List 이다. 무인자 자료형은 자료형 선언에서 제네릭 자료형 정보가 전부 삭제된 것처럼 동작한다.
//무인자 반복자 자료형. 앞으로는 이렇게 코딩 하면 안된다.
for (Iterator i = stamps.iterator(); i.hasNext(); ){
Stamp s = new (Stamp) i.next(); //ClassCastException 예외 발생
}
- 무인자 자료형을 쓰면 형 안정성이 사라지고, 제네릭의 장점중 하나인 표현력 측면에서 손해를 보게된다.
- 그런데 무인자 자료형이 그렇게 나쁘나면 왜 아직 자바는 무인자 자료형을 지원하느가 ? 그것은 호환성 때문이다. 이미 많은 양의 코드가 제네릭 없이 구현된 상태 였음으로 이 코드를 계속 컴파일 되도록 하는 제네릭을 사용하는 새로운 코드와 호환되도록 하는 것이 중요했다
- 새로 만드는 코드에서는 List와 같은 무인자 자료형을 쓰면 안되지만 아무 객체나 넣을 수 있는 List 같은 자료형은 써도 괜찮다.
- List와 List 차이는 간단히 말해서 List는 형 검사 절차를 완전히 생략한 것이고 List는 는 아무 객체나 넣을 수 있다는 것을 컴파일러에게 알려누는 것이다.
- List와 같은 무인자 자료형을 사용하면 형 안전성을 읽데 되지만, List와 같은 형인자 자료혀응ㄹ 쓰면 그렇지 않다
//실행 도중에 오류를 일으키는 무인자 자료형 List 예 public static void main(String[] args){ List<String> strings = new ArraList<String>(); unsafedAdd(strings, new Interger(42)); String s = strings.get(0); // 컴파일러가 자동으로 형변환 } private static void unsafeAdd(List list, Obejct o){ list.add(o); }
- 이 프로그램은 컴파일은 잘 될지만 무인자 자료형 List를 썻기 때문에 경고가 뜬다.
- 런타임 중에는 strings.get(0) 메서드가 String으로 변환하려 하기 때문에 ClassCastException 예외가 발생한다.
- 무인자 자료형을 쓰먄 프로그램 실행 도중에 예외가 발생할 수 있음으로 새로 만드는 코드드에는 무인자 자료형을 쓰지말아야한다. 무인자 자료형은 제네릭 도입 전에 작성된 코드와 호환성을 유지하기 위해 제공되는 것에 불과하다.
- Sub가 Super의 하위 자료형 이라면 Sub[]도 Super[]의 하위자료형이라는 것이다.
Type1, Type2 가 있을 때 상위 자료형이나 하위 자료형이 될 수 없다.
// 실행 중에 문제를 일으킴 Object[] objectArray = new Long[1]; objectArray[0] = "I dont`t fit in"; // ArrayStoreException 예외 발생 // 컴파일 되지 않은 코드 List<Object> ol = new ArrayList<Ling>(); //자료형 일치 ol.add("I dont`t fit in");
둘 중에 어떤 방법을 써도 Long 객체에 컨테이너에 String 객체를 넣을 수는 없다. 그러나 배열을 쓰면 실수를 저지른 사실을 프로그램 실행 중에 알수 있다. 반면 리스트를 사용하면 컴파일할 때 알 수 있다. 물론 컴파일 시에 문제를 반경하는 편이 무조건 좋다
- 배열은 실체화 되는 자료형이라는 것이다. 즉 자료형은 실행시간에 결정된다는 것이다. 위의 예제에서 보듯이 Stirng 객체를 Long 배열에 넣으려고 하면 ArrayStoreException이 발생한다.
- 반면 제네릭은 삭제 과정을 통해 구현된다. 즉 자료형에 관계된 조건들은 컴파일 시점에만 적용되고, 그 각원소의 자료형 정보는 프로그램이 실행될때는 삭제된다는 것이다.
- 자료형 삭제 덕에, 네제릭 자료형은 제네릭을 사용하지 않고 작성된 오래된 코드와도 아무문제없이 연동된다.
제네릭과 배열은 따르는 자료형 규칙은 서로 많이 다르다. 배열은 공변 자료형이자 실체화 가능성 자료형이다. 제네릭은 분변 자료형이며, 실행 시간에 형인자 의 정보는 삭제된다. 따라서 배열은 컴파일 시간에 형 안전성을 보장하지 못하며, 제네릭은 그 반대이다. 일반적으로 배열과 제네릭은 쉽게 혼용 할 수없다. 만일 배열과 제네릭을 뒤썩어 쓰다가 컴파일 오류나 경고메시지를 만나면, 배열을 리스트로 바꿔야 겠다는 생각이 본능적으로 들어야한다.
- 컬렉션 객체 선언을 제네릭화 하거나, JDK 가 제공하는 제네릭 자료형과 메서드를 사용하는 것은 일반적으로 어렵지 않다.
- 제네릭 자료형을 직접 만드는 것은 좀더 까다로운데, 그렇다 해도 배워둘 가치는 충분히 있다.
// Object를 사용한 컬렉션 - 제네릭을 적응할 중요 후보 public class Static { private Object[] elements; // Obejct 제네릭 후보 private int size = 0; private static final int DEFUALT_INITTAL_CAPACITY = 16; public Stack(){ elements = new Obejct[DEFUALT_INITTAL_CAPACITY]; } public void push(Object e){ ensurceCapacity(); elements[size++] = e; } public Object pop(){ if (size == 0) throw new EmptyStackException(); Obejct result = elements[--size]; elements[size] == nul; // 만기 참조 제거 return result; } public boolean isEmpty(){ return size == 0; } private void ensureCapacity(){ if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size +1); } }
- 호환성을 유지하면서 제네릭 자료형을 사욛하도록 개선할 수 있다. 위의 코드를 사용하면 스택에서 꺼낸 객체를 사용하기 전에 형변환을 해야하는데. 그런 형변환 프로그램실행 중에 실패항 가능성이 너무 높다.
- 선언부에 형인자(Type parameter)를 추가
- Object를 자료형으로 사용하는 부분들을 전부 찾아내서, 형으로 대체
// Object를 사용한 컬렉션 - 제네릭을 적응할 중요 후보 public class Static<E> { private E[] elements; // Obejct 제네릭 후보 private int size = 0; private static final int DEFUALT_INITTAL_CAPACITY = 16; public Stack(){ elements = new Obejct[DEFUALT_INITTAL_CAPACITY]; } public void push(E e){ ensurceCapacity(); elements[size++] = e; } public E pop(){ if (size == 0) throw new EmptyStackException(); Obejct result = elements[--size]; elements[size] == nul; // 만기 참조 제거 return result; } // 아래는 동일... }
Stack.java8: generic array creation elements = new E[DEFAULT_INITAL_CAOACITY];
- E 같은 실체화 불가능한 자료형으로 배열을 생성할 수 없다.
- 배열을 사용한데 제네릭 자료형을 구현 핼 때마다 이런문제를 겪게된다.
- 제네릭 배열을 만들 수 없다는 조건을 후회하는 것이다. Obejct 배열을 만들어서 제네릭 배열 자료형으로 형 변환 하는 방법이다. 그런데 그런 방법을 사용한 코드를 컴파일해 보면 오류 대신 경고 메시지가 출력된다. 문법적으로 문제는 없지만, 일반적으로 형 안전 성을 보장하는 방법은 아니다.
- 컴파일러는 프로그램의 형 안전성을 입증할 수 ㅇ벗을지도 모르지만, 푸로그래머는 할 수 있다. 무점검 형변환을 하기 전에 개발자는 반드시 그런 형변환이 프로그램의 형 안전성을 해치지 않음을 확실히 해야한다.
제네릭 자료형은 클라이언트가 형변환을 해야만 사용할 수 있다는 자료형보다 안전할 뿐 아니라 사용하기도 쉽다. 새로운 자료혀을 설계할 때는 형변한 없이도 사용할 수 있도록하라, 그러면 제네릭 자료형으로 만들어야할 때가 많을것이다. 시간 있을 대마다 기존 라형을 제네릭 자료형으로 변환하라. 기존 클라이언트 코드를 깨지지 않고도 새로운 사용자에게 더 좋운 API를 제공할 수 있게 될 것이다.
- 제네릭화로 혜택을 보는 것은 클래스뿐만이 아니다. 메서드로 혜택을 본다. static 유틸리티 메서드는 측히 제네릭화하기 좋은 후보이다.
//무인자 자료형 사용 - 권할 수 없는 방법 public static Set union(Set s1, Set s2){ Set result = new HashSet(s1); result.addAll(s2); return result; }
- 컴파일 되긴 하지만 경고가 두 개 뜬다.
- 이 경고는 형 안전성 보장된 메소드를 구현하라는 경고이다.
- 형인자를 선언하는 형인자 목록은 메서드 수정자 와 반환 값 자료형 사이에둔다
public static <E> Set<E> union(Set<E> s1, Set<E> s2){ Set<E> result = new HashSet<E>(s1); result.addAll(s2); return result; }
- 간단한 제네릭 메서드의 경우라면 이정도면 충분하다. 이 메서드는 아무런 경고 메시지 없이 컴파일 되며, 사용하기 편히할 뿐 아니라 형 안전성도 보장한다.
- 제네릭 생성자를 호출 할 때는 명시적으로 주어야 했던 형인자를 전달할 필요가 없다는 것이다. 컴파일러는 메시지 메서드에 전해진 인자의 자료형을 보고 형인자값을 알아낸다
- 위의 예제에서는 union에 전달된 두 인자의 자료형이 Set 이므로, 컴파일러는 E가 String임을 알아낼 수 있다.
- 이 과정을 자료형 우추라고 한다.
- 형인자 자료형은 불변 자료형이다. 다시 말해 Type1과 Type2가 있을 때 List, List 상이의 어떤 상위-하위 자료형 관계도성립 할 수없다.
PESC (Produce - Extedns, Consumer - super)
- 인자 T가 생성자라면 <? extends T> 라고 하고 T가 소비자라면 <? super T> 하라는 예이다.
- varargs 메서드는 인자 개수가 가변적인 메서드를 정의할 때 편리하지만 남용되면 곤란하다. 부적절하게 사용되면 혼란스러운 결과를초래할 수 있다.
- null 대신에 빈 배열이나 빈 컬렉션을 반환하라
- null 리턴하면 안된다 일반적으로 NullPointExcetpion이 발생한다.
- null을 반환 하는 것은 C언에서 전해진 관습으로 C에서 배열의 길이가 배열과 따로 반환된다. 이런 관습 때문에 null을 리턴 하면 안된다.
-
- 캘래스와 멤버의 접근 권한은 최소화라와 유사하다
- 지역 변수의 유효범위를 초솨화면 가독성과 유지보수성이 좋아지고, 오루 발생 가능성도 줄어든다.
- 지역 변수의 유효범위를 최소화하는 가장 강력한 기법은, 처음으로 사용하는 곳에서 선언하는 것이다.
- 지역 변수를 너무 빨리 선언하면 유혀범위가 너무 앞으쪽으로 확장될 뿐 아니라. 너무 뒤쪽으로도 확장된다.
- 지역 변수의 유효범위는 선언된 지점부터 해당 블록 끝까지다.
- 거의 모든 지역 변수에는 초기값이 포함되어야 한다
- 변수를 적절히 초기화하기에 충분한 정보가 없다면 그때까지 선언을 미뤄야한다.
- 지역 변수의 유효범위를 최소화하는 마지막 전략은 메서드의 크기를 줄이고 특정한 기능에 집중하라는 것이다.
//배열 순환할 때 한동안 많이 사용한 숙어 for (int i = 0; i < a.lenth; i++){ doSomethis(a[i]); }
- 반복자나 세번 등장하고 첨자 변수는 네번 등장한다. 실수로 다른 변수를 이용할 가능성이 충분히 높다.
- 그리고 그런 오류는 컴파일 과정에서 탐지되지 않는다.
// 컬렉션이나 배열을 순회할 때는 이 숙어를 따르라 for(Elment e : elemtes){ doSomething(e); }
- for-earch문은 성능은 for문과 같다. 어떤 상황에서는 일반 for문보다 더 나은 성능을 보이기도 한다.
- 배열 첨자가 이동할 수 있는 한계를 딱 한번만 계산하기 때문이다.
- for-each 문의 장점은 여려 컬렉션의 중첩되는 순화문을 만들때 더 빛이 난다
- 일반적인 for i, j, k 등 변수를 지속 적으로 추가 해야하고
- 그런 추가는 실수를 만들기 쉽다. 또 이런 실수는 컴파일 단계에서 알아내기 어렵다.
- 필터링
- 컬렉션을 순회하거나 특정한 원스를 삭제할 필요가 있다면, 반복자를 명시적으로 사용해야한다. 반복자의 remove 메서드를호출해야 하기 때문이다.
- 변환
- 리스트나 배열을 순회하거나 그 원소 가운데 일부 또는 전부의 값을 변경해야 한다면 원소의 값을 수정하기 위해서 리스트 반복이나 배열 첨자가 필요하다.
- 병렬 순회
- 여러 컬랙션을 병렬적으로 순회해야 하고, 모든 반복자나 첨자 변수가 발맞춰 나아가도록 구현해야 한다면 반복자나 첨자 변수를 명시적으로 제어할 필요가 있다는 것이다.
- 정확한 답을 요구하는 문제를 풀 때는 float, dobule을 쓰지말라
- 소수점 이하 처리를 시스템이 알아서 해줬으면하거, 기본 잘형 보다 사용하기가 좀 불편해도 괜찮으며 성능이 조금 떨어져도 상관없을 경우에는 BigDeimal을 쓰라.
- BigDeimal은 올림 연산을 어떻게 수행해야하는지 여덟 가지 올림 모드 가운데 하나로 지정할 수 있다
- 그러나 성능이 중요하고 소수점 이래 수를 직접 관리해도 상관 없으며 계산할 수가 심하게 크지 않을 ㄱ때는 int나 long을 쓰라. 관계된 수치들이 십진수 아홉 개 이하로 표현이 가능할 때는 int를 쓰라 18개 이하로 펴햔 가능할 때는 long을 쓰라 그 이상일 때는 BigDecimal을 써야한다.
- 자바의 자료형 시스템은 두 부분으로 나눈다.
- 기반 자료형, 참조 자료형이다. 모든 기본 자료형에는 대응되는 참조 자료형이 있는데, 이를 객체화된 기본 자료형 이라 부른다.
- 이 둘 사이에는 실질적인 차이가 있음으로, 둘 가운대 무엇을 사용하고 있는지를 아는 것이 중요하며, 어던 것을 사용할지 신중하게 결정 해야한다.
- 객체화된 기본 자료형에 == 연산자를 사용하는 것은 거의 항상 오류라고 봐야한다.
public class Unbelivalbe { static Interger i; public static void main(String[] args){ if (i == 42) System.out.println("Unbelievable"); // 기본 자료형과 객체 기본 자료형이 같이 있으니 객체 기본 자료형은 자동으로 형변환이 시도 된다. 하지만 i 값은 초기화가 안되있으니 null로 초기화 되있을 것이고 그때 NullPotinException이 발생한다. } }
- 기본 자료형과 객체화된 기본 자료형을 한 연산 안에 엮어 놓으면 객체화된 기본 자료형은 자동으로 기본 자료형으로 변환된다.
- null인 객체 참조를 기본 자료형으로 변환 하려 시도한다면 NullPotinException이 발생한다.
- 거의 모든 곳에서 이런 상태가 발생하게 된다.
//무시무시 할 정도로 느린 프로그램, 어디서 객체가 생성되는 알겟는가? public static void main(String[] args){ Long sum = 0L; for (long i = 0; i < Intger.MAX_VALUE; i++){ sum += i; } }
- 이 프로그램은 예상보다 훨씬 느리다.
- 지역 변수 sum을 long이 아니라 Long으로 선언했기 때문이다. 오류나 경고 없이 컴파일되는 프로그램이지만 변수가 계석해서 객체와 비객체화를 반복하기 때문에 선능이 느려진다.
- 그렇다면 객체화된 기본자려형은 언제 사용해야하나 ?
- 컬렉션 요소, 키, 갑승로 사용 할때 이다. 컬렉션에는 기본 자료형을 넣읗 수 없으므로 객체화된 자료형을 써야한다.
- 기본 자료형을 사용하는것이 좋다. 기본 자료형이 더 단순하고 빠르다. 객체화된 기본 자료형을 사용해야 한다면 주의하라
- 자동 객체화는 번거로운 일을 줄여주긴 하지만, 객체화된 기본자료형을 사용할 때 생길 수 있는 문제들까지 없애주진 않는다.
- 객체화된 기본 자료형과 기본 자료형을 한 표현식으로 뒤썩으면 비객체화가 자동으로 일어나며, 그 과정에서 NullPotinExcpeion이 발생할 가능성이 있다.
- 자바는 1.5 부터 제네릭이라는 개념을 지원하기 시작했다.
- 제네릭이 도입전, 프로그래머는 컬렉션에 객체를 넣을 때마다 형변환 작업을 해야만 했다
- 그래서 누군가 컬렉션에 엉뚱한 자료형을 객체를 넣으면 프로그램 실행 중에 오류가 나고 만다.
- 하지만 제네릭을 사용하면 컬렉션에 넣는 객체의 자료형이 무엇인지 컴파이얼ㄹ에게 알릴 수 있다.
- 형변환 코드는 컴파일러가 알아서 넣어 줄 것이고, 잘못된 자료형의 객체를 컬렉션에 넣으려는 시도는 컴파일 과정에서 차단된다.
- 드 덕에 프로그램은 더 안전해지고 명료 해졌다. 하지만 프록램이 복잡해지는 단점도 있다
- 이번 파트에서는 제네릭의 혜택을 최대한 누리면서 복잡함은 피하는 방법을 살펴 본다.
- 타입 세이프를 위하 나왔다라고 생각한다
제네릭은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다.
package org.opentutorials.javatutorials.generic; class EmployeeInfo{ public int rank; EmployeeInfo(int rank){ this.rank = rank; } } class Person<T, S>{ public T info; public S id; Person(T info, S id){ this.info = info; this.id = id; } public <U> void printInfo(U info){ System.out.println(info); } } public class GenericDemo { public static void main(String[] args) { EmployeeInfo e = new EmployeeInfo(1); Integer i = new Integer(10); Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i); p1.<EmployeeInfo>printInfo(e); p1.printInfo(e); } }
- 생성자를 통해서 제네릭을 이용 가능
- 제네릭에서 extends를 이용해서 타입 세이프 하게 받을 수 있다.
- 제네릭을 쓰면 모든 자료형을 받을 수 있으니, extends를 사용해서 동일부모를 갖는 자료형을 받을 수 있다.
- super로 부모를 제안 할 수도 있다는데 이건 나중에 알아보자
- 문자열은 텍스트 표현과 처리에 걸맞도록 설계되었다. 그런데 문자열은 워낙 흔한 데다 자바 문자열 지원도 아주 훌룡하기 때문에 원래 설계된 목적 이외의 용도로 많이 활용되는 경향이 있다
- 데이터가 파일이나 네트워크나 키보드를 통해서 들어올 때는 보통 문자열 형태다. 그러니 그대로 두려는 경향이 있다. 하지만 데이터가 원래 텍스트 형태 일 때나 그렇게 하는 것이 좋다.
- 순자라면 int, folat 같은 수 자료형으로 변환해야한다.
- enum은 문자열 보다 훨씬 좋은 열거 자료형 상수를 만들어준다
- 여러 컴포넌트가 있는 객체를 문자열로 표현하는 것은 좋은 생각이 아니다.
- 문자열 연결 연산자 +는 문자열을 하나로 합하는 편리한 수단이다.
- 훈줄 정도는 풀려갛ㄹ 때나 몇 개 정도의 객체를 문자열로 반환해서 연결할 때는 좋다
- 하지만 연결할 것들이 많아지면 성능에 문제가 생긴다.
- n개의 문자열에 연결 연산자를 반복 적용해서 연결하는 데 드는 시간은, n의 2승에 비례한다
- 문자열이 변경 불가능 하기 때문이다. 문자열 두 개를 연결할 때는 그 두개의 문자열의 내용은 전부 복사된다.
//문자열 연결하는 잘못된 방법 - 성능이 엉망이다. public String statment(){ String result = ""; for (int i = 0; i < numItems(); i++) result += lienForItem(i); //String Concatenation return result; }
- 만족스러운 선능을 얻드려면 String 대신 StringBuilder를 사용하라
- StringBuilder는 StringBuilder에서 동기화 기능을 뺸것이다. (StringBuilder는 잘사용하지 않는다)
public String statment(){ StringBuilder b = new StringBuklder(numItems() * LINE_WIDTH); for(int i = 0; i < numItems(); i++) b.append(lineForItem(i)); return b.toString(); }
- 성능 차이는 실로 엄청나다. 약 85 배정도 차이
- 항목 개수가 많아지면 성능 차이는 더 심해진다.
- 성능이 걱정된다면, 많은 문자열을 연결 할 때 + 연산자 사용은 피하라.
- StringBuilder의 append 메서드를 사용해라.
- 문자 배열을 사용하거나, 문자열을 연결하는 대신 순차적으로 처리하는 방법을 사용할 수도 있다.
- 인자의 자료형으로 클래스 대신 인터페이스를 사용해야한다.
- 객체를 참조할 때는 클래스보다 인터페이스를 사용해야 한다는 것으로 이해할 수 있다.
- 만일 적당한 인터페이스 자료형이 있다면 인자나 반환값, 변수 그리고 필드의 자료형은 클래스 대신 인터페이스로 선언하자
- 객체의 실제 클래스를 참조할 필요가 있는 유일한 순간은 생성자로 객체를 생성할 때다.
// 인터페이스를 자료형으로 사용하고 있는, 바람직한 예제 List<Subscriber> subscribers = new Vector<Subscriber>(); // 클래스를 자료형으로 사용하는, 나쁜 예제 Vector<Subscriber> subscribers = new Vector<subscribers>();
- 인터페이스를 자료형으로 쓰는 습관을 들이면 프로그램은 더욱 유연해진다.
- 적당한 인터패ㅔ이스가 업슨ㄴ 경우에는 갹체를 클래스로 참조하는 것이 당연하다.
- 적절한 인터페이스가 있다면 인터페이스를 사용하면 훨씬 유연한 프로그램을 만들 수 있다. 인터페이스가 없는 경우에는 필요한 기느응ㄹ 제공하는 클래스 가운데 가장 일반적인 클래스를 클래스 계층 안에서 찾아서 이용해야한다.
- 리플랙션 기능을 이용하면 메모리에 적재된 클래스의 정보를 가져오는 프로그램을 작성할 수 있다.
- Class 객체가 주어지면 해당 객체가 나타내는 클래스의 생성자, 메서드 필드 등을 나타내는 Constructor, Method, Field 객체들을 가쟈ㅕ올 수 있는데, 이 객체들을 사용하면 클래스의 멤버 이름이나. 필드 자료형, 메서드 시그니처 등의 정보를 얻어낼 수 있다.
- 게다가 Constructor, Method, Field 객체들을 이용하면 거기에 연결되있는 실세 성성자, 메서드, 필드들을 반영적으로 조작할 수 있다.
- 예를 들어 Method invoke를 이용하면 어던 클래스의 어떤 객체에 정의된 어떤 메소드라도 호출할 수 있다.
- 컴파일 시점에 자료형을 검사함으로 얻을 수 있는 이점을 포기해야한다.. 리플렉션을 통해서 존재하지 않은, 또는 접근할 수없는 메서드릉ㄹ 호출하려면 실행 도중에 오류가 발생할 것이다. 그러니 특별히 주의해야 한다.
- 리플렉션 기능을 이용하는 코드는 보기 싫은데다 장황하다. 영리한 코드와는 거리가 멀고, 가독성이 떨어진다.
- 성능이낮다. 리플렉션을 통한 메서드 호출 성능은, 일반 메서드 호출에 비해 훨씬 낮다. 얼마나 낮은 지 정확히 마랗기는 어렵다. 고려해야 할 요건 들이 다양하기 때문이다. 저자의 컴퓨터에서는 속도차이는 2배에서 50배 가량 차이었다.
- 명심할 것은, 일반적인 프로그램은 프로그램 실행 중에 이플랙션을 통해 객체를 이용하려 하면 안된다는 것이다.
- 리플렉션ㅇ을 아주 제한적으로 사용하면 오버헤드는 피하면서도 리플렉션의 다양한 장점을 누릴수 있다.
- 호출해야 하는 생성자가 아무런 인자도 받지 않을 때는 리플랙션을 이용할 필요 조차 없다. Class.newInstance 메서드를 호출하는 것으로 충분하다.
- 자바의 네이티브 인터페이스는 C나 C++ 등의 네이티브 프로그래밍 언어로 작성된 네이티브 메서드를 호출하는 데 이용 되는 기능이다.
- 네이티브 메서드를 사용하면 선능이 중요한 부분의 처리를 네이티브 언어에 맡길수 있다. 특정 플랫폼에만 있는 기능을 이용하는 데 는 네이티브 메서드가 적당하다.
- 그러나 네이티브 메서드를 통해 성능을 개선하는 것은 추천하고 싶지 않다.. JVM의 발전으로 차이는 많이 없고 신중하게 사용해야한다.
- 성능 때문에 구조적인 원칙을 희생하지마라
- 좋은 프로그렘인데 충분히 빠르지 않다면, 좋은 구조를 갖추었기 때문에 최적화의 여지도 충분할 것이다.
- 좋은 프로그램은 정보 은닉 원칙을 지킨다. 설계에 관한 결정은 각 모듈 내부적으로 내려진다. 따라서 그 설계는 시스템의 다른 부분에 영향을 주지 않으면서 독립적으로 변경될 수 있다.
- 설계 가운데 성능에 문제가 있다는 사실이 발견된 후에 코치기가 가장 까다로운 부분은 모듈 간 상호작용이나 외부와의 상호작용을 명시하는 부분이다. 그중에 가장 중요한 것은 지속성 데이터 형식등이 있다.
- 이런 부분은 성능 문제가 발뎐된 후에 수정하기 어렵다.
- public 자료형은 변경 가능하게 만들면 쓸데 없이 방어적 복사를 많이 해야 할 수 있다.
- 다음번 릴리즈에 플랫폼이나 기타 내부 소프트웨어가 바뀌면 성능 문제는 사라질 수도 있다.
- 빠른 프로그램을 만들고자 애쓰지 말라는 것이다. 대신 좋은 프로그램을 짜기 위해 애써라. 성능은 따라올 것이다.
- 시스템을 설계 할 때, 특히 API나 통신 프로토콜, 또는 지속성 데이터 형식을 설계할 때는 성능 문제를 따져 보라
- 패키지, 클래스 인터페이스, 메서드 필드, 자료형 변수에 관한 것으로 그양은 얼마 되지 않는다.
- 그럴듯한 이유가 없이는 이 규칙을 어겨서는 안된다.
- 자바 플랫펌에는 작명 관습이 잘 정립되어 있다.
- 자바의 작명 관습은 두 가지 범주로 나눌 수 있다. 하나는 철자에 관한 것이고, 문법에 관한 것이다.
- 예외는 예외적인 상황에만 사용해야 한다. 평상시 제어 흐름에 이요하면 안된다.
- 잘 설계된 API는 클라이언트에게 평상시 제어 흐름의 일부로 예외를 사욛하도록 강요해서는 안된다.
- 자바는 세 사지 종류의 throwable을 제공한다
- 호출자 측에서 보구할 것으로 여겨지는 상황에 대해서는 checked exception 예외를 이용 하라
- checked exception을 던지는 메서드를 호출한 클라이언트는 해당 예외를 catch 절에서 잡아서처리하든지, 아니면 계속 박으로 던져지도록 놔두든지 해야한다
- 따라서 예외와 관계된 상황이 발생 할 수 있음을 API 사용자에게 알리는 구실을 한다.
- 프로그래밍 오류를 표현 할 대는 Runtime Exception을 사용하라
- 대부분의 실행 시점예외는 선행조건 위반을 나태낸다.
- 클라이언트가 API 명세에 기술된 규약을 지키지 않았다는 뜻이다.
- checked Exception 예외는 자바가 제공하는 멋진 기능 가운데 하나다. 코드를 반환하느 것과 달리, 프로그래머로 하여금 예외적인 상황을 처리하도록 강제함으로써 안정성을 높인다.
- 너무 남발하면 사용하기 불편한 API가 될 수도 있다느 뜻이다.
- checked Exception 메서드를 호출 하면 예외를 바다 처리하는 catch 블록을 하나 이상 만들든가, 아니면 예외를 다시 바끙로 던진다고 선언하고는 외부로 전파되도록 내버려둬야 한다. 어떻게 하든 프로그래머 입장에서는 단단치 않은 일이다.
예외, 용례 IllegalArgumentException, null이 아닌 인자의 값이 잘못되었을 때 IllegaStateException, 객체 상태가 메서드 호출을 처리하기에 적절치 않을 때 NullPointException, null 값을 받으면 안 되는 인자에게 null이 전잘되었을 때 IndexOutOfBoundsException, 인자로 주어진 첨자가 허용 범위를 벗어났을 때 ConcyrrentModificationException, 병렬적 사용이 금지된 객체에 대한 병려 접근이 탐지되어쓸 때 UnsupportedOperationException, 객체가 해당 메서드를 지원하지 않을 때 - 메서드가 하는 일과 뚜렷한 관련성이 없는 예외가 메서드에서 발생한다면 당혹스럽다.
- 추상화 수준아 낮은 곳에서 발생한 예외를 그대로 바끙로 전달하면 이런 문제가 생긴다.
- 당혹스러울 뿐더러, 추상화 수준이 높은 API가 구현 세부사항으로 오염되는 일까지 생기긴다.
- 이런 문제를 피하려면 상위 계층에서 하위 계층에서 발생하는 예외를 반드시 받아서 상위 계층 추상화 수준에 맞는 예외로 바꿔서 던져야 한다. 이것을 exception translation 이라고 부른다
//예외 반환 try { //낮은 수준의 추상화 계층 이용 } catch (LowerLevelException e){ throw new HigherLevelException(...); }
- 하위 계층에서 발생하는 예외를 막거나 처리할 수 없다면, 상위 계층에 보여주면 곤란한 예외는 예외 반환을 통해 처리하는 것이다.
- checked exception는 독립적으로선안하고, 해당 예외가 발생하는 상황은 Javadoc @throws 태그를 사용해서 정확하게 밝혀라.
- unchecked exception 때문에 프로그램이 죽으면, 시스템은 자동적으로 해당 예외의 스택 추정 정보를 출력한다.
- 이 정보보는 해당 예외 객체의 toString 메서드가 예외 정보를 문자여로 변환한 결과다. 스택 추정 정보는 예외 객체의 크래스 명 뒤에 상세 메시지가 오는 형태로 구성되 있다.
- toString 메서드가 반환하는 문자열에 오류 원인에 관계된 정보를 쵀대한 많이 남겨주는 것이 중요하다
- 예외를 try catch 문으로 감싸면 예외를 쉽게 무시할 수 있다.
//catch 블록을 비워 놓으면 예외는 무시한된 심히 의심스러운 코드! try { ... } catch (SomeException e){ }
- 빈 catch 블록은 예외를 선언한 목적, 그러니까 예외적 상황을 반드시 처리하도록 강제하는 목적에 배치된다.
- 예외를 무시하는 것은 화재 경고를 무시하는 것과 같다.
- 적도 catch 하는 블록안에 예외를 무시해도 괜찮은 이유라도 주석으로 남겨둬야한다.
- 이런 예외가 발생하더라도 catch 블록으로 예외를 무시하는 프로그램은 오류가 생겨도 조용히 실행을 계속한다. 예외를 적절히 처리하면 오류도 프로그램이 죽는일은 피할 수 있다 하지만 명확한 이유가 없지 않은 이상 사용하지마라 (내 개인적 생각)