Java에서 변경 불가능한 클래스를 final로 선언하는 이유는 무엇입니까?


83

Java에서 클래스를 변경 불가능 하게 만들 려면 다음을 수행해야합니다.

  1. 세터를 제공하지 마십시오.
  2. 모든 필드를 비공개로 표시
  3. 수업 최종 결정

3 단계가 필요한 이유는 무엇입니까? 수업에 왜 표시를해야 final합니까?


java.math.BigInteger클래스는 예이며 값은 변경할 수 없지만 최종 값은 아닙니다.
Nandkumar Tekale 2012 년

@Nandkumar가있는 BigInteger경우 변경 불가능한지 여부를 알 수 없습니다. 엉망인 디자인입니다. / java.io.File더 흥미로운 예입니다.
Tom Hawtin-tackline 2009 년

1
하위 클래스가 메서드를 재정의하도록 허용하지 않음-가장 간단한 방법은 클래스를 최종 클래스로 선언하는 것입니다. 보다 정교한 접근 방식은 생성자를 비공개로 만들고 팩토리 메서드에서 인스턴스를 생성하는 것입니다.-from- docs.oracle.com
Raúl

1
@mkobit File는 변경 불가능한 것으로 신뢰되어 TOCTOU (Time Of Check / Time Of Use) 공격으로 이어질 수 있기 때문에 흥미 롭습니다. 신뢰할 수있는 코드 File는에 허용 가능한 경로가 있는지 확인한 다음 해당 경로를 사용합니다. 서브 클래스 File는 확인과 사용 사이에 값을 변경할 수 있습니다.
Tom Hawtin-tackline apr

1
Make the class final또는 모든 하위 클래스가 변경 불가능한지 확인하십시오
O.Badr

답변:


138

클래스를 표시하지 않으면 final갑자기 변경 불가능 해 보이는 클래스를 실제로 변경 가능하게 만들 수 있습니다. 예를 들어 다음 코드를 고려하십시오.

public class Immutable {
     private final int value;

     public Immutable(int value) {
         this.value = value;
     }

     public int getValue() {
         return value;
     }
}

이제 다음을 수행한다고 가정합니다.

public class Mutable extends Immutable {
     private int realValue;

     public Mutable(int value) {
         super(value);

         realValue = value;
     }

     public int getValue() {
         return realValue;
     }
     public void setValue(int newValue) {
         realValue = newValue;
     }

    public static void main(String[] arg){
        Mutable obj = new Mutable(4);
        Immutable immObj = (Immutable)obj;              
        System.out.println(immObj.getValue());
        obj.setValue(8);
        System.out.println(immObj.getValue());
    }
}

Mutable하위 클래스 에서 내 하위 클래스에 getValue선언 된 변경 가능한 새 필드를 읽기 위해 의 동작을 재정의했습니다 . 결과적으로 처음에는 변경 불가능 해 보이는 클래스가 실제로 변경 불가능하지 않습니다. 객체가 예상되는 Mutable곳에이 객체 를 전달할 수 있으며 Immutable, 객체가 진정으로 불변이라고 가정하여 코드에 매우 나쁜 일을 할 수 있습니다. 기본 클래스를 표시 final하면 이런 일이 발생하지 않습니다.

도움이 되었기를 바랍니다!


13
Mutable m = new Mutable (4); m.setValue (5); 여기에서 나는 Immutable 클래스 객체가 아닌 Mutable 클래스 객체를 가지고 놀고있다. 그래서 나는 여전히 왜 Immutable 클래스가 불변하지 않는지 혼란스러워한다
Anand

18
@ anand- Immutable인수를 받는 함수가 있다고 상상해보십시오 . Mutable그 함수에 객체를 전달할 수 있습니다 Mutable extends Immutable. 이 함수 내에서 개체가 변경 불가능하다고 생각하는 동안 함수가 실행될 때 값을 변경하는 보조 스레드가있을 수 있습니다. Mutable함수가 저장 하는 객체를 제공 한 다음 나중에 그 값을 외부에서 변경할 수도 있습니다. 즉, 함수가 값이 변경 불가능하다고 가정하면 변경 가능한 객체를 제공하고 나중에 변경할 수 있으므로 쉽게 깨질 수 있습니다. 말이 돼?
templatetypedef

