Notepad


  • 홈

  • 아카이브

  • 태그

[Effective Java] 인터페이스는 자료형을 정의할 때만 사용하라

작성일 2018-12-19 | In Effective Java |

인터페이스 사용의 부적절한 예

인터페이스는 인스턴스를 만들지는 못하지만 인터페이스 자료형으로 해당하는 인터페이스를 구현하고 있는 클래스는 모두 참조가 가능하다. 이 외의 다른 목적으로 사용은 바람직하지 않다. 그 예로 상수 인터페이스가 있다.

// 상수 인터페이스 안티패턴 - 사용하지 말 것!
public interface PhysicalConstants {
    // 아보가드로 수(1 / mod)
    static final double AVOGADROS_NUMBER = 6.02214199e23;
    
    // 볼쯔만 상수 (j / k)
    static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    
    // 전자 질량 (kg)
    static final double ELECTRON_MASS = 9.10938188e-31;
}

클래스가 어떤 상수를 사용한다는 것은 구현의 세부 사항이고 클라이언트가 이를 알아둘 필요가 없다. 클라이언트를 혼란 시킬것이다.

이를 해결할 방법이 있다. 상수가 해당 클래스에 강하게 연결 되어 있을 경우 그 상수들을 클래스에 추가 시킨다. Integer 클래스에 MAX_VALUE, MIN_VALUE 처럼 말이다.

public class PhysicalConstants {
    private PhysicalConstants() {} // 객체의 생성을 막음
    
    public static final double AVOGADROS_NUMBER = 6.02214199e23;
    public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    public static final double ELECTRON_MASS = 9.10938188e-31; 
}
더 읽어보기 »

[Effective Java] 추상 클래스 대신 인터페이스를 사용하라

작성일 2018-12-11 | In Effective Java |

추상 클래스와 인터페이스

추상 클래스는 메소드의 구현부가 포함 될 수 있지만 인터페이스는 그렇지 않다. 좀 더 큰 차이는 추상 클래스는 구현하기 위해 상속을 필요로 하지만 인터페이스는 그렇지 않기 때문에 다중상속을 만들어 낼 수 있다.

믹스인(Mixin)

인터페이스는 믹스인을 정의하는 데 이상적이다. 믹스인이란 이미 사용중이던 클래스에 쉽게 붙여서 기능을 확장하는 것을 말한다. 예를 들어 가수를 표현하는 인터페이스와 작곡가를 표현하는 인터페이스가 있다고 하자.

public interface Singer {
    AudioClip sing(Song s);
}

public interface Songwriter {
    Song compose(boolean hit);
}

그런데 가수 가운데는 작곡가인 사람도 있다. 추상 클래스 대신 인터페이스를 사용해 자료형을 만들었으므로, 아무런 문법적 문제없이 Singer와 Songwriter를 동시에 구현하는 클래스를 만들 수 있다.

public interface SingerSongwriter extends Singer, Songwriter {
    AudioClip strum();
    void actSensitive();
}

추상 클래스와 인터페이스의 결합

추상 클래스와 인터페이스를 같이 사용하면 두개의 장점 모두 사용할 수 있다. 인터페이스는 자료혀을 정의하고 구현하는 일은 골격 구현 클래스에게 맡기면 된다.

// 골격 구현 위에서 만들어진 완전한 List 구현
static List<Integer> intArrayAsList(final int[] a) {
    if (a == null) {
        throw new NullPointerException();
    }
    
    return new AbstractList<Integer>() {
        public Integer get(int i) {
            return a[i];
        }
        
        @Override
        public Integer set(int i, Integer val) {
            int oldVal = a[i];
            a[i] = val; // auto unboxing
            return oldVal; // auto boxing
        }
        
        public int size() {
            return a.length;
        }
    }
}

