Effective Java 독서노트 - 6장 열거 및 주석
제30조: int 대신 enum
매거 유형은 고정된 상수로 합법적인 값을 구성하는 유형을 가리킨다.예를 들어 일년 중의 계절, 태양계 스마트 캐비닛의 행성이나 패의 색깔.열거 형식이 나타나기 전에 열거 형식을 나타내는 모델은 하나의 구명을 사용하는 int 상수입니다.
//JDK1.5
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_CRNNY_SMITH = 2;
...
이런 방식을 int 매거 모드라고 부른다.결점이 많다.
대신 열거 유형을 사용합니다.
public enum Apple {
FUJI, PIPPIN, CRANNY_SMITH
...
}
Java 열거는 본질적으로 int 값입니다.
자바 매거 형식은 공유된 정적 final 필드를 통해 매거 상수마다 실례를 내보내는 클래스입니다.접근할 수 있는 구조 방법이 없기 때문에 클라이언트는 매거 유형의 실례를 만들 수 없고 확장할 수 없다. 즉, 매거 유형은 실례가 제어하는 것이다.그것들은 일례이다.
매거 유형은 임의의 방법과 영역을 추가하고 인터페이스를 실현할 수 있습니다.매거 유형은 모든 Object 방법의 고급 실현을 제공하고 Comparable와 Serializable 인터페이스를 먼저 제공합니다.
밤을 들다.
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS(4.869e+24, 6.052e6),
EARTH(5.975e+24, 6.378e6),
MARS(6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN(5.685e+26, 6.027e7),
URANUS(8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass;
private final double radius;
private double surfaceGravity;
private static final double G = 6.67300E-11;
//
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
this.surfaceGravity = G * mass / (radius * radius);
}
public double mass() {
return mass;
}
public double radius() {
return radius;
}
public double surfaceweight(double mass) {
return mass * surfaceGravity;
}
}
플래닛 클래스에서는 태양계의 8개의 행성이 있습니다.모든 행성은 질량과 반경이 있는데 이 두 속성을 통해 행성의 표면 중력 가속도를 계산하여 물체가 받는 중력을 계산할 수 있다.모든 매거 상량 뒷괄호의 수치는 구조기에 전달되는 매개 변수로 행성의 질량과 반경을 가리킨다.데이터를 매거 상수와 연결하기 위해서는 실례역을 설명하고 데이터가 있는 구조기를 작성해야 한다.매거류는 변할 수 없는 클래스이기 때문에 모든 영역은final이어야 하며,private가 가장 좋다.
아래의 이 방법은 지구상에 있는 어떤 물체가 어떤 행성에 놓인 후에 받는 중력을 계산해 낼 수 있다.
public class WeightTable {
public static void main(String[] args){
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for(Planet p : Planet.values()) {
System.out.println("Weight on %s is %f%n",p,p.surfaceWeight(mass));
}
}
}
모든 매거류는 정적values 방법이 있으며, 성명한 순서에 따라 값 그룹을 되돌려줍니다.출력 결과는 다음과 같습니다.
Weight on MERCURY is 66.133672
Weight on VENUS is 158.383926
Weight on EARTH is 175.000000
Weight on MARS is 66.430699
Weight on JUPITER is 442.693902
Weight on SATURN is 186.464970
Weight on URANUS is 158.349709
Weight on NEPTUNE is 198.846116
특정한 상량에 대한 방법은 특정한 상량에 대한 액 데이터와 결합할 수 있다.예를 들어, 다음 Operation은 toString을 덮어쓰고 일반적으로 연산자와 연관된 기호를 반환합니다.
public enum Operation {
PULS("+") {
double apply(double x , double y){return x + y;}
}
MINUS("-") {
double apply(double x , double y){return x - y;}
}
TIMES("*") {
double apply(double x , double y){return x * y;}
}
DIVIDE("/") {
double apply(double x , double y){return x / y;}
}
}
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
abstract double apply(double x,double y);
위의 클래스 사용:
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for(Operation op : Operation.values()) {
System.out.println("%f %s %f = %f%n",x,op,y,op.apply(x,y));
}
}
출력:
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
열거 유형은 중첩으로 나타날 수도 있습니다.
enum PayrollDay {
MONDAY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
}
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked,payRate);
}
private enum PayType {
WEEKDAY {
double overtimePay(double hours,double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours,double payRate) {
return hours * payRate / 2;
};
private static final int HOURS_PER_SHIFT = 0;
abstract double overtimePay(double hrs,double payRate);
double pay(double hourWorked,double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hourWorked,payRate);
}
}
총결산: 매거 유형을 언제 사용해야 합니까?고정된 상수가 필요할 때마다물론 이것은'천연의 매거 유형'을 포함한다. 예를 들어 행성, 일주일 일수, 바둑알 등이다.메뉴 옵션, 조작 코드 등 가능한 모든 집합을 컴파일할 때 알 수 있는 것도 포함된다.한 마디로 하면 int 상량에 비해 매거 유형의 장점은 말하지 않아도 알 수 있다. 매거는 쉽게 읽을 수 있고 많은 매거는 현식의 구조기나 구성원이 필요하지 않다. 다른 부분은'매거 상량과 속성의 관련'과'행위가 이 속성의 영향을 받는 방법을 제공하는 것'에서 이득을 본다.동일한 동작을 공유하는 여러 개의 열거가 있다면, 상기와 같이 덧붙이는 열거를 고려할 수 있다.
제31조: 시퀀스 대신 실례역으로
각 열거 유형마다 하나의 ordinal 방법이 있는데, 이것은 매 열거 상수가 유형의 숫자 위치를 되돌려줍니다.
public enum Ensemble {
SOLO, DUET, TRIO,QUARTET,QUINTET,SWXTETE,SEPTET,OCNET,DECTET;
public int numberOfMusicians() {
return ordinal() + 1;
}
}
위의 매거 클래스에는 문제가 있습니다. 매거 상수의 순서를 바꾸면number OfMusicians 방법은 다른 결과를 되돌려줍니다.
이러한 열거 상수의 값을 명시적으로 지정할 수 있습니다.
public enum Ensemble {
SOLO(1),DEUT(2),TRIO(3),QUARTET(4),QUINTET(5),SEXTET(6),SEPTET(7),OCTET(8),DOUBLE_QUARTET(9),NONET(10),DECTET(11),TRIPLE_QUARTET(12);
private final int numberOfMusicians;
Ensemble(int size) {
this.numberOfMusicians = size;
}
public int numberOfMusicians() {
return numberOfMusicians;
}
}
제32조: 역위 대신 EnumSet을 사용
생략
제 33 조: 시퀀스 인덱스 대신 EnumMap 사용
다음과 같은 열거 유형을 사용할 수 있습니다.
public class Herb {
public enum Type {
ANNUAL , PERENNIAL, BIENNIAL
}
private final String name;
private final Type type;
Herb(String name,Type type) {
this.name = name;
thos.type = type;
}
@Override
public String toString() {
return name;
}
}
현재 바닐라의 수조를 가설하면 한 정원에 있는 식물을 나타낸다. 만약에 유형(일년생, 다년생, 2년생 등)에 따라 조직한 후에 이 식물을 열거하면 3개의 집합이 필요하다. 각 유형에 하나씩 정원을 두루 돌아다니며 각 바닐라를 상응하는 집합에 넣는다.
Herb[] garden = ...;
Set[] herbsByType = new (Set[]) Set[Herb.Type.values().length];
for(int i = 0; i < herbsByType.length; ++i) {
herbsByType[i] = new HashSet();
}
for(Herb h : garden) {
herbsByType[h.type.ordinal()].add(h);
for(int i = 0;i < herbVyType.length;++i) {
System.out.println("%s: %s%n",Herb.Type.values()[i],herbsByType[i]);
}
}
상기 코드는 작업을 완성할 수 있으나 문제가 존재한다. 매거진 순서에 따라 인용하는 수조에 접근할 때 정확한 int값을 사용하는 것이 프로그래머의 직책이다. int는 매거 유형의 안전을 제공할 수 없고 잘못된 값을 사용하면 논리적 오류가 발생할 수 있다.
해결 방법은 EnumMap을 사용하는 것입니다. 하나의 매우 빠른 Map으로 열거 키만 사용할 수 있습니다.
Map.Type, Set> herbByType = new EnumMap.Type,Set>(Herb.Type.class);
for(Herb.Type t : Herb.Type.values()) {
herbByType .put(t,new HashSet());
}
for(Herb h : garden) {
herbByType.get(h.type).add(h);
}
System.out.println(herbByType);
한 마디로 하면, 색인 그룹을 색인하는 대신, EnumMap을 사용하는 것이 좋다.
제34조: 인터페이스로 신축 가능한 매거 시뮬레이션
이 면의 프로그램은 30개의 코드를 수정했습니다.
public interface Operation {
double apply(double x,double y);
}
public enum Operation implements Operation {
PULS("+") {
double apply(double x , double y){return x + y;}
}
MINUS("-") {
double apply(double x , double y){return x - y;}
}
TIMES("*") {
double apply(double x , double y){return x * y;}
}
DIVIDE("/") {
double apply(double x , double y){return x / y;}
}
}
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
열거 유형은 확장할 수 없지만 인터페이스는 확장할 수 있습니다.
위의 작업에 대한 확장을 정의해야 한다고 가정하면 구멱과 구여로 구성됩니다.
public enum ExtendedOperation implements Operation {
EXP("^"){
public double apply(double x,double y) {
return Math.pow(x,y);
}
}
REMAINDER("%"){
public double apply(double x,double y) {
return x % y;
}
}
}
한 마디로 하면 확장 가능한 매거 유형을 작성할 수 없지만 인터페이스를 작성하고 이 인터페이스의 기초 매거 유형을 실현할 수 있다.
제35조: 주석이 명명 모드보다 낫다
이름 지정 모드는 JDK1입니다.5 이전에 사용한 모드가 주석으로 대체되었습니다.
간단한 테스트를 지정하기 위해 콜아웃 유형을 정의하려고 하면 콜아웃 유형이 자동으로 실행되고 예외가 발생할 때 실패합니다.
import java.lang.annotation.*;
/**
*
*
**/
@Retention(RententionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
위에는 Test라는 메모 유형이 있지만 실제로는 인터페이스입니다.이 자체는 Rentention 및 Target 메모를 통해 메모됩니다.
이 두 종류의 주해를 원주해라고 부른다.메모
@Retention(RententionPolicy.RUNTIME)
는 Test 메모가 런타임에 유지되어야 함을 나타냅니다.보존되지 않으면 테스트 도구는 Test 메모를 알 수 없음을 알 수 있습니다.메타 주석@Target(ElementType.METHOD)
은 Test 주석이 메소드 성명에서 시간 초과만 합법적임을 나타낸다. 즉, 클래스 성명, 필드 성명 또는 다른 프로그램 요소의 성명에 적용될 수 없다는 것이다.Test 메모에는 "이 메모는 참조 없는 정적 방법에만 사용됩니다"라는 주석이 있습니다. 이 검사는 컴파일러가 감지할 수 없으며 매개변수가 있는 정적 방법이 아닌 경우에도 컴파일러 검사에서 오류가 발생하지 않습니다.
다음은 사용자 정의 Test 메모를 메모 표기 메모라고 하는 데 사용되는 Test 메모를 적용합니다. 매개변수가 없고 마크업이 메모된 요소일 뿐입니다. 메모를 잘못 맞추거나 클래스나 필드에 사용한 경우 컴파일할 수 없습니다.
public class Sample {
//
@Test
public static void mi(){}
public static void m2(){}
// ,
@Test
public static void m3() {
throw new RunTimeException("Boom");
}
// ,
@Test
public void m4(){}
// ,
@Test
public void m5(){
throw new RuntimeException("Crash");
}
}
위의 테스트에서 m3()와 m5()가 이상을 던졌고 m4()는 비합법적이며 m1()는 합법적이며 m2()는 테스트 도구에서 무시되었습니다.
메모는 프로그램의 의미에 영향을 주지 않으며 관련 프로그램에만 정보를 제공하지만 메모는 도구를 통해 특수 처리할 수 있습니다.
밤을 들다.
public class RunTests {
public static void main(String[] args) {
int tests = 0;
int passed = 0;
Class testClass = Class.forName(args[0]);
for(Method m : testClass.getDeclaredMethod()) {
if(m.isAnnotationPresent(Test.Class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch(InvocationTargetException w) {
Throwable exc = w.getClass();
System.out.println(m + " failed: " + exc);
} catch(Exception e) {
System.out.println("Invalid @Test: " + e);
}
}
}
System.out.printf("Passed: %d,Failed: %d%n", passed,tests - passed);
}
}
위 코드는 명령줄에 클래스 이름(예를 들어 Sample)을 입력하고 반사하여 이 클래스에 주석된 모든 방법을 찾고 이 방법을 실행한다. 방법이 실행될 때 이상이 나오면 Invocation Target Exception 이상에 봉하여 출력을 출력한다.
두 번째catch는Test 주해의 잘못된 사용법을 포획하는 것을 책임진다. 예를 들어 비정상적인 방법에Test를 주해했고, 파라미터를 주는 방법에Test를 주해했다.
분명히 Sample 클래스를 테스트하면 방법 m3()과 m5()는 Invocation Target Exception 이상에 의해 포착되고 방법 m1()만 테스트할 수 있으며 m4()는 두 번째 catch, 즉 Exception에 포착된다.
이상을 던질 때만 성공하는 테스트에 지원을 추가합니다.이를 위해 카운티의 메모 유형을 사용자 정의해야 합니다.
@RententionPolicy.RUNTIME
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class extends Exception> value();
}
이 사용자 정의 주석의 매개 변수 형식은
Class extends Exception>
인데, 이 어댑터 형식은 의심할 여지없이 매우 까다롭다."Exception의 클래스를 확장하는 Class 객체"라는 뜻으로, 이 메모를 사용하는 사용자가 예외 유형을 지정할 수 있도록 허용합니다. 이 사용법은 제한된 유형 토큰의 예입니다. 다음은 메모를 적용합니다.public class Sample {
@ExceptionTest(ArithmeticException.class)
public static void m1() {
int i = 0;
i = i / i;
}
@ExceptionTest(ArithmeticException.class)
public static void m2() {
int[] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmeticException.class)
public static void m3() {
}
}
테스트 클래스를 수정하려면 다음과 같이 하십시오.
if(m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.println(Test %s failed: no exception%n",m);
}
catch (InvocationTargetException: wrappedEx) {
Throwable exc = wrappedEx.getCause();
Class extends Exception> excType = m.getAnnotation(ExceptionTest.class).value();
if(excType.isInstance(exc)) {
passed++;
}else {
System.out.printf("Test %s failed: expected %s, got %s%n",m,excType.getName(),exc);
}
} catch(Exception exc) {
System.out.println("INVALID @Test: " + m);
}
}
이 코드는 주석 파라미터의 값을 추출하여 이 테스트에서 나온 이상이 정확한 유형인지 확인합니다.
제 36 조: Override 주해 를 꾸준히 사용 하다
JDK 1.5 추가 주석, 클래스 라이브러리에도 몇 가지 주석 유형이 추가되었습니다.가장 자주 사용하는 주석은 Override 형식과 다릅니다. 이 주석은 방법 성명에만 사용할 수 있습니다. 비고 거리를 나타내는 방법 성명은 슈퍼클래스의 성명을 덮어씁니다.
이 주해를 꾸준히 사용하면 일련의 불법 오류를 피할 수 있다.다음 클래스 Bigram은 이중 문자 그룹 또는 정렬된 문자 쌍을 나타냅니다.
public class Bigram {
private final char first;
private final char second;
public Bigram(char first,char second) {
this.first = first;
this.second = second;
}
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
public int hashCode() {
return 31 * first + second;
}
public static void main(String[] args) {
Set s = new HashSet();
for(int i = 0; i < 10 ; ++i) {
for(char ch = 'a';ch <= 'z'; ++ch) {
s.add(new Bigram(ch,ch));
}
}
System.out.println(s.size());
}
}
위의 방법이 기대한 결과는'aa'에서'zz'까지의 문자 순환을 얻었고 Set는 최종적으로 26개의 요소를 가지고 있다.그러나 Set 컬렉션에는 최종적으로 260개의 요소가 저장됩니다.이것은 실제 취지와 서로 어긋난다.왜냐하면 이퀄스를 다시 쓰는 방법이 없기 때문이다.equals 방법을 다시 불러왔습니다. 실제 프로그램은 실행 중에 이 다시 불러오는 equals 방법을 사용하지 않고 기본 클래스의 equals 방법을 호출합니다. 기본 클래스의 대상 지식은 대상의 주소를 비교하여 삽입하고자 하는 요소가 Set 용기의 요소와 같은지 아닌지를 판단합니다. 같지 않으면 삽입합니다. 새 new의 대상마다 주소가 다릅니다.그래서 새 new 대상들만 해시셋 용기에 삽입됩니다.
다행히도 컴파일러는 이 오류를 발견하는 데 도움을 줄 수 있었다.그러나 다시 불러오는 것이 아니라 컴파일러에게 덮어쓰는 방법을 알려줘야 한다.이를 위해서는 주석 @Override 태그 Bigram이 필요합니다.equals 방법:
@Override
public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
컴파일한 후에 컴파일러가 잘못 보고한 것을 발견하였다.수정:
@Override
public boolean equals(Object o) {
if(!(o instanceof Bigram)) {
return false;
}
Bigram b = (Bigram)o;
return b.first == first && b.second == second;
}
따라서 하위 클래스에서 수퍼 클래스를 덮어쓰려면 Override 메모를 사용해야 합니다.
제37조: 표지 인터페이스로 유형 정의
태그 인터페이스는 방법 설명을 포함하지 않은 인터페이스입니다. 이 인터페이스는 클래스가 특정한 속성을 가진 인터페이스를 실현했다는 것을 가리킬 뿐입니다. 예를 들어 Serializable 인터페이스를 실현한 클래스는 이 클래스가 Object OutputStream에 쓸 수 있음을 나타냅니다.
표지 인터페이스가 주석보다 낫기 때문에 그것들은 더욱 정확하게 잠길 수 있다.콜아웃 유형이 @Target(ElementType.TYPE) 선언을 사용하는 경우 클래스나 인터페이스에 적용할 수 있습니다.특수 인터페이스의 구현에만 적용되는 태그가 있다면, 태그 인터페이스로 정의하면 유일한 인터페이스를 적용 가능한 인터페이스로 확장할 수 있다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
로그'메타프로그램 루비 버전 2'3장 읽기동적 방법 Object#send 호출 방법은 약간 메모와 Object#send obj.send(:my_method, 3) Object#send를 사용하면 어떤 방법으로든 호출할 수 있습니다. privete 방법을 호...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.