2010-05-17 5 views
5

마지막 필드가 Java로 변경되지 않는 불변 객체에 대해 자주 읽습니다. 실제로 사실입니까? 아니면 대중의 변화가없고 실제로 주를 돌연변이시키는 것만으로 충분합니까?필드가 "적절한"불변 개체를 갖기 위해 명시 적으로 최종적으로 필요합니까?

예를 들어 빌더 패턴으로 작성된 불변 오브젝트가있는 경우 빌더가 빌드 할 때 개별 필드를 지정하게하거나 빌더가 필드 자체를 보유하고 궁극적으로 변경 불가능한 오브젝트를 리턴하도록 할 수 있습니다 값을 (private) 생성자에 전달합니다.

마지막 필드를 사용하면 구현 오류를 방지 할 수 있다는 분명한 이점이 있습니다 (예 : 코드에 빌더에 대한 참조를 유지하고 기존 객체를 실제로 변경하는 동안 객체를 여러 번 "빌드"할 수 있음).하지만 빌더 저장소 객체 내부의 데이터는 DRYer로 작성됩니다.

그래서 질문 : 작성기가 개체를 조기에 누설하지 않고 빌드 된 개체를 수정하지 못한다고 가정하면 (예 : 개체 참조가 null로 설정 됨) 실제로 확보 된 것 (예 : 향상된 스레드 안전성) 개체의 필드가 최종 대신 만들어지면 개체의 "불변성"에서?

+1