이미 존재하는 List 구현체가 프로그래머를 위해 어떤 일을 할 수 있ㅇㄹ지 알고 싶은 사람에게 아주 유용할 것이다. (위 코드는 성능면에서는 좋지 않다.) 골격 구현 클래스를 만드는 작업은 상대적으로 멍청해 보일지도 모르겠지만 단순한 작업이다. 우선 인터페이스를 살펴보고 다른 메서드를 구현할 때 쓸 기본 메서드들을 추려 낸다. 그리고 이 기본 메서드들을은 골격 구현 클래스에 추상 메서드로 선언하고, 나머지 메서드들은 실제로 구현해서 넣는다.

// 골격 구현
public abstract class AbstractMapEntry<K, V> implements Map.Entry<K, V> {
    // 기본 메서드
    public abstract K getKey();
    public abstract V getValue();
    
    // 변경 가능 맵에 들어갈 Entry는 이 메서드를 재정의해야 한다.
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }
    
    // Map.Entry.equals의 일반 규약 구현
    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        
        if (!(o instanceof Map.Entry)) {
            return false;
        }
        
        Map.Entry<?, ?> arg = (Map.Entry) o;
        
        return equals(getKey(), arg.getKey()) && equals(getValue(), arg.getValue());
    }
    
    private static boolean equals(Object o1, Object o2) {
        return (o1 == null) ? o2 == null : o1.equals(o2);
    }
    
    // Map.Entry.hashCode의 일반 규약 구현
    @Override
    public int hashCode() {
        return hashCode(getKey()) ^ hashCode(getValue());
    }
    
    private static int hashCode(Object obj) {
        return (obj == null) ? 0 : obj.hashCode();
    }
}
더 읽어보기 »

[Effective Java] 계승을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 계승을 금지하라

작성일 2018-07-19 | In Effective Java |

상속을 위해서는 어떤 문서를 만들어야 할까?

오버라이딩이 되는 메소드에 대해서는 내부적으로 어떻게 사용하지 문서에 남겨야한다. 좀 더 세부적으로 어떤 메소드를 호출하고 어떤 결과를 반환하는지를 명시하면 좋다.

Java Doc Example

public boolean remove(Object o)
Removes a single instance of the specified element from this collection, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call).
This implementation iterates over the collection looking for the specified element. If it finds the element, it removes the element from the collection using the iterator's remove method.

Note that this implementation throws an UnsupportedOperationException if the iterator returned by this collection's iterator method does not implement the remove method and this collection contains the specified object.

Specified by:
remove in interface Collection<E>
Parameters:
o - element to be removed from this collection, if present
Returns:
true if an element was removed as a result of this call
Throws:
UnsupportedOperationException - if the remove operation is not supported by this collection
ClassCastException - if the type of the specified element is incompatible with this collection (optional)
NullPointerException - if the specified element is null and this collection does not permit null elements (optional)

java8 버전에 있는 java.util.AbstractCollection에 remove 메소드이다. 메소드에 설명을 보면 어떤 예외가 발생할 수 있는지에 대한 명시가 제대로 적혀있다.

더 읽어보기 »

[Effective Java] 계승하는 대신 구성하라

작성일 2018-07-18 | In Effective Java |

계승

상속을 하게 되면 코드를 재활용하고 다형성을 사용할 수 있다는 점은 좋은 이점이지만 단점이 더 많다.

어떤 단점?

public class InstrumentedHashSet<E> extends HashSet<E> {
    private int addCount = 0;

    public InstrumentedHashSet() {}

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}

다음과 같이 HashSet 클래스를 상속받아 데이터가 몇번 삽입 되는지 확인하는 코드가 있다. add를 사용하여 데이터를 삽입하면 정상적으로 작동하지만 다음과 같은 코드는 문제가 발생 하게 된다.

InstrumentedHashSet<Integer> i = new InstrumentedHashSet<>();
i.addAll(Arrays.asList(1, 2, 3));