6
@ anand- Mutable개체를 전달하는 메서드는 개체를 변경하지 않습니다. 문제는 해당 메서드가 실제로 그렇지 않은 경우 개체가 변경 불가능하다고 가정 할 수 있다는 것입니다. 예를 들어, 메서드는 객체가 변경 불가능하다고 생각하기 때문에 .NET Framework에서 키로 사용할 수 있다고 가정 할 수 있습니다 HashMap. 그런 다음을 전달 Mutable하고 객체가 키로 저장 될 때까지 기다린 다음 Mutable객체 를 변경하여 해당 함수를 중단 할 수 있습니다. 이제 HashMap개체와 관련된 키를 변경했기 때문에 검색 이 실패합니다. 말이 돼?
templatetypedef

3
@ templatetypedef- 예, 지금 얻었습니다. 굉장한 설명이었다고 말해야합니다. 그래도 이해하는 데 약간의 시간이 걸렸습니다
Anand

1
@ SAM- 맞습니다. 그러나 재정의 된 함수가 해당 변수를 다시 참조하지 않기 때문에 실제로는 중요하지 않습니다.
templatetypedef

47

많은 사람들이 믿는 것과는 달리 불변 클래스를 만들 필요 final없습니다 .

불변 클래스를 만들기위한 표준 인수 final 이렇게하지 않으면 하위 클래스가 변경 가능성을 추가하여 수퍼 클래스의 계약을 위반할 수 있다는 것입니다. 클래스의 클라이언트는 불변성을 가정하지만 그 아래에서 무언가가 변하면 놀라게 될 것입니다.

이 인수를 논리적 인 극단으로 가져 가면 모든 메서드를 만들어야합니다. final그렇지 않으면 하위 클래스가 수퍼 클래스의 계약을 준수하지 않는 방식으로 메서드를 재정의 할 수 있기 때문입니다. 대부분의 자바 프로그래머가 이것을 우스꽝스럽게 여기는 것은 흥미롭지 만, 불변 클래스는 final. 나는 그것이 일반적으로 Java 프로그래머가 불변성 개념에 완전히 익숙하지 않은 것과 관련이 있다고 생각 final하며 Java 에서 키워드 의 여러 의미와 관련된 일종의 모호한 사고와 관련이 있다고 생각 합니다.

수퍼 클래스의 계약을 준수하는 것은 컴파일러가 항상 강제 할 수있는 것이 아닙니다. 컴파일러는 계약의 특정 측면 (예 : 최소한의 메서드 및 해당 유형 서명 집합)을 적용 할 수 있지만 일반적인 계약에는 컴파일러가 적용 할 수없는 많은 부분이 있습니다.

불변성은 클래스 계약의 일부입니다. 대부분의 Java (일반적으로 OOP) 프로그래머는 계약을 다음과 관련하여 생각하는 경향이있는 반면, 클래스 (및 모든 하위 클래스)가 할 수없는 것을 말하고 있기 때문에 사람들이 더 익숙한 것 중 일부와는 약간 다릅니다. 수업이 할 수 있는 것이 아니라 할 수있는 것.

불변성은 또한 단일 메소드 이상에 영향을 미칩니다. 전체 인스턴스에 영향을 미칩니다. 그러나 이것은 Java 작업 방식 equals과 크게 다르지 않습니다 hashCode. 이 두 가지 방법에는 Object. 이 계약은 이러한 방법으로 할 수없는 일을 매우 신중하게 설명 합니다. 이 계약은 하위 클래스에서 더 구체적으로 만들어집니다. 계약 을 무시 equals하거나 hashCode위반하는 것은 매우 쉽습니다 . 실제로이 두 방법 중 하나만 다른 방법없이 재정의하면 계약을 위반할 가능성이 있습니다. 그래서해야 equals하고 hashCode선언 된 final에 .Object 이 문제를 방지하려면? 나는 대부분의 사람들이 그렇게해서는 안된다고 주장 할 것이라고 생각합니다. 마찬가지로 불변 클래스를 만들 필요는 없습니다.final

말했다 즉, 불변 여부 수업의 대부분은 아마 해야final. 효과적인 Java Second Edition 항목 17 : "상속을위한 설계 및 문서화"를 참조하십시오 .

따라서 3 단계의 올바른 버전은 다음과 같습니다. "클래스를 최종 클래스로 만들거나, 서브 클래스를 위해 디자인 할 때 모든 서브 클래스가 계속 변경 불가능해야한다는 것을 명확하게 문서화하십시오."


