[java] 불변 클래스가 필요한 이유는 무엇입니까?

불변 클래스가 필요한 시나리오를 얻을 수 없습니다.
그러한 요구 사항에 직면 한 적이 있습니까? 아니면 우리가이 패턴을 사용해야 할 실제 예를 들어 주시겠습니까?



답변

다른 답변은 불변성이 좋은 이유를 설명하는 데 너무 집중된 것 같습니다. 그것은 매우 좋으며 가능할 때마다 사용합니다. 그러나 그것은 귀하의 질문이 아닙니다 . 나는 당신이 필요한 답과 예제를 얻고 있는지 확인하기 위해 당신의 질문을 한 점씩 가져갈 것입니다.

불변 클래스가 필요한 시나리오를 얻을 수 없습니다.

여기서 “Need”는 상대적인 용어입니다. 불변 클래스는 다른 패러다임 / 패턴 / 도구와 마찬가지로 소프트웨어를 더 쉽게 구성 할 수있는 디자인 패턴입니다. 마찬가지로, OO 패러다임이 나오기 전에 많은 코드가 작성되었지만, OO 를 “필요로 하는 프로그래머 중에는 저를 포함 시키십시오 . OO와 같은 불변 클래스는 꼭 필요한 것은 아니지만 필요한 것처럼 행동 할 것입니다.

그러한 요구 사항에 직면 한 적이 있습니까?

올바른 관점으로 문제 도메인의 개체를 보지 않으면 불변 개체에 대한 요구 사항이 표시되지 않을 수 있습니다 . 언제 유용하게 사용할지 잘 모르는 경우 문제 도메인에는 불변 클래스가 필요 하지 않다고 생각하기 쉽습니다 .

나는 종종 내 문제 영역에서 주어진 객체를 값 또는 고정 인스턴스 로 생각하는 불변 클래스를 사용 합니다 . 이 개념은 때때로 관점이나 관점에 의존하지만 이상적으로는 좋은 후보 개체를 식별하기 위해 올바른 관점으로 쉽게 전환 할 수 있습니다.

불변 클래스에 대해 생각하는 방법에 대한 좋은 감각을 개발하기 위해 다양한 책 / 온라인 기사를 읽어 보면 불변 객체가 정말로 유용한 곳 (엄격히 필요한 것은 아니지만)을 더 잘 이해할 수 있습니다. 시작하기에 좋은 기사 중 하나는 Java 이론과 실습입니다. 돌연변이를 원하십니까?

나는 관점이 의미하는 바를 명확히하기 위해 어떻게 다른 관점 (변경 가능 vs 불변)에서 객체를 볼 수 있는지에 대한 몇 가지 예를 아래에 제공하려고합니다.

…이 패턴을 사용해야하는 실제 예를 들어 주시겠습니까?

실제 예제를 요청 했으므로 몇 가지를 제공하지만 먼저 몇 가지 고전적인 예제부터 시작하겠습니다.

클래식 값 개체

문자열과 정수는 종종 값으로 간주됩니다. 따라서 String 클래스와 Integer 래퍼 클래스 (다른 래퍼 클래스뿐만 아니라)가 Java에서 변경 불가능하다는 사실은 놀라운 일이 아닙니다. 색상은 일반적으로 값으로 간주되므로 변경할 수없는 Color 클래스입니다.

반례

반대로 자동차는 일반적으로 가치 대상으로 간주되지 않습니다. 자동차를 모델링한다는 것은 일반적으로 상태 (주행 거리계, 속도, 연료 수준 등)가 변경되는 클래스를 만드는 것을 의미합니다. 그러나 자동차가 가치 객체가 될 수있는 일부 도메인이 있습니다. 예를 들어, 자동차 (또는 특히 자동차 모델)는 특정 차량에 적합한 엔진 오일을 찾기위한 앱에서 값 개체로 간주 될 수 있습니다.

카드 놀이

카드 놀이 프로그램을 작성한 적이 있습니까? 내가했다. 나는 트럼프 패를 수트와 계급이 변경 가능한 개체로 표현할 수 있었다. 드로우-포커 핸드는 5 개의 고정 된 경우가 될 수 있습니다. 내 손에있는 5 번째 카드를 교체하는 것은 그 슈트와 랭크 ivars를 변경하여 5 번째 플레잉 카드 인스턴스를 새 카드로 변경하는 것을 의미합니다.

그러나 나는 트럼프 카드를 일단 만들어지면 변하지 않는 고정 된 수트와 랭크를 가진 불변의 개체로 생각하는 경향이 있습니다. 제 드로우 포커 핸드는 5 개의 인스턴스가 될 것이고 제 핸드의 카드를 교체하는 것은 그 인스턴스 중 하나를 버리고 제 손에 새로운 무작위 인스턴스를 추가하는 것을 포함합니다.

지도 투영

마지막 예는지도가 다양한 투영으로 표시 될 수있는지도 코드를 작업했을 때입니다. 입니다. 원래 코드는지도에서 고정되었지만 변경 가능한 투영 인스턴스 (위의 변경 가능한 재생 카드처럼)를 사용했습니다. 지도 투영을 변경하면지도의 투영 인스턴스의 ivar (투영 유형, 중심점, 확대 / 축소 등)가 변경되었습니다.

하지만 투영을 불변의 값이나 고정 된 인스턴스로 생각하면 디자인이 더 간단하다고 느꼈습니다. 지도 투영을 변경한다는 것은지도의 고정 투영 인스턴스를 변경하는 대신지도가 다른 투영 인스턴스를 참조하도록하는 것을 의미했습니다. 이는 또한 MERCATOR_WORLD_VIEW.