실행하고 결과를 보면 6이라는 결과 값이 나오게 된다. 왜 그러냐면 HashSet의 addAll은 add를 통해서 데이터를 삽입된다. 처음 addAll을 호출하고 addCount += c.size();를 통해 3이 증가하고 상위 클래스의 addAll를 호출해 데이터를 삽입하면 다시 add를 호출하게 되어서 addCount++ 때문에 또 증가되기 때문이다. 이처럼 상위 클래스가 어떻게 작성되었는지 정확하게 모르면 어이없는 실수를 범하게 될것이다. 또 다른 문제는 상위 클래스가 변경되면 그에 따라 하위 클래스가 제대로 작동할지 보장되지 않는다.

그럼 어떻게?

새롭게 작성될 클래스에 기존 클래스를 private 멤버로 두는 것이다. 이런 설계 기법을 구성이라고 부르는데 기존 클래스가 새 클래스의 일부가 되기 때문이다.

// 계승 대신 구성하는 클래스
public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() {
        return addCount;
    }
}

// 재사용 가능한 전달 클래스
public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s;

    public ForwardingSet(Set<E> s) {
        this.s = s;
    }

    @Override
    public int size() {
        return s.size();
    }

    @Override
    public boolean isEmpty() {
        return s.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return s.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return s.iterator();
    }

    @Override
    public Object[] toArray() {
        return s.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return s.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return s.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return s.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return s.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return s.addAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return s.retainAll(c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return s.removeAll(c);
    }

    @Override
    public void clear() {
        s.clear();
    }
}

계승을 사용하게 되면 어떤 한 클래스에서만 적용이 가능하고, 상위 클래스 생성자 마다 별도의 생성자를 만들어야 한다. 하지만 포장 클래스 기법을 사용하면 어떠한 Set 클래스에 대해 구현할 수 있고 이미 있는 생성자도 그대로 사용할 수 있다.

그럼 상속은 언제?

옛날 부터 수 없이 들어 왔던 말이 있다 상속을 하기 위한 최선의 조건은 “IS-A”조건이 이다. 이 조건을 다시 한번 생각하면서 상속을 해야할지 포장을 해야할지 생각해야한다.

더 읽어보기 »

[Effective Java] 변경 가능성을 최소화하라

작성일 2018-07-17 | In Effective Java |

변경 불가능 클래스

변경 불가능 클래스는 말 그대로 내부의 정보를 수정할 수 없는 클래스이다. 객체 내부의 정보는 객체가 생성될 때 주어진 것이며 객체가 만들어 지고 없어질 때까지 변경되지 않는다. 이러한 클래스를 만드는 이유는 다양하다. 변경 가능 클래스보다 설계 및 구현 하기가 쉽고, 오류가 적으며, 사용하기도 쉽다.

변경 불가능 클래스를 만들기 위한 규칙

  1. 객체 상태를 변경하는 메소드를 제공하지 않는다.
  2. 상속하지 못하게 final로 클래스를 만든다. 상속을 받아서 클래스를 정의하게 되면 객체 상태가 변경된 것처럼 동작할 수 있기 때문이다.
  3. 모든 필드를 final로 선언한다. 시스템이 강제하는 형태대로 프로그래머의 의도가 분명히 드러나고 새로 생성된 객체에 대한 참조가 동기화 없이 다른 스레드로 전달 되어도 안전하기 때문이다.
  4. 모든 필드를 private로 선언한다. 이 객체를 사용하는 다른 사용자가 필드를 변경할 수 없기 때문이다.
  5. 변경 가능 컴포넌트에 대한 독점적 접근권을 보장한다. 클래스에 포함된 변경 가능 객체에 대한 참조를 클라이언트는 획득할 수 없어야 한다.

변경 불가능 클래스 예시

public final class Complex {
    // 많이 사용하는 것들은 미리 만들어서 사용하는 것이 좋다
    public static final Complex ZERO = new Complex(0, 0);
    public static final Complex ONE = new Complex(1, 0);
    public static final Complex I = new Complex(0, 1);

    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    // 대응되는 수정자가 없는 접근자들
    public double realPart() {
        return re;
    }

    public double imginaryPart() {
        return im;
    }

    public Complex add(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }

    public Complex subtract(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex multiply(Complex c) {
        return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
    }

    public Complex divide(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof Complex)) {
            return false;
        }

        Complex c = (Complex) o;

        return Double.compare(re, c.re) == 0 && Double.compare(im, c.im) == 0;
    }

    @Override
    public int hashCode() {
        int result = 17 + hashDouble(re);
        result = 31 * result + hashDouble(im);
        return result;
    }

    private static int hashDouble(double val) {
        long longBits = Double.doubleToLongBits(val);
        return (int) (longBits ^ (longBits >>> 32));
    }

    @Override
    public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
  • 간단하게 작성된 것이며 실제로 사용하기에는 부적합할 것이다.

단점

유일한 단점은 만약 반환해야 한다면 새로운 객체를 만들어서 반환해햐 한다. 이러면 많은 메모리가 낭비 될수도 있다.

더 읽어보기 »

[Android] Dalvik과 ART

작성일 2018-04-02 | In Android |

Dalvik

  • 구글의 댄 본스타인이 만든 VM
  • Java는 JVM위에서 실행되고, Android Dalvik위에서 실행된다.
  • Android 5.0 이하에서만 사용되고 그 이상 버전은 ART로 교체되었다.
  • Dalvik에서는 JIT(Just In Time)컴파일러를 사용하였다.

JIT

  • 처음 실행될 때 한번 컴파일하고 그 뒤에 자주 쓰이는 코드는 캐시에 저장하고 가져다 쓴다. 인터프리터의 느린 실행 속도를 개선할 수 있었다.
  • 하지만 처음 컴파일 단계에서 모든 코드를 메모리에 올려두고 작업해야 하기 때문에 메모리 소비가 많다

ART

  • Dalvik의 한계점을 개선한 새로운 런타임
  • 앱을 설치할 때 완전히 네이티브 코드로 변환하여 설치한다.
  • JIT대신 AOT 컴파일을 사용한다.

AOT

  • AOT는 중간 언어로 배포된 후 플랫폼에 맞계 기계어로 미리 번역하는 방식
  • JIT는 실행 시점에서 컴파일을 수행하는데 추가적인 메모리와 CPU 사이클이 필요한데, 이를 보완하는 방법으로 사용될수 있다.
  • 설치 속도가 Dalvik보다 느리고 공간도 많이 차이한다.
  • 처음 설치할 때 컴파일 작업을 다 해놓기 때문에 생기는 근본적인 한계
  • Android 5.0이하에서 개발된 어플들이 제대로 동작하지 못할 때도 있다.
더 읽어보기 »

[pattern] Observer Pattern

작성일 2018-04-02 | In Pattern |

Observer Pattern

어떤 클래스에 변화가 있을 때 변화를 알려주는 패턴

더 읽어보기 »

[Android] JNI 사용하기

작성일 2018-03-31 | In Android |

예제 코드

  • https://github.com/KimBoWoon/Android-Practice/tree/master/JNI

오류 수정

  • jar 파일을 만들 때 gradle 명령어를 조심히 사용하자
  • jar 파일을 만들고 나서 확인해보니 libs.com.android.~ 로 시작해서 클래스를 못찾은거 같다
  • 인터넷에서 본 exportJar 라는 gradle task가 안된다.
  • build/intermediates/bundle라는 폴더가 생성이 안되서 command line으로 실행

JNI(Java Native Interface) 란

  • 자바가 다른 언어들과 상호작용을 할 수 있도록 도와주는 인터페이스
  • 자바로 구현하기에는 성능이 많이 떨어진다고 생각되면 JNI를 사용해 구현할 수 있다
  • 다른 하드웨어를 제어하기위해 사용하기도 함
  • C/C++를 사용