3
두 객체를 주어진 자바 "상하는"하지만 시행하지 않는 것을주의 그것의 가치, X그리고 Y의 가치 X.equals(Y)불변 것이다 (한로 XY같은 개체를 참조 계속). 해시 코드에 대해서도 비슷한 기대치를 가지고 있습니다. 아무도 컴파일러가 동등성 관계와 해시 코드의 불변성을 강제 할 것으로 기 대해서는 안된다는 것은 분명합니다 (단지 평범 할 수 없기 때문에). 나는 사람들이 유형의 다른 측면에 대해 시행 될 것으로 기대해야 할 이유가 없다고 생각합니다.
supercat 2012-09-11

1
또한 계약이 불변성을 지정하는 추상 유형을 갖는 것이 유용한 경우가 많이 있습니다. 예를 들어, 좌표 쌍이 주어지면를 반환하는 추상 ImmutableMatrix 유형이있을 수 있습니다 double. 하나 GeneralImmutableMatrix는 배열을 백업 저장소로 사용하는 a 를 파생시킬 수 있지만, 예 ImmutableDiagonalMatrix를 들어 단순히 대각선을 따라 항목 배열을 저장하는 항목을 가질 수도 있습니다 (X == y 인 경우 항목 X, Y를 읽으면 Arr [X]가 나오고 그렇지 않으면 0이됩니다). .
supercat

2
이 설명이 가장 좋습니다. 항상 불변 클래스를 만들면 final특히 확장 될 API를 디자인 할 때 유용성이 제한됩니다. 스레드 안전성을 위해 클래스를 가능한 한 변경 불가능하게 만들고 확장 가능하게 유지하는 것이 좋습니다. protected final대신 필드 를 만들 수 있습니다 private final. 그런 다음 불변성 및 스레드 안전성 보장을 준수하는 하위 클래스에 대한 계약 (문서에서)을 명시 적으로 설정합니다.
curioustechizen 2013 년

4
+1-Bloch가 인용 할 때까지 나는 당신과 함께 있습니다. 그렇게 편집증적일 필요는 없습니다. 코딩의 99 %는 협력 적입니다. 누군가가 정말로 무언가를 재정의해야한다면 큰 문제는 아닙니다. 99 %는 String 또는 Collection과 같은 핵심 API를 작성하지 않습니다. 불행히도 많은 Bloch의 조언은 이러한 종류의 사용 사례를 기반으로합니다. 대부분의 프로그래머, 특히 새로운 프로그래머에게는 도움이되지 않습니다.
중위

불변 클래스를 선호합니다 final. 그냥 때문에 equals그리고 hashcode내가 그렇게 할 수있는 유효한 이유가없는 나는, 불변 클래스의 같은 용납 할 수 의미하지 않는다 최종되지 않을 수 있으며,이 경우 나는 문서 또는 방어 라인으로 테스트를 사용할 수 있습니다.
O.Badr

21

전체 수업을 최종으로 표시하지 마십시오.

다른 답변 중 일부에 명시된 것처럼 불변 클래스를 확장하도록 허용하는 유효한 이유가 있으므로 클래스를 최종 클래스로 표시하는 것이 항상 좋은 생각은 아닙니다.

속성을 비공개 및 최종으로 표시하고 "계약"을 보호하려면 게터를 최종으로 표시하는 것이 좋습니다.

이런 식으로 클래스를 확장 할 수 있지만 (예, 변경 가능한 클래스에 의해서도 가능) 클래스의 변경 불가능한 측면은 보호됩니다. 속성은 비공개이며 액세스 할 수 없습니다. 이러한 속성에 대한 getter는 최종 속성이며 재정의 할 수 없습니다.

불변 클래스의 인스턴스를 사용하는 다른 코드는 전달되는 하위 클래스가 다른 측면에서 변경 가능하더라도 클래스의 불변 측면에 의존 할 수 있습니다. 물론 클래스의 인스턴스를 사용하기 때문에 이러한 다른 측면에 대해서도 알지 못할 것입니다.


1
차라리 불변의 모든 측면을 별도의 클래스에 캡슐화하고 구성을 통해 사용하고 싶습니다. 단일 코드 단위에서 변경 가능 상태와 불변 상태를 혼합하는 것보다 추론하기가 더 쉽습니다.
toniedzwiedz

1
전적으로 동의합니다. 변경 가능한 클래스에 변경 불가능한 클래스가 포함되어 있다면 구성은이를 수행하는 매우 좋은 방법 중 하나입니다. 구조가 다른 방법이라면 부분적인 해결책이 될 것입니다.
Justin Ohms

5

최종적이지 않다면 누구나 클래스를 확장하고 setter를 제공하고, 개인 변수를 섀도 잉하고, 기본적으로 변경 가능하게 만드는 것과 같이 원하는대로 할 수 있습니다.