실수가 아닌 경우 * 불량 프로그래머가 리플렉션을 사용하여 클래스 필드에 액세스하고 콘텐츠를 수정하는 "반사 공격"을 막을 수 있습니다. 유명한 예제가 * String * 클래스를 완전히 왜곡하고 많은 경우에 * String *이 실제로 수정 될 수 있음을 보여줍니다 (비 finality btw로 인한 것이 아니라 일단 기본 * char [] * 당신이 배열의 내용에 불변성을 강요 할 수 없기 때문에 "게임 오버"였다면 액세스되었습니다 ...하지만 그건 내 요점이 아닙니다 : 요점은 반사가 정말로 더러운 일을하는 데 도움이된다는 것입니다. – TacticalCoder

+1

@user, 그런 반사에 직면하여, 당신은 불변성을 확립 할 수 없습니다. 그러나 보안 관리자는 실행 가능한 타사 코드를 실행하려고합니다. – Yishai

+0

wait ... * final * 클래스에 고유 * final int *가있는 경우 리플렉션을 사용하여 수정할 수 있습니까? 내 요점은 정확하게 * final *을 사용하지 않고서는 * 리플렉션 공격을 막을 수는 없다는 것입니다. (Security Manager를 설치하지 않는 한, 거의 불가능합니다. 그래서 많은 경우에 수정 될 "* ...). 그러나 당신이 반사를 사용하여 * final int *를 수정할 수 있는지 확신하지 못합니다. 그렇다면 다음과 같이 내 의견이 질문과 관련이 있습니다 :) (나는 어제 당신의 질문에 +1했습니다 :) – TacticalCoder

답변

6

예, 당신은 final 필드에서 "스레드 안전"을 얻는다. 즉, 구성 중에 final 필드에 할당 된 값은 모든 스레드에서 볼 수 있음을 보장합니다. 스레드 안전을위한 또 다른 대안은 필드 volatile을 선언하는 것입니다. 그런 다음 모든 읽기 및 hellip에서 높은 오버 헤드가 발생합니다. 자신의 수업을 보는 사람과이 "불변"클래스의 필드가 왜 "휘발성"으로 표시되는지 궁금해하는 사람을 혼란스럽게합니다. 필드 final을 표시

기술적으로 가장 정확하고, 가장 명확하게 의도를 전달한다. 불행히도 빌더 패턴을 매우 복잡하게 만듭니다. Project Lombok이 setter 및 getter와 마찬가지로 불변 클래스에 대한 빌더를 합성하는 주석 프로세서를 만드는 것이 가능해야한다고 생각합니다. 실제로 존재하지 않는 빌더를 대상으로 코드를 작성하려면 IDE 지원이 필요합니다.

+0

왜 '휘발성'이 필요한가요? – curiousguy

+0

@curiousguy :'volatile'은 한 쓰레드에 의해 쓰여진 값이 다른 쓰레드가 볼 수 있도록합니다. 그렇지 않으면 판독기가 레지스터에 캐시 된 값을 지우지 않고 주 메모리로 읽지 않을 수 있습니다. – erickson

+0

내 코멘트 답하기를 참조하십시오. – curiousguy

2

개체는 확실히 변경할 수있는 전용 필드를 가질 수 있으며 여전히 변경할 수없는 개체로 작동합니다. 불변의 계약을 맺는 데 중요한 것은 그 물건이 바깥에서 불변으로 보인다는 것입니다. 비 최종 개인 필드가 있지만 설정자가없는 객체는 예를 들어이 요구 사항을 충족시킵니다.

는 사실, 당신의 캡슐화는 바로 다음의 경우 실제로 내부 상태를 돌연변이 수 있으며, 여전히 "불변"개체로 성공적으로 운영하고 있습니다. 예를 들어 일종의 게으른 평가 또는 데이터 구조의 캐싱 일 수 있습니다. 그들이 불변하지만, 실제로 계산하고 직접 요청 될 때 미래 가치를 저장하는 것처럼

Clojure의 예를 들어 게으른 시퀀스의 내부 구현이 작업을 수행,이 개체는 동작합니다. 후속 요청은 저장된 값을 검색합니다.

그러나 - 나는 당신이 실제로 불변의 객체의 내부를 돌연변이 싶은 장소의 수는 아마 매우 드물다 있다는 경고로 추가합니다. 의심 스러울 경우 최종적으로하십시오.

+1

"비 최종 개인 필드가 있지만 설정자가없는 객체가이 요구 사항을 충족시킵니다."-이 자체로 불변성을 보장하지는 않습니다. Getters는 데이터 멤버 참조를 반환 할 수 있으며 호출자가이를 수정할 수 있습니다. –

+0

@Eyal : true - 참조 자체는 변경되지 않습니다. 당신이 불변의 멤버로 객체를 구성했다면, 변경되지 않은 상태로 유지 될 것입니다. 또는 변경 가능 객체의 불변 컴포지션 또는 콜렉션을 작성하는 것이 일부 컨텍스트에서는 의미가 있습니다. – mikera

+0

다른 사람들은 불변 성의 정의가 약간 다릅니다 ... 내 것이 가장 유용하다고 생각하는 "관찰자의 관점에서 불변 함"입니다. 그러나 나는 사람들이 불변성을 절대적으로 변경하지 않는다고 말하는 것을 들었습니다. – mikera

0

난 당신이 단지에서 환경에게 그것의 실행을 고려하고 반사를 사용하는 프레임 워크는 오브젝트가 위험하다 조작 여부를 결정해야합니다 생각합니다.

bean setter 대신 리플렉션을 사용하도록 설정된 웹 바인딩 프레임 워크로 인해 불변 인 객체가 POST 주입 공격을 통해 clobbered되는 괴상한 시나리오를 쉽게 해결할 수 있습니다.

0

최종 필드가 아닌 불변 개체가있을 수 있습니다.

예를 들어 java.lang.String의 java 1.6 구현을 참조하십시오.

+0

@ user988052 클래스 무결성과 프로그램 안전이 깨지지 않습니까? – curiousguy

+0

@TacticalCoder - 최종 필드조차도 리플렉션을 통해 수정할 수 있습니다. 따라서 "불변이 아닌 문자열"을 가질 수 있다는 주장은 자바에서 불변의 객체를 가질 수 없다는 말을 기꺼이하지 않는 한, 사실이 아닙니다. String 클래스는 기본적으로 변경할 수 없습니다. 리플렉션은 수업 설계를 단계적으로 진행하며 원래 질문이나이 포스터의 응답과 관련이 없습니다. – Scrubbie

+0

@TacticalCoder. 왜 -1일까요? String이 변경 가능하다고 생각하십니까? 1.6에서 구현 된 것을 보았습니까? 어떤 디자인 철학과 마찬가지로, 당신은 당신의 하청 업체와 당신 자신을, 작동 스펙에 따라 장비와 하위 부품을 작동시키는 것을 신뢰합니다. 이러한 사양을 벗어난 작동을 시작하면 (예 : 불변의 객체에 대한 리플렉션 사용) 최종 결과는 체르노빌 -> http://en.wikipedia.org/wiki/Chernobyl_disaster와 유사 할 수 있습니다. –

0

코멘트 : 그런 @erickson

:

 
class X { volatile int i, j; } 
X y; 

// thread A: 
X x = new X; 
x.i = 1; 
x.j = 2; 
y = x; 

// thread B: 
if (y != null) { 
    a = y.i; 
    b = y.j; 
} 

?

+2

스레드 B는 'y'가 할당 된 것을 보장하지 않습니다. B가 그것을 읽기 전에 "y"가 할당 될지라도, 벽에있는 시계에 따르면, 메모리 장벽이 없기 때문에 B는 y로부터 "null"이라는 부실 값을 읽을 수 있습니다. 여기서, 만약 당신이'i'''''''''''''''''''''''''''''''''''''''''''volatile'을' 휘발성 필드에 기록하면 모든 필드의 쓰기가 주 메모리로 플러시되고 휘발성 변수를 읽으면 캐시 된 값이 주 메모리에서 새로 고침되기 때문입니다. – erickson

관련 문제