final
키워드를 탑 레벨 클래스에 접근 제한자 뒤에 붙여 사용하자final
키워드를 붙여서 사용하는 개념이라고 생각하고 들어가자.[핵심 정리]
불변 클래스를 사용하면 여러 이점이 있다 단점이라곤 잠재적 성능 저하라는 문제뿐, 그럼에도 불구하고 우리는 모든 클래스를 불변으로 만들어서 사용할 순 없다. 그렇다면 우리는 변경 가능한 부분을 최소화시켜야 한다. 별다른 이유가 없다면 멤버의 필드는private final
형태로 작성하자
public final class ImmutableClass {
// 기본 불변 객체를 만드는 틀입니다.
// final 키워드를 해당 클래스에 붙여주면 해당 객체는 불변 객체가 됩니다.
}
// final 키워드로 불변 객체를 만들자.
public final class Complex {
private final double re;
private final double im;
private Complex(double re, double im){
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im){
return new Complex(re, im);
}
public double realPart() {
return re;
}
public double imaginaryPart(){
return im;
}
public Complex plus(Complex c){
return new Complex(re+c.re, im+c.im);
}
public Complex minus(Complex c){
return new Complex(re-c.re, im-c.im);
}
public Complex times(Complex c){
return new Complex(re*c.re - im*c.im, re*c.im + im*c.re);
}
public Complex divdedBy(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(c.re, re) == 0 && Double.compare(c.im, im) == 0;
}
@Override
public int hashCode(){
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override
public String toString(){
return "("+re+" + "+im+"i)";
}
}
realPart()
imaginaryPart()
package test;
import java.util.Hashtable;
import java.util.Map;
public final class Person {
private static final Map<String, Person> personCache = new Hashtable<>();
private final String name;
private final int age;
//private 생성자로 외부에서 해당 객체를 직접 생성하는 것을 막는다.
private Person(String name, int age) {
this.name = name;
this.age = age;
}
//정적 팩토리 메서드를 사용해서 객체 생성에 관여한다.
public static Person createPerson(String name, int age){
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("이름은 필수입니다.");
}
if (age < 0) {
throw new IllegalArgumentException("나이는 음수일 수 없습니다.");
}
String key = name + age;
return personCache.computeIfAbsent(key, k -> new Person(name, age));
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public final class Person
: 불변 객체private Person(String name, int age)
: 외부에서 해당 객체를 직접 생성하는 행위를 막아줌public static Person createPerson(String name, int age)
: 정적 팩토리 메서드를 사용해 해당 이름과 나이를 가진 사람이 있는지 살펴보고(캐시) 없으면 해당 사용자를 캐싱에 넣어두고 해당 Person을 생성해서 돌려준다.참조를 변경할 수 없도록
하는 것이 final 키워드를 사용한 이유이다.public final class Immutable {
}
[final method - parent]
public class Parent {
public final void immutableMethod(){
System.out.println(" 부모 메소드 변경 금지 !! ");
}
}
[final method - child]
public class ChildA extends Parent {
}
[overriding 불가]
public class Immutable {
private final int age;
}
- 불변성 유지: 참조 변경을 막는 것은 불변성(immutability)을 강제하는 일환입니다. 불변 객체는 한 번 생성된 후에 내부 상태가 변경되지 않습니다. 이는 객체를 예측 가능하게 만들고, 다중 스레드 환경에서도 안전하게 사용할 수 있도록 도와줍니다.
- 부작용 방지: 객체의 상태를 변경하면서 발생하는 부작용(side effect)을 방지하기 위해 참조 변경을 막습니다. 부작용이 발생하면 코드의 동작을 예측하기 어려워집니다. 특히 큰 시스템에서는 부작용이 코드의 복잡성을 증가시키고 유지보수를 어렵게 만들 수 있습니다.
- 다중 스레드 환경에서 안전성: 여러 스레드가 동시에 객체의 상태를 변경하면 예상치 못한 결과가 발생할 수 있습니다. 참조 변경을 막음으로써 객체가 공유될 때 다중 스레드 환경에서 더 안전하게 사용할 수 있습니다.
- 프로그램의 예측 가능성 향상: 참조 변경이 제한되면 프로그램의 동작이 더 예측 가능해집니다. 객체의 상태가 변하지 않는다면 해당 객체를 사용하는 코드에서 예상치 못한 결과가 발생할 가능성이 줄어듭니다.
- 디버깅 용이성: 참조 변경을 최소화하면 코드를 디버깅하기가 더 쉬워집니다. 상태가 변하지 않으면 특정 시점에서 객체의 상태를 파악하기가 더 용이하며, 버그를 찾고 수정하기가 더 간단해집니다.
- 설계의 단순화: 불변 객체와 참조 변경 제한은 프로그램의 설계를 단순화하고 모듈화하는 데 도움이 됩니다. 상태가 변하지 않는 객체는 일종의 "값"처럼 동작하여 프로그램의 전반적인 복잡성을 낮출 수 있습니다.
요약하면, 참조 변경을 막는 것은 예측 가능성, 안전성, 디버깅 용이성, 코드의 간결성 등을 향상시키기 위한 목적이 있습니다. 특히 불변 객체를 사용하면 객체의 상태를 변경하지 않으므로써 이러한 이점들을 얻을 수 있습니다.