당신이 옳지 만, 기본 클래스 필드가 모두 비공개이고 최종적인 경우 변경 가능한 동작을 추가 할 수 있다는 사실이 반드시 문제를 일으키는 이유는 명확하지 않습니다. 진짜 위험은 하위 클래스가 동작을 재정 의하여 이전에 변경 불가능한 객체를 변경 가능한 객체로 바꿀 수 있다는 것입니다.
templatetypedef

4

이는 클래스를 확장하는 다른 클래스를 제한합니다.

최종 클래스는 다른 클래스에 의해 확장 될 수 없습니다.

클래스가 변경 불가능하게 만들고 싶은 클래스를 확장하면 상속 원칙으로 인해 클래스의 상태가 변경 될 수 있습니다.

"변경 될 수 있음"을 명확히하십시오. 하위 클래스는 메서드 재정의 사용과 같은 수퍼 클래스 동작을 재정의 할 수 있습니다 (예 : templatetypedef / Ted Hop 답변).


2
사실이지만 여기에 왜 필요한가요?
templatetypedef

@templatetypedef : 너무 빠릅니다. 나는 지원 포인트로 내 대답을 편집하고 있습니다.
kosa

4
지금 귀하의 대답이 너무 모호해서 질문에 전혀 대답하지 않는 것 같습니다. "상속 원칙으로 인해 클래스의 상태가 변경 될 수 있습니다."라는 의미는 무엇입니까? 표시해야하는 이유를 이미 알고 있지 않다면 final이 답변이 어떻게 도움이되는지 알 수 없습니다.
templatetypedef

4

최종적으로 만들지 않으면 확장하여 변경 불가능하게 만들 수 있습니다.

public class Immutable {
  privat final int val;
  public Immutable(int val) {
    this.val = val;
  }

  public int getVal() {
    return val;
  }
}

public class FakeImmutable extends Immutable {
  privat int val2;
  public FakeImmutable(int val) {
    super(val);
  }

  public int getVal() {
    return val2;
  }

  public void setVal(int val2) {
    this.val2 = val2;
  }
}

이제 Immutable을 예상하는 모든 클래스에 FakeImmutable을 전달할 수 있으며 예상되는 계약대로 작동하지 않습니다.


2
나는 이것이 내 대답과 거의 동일하다고 생각합니다.
templatetypedef

예, 게시 중간에 확인했으며 새로운 답변은 없습니다. 그리고 게시되었을 때 당신의 것이 나보다 1 분 전에 있었다. 적어도 우리는 모든 것에 같은 이름을 사용하지는 않았습니다.
Roger Lindsjö 2012 년

한 가지 수정 사항 : FakeImmutable 클래스에서 생성자 이름은 FakeImmutable NOT Immutable이어야합니다
Sunny Gupta

2

변경 불가능한 클래스를 생성하기 위해 클래스를 최종 클래스로 표시 할 필요는 없습니다.

자바 클래스 자체에서 그러한 예 중 하나를 취해 보겠습니다. "BigInteger"클래스는 불변이지만 최종적인 것은 아닙니다.

실제로 불변성은 객체가 생성 한 객체에 따라 수정할 수없는 개념입니다.

JVM 관점에서 생각해 봅시다. JVM 관점에서 모든 스레드는 객체의 동일한 복사본을 공유해야하며 스레드가 액세스하기 전에 완전히 구성되고 구성 후 객체의 상태가 변경되지 않습니다.

불변성은 객체가 생성되면 객체의 상태를 변경할 방법이 없음을 의미하며 이는 컴파일러가 클래스가 불변임을 인식하도록 만드는 세 가지 규칙에 의해 달성되며 다음과 같습니다.

  • 비공개가 아닌 모든 필드는 최종 필드 여야합니다.
  • 객체의 필드를 직접 또는 간접적으로 변경할 수있는 메서드가 클래스에 없는지 확인하십시오.
  • 클래스에 정의 된 개체 참조는 클래스 외부에서 수정할 수 없습니다.

자세한 내용은 아래 URL을 참조하십시오.

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html


1

다음과 같은 수업이 있다고 가정 해 보겠습니다.

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PaymentImmutable {
    private final Long id;
    private final List<String> details;
    private final Date paymentDate;
    private final String notes;

    public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) {
        this.id = id;
        this.notes = notes;
        this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime());
        if (details != null) {
            this.details = new ArrayList<String>();

            for(String d : details) {
                this.details.add(d);
            }
        } else {
            this.details = null;
        }
    }

    public Long getId() {
        return this.id;
    }

    public List<String> getDetails() {
        if(this.details != null) {
            List<String> detailsForOutside = new ArrayList<String>();
            for(String d: this.details) {
                detailsForOutside.add(d);
            }
            return detailsForOutside;
        } else {
            return null;
        }
    }

}

