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클래스는 예이며 값은 변경할 수 없지만 최종 값은 아닙니다.