Example

  1. 안드로이드 Empty Activity 프로젝트를 만든다.
  2. sdk tools에서 CMake와 LLDB, NDK를 설치한다.

    그림

  3. File -> New Module를 선택한다.

    그림

  4. android library를 선택한다.

    그림

  5. android library를 생성하고 난 뒤에 File -> Setting에 Tool -> ExternalTool을 다음과 같이 설정한다.

    그림

    • javah
        program : javah.exe
        argument : -classpath "$Classpath$" -v -jni $FileClass$
        working directory : $ProjectFileDir$\<라이브러리 이름>\src\main\jni
      
    • NDK-Build
        program : ndk-build.cmd
        working directory : $ProjectFileDir$\<라이브러리 이름>\src\main
      
    • NDK-Clean
        program : ndk-build.cmd
        argument : clean
        working directory : $ProjectFileDir$\<라이브러리 이름>\src\main
      
    • javah.exe : java class 대로 Header File을 만들어준다. 설치된 jdk에 bin에 존재한다.
    • ndk-build.cmd : C/C++로 작성된 코드를 인터페이스에 맞게 build 시켜준다. 설치된 android-sdk에 ndk-bundle에 존재한다.(2번에 NDK를 설치하면 생성됨)
    • 각각의 환경변수가 설정되어 있지 않으면 절대경로를 넣어 설정한다.
  6. src -> main 아래에 jni폴더를 생성한다. NDK-Build를 진행하게 되면 여기에 *.so파일이 생성된다.
  7. 이제 자신의 패키지명 아래에 java class를 생성한다. class에는 메소드만 선언해주고 나중에 javah.exe를 사용하면 jni폴더 아래에 헤더 파일이 생성된다.

    그림

    • System.loadLibrary()은 전달인자와 같은 이름의 모듈을 로드시킨다는 의미
    • 나중에 mk 파일에서 LOCAL_MODULE의 이름을 적어주면 된다.
  8. class를 만들고 난 뒤에 javah.exe를 실행한다. 단, 먼저 빌드가 되어 있어야 순조롭게 진행된다.

    그림

    그림

    • 실패 했을 때

    그림

    • 성공 했을 때
  9. 지금은 참조를 제대로 못하고 있지만 Link를 해주면 정상적으로 참조를 하게 된다.

    그림

  10. Android.mk와 Application.mk 파일을 만들어 준다.
    • LOCAL_MODULE := (원하는 module 이름)
    • FILES := (나중에 만들 cpp file 이름)
    • APP_ABI := all : android cpu가 지원하는 모든 ABI에 대해 *.so 파일을 만든다.

    그림

  11. Link C++ Project With Gradle을 진행한다.

    그림

    그림

    • 위와 같이 build.gradle에 mk 파일의 경로가 추가된걸 볼 수 있다.
  12. 이제 jni 폴더 아래에 cpp 파일을 만들어 원하는대로 구현한다.

    그림

    그림

  13. ndk-build를 실행한다. 실행하면 다음과 같이 모든 ABI에 대해 *.so 파일이 만들어진것을 볼수있다.

    그림

    그림

  14. 만들어둔 java class에 대해서는 jar 파일을 만들어야 연동이 가능하다. jar파일이 없으면 해당하는 메소드를 찾을 수 없다고 오류가 발생한다. build.gradle에 다음과 같은 task를 넣는다.

    그림

  15. gradle 창에 other으로 가면 exportJar가 생성된걸 볼 수 있고 실행하면 jar파일이 만들어진것을 볼 수 있다.

    그림

  16. 이제 이걸 사용하려면 원하는 프로젝트에 app -> libs에 jar 파일을 넣고 src -> main -> jniLibs에 <abi name>/*.so를 넣고 build.gradle에 implementation files('libs/(만들어둔 jar 파일 이름))을 넣으면 사용이 가능하다.

실행 결과

그림

  • x와 y를 더한 값을 반환하는 JNI Library를 만들었고 Toast로 결과값을 확인

참조

  • https://developer.android.com/studio/projects/android-library.html?hl=ko
  • http://duzi077.tistory.com/134
더 읽어보기 »

[Effective Java] public 클래스 안에는 public 필드를 두지 말고 접근자 메소드를 사용하라

작성일 2017-12-19 | In Effective Java |

접근자 메소드

어느 클래스는 필드만 가지고 있는 클래스가 있다. 이 클래스에는 접근자 메소드가 있어야 여러모로 좋다. 캡슐화의 이점을 누릴 수 없고 API를 변경하지 않고서는 내부 표현을 변경할 수도 없다. 또한 불변식도 강제할 수 없고, 필드를 사용하는 순간에 어떤 종작이 실행되록 만들 수도 없다. 그래서 다음과 같이 접근자 메소드를 가져야한다.

public class Point {
	private double x;
    private double y;
    
    public Point(double x, double y) {
    	this.x = x;
        this.y = y;
    }
    
    public getX() {
    	return x;
    }
    
    public getY() {
    	return y;
    }
    
    public setX(double x) {
    	this.x = x;
    }
    
    public setY(double y) {
    	this.y = y;
    }
}

이렇게 만들어야 클래스 내부표현을 자유로이 수정할 수 있게 된다.

더 읽어보기 »

[Effective Java] 클래스와 멤버의 접근 권한은 최소화하라

작성일 2017-12-15 | In Effective Java |

정보은닉과 캡슐화

잘 설계된 모듈과 그렇지 못한 모듈을 구별하는 방법 중에 하나는 모듈 내부의 데이터를 비롯한 여러 구현 세부사항을 다른 모듈에 잘 감추느냐의 여부다. 잘 설계된 모듈을 모두 API 뒤편으로 감춘다. 그리고 모듈은 API를 통해서만 서로 통신한다. 또한 내부적으로는 무슨 짓을 하는지는 신경 쓰지 않는다. 이 개념이 바로 정보은닉과 캡슐화이다. 정보은닉은 여러모로 중요하다. 대부분이 정보은닉이 시스템을 구성하는 모듈 사이의 의존성을 낮춰서 각자 개별적으로 개발하고 시험하고 최적화하고 이해하고 변경할 수 있도록 한다는 사실에 기초한다. 그렇게 되면 병렬적으로 개발할 수 있기 때문에 개발 속도가 올라간다. 이러한 장점을 지키위해서 행해야 하는것은 정말 간단하다. 각 클래스의 멤버는 가능한 한 접근 불가능하도록 만들면 된다.

java 접근자

  • private : 최상위 레벨 클래스 내부에서만 접근 가능
  • package-private : 간은 패키지 내의 아무 클래스나 사용 가능, 기본 접근 권한으로 알려져 있다.
  • protected : 해당하는 클래스와 이 클래스를 상속받은 클래스들 내에서 사용이 가능하다.
  • public : 언제 어디서는 사용이 가능하다.

public은 항상 조심해야한다. 스레드에도 안전하지 않고 누구나 접근이 가능해서 값이 변경되기 때문이다. 또한 길이가 0이 아닌 배열은 언제나 변경 가능하므로, public static final 배열 필드를 두거나, 배열 필드를 반환하는 접근자를 정의하면 안 된다. 클라이언트가 배열 내용을 변경할 수 있기 때문에 보안에 문제가 생긴다. 따라서 다음과 같이 사용하는게 좋다.

public으로 선언되었던 배열은 private로 바꾸고, 변경이 불가능한 public 리스트를 하나 만드는 것이다.

private static final Integer[] PRIVATE_VALUES = {};
public static final List<Integer> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

배열은 private 으로 선언하고, 해당 배열을 복사해서 반환하는 public 메소드를 하나 추가하는 것이다.

private static final Integer[] PRIVATE_VALUES = {};
public static final Integer[] values() {
    return PRIVATE_VALUES.clone();
}

두 방법 가운데 하나를 택할 때는 클라이언트가 어떤 작업을 하길 원하는지 따져야 한다.

더 읽어보기 »
1 2 3 4
Kim BoWoon

Kim BoWoon

https://kimbowoon.github.io/

38 포스트
3 카테고리
RSS
© 2020 Kim BoWoon
Powered by Jekyll
Theme - NexT.Muse