그런 다음 확장하고 불변성을 깨뜨립니다.

public class PaymentChild extends PaymentImmutable {
    private List<String> temp;
    public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) {
        super(id, details, paymentDate, notes);
        this.temp = details;
    }

    @Override
    public List<String> getDetails() {
        return temp;
    }
}

여기에서 테스트합니다.

public class Demo {

    public static void main(String[] args) {
        List<String> details = new ArrayList<>();
        details.add("a");
        details.add("b");
        PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes");
        PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes");

        details.add("some value");
        System.out.println(immutableParent.getDetails());
        System.out.println(notImmutableChild.getDetails());
    }
}

출력 결과는 다음과 같습니다.

[a, b]
[a, b, some value]

원래 클래스가 불변성을 유지하는 동안 보시다시피 자식 클래스는 변경 가능할 수 있습니다. 따라서 디자인에서 클래스를 최종적으로 만들지 않는 한 사용중인 객체가 변경 불가능한지 확인할 수 없습니다.


0

다음 클래스가 아니라고 가정합니다 final.

public class Foo {
    private int mThing;
    public Foo(int thing) {
        mThing = thing;
    }
    public int doSomething() { /* doesn't change mThing */ }
}

하위 클래스조차 수정할 수 없기 때문에 분명히 불변 mThing입니다. 그러나 하위 클래스는 변경 가능합니다.

public class Bar extends Foo {
    private int mValue;
    public Bar(int thing, int value) {
        super(thing);
        mValue = value;
    }
    public int getValue() { return mValue; }
    public void setValue(int value) { mValue = value; }
}

이제 유형의 변수에 할당 할 수있는 객체 Foo는 더 이상 mmutable이 보장되지 않습니다. 이로 인해 해싱, 동등성, 동시성 등과 같은 문제가 발생할 수 있습니다.


0

디자인 그 자체로는 가치가 없습니다. 디자인은 항상 목표를 달성하는 데 사용됩니다. 여기서 목표는 무엇입니까? 코드에서 놀라움의 양을 줄이고 싶습니까? 버그를 방지하고 싶습니까? 맹목적으로 규칙을 따르고 있습니까?

또한 디자인에는 항상 비용이 듭니다. 이름에 걸 맞는 모든 디자인은 목표 충돌이 있음을 의미합니다. .

이를 염두에두고 다음 질문에 대한 답을 찾아야합니다.

  1. 이로 인해 얼마나 많은 명백한 버그가 방지됩니까?
  2. 이것이 얼마나 많은 미묘한 버그를 예방할 수 있습니까?
  3. 이로 인해 다른 코드가 얼마나 자주 더 복잡해 집니까 (= 오류가 더 많이 발생합니까)?
  4. 이것이 테스트를 더 쉽고 어렵게 만들까요?
  5. 프로젝트의 개발자는 얼마나 좋은가요? 썰매 망치로 얼마나 많은 안내가 필요합니까?

팀에 많은 주니어 개발자가 있다고 가정 해 보겠습니다. 그들은 자신의 문제에 대한 좋은 해결책을 아직 모른다고해서 어리석은 일을 필사적으로 시도 할 것입니다. 클래스를 final로 만들면 버그를 방지 할 수 있지만 (좋은) 모든 클래스를 코드의 모든 곳에서 변경 가능한 클래스로 복사하는 것과 같은 "영리한"솔루션을 제시 할 수도 있습니다.

반면에 클래스 final를 어디에서나 사용하고 나면 만들기가 매우 어렵지만 나중에 확장해야한다는 것을 알게되면 나중에 final클래스가 아닌 클래스 로 만들기가 쉽습니다 final.

인터페이스를 적절하게 사용하면 항상 인터페이스를 사용하고 나중에 필요할 때 변경 가능한 구현을 추가하여 "이를 변경 가능하게 만들어야합니다"문제를 피할 수 있습니다.

결론 :이 답변에 대한 "최상의"솔루션은 없습니다. 그것은 당신이 기꺼이 어떤 가격을 지불해야 하는가에 달려 있습니다.


0

equals ()의 기본 의미는 참조 동등성과 동일합니다. 불변 데이터 유형의 경우 이것은 거의 항상 잘못되었습니다. 따라서 equals () 메서드를 재정 의하여 자체 구현으로 대체해야합니다. 링크

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.