답변

변경 불가능한 클래스는 일반적으로 올바르게 설계, 구현 및 사용하기가 훨씬 간단합니다 . 예는 String입니다.의 구현은 대부분 불변성으로 인해 C ++ 의 구현 java.lang.String보다 훨씬 간단 std::string합니다.

불변성이 특히 큰 차이를 만드는 특정 영역 중 하나는 동시성입니다. 불변 객체는 여러 스레드간에 안전하게 공유 할 수있는 반면, 변경 가능한 객체는 신중한 설계 및 구현을 통해 스레드로부터 안전하게 만들어야합니다. 일반적으로 이것은 사소한 작업과 거리가 멀습니다.

업데이트 : Effective Java 2nd Edition 은이 문제를 자세히 다루고 있습니다. 항목 15 : 변경 가능성 최소화를 참조하십시오 .

다음 관련 게시물도 참조하십시오.


답변

Joshua Bloch의 Effective Java는 불변 클래스를 작성하는 몇 가지 이유를 설명합니다.

  • 단순성-각 클래스는 하나의 상태에만 있습니다.
  • 스레드 안전-상태를 변경할 수 없기 때문에 동기화가 필요하지 않습니다.
  • 변경 불가능한 스타일로 작성하면 더 강력한 코드가 생성 될 수 있습니다. 문자열이 불변이 아니라고 상상해보십시오. String을 반환 한 모든 getter 메서드는 String이 반환되기 전에 방어적인 복사본을 생성하도록 구현해야합니다. 그렇지 않으면 클라이언트가 실수로 또는 악의적으로 객체의 해당 상태를 손상시킬 수 있습니다.

일반적으로 심각한 성능 문제가 발생하지 않는 한 객체를 변경 불가능하게 만드는 것이 좋습니다. 이러한 상황에서 변경 가능한 빌더 객체는 StringBuilder와 같은 변경 불가능한 객체를 빌드하는 데 사용될 수 있습니다.


답변

해시 맵은 전형적인 예입니다. 지도의 키는 변경 불가능해야합니다. 키가 변경 불가능하지 않은 경우 hashCode ()가 새 값을 생성하도록 키의 값을 변경하면 이제 맵이 손상됩니다 (이제 키가 해시 테이블의 잘못된 위치에 있음).


답변

Java는 사실상 하나의 모든 참조입니다. 때때로 인스턴스는 여러 번 참조됩니다. 이러한 인스턴스를 변경하면 모든 참조에 반영됩니다. 때로는 견고성과 스레드 안전성을 향상시키기 위해 이것을 원하지 않을 수도 있습니다. 그런 다음 변경 불가능한 클래스가 유용하므로 인스턴스 를 만들고 현재 참조에 다시 할당해야합니다. 이렇게하면 다른 참조의 원래 인스턴스가 그대로 유지됩니다.

Java String가 변경 가능 하다면 어떻게 보일지 상상해보십시오 .


답변

우리는 그 자체로 불변 클래스 가 필요 하지 않지만 , 특히 여러 스레드가 관련된 경우 일부 프로그래밍 작업을 더 쉽게 만들 수 있습니다. 변경 불가능한 객체에 액세스하기 위해 잠금을 수행 할 필요가 없으며 이러한 객체에 대해 이미 설정 한 사실은 향후에도 계속 적용됩니다.


답변

극단적 인 경우를 보자 : 정수 상수. “x = x + 1″과 같은 문을 작성하면 프로그램의 다른 곳에서 어떤 일이 발생하더라도 숫자 “1”이 어떻게 든 2가되지 않을 것이라고 100 % 확신하고 싶습니다.

이제 정수 상수는 클래스가 아니지만 개념은 동일합니다. 내가 다음과 같이 쓴다고 가정하자.

String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);

충분히 간단 해 보입니다. 그러나 Strings가 변경 불가능하지 않은 경우 getCustomerName이 customerId를 변경할 수있는 가능성을 고려해야하므로 getCustomerBalance를 호출 할 때 다른 고객에 대한 잔액을 얻습니다. 이제 “왜 누군가 getCustomerName 함수를 작성하여 ID를 변경하겠습니까? 그것은 말이되지 않을 것입니다.”라고 말할 수 있습니다. 그러나 그것이 바로 당신이 곤경에 처할 수있는 곳입니다. 위의 코드를 작성하는 사람은 함수가 매개 변수를 변경하지 않는다는 것을 명백히 받아 들일 수 있습니다. 그런 다음 고객이 동일한 이름으로 여러 계정을 가지고있는 경우를 처리하기 위해 해당 기능의 다른 사용을 수정해야하는 누군가가 나타납니다. 그리고 그는 “이미 이름을 찾고있는이 편리한 getCustomer 이름 함수가 있습니다.

불변성은 단순히 특정 클래스의 객체가 상수임을 의미하며이를 상수로 취급 할 수 있습니다.

(물론 사용자는 변수에 다른 “상수 객체”를 할당 할 수 있습니다. 누군가는 String s = “hello”를 작성하고 나중에 s = “goodbye”를 작성할 수 있습니다. 변수를 최종화하지 않는 한 확신 할 수 없습니다. 내 자신의 코드 블록 내에서 변경되지 않습니다. 정수 상수처럼 “1”은 항상 같은 숫자이지만 “x = 1″은 “x = 2″를 써도 변경되지 않습니다. 변경 불가능한 객체에 대한 핸들이 있으면 전달하는 함수가 나에게 변경할 수 없거나 두 개의 사본을 만들면 하나의 사본을 보유하는 변수를 변경해도 변경되지 않는다고 확신 할 수 있습니다. 기타 등등.