Java에서 클래스를 변경 불가능 하게 만들 려면 다음을 수행해야합니다.
- 세터를 제공하지 마십시오.
- 모든 필드를 비공개로 표시
- 수업 최종 결정
3 단계가 필요한 이유는 무엇입니까? 수업에 왜 표시를해야 final
합니까?
Java에서 클래스를 변경 불가능 하게 만들 려면 다음을 수행해야합니다.
3 단계가 필요한 이유는 무엇입니까? 수업에 왜 표시를해야 final
합니까?
BigInteger
경우 변경 불가능한지 여부를 알 수 없습니다. 엉망인 디자인입니다. / java.io.File
더 흥미로운 예입니다.
File
는 변경 불가능한 것으로 신뢰되어 TOCTOU (Time Of Check / Time Of Use) 공격으로 이어질 수 있기 때문에 흥미 롭습니다. 신뢰할 수있는 코드 File
는에 허용 가능한 경로가 있는지 확인한 다음 해당 경로를 사용합니다. 서브 클래스 File
는 확인과 사용 사이에 값을 변경할 수 있습니다.
Make the class 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
하면 이런 일이 발생하지 않습니다.
도움이 되었기를 바랍니다!
Immutable
인수를 받는 함수가 있다고 상상해보십시오 . Mutable
그 함수에 객체를 전달할 수 있습니다 Mutable extends Immutable
. 이 함수 내에서 개체가 변경 불가능하다고 생각하는 동안 함수가 실행될 때 값을 변경하는 보조 스레드가있을 수 있습니다. Mutable
함수가 저장 하는 객체를 제공 한 다음 나중에 그 값을 외부에서 변경할 수도 있습니다. 즉, 함수가 값이 변경 불가능하다고 가정하면 변경 가능한 객체를 제공하고 나중에 변경할 수 있으므로 쉽게 깨질 수 있습니다. 말이 돼?
Mutable
개체를 전달하는 메서드는 개체를 변경하지 않습니다. 문제는 해당 메서드가 실제로 그렇지 않은 경우 개체가 변경 불가능하다고 가정 할 수 있다는 것입니다. 예를 들어, 메서드는 객체가 변경 불가능하다고 생각하기 때문에 .NET Framework에서 키로 사용할 수 있다고 가정 할 수 있습니다 HashMap
. 그런 다음을 전달 Mutable
하고 객체가 키로 저장 될 때까지 기다린 다음 Mutable
객체 를 변경하여 해당 함수를 중단 할 수 있습니다. 이제 HashMap
개체와 관련된 키를 변경했기 때문에 검색 이 실패합니다. 말이 돼?
많은 사람들이 믿는 것과는 달리 불변 클래스를 만들 필요 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 단계의 올바른 버전은 다음과 같습니다. "클래스를 최종 클래스로 만들거나, 서브 클래스를 위해 디자인 할 때 모든 서브 클래스가 계속 변경 불가능해야한다는 것을 명확하게 문서화하십시오."
X
그리고 Y
의 가치 X.equals(Y)
불변 것이다 (한로 X
와 Y
같은 개체를 참조 계속). 해시 코드에 대해서도 비슷한 기대치를 가지고 있습니다. 아무도 컴파일러가 동등성 관계와 해시 코드의 불변성을 강제 할 것으로 기 대해서는 안된다는 것은 분명합니다 (단지 평범 할 수 없기 때문에). 나는 사람들이 유형의 다른 측면에 대해 시행 될 것으로 기대해야 할 이유가 없다고 생각합니다.
double
. 하나 GeneralImmutableMatrix
는 배열을 백업 저장소로 사용하는 a 를 파생시킬 수 있지만, 예 ImmutableDiagonalMatrix
를 들어 단순히 대각선을 따라 항목 배열을 저장하는 항목을 가질 수도 있습니다 (X == y 인 경우 항목 X, Y를 읽으면 Arr [X]가 나오고 그렇지 않으면 0이됩니다). .
final
특히 확장 될 API를 디자인 할 때 유용성이 제한됩니다. 스레드 안전성을 위해 클래스를 가능한 한 변경 불가능하게 만들고 확장 가능하게 유지하는 것이 좋습니다. protected final
대신 필드 를 만들 수 있습니다 private final
. 그런 다음 불변성 및 스레드 안전성 보장을 준수하는 하위 클래스에 대한 계약 (문서에서)을 명시 적으로 설정합니다.
final
. 그냥 때문에 equals
그리고 hashcode
내가 그렇게 할 수있는 유효한 이유가없는 나는, 불변 클래스의 같은 용납 할 수 의미하지 않는다 최종되지 않을 수 있으며,이 경우 나는 문서 또는 방어 라인으로 테스트를 사용할 수 있습니다.
전체 수업을 최종으로 표시하지 마십시오.
다른 답변 중 일부에 명시된 것처럼 불변 클래스를 확장하도록 허용하는 유효한 이유가 있으므로 클래스를 최종 클래스로 표시하는 것이 항상 좋은 생각은 아닙니다.
속성을 비공개 및 최종으로 표시하고 "계약"을 보호하려면 게터를 최종으로 표시하는 것이 좋습니다.
이런 식으로 클래스를 확장 할 수 있지만 (예, 변경 가능한 클래스에 의해서도 가능) 클래스의 변경 불가능한 측면은 보호됩니다. 속성은 비공개이며 액세스 할 수 없습니다. 이러한 속성에 대한 getter는 최종 속성이며 재정의 할 수 없습니다.
불변 클래스의 인스턴스를 사용하는 다른 코드는 전달되는 하위 클래스가 다른 측면에서 변경 가능하더라도 클래스의 불변 측면에 의존 할 수 있습니다. 물론 클래스의 인스턴스를 사용하기 때문에 이러한 다른 측면에 대해서도 알지 못할 것입니다.
최종적이지 않다면 누구나 클래스를 확장하고 setter를 제공하고, 개인 변수를 섀도 잉하고, 기본적으로 변경 가능하게 만드는 것과 같이 원하는대로 할 수 있습니다.
이는 클래스를 확장하는 다른 클래스를 제한합니다.
최종 클래스는 다른 클래스에 의해 확장 될 수 없습니다.
클래스가 변경 불가능하게 만들고 싶은 클래스를 확장하면 상속 원칙으로 인해 클래스의 상태가 변경 될 수 있습니다.
"변경 될 수 있음"을 명확히하십시오. 하위 클래스는 메서드 재정의 사용과 같은 수퍼 클래스 동작을 재정의 할 수 있습니다 (예 : templatetypedef / Ted Hop 답변).
final
이 답변이 어떻게 도움이되는지 알 수 없습니다.
최종적으로 만들지 않으면 확장하여 변경 불가능하게 만들 수 있습니다.
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을 전달할 수 있으며 예상되는 계약대로 작동하지 않습니다.
변경 불가능한 클래스를 생성하기 위해 클래스를 최종 클래스로 표시 할 필요는 없습니다.
자바 클래스 자체에서 그러한 예 중 하나를 취해 보겠습니다. "BigInteger"클래스는 불변이지만 최종적인 것은 아닙니다.
실제로 불변성은 객체가 생성 한 객체에 따라 수정할 수없는 개념입니다.
JVM 관점에서 생각해 봅시다. JVM 관점에서 모든 스레드는 객체의 동일한 복사본을 공유해야하며 스레드가 액세스하기 전에 완전히 구성되고 구성 후 객체의 상태가 변경되지 않습니다.
불변성은 객체가 생성되면 객체의 상태를 변경할 방법이 없음을 의미하며 이는 컴파일러가 클래스가 불변임을 인식하도록 만드는 세 가지 규칙에 의해 달성되며 다음과 같습니다.
자세한 내용은 아래 URL을 참조하십시오.
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
다음과 같은 수업이 있다고 가정 해 보겠습니다.
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]
원래 클래스가 불변성을 유지하는 동안 보시다시피 자식 클래스는 변경 가능할 수 있습니다. 따라서 디자인에서 클래스를 최종적으로 만들지 않는 한 사용중인 객체가 변경 불가능한지 확인할 수 없습니다.
다음 클래스가 아니라고 가정합니다 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이 보장되지 않습니다. 이로 인해 해싱, 동등성, 동시성 등과 같은 문제가 발생할 수 있습니다.
디자인 그 자체로는 가치가 없습니다. 디자인은 항상 목표를 달성하는 데 사용됩니다. 여기서 목표는 무엇입니까? 코드에서 놀라움의 양을 줄이고 싶습니까? 버그를 방지하고 싶습니까? 맹목적으로 규칙을 따르고 있습니까?
또한 디자인에는 항상 비용이 듭니다. 이름에 걸 맞는 모든 디자인은 목표 충돌이 있음을 의미합니다. .
이를 염두에두고 다음 질문에 대한 답을 찾아야합니다.
팀에 많은 주니어 개발자가 있다고 가정 해 보겠습니다. 그들은 자신의 문제에 대한 좋은 해결책을 아직 모른다고해서 어리석은 일을 필사적으로 시도 할 것입니다. 클래스를 final로 만들면 버그를 방지 할 수 있지만 (좋은) 모든 클래스를 코드의 모든 곳에서 변경 가능한 클래스로 복사하는 것과 같은 "영리한"솔루션을 제시 할 수도 있습니다.
반면에 클래스 final
를 어디에서나 사용하고 나면 만들기가 매우 어렵지만 나중에 확장해야한다는 것을 알게되면 나중에 final
클래스가 아닌 클래스 로 만들기가 쉽습니다 final
.
인터페이스를 적절하게 사용하면 항상 인터페이스를 사용하고 나중에 필요할 때 변경 가능한 구현을 추가하여 "이를 변경 가능하게 만들어야합니다"문제를 피할 수 있습니다.
결론 :이 답변에 대한 "최상의"솔루션은 없습니다. 그것은 당신이 기꺼이 어떤 가격을 지불해야 하는가에 달려 있습니다.
java.math.BigInteger
클래스는 예이며 값은 변경할 수 없지만 최종 값은 아닙니다.