2012-01-03 4 views
10

Java Concurrency In Practice에서는 "효과적으로 변경할 수없는"개체와 변경 가능한 개체의 동시성에 따른 이점을 설명합니다. 그러나 "효과적으로 immutables"객체가 불변 객체를 통해 제공 할 수있는 이점을 설명하지 못합니다.효과적으로 불변의 객체가 의미가 있습니까?

그리고 나는 그것을 얻지 못합니다. 항상 "불변의 개체"를 안전하게 게시하기로 결정한 순간에 정말로 불변의 개체를 만드시겠습니까? 내가 수업을 설계있을 때 내가 항상 정말 불변의 객체를 만들 수없는 경우를 (참조하지

은 (대신 "안전 게시"를하고 당신은 정말 불변의 객체를 구축 할 것입니다 그리고 그것 뿐이다) 필요에 따라 위임을 사용하여 다른 포장 된 객체를 구축 할 수 있습니다. 물론 안전하게 옮길 수 있습니다.

나쁜 디자인이나 가난한 API의 경우와 마찬가지로 "효과적으로 변경할 수없는"개체와 "안전한 게시"가 있습니까?

어디에서 효과적으로 불변 개체를 사용하고 실제로 훨씬 더 우수한 불변 개체를 만들 수없는 곳에 안전하게 게시해야합니까?

답변

9

예, 일부 경우에 의미가 있습니다. 쉬운 예는 일부 속성이 느리게 생성되고 캐시되도록하므로 액세스하지 않을 경우 생성되는 오버 헤드를 피할 수있는 경우입니다. String은 해시 코드와 함께이를 수행하는 실질적으로 변경 불가능한 클래스의 예입니다.

1

효과적으로 불변 개체를 사용하면 많은 수의 클래스를 만들지 않아도됩니다. [변경 가능한 빌더]/[변경 불가능한 객체] 클래스 쌍을 만드는 대신 효과적으로 변경할 수없는 클래스를 만들 수 있습니다. 보통 불변의 인터페이스와이 인터페이스를 구현하는 변경 가능한 클래스를 정의합니다. 객체는 변경 가능한 클래스 메소드를 통해 구성한 다음 변경 불가능한 인터페이스를 통해 게시됩니다. 라이브러리 프로그램의 클라이언트가 인터페이스에 연결되어있는 한, 오브젝트는 게시 된 수명 동안 변경되지 않습니다. 원형 immutables를 들어

+0

이 접근법의 예가 있습니까? – user77115

3

:

class Foo 
{ 
    final Object param; 
    final Foo other; 

    Foo(Object param, Foo other) 
    { 
     this.param = param; 
     this.other = other; 
    } 

    // create a pair of Foo's, A=this, B=other 
    Foo(Object paramA, Object paramB) 
    { 
     this.param = paramA; 
     this.other = new Foo(paramB, this); 
    } 

    Foo getOther(){ return other; } 
} 



// usage 
Foo fooA = new Foo(paramA, paramB); 
Foo fooB = fooA.getOther(); 
// publish fooA/fooB (unsafely) 

질문이 fooAthis는 생성자 내부에서 유출되어 있기 때문에, 여전히 불변 스레드 안전 fooA입니다입니까? 즉, 다른 스레드가 fooB.getOther().param을 읽으면 paramA이 표시됩니까? 대답은 예입니다. 동결 조치 전에 this이 다른 스레드로 유출되지 않았으므로 예입니다. 우리는 paramA이 읽을 수있는 유일한 가시 값임을 증명하기 위해 사양에 필요한 hb/dc/mc 주문을 수립 할 수 있습니다.

원래 질문으로 돌아 가기. 실제로는 순수한 기술적 인 것 이상의 제약이 있습니다. 모든 엔지니어링, 운영, 정치 및 기타 인간의 이유를 고려하여 생성자 내부의 모든 것을 초기화하는 것이 반드시 설계를위한 최상의 옵션은 아닙니다.

우리가 왜 그것이 최고의 생각이라고 생각하게되었는지 궁금한가요?

더 깊은 문제는 Java입니다. 에는이 부족합니다. 휘발성보다 비용이 적게 드는 안전한 발행을 위해 일반적으로 저렴한 가격입니다. Java는 final 필드에만 있습니다. 웬일인지, 울타리는 그렇지 않으면 이용할 수 없다.

이제 final은 두 가지 독립적 인 의미를 전달합니다. 첫째, 최종 필드가 정확히 한 번 지정되어야합니다. 둘째, 안전한 출판의 기억 의미론. 이 두 가지 의미는 서로 관련이 없습니다. 그것들을 함께 묶는 것은 매우 혼란 스럽습니다. 사람들이 제 2의 의미를 필요로 할 때, 제 1의 의미도 받아 들여야합니다. 1 위가 디자인에서 달성하기가 매우 불편할 때 사람들은 자신이 잘못한 것을 궁금해 할 것입니다. Java가 잘못되었다는 사실을 깨닫지 못합니다.

final에서 두 개의 의미를 묶어 두 번 더하기 좋게 만듭니다. 따라서 분명히 더 많은 이유와 동기가 있습니다. final. 더 불길한 이야기는 실제로 우리가 더 유연한 선택을 할 수 없기 때문에 그것을 사용해야 만합니다.

+0

큰 예와 설명은 +1입니다. 또한 나는 "final"에 대해서는 언급하지 않았지만, 실제로 우리가 사용해야하는 것은 ... 두 가지 의미에 관해서 : * final *이 메소드에도 적용될 수 있지만 나는 벗어납니다.) 팩터 리 및/또는 유창한 인터페이스를 사용하여 생성자를 구현할 수도 있지만 생성자 예제가 마음에 들다. – NoozNooz42

0

한다고 가정 한 등 Beta, Alpha라는 다섯 개 가지 속성이 불변 클래스 Foo을 가지며, 하나는 특정 제외하고 원래의 동일 인스턴스를 반환 WithAlpha, WithBeta 등의 방법을 제공하고자 속성이 변경되었습니다. 클래스가 정말로 깊고 불변 인 경우 메서드는 다음 형식을 취해야합니다.

 
Foo WithAlpha(string newAlpha) 
{ 
    return new Foo(newAlpha, Beta, Gamma, Delta, Epsilon); 
} 

Foo WithBeta(string newBeta) 
{ 
    return new Foo(Alpha, NewBeta, Gamma, Delta, Epsilon); 
} 

Ick. "Do not Repeat Yourself"(DRY) 원칙에 대한 엄중 한 위반. 또한 클래스에 새 속성을 추가하려면 해당 메서드 중 하나 하나를 모두 추가해야합니다. 한편

, 각 Foo는 복사 생성자를 포함하는 내부 FooGuts 개최하는 경우, 하나 대신 같은 것을 할 수있는 :

 
Foo WithAlpha(string newAlpha) 
{ 
    FooGuts newGuts = new FooGuts(Guts); // Guts is a private or protected field 
    newGuts.Alpha = newAlpha; 
    return new Foo(newGuts); // Private or protected constructor 
} 

각 방법에 대한 코드 라인의 수는 증가를하지만, 메소드는 더 이상 "관심이없는"모든 속성에 대한 참조를 만들 필요가 없습니다. 외부 참조가 존재하는 FooGuts으로 생성자를 호출하면 Foo이 불변이 아닐 수도 있지만 그 생성자는 시공 후에 그러한 참조를 유지하지 않을 것이라고 믿어지는 코드.