Nice programing

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

nicepro 2020. 10. 20. 08:09
반응형

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


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

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

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


클래스를 표시하지 않으면 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하면 이런 일이 발생하지 않습니다.

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


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

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

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

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

불변성은 클래스 계약의 일부입니다. 이 클래스 (및 모든 서브 클래스) 무엇을 말한다 있기 때문에, 사람들이 이상으로 사용되는 몇 가지에서 조금 다릅니다 수 없습니다 합니까 대부분의 자바 (일반적으로 OOP) 프로그래머에 관한 같은 계약에 대해 생각하는 경향이 생각하는 동안, 수업이 할 수 있는 것이 아니라 할 수있는 것.

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

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

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


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

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

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

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

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


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


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

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

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

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


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

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을 전달할 수 있으며 예상되는 계약대로 작동하지 않습니다.


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

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

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

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

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

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

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

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


다음 클래스가 아니라고 가정합니다 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이 보장되지 않습니다. 이로 인해 해싱, 동등성, 동시성 등과 같은 문제가 발생할 수 있습니다.


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

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

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

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

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

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

If you properly use interfaces, you can avoid the "I need to make this mutable" problem by always using the interface and then later adding a mutable implementation when the need arises.

Conclusion: There is no "best" solution for this answer. It depends on which price you're willing and which you have to pay.


The default meaning of equals() is the same as referential equality. For immutable data types, this is almost always wrong. So you have to override the equals() method, replacing it with your own implementation. link


Let's say you have the following class:

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;
        }
    }

}

Then you extend it and break its immutability.

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;
    }
}

Here we test it:

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());
    }
}

Output result will be:

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

As you can see while original class is keeping its immutability, child classes can be mutable. Consequently, in your design you cannot be sure that the object you are using is immutable, unless you make your class final.

참고URL : https://stackoverflow.com/questions/12306651/why-would-one-declare-an-immutable-class-final-in-java

반응형