동일한 객체에 대한 두 개의 참조를 언제 원하십니까?


20

Java에서는 구체적으로, 다른 언어에서도 가능합니다. 동일한 객체에 대한 두 개의 참조를 갖는 것이 언제 유용합니까?

예:

Dog a = new Dog();
Dob b = a;

이것이 유용한 상황이 있습니까? ? a로 표시된 객체와 상호 작용할 때마다 이것이 선호되는 솔루션 인 이유는 무엇 a입니까?



@MichaelT 좀 더 자세히 설명해 주시겠습니까?
Bassinator

7
a그리고 b 항상 같은 참조하는 경우 Dog아무 의미가 없습니다. 그들이 때때로 할 수 있다면 , 그것은 매우 유용합니다.
Gabe

답변:


45

예를 들어 두 개의 별도 목록 에 동일한 객체를 원할 경우가 있습니다 .

Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();

dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog

또 다른 용도는 동일한 객체가 여러 역할을하는 경우입니다 .

Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;

4
죄송합니다. Bruce Wayne의 사례에는 디자인 결함, 배트맨 및 CEO 직책이 사람의 역할이되어야한다고 생각합니다.
Silviu Burcea

44
배트맨의 비밀 정체성을 밝히기 위해 -1.
Ampt

2
@Silviu Burcea-Bruce Wayne 예제가 좋은 예가 아니라고 강력히 동의합니다. 한편, 글로벌 텍스트 편집이 'ceoOfWayneIndustries'및 'batman'이라는 이름을 'p'(이름의 충돌이나 범위의 변경이 없다고 가정)로 변경 한 경우 프로그램 의 의미 가 변경되면 해당 항목이 손상된 것입니다. 참조 된 객체는 프로그램 내의 변수 이름이 아니라 실제 의미를 나타냅니다. 다른 의미를 갖기 위해, 그것은 다른 객체이거나 참조 (투명해야 함)보다 동작이 많은 것으로 참조되므로 2+ 참조의 예는 아닙니다.
gbulmer

2
서면으로 작성된 Bruce Wayne의 예는 효과가 없을 수 있지만, 언급 된 의도 가 맞다고 생각합니다 . 아마도 더 가까운 예가있을 수 있습니다. Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;여러 가능한 값 (또는 사용 가능한 값 목록)과 현재 활성화 / 선택된 값에 대한 참조가 있습니다.
Dan Puzey

1
@ gbulmer : 두 객체에 대한 참조를 가질 수 없다고 생각합니다. 참조 currentPersona는 하나의 객체 또는 다른 객체를 가리 키지 만 둘 다를 가리지 않습니다. 특히, 쉽게 그 가능할 수도 currentPersona있다 결코 로 설정 bruce이 두 객체에 대한 참조가 확실히의 경우에. 내 예에서, 그런 말을 모두 줄 batmancurrentPersona같은 인스턴스에 대한 참조하지만, 프로그램 내에서 서로 다른 의미 론적 의미를 제공합니다.
Dan Puzey

16

그것은 실제로 놀랍도록 심오한 질문입니다! 현대 C ++ (및 Rust와 같은 현대 C ++에서 사용되는 언어)의 경험에 따르면 매우 원하지 않습니다! 대부분의 데이터에는 단일 또는 고유 한 ( "소유") 참조가 필요합니다. 이 아이디어는 선형 타입 시스템의 주된 이유이기도합니다 .

그러나 그때조차도 일반적으로 메모리에 잠시 액세스하는 데 사용되지만 데이터가 존재하는 시간의 상당 부분 동안 지속되지 않는 단기 "빌린"참조를 원합니다. 객체를 다른 함수에 인수로 전달할 때 가장 일반적으로 사용됩니다 (매개 변수도 변수입니다).

void encounter(Dog a) {
  hissAt(a);
}

void hissAt(Dog b) {
  // ...
}

조건에 따라 두 가지 개체 중 하나를 사용하는 경우 덜 일반적인 경우입니다. 선택한 항목에 관계없이 기본적으로 동일한 작업을 수행합니다.

Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);

더 일반적인 용도로 돌아가서 로컬 변수를 뒤로 남겨두고 필드로 전환합니다. 예를 들어, GUI 프레임 워크의 위젯에는 종종 부모 위젯이 있으므로 10 개의 버튼이 포함 된 큰 프레임에는 10 개의 참조가 있습니다 (더 이상 에서 부모와) 아마 이벤트 리스너에서 등등. 모든 종류의 개체 그래프 및 일부 종류의 개체 트리 (부모 / 형제 참조가있는 개체 트리)에는 여러 개체가 동일한 개체를 각각 참조합니다. 그리고 사실상 모든 데이터 세트는 실제로 그래프입니다 ;-)


3
"흔하지 않은 경우"는 매우 일반적입니다. 객체를 목록이나 맵에 저장하고 원하는 것을 검색하고 필요한 작업을 수행합니다. 맵이나 목록에서 참조를 지우지 않습니다.
SJuan76

1
@ SJuan76 "흔하지 않은 경우"는 지역 변수를 취하기위한 것입니다. 데이터 구조와 관련된 모든 것은 마지막 시점에 해당합니다.

참조 카운트 메모리 관리로 인해 하나의 참조에만 이상적입니까? 그렇다면 동기 부여가 다른 가비지 수집기 (예 : C #, Java, Python)를 가진 다른 언어와 관련이 없다고 언급 할 가치가 있습니다.
MarkJ

@MarkJ Linear 유형은 이상적인 몇 가지 코드에 대한 의미가 맞기 때문에 이상적이며 기존 코드가 무의식적으로 적합하지 않습니다. 그것은 잠재적 인 성능 이점을 가지고있다 (그러나 어느 것도 실제로 생략 모두 refcount를을 그 지식을 활용하고 추적하는 다른 전략을 사용할 때 참조 횟수도 추적 GC를 위해, 그것은 단지 도움). 보다 흥미로운 것은 간단하고 결정적인 자원 관리에 대한 응용 프로그램입니다. RAII 및 C ++ 11이 의미를 이동하지만 더 자주 적용된다고 생각하십시오 (컴파일러에서 실수가 더 많이 발생 함).

6

임시 변수 : 다음 의사 코드를 고려하십시오.

Object getMaximum(Collection objects) {
  Object max = null;
  for (Object candidate IN objects) {
    if ((max is null) OR (candidate > max)) {
      max = candidate;
    }
  }
  return max;
}

변수 maxcandidate같은 객체를 가리킬 수 있지만, 다른 규칙을 사용하여 변수 할당 변경 및 서로 다른 시간에.


3

다른 답변을 보완하기 위해 동일한 위치에서 시작하여 데이터 구조를 다르게 탐색 할 수도 있습니다. 예를 들어,가있는 경우 다음 과 같은 BinaryTree a = new BinaryTree(...); BinaryTree b = a방법으로 트리의 가장 왼쪽 경로와 a가장 오른쪽 경로를 탐색 할 수 있습니다 b.

while (!a.equals(null) && !b.equals(null)) {
    a = a.left();
    b = b.right();
}

Java를 작성한 이후 오랜 시간이 지났으므로 코드가 정확하지 않거나 합리적이지 않을 수 있습니다. 의사 코드로 더 가져 가십시오.


3

이 메소드는 컨텍스트없이 사용할 수있는 다른 오브젝트를 다시 호출하는 여러 오브젝트가있는 경우 유용합니다.

예를 들어, 탭 인터페이스가있는 경우 Tab1, Tab2 및 Tab3이있을 수 있습니다. 코드를 단순화하기 위해 사용자가 어떤 탭에 있는지에 관계없이 공통 변수를 사용할 수도 있고, 사용자가 어떤 탭을 통해 즉시 탭을 파악해야 하는지를 줄일 수도 있습니다.

Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();

그런 다음 onClick의 각 숫자 탭에서 CurrentTab을 변경하여 해당 탭을 참조 할 수 있습니다.

CurrentTab = Tab3;

이제 코드에서 실제로 어떤 탭에 있는지 알 필요없이 "CurrentTab"을 불명료하게 호출 할 수 있습니다. CurrentTab의 속성을 업데이트 할 수도 있으며 참조 탭으로 자동 이동합니다.


3

유용하려면 알 수없는 " "에 대한 참조 b 여야 하는 많은 시나리오 a가 있습니다. 특히:

  • b컴파일 타임에 무엇을 가리키는 지 알 수 없을 때마다.
  • 컴파일 타임에 알려 지든 아니든 컬렉션을 반복해야 할 때마다
  • 범위가 제한 될 때마다

예를 들면 다음과 같습니다.

매개 변수

public void DoSomething(Thing &t) {
}

t 외부 범위의 변수에 대한 참조입니다.

반환 값 및 기타 조건부 값

Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;

biggerThingz중 하나에 각각의 참조입니다 a또는 b. 우리는 컴파일 타임에 어느 것을 모릅니다.

람다와 그 반환 값

Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);

x매개 변수 (위의 예 1)이고 t리턴 값입니다 (위의 예 2)

컬렉션

indexByName.add(t.name, t);
process(indexByName["some name"]);

index["some name"]보다 세련된 외관 b입니다. 컬렉션에 만들어지고 채워진 개체의 별칭입니다.

루프

foreach (Thing t in things) {
 /* `t` is a reference to a thing in a collection */
}

t 반복자 (이전 예)에 의해 반환 된 항목 (예 2)에 대한 참조입니다.


귀하의 예는 따르기가 어렵습니다. 나를 잘못 이해하지 마라, 나는 그들을 통과했다. 그러나 나는 그것을 해결해야했다. 앞으로는 코드 예제를 개별 블록으로 분리하고 (각각 구체적으로 설명하는 메모와 함께) 한 블록에 관련이없는 예제를 두지 않는 것이 좋습니다.
Bassinator

1
@HCBPshenanigans 선택한 답변을 변경할 것으로 기대하지는 않습니다. 그러나 가독성을 높이고 선택한 답변에서 누락 된 사용 사례를 채우도록 광산을 업데이트했습니다.
svidgen

2

중요한 점이지만 IMHO는 이해할 가치가 있습니다.

모든 OO 언어는 항상 참조의 사본을 만들고 절대 '비가 시적'으로 객체를 복사하지 않습니다. OO 언어가 다른 방식으로 작동하면 프로그램을 작성하는 것이 훨씬 어려울 것입니다. 예를 들어 함수와 메서드는 절대 개체를 업데이트 할 수 없습니다. Java 및 대부분의 OO 언어는 상당한 추가 복잡성 없이는 사용하기가 거의 불가능합니다.

프로그램의 객체는 어떤 의미가 있어야합니다. 예를 들어 실제 실제 세계에서 특정한 것을 나타냅니다. 일반적으로 같은 것에 대한 많은 참조를 갖는 것이 합리적입니다. 예를 들어, 집 주소는 많은 사람과 조직에 제공 될 수 있으며 해당 주소는 항상 동일한 물리적 위치를 나타냅니다. 첫 번째 요점은, 대상은 종종 구체적이거나 실제적이거나 구체적인 것을 나타냅니다. 따라서 같은 것에 대한 많은 참조를 가질 수 있다는 것은 매우 유용합니다. 그렇지 않으면 프로그램을 작성하기가 더 어려워집니다.

a호출
foo(Dog aDoggy);
하거나 메소드를 적용하는 다른 함수에 인수 / 매개 변수로 전달할 때마다 a기본 프로그램 코드는 참조의 사본을 만들어 동일한 객체에 대한 두 번째 참조를 생성합니다.

또한, 복사 된 참조가있는 코드가 다른 스레드에있는 경우, 둘 다 동시에 사용하여 동일한 오브젝트에 액세스 할 수 있습니다.

따라서 가장 유용한 프로그램에는 동일한 객체에 대한 여러 참조가 있습니다. 왜냐하면 대부분의 OO 프로그래밍 언어의 의미이기 때문입니다.

이제 우리가 그것에 대해 생각한다면, 참조로 전달하는 것이 많은 OO 언어에서 사용할 수 있는 유일한 메커니즘 이기 때문에 (C ++는 둘 다 지원), 이것이 '올바른' 기본 행동 일 것으로 기대할 수 있습니다.

IMHO는 참조를 사용 하는 것이 몇 가지 이유로 올바른 기본값 입니다.

  1. 서로 다른 두 곳에서 사용되는 객체의 가치가 동일하다는 것을 보장합니다. 객체를 두 개의 서로 다른 데이터 구조 (배열, 목록 등)에 넣고 객체를 변경하는 객체에 대해 일부 작업을 수행한다고 상상해보십시오. 디버깅하기에는 악몽이 될 수 있습니다. 더욱 중요한 것은 양 데이터 구조에서 동일한 객체, 또는 프로그램의 버그를 갖는다.
  2. 행복하게 코드를 여러 함수로 리팩토링하거나 여러 함수의 코드를 하나로 병합 할 수 있으며 시맨틱은 변경되지 않습니다. 언어가 참조 의미를 제공하지 않으면 코드를 수정하는 것이 훨씬 더 복잡합니다.

효율성 주장도 있습니다. 전체 객체를 복사하는 것은 참조를 복사하는 것보다 덜 효율적입니다. 그러나 나는 그것이 요점을 그리워한다고 생각합니다. 동일한 객체에 대한 여러 참조는 실제 물리적 세계의 의미와 일치하기 때문에 더 이해하기 쉽고 사용하기 쉽습니다.

따라서 IMHO, 일반적으로 동일한 객체에 대한 여러 참조를 갖는 것이 좋습니다. 알고리즘과 관련하여 의미가없는 비정상적인 경우 대부분의 언어는 '복제'또는 딥 카피를 만들 수있는 기능을 제공합니다. 그러나 이것이 기본값이 아닙니다.

이것이 기본값이 아니 어야한다고 주장하는 사람들 은 자동 가비지 수집을 제공하지 않는 언어를 사용하고 있다고 생각합니다 . 예를 들어 구식 C ++입니다. 문제는 '죽은'객체를 수집하고 여전히 필요할 수있는 객체를 회수하지 않는 방법을 찾아야한다는 것입니다. 동일한 객체에 대한 여러 참조가 있으면 어렵습니다.

C ++에 충분히 저렴한 가비지 수집이있어 모든 참조 된 객체가 가비지 수집되면 많은 반대 의견이 사라집니다. 참조 시맨틱 이 필요 하지 않은 경우가 여전히 있습니다 . 그러나 내 경험상 이러한 상황을 식별 할 수있는 사람들은 일반적으로 적절한 의미를 선택할 수 있습니다.

가비지 수집을 처리하거나 완화하기 위해 C ++ 프로그램에 많은 양의 코드가 있다는 증거가 있다고 생각합니다. 그러나 이러한 종류의 '인프라'코드를 작성하고 유지 관리하면 비용이 추가됩니다. 언어를보다 쉽게 ​​사용하거나 더 강력하게 만들 수 있습니다. 예를 들어, Go 언어는 C ++의 약점을 개선하는 데 중점을두고 설계되었으며 가비지 수집 이외의 선택은 없습니다.

물론 이것은 Java와 관련이 없습니다. 또한 사용하기 쉽게 설계되었으며 가비지 수집 기능도 있습니다. 따라서 여러 참조를 갖는 것이 기본 시맨틱이며, 참조가있는 동안 오브젝트가 회수되지 않는다는 점에서 비교적 안전합니다. 물론 프로그램이 실제로 객체로 끝났을 때 제대로 정리되지 않기 때문에 데이터 구조에 의해 유지 될 수 있습니다.

그래서, 당신은 당신의 질문으로 돌아갑니다 (약간 일반화), 당신은 언제 같은 객체에 대한 하나 이상의 참조를 원합니까? 내가 생각할 수있는 모든 상황에서 거의. 그것들은 대부분의 언어 매개 변수 전달 메커니즘의 기본 의미입니다. 현실 세계에 존재하는 객체를 다루는 기본 의미론은 거의 참조를 통해 이루어져야하기 때문입니다 ( '실제 객체가 존재하기 때문에).

다른 의미 체계는 다루기가 더 어려울 것입니다.

Dog a = new Dog("rover");  // initialise with name 
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")

"로버"는 dl영향을 setOwner받거나 프로그램을 작성, 이해, 디버그 또는 수정하기가 어려워 야합니다. 나는 대부분의 프로그래머가 당황하거나 당황 할 것이라고 생각한다.

나중에 개가 판매됩니다.

soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")

이러한 종류의 처리는 일반적이며 일반적입니다. 따라서 참조 의미론은 일반적으로 가장 의미가 있기 때문에 기본값입니다.

요약 : 항상 동일한 객체에 대한 여러 참조를 갖는 것이 좋습니다.


좋은 지적이지만 이것은 논평으로 더 적합합니다
Bassinator

@ HCBPshenanigans-요점이 너무 간결하고 말도 많이하지 않은 것 같습니다. 그래서 추론을 확장했습니다. 귀하의 질문에 대한 '메타'답변이라고 생각합니다. 요약하면, 동일한 객체에 대한 여러 참조는 프로그램을 작성하기 쉽도록 만드는 데 중요합니다. 프로그램의 많은 객체는 실제 세계에서 특정 또는 고유 한 객체를 나타 내기 때문입니다.
gbulmer

1

물론 다른 시나리오는 다음과 같습니다.

Dog a = new Dog();
Dog b = a;

코드를 유지 관리 b하고 다른 개 또는 다른 클래스로 사용되었지만 지금은 서비스를 제공합니다 a.

일반적으로 중기 적으로는 a직접 참조하기 위해 모든 코드를 다시 작성해야 하지만 곧바로 발생하지 않을 수 있습니다.


1

프로그램이 다른 구성 요소에서 사용하기 때문에 하나 이상의 스팟에서 엔티티를 메모리로 가져올 수있는 기회가있을 때마다 이것을 원할 것입니다.

아이덴티티 맵 은 두 개 이상의 개별 표현을 피할 수 있도록 명확한 로컬 엔티티 저장소를 제공했습니다. 동일한 객체를 두 번 나타낼 때 객체의 한 참조가 다른 인스턴스보다 먼저 상태 변경을 유지하는 경우 클라이언트는 동시성 문제를 일으킬 위험이 있습니다. 아이디어는 고객이 항상 엔티티 / 객체에 대한 명확한 참조를 처리하도록하기위한 것입니다.


0

스도쿠 솔버를 쓸 때 이것을 사용했습니다. 행을 처리 할 때 셀 수를 알면 열을 처리 할 때 포함 열에 해당 셀 번호도 알기를 원합니다. 따라서 열과 행은 모두 겹치는 Cell 객체의 배열입니다. 받아 들여진 대답과 정확히 같습니다.


-2

웹 애플리케이션에서 오브젝트 관계형 맵퍼는 지연로드를 사용하여 동일한 데이터베이스 오브젝트 (적어도 동일한 스레드 내)에 대한 모든 참조가 동일한 것을 가리 키도록 할 수 있습니다.

예를 들어, 테이블이 두 개인 경우 :

개 :

  • 아이디 | owner_id | 이름
  • 1 | 1 | 껍질 켄트

소유자 :

  • 아이디 | 이름
  • 1 | 나를
  • 2 | 당신

다음과 같은 호출이있을 경우 ORM이 수행 할 수있는 몇 가지 방법이 있습니다.

dog = Dog.find(1)  // fetch1
owner = Owner.find(1) // fetch2
superdog = owner.dogs.first() // fetch3
superdog.name = "Superdog"
superdog.save! // write1
owner = dog.owner // fetch4
owner.name = "Mark"
owner.save! // write2
dog.owner = Owner.find(2)
dog.save! // write3

순진 전략에서 모델 및 관련 참조에 대한 모든 호출은 별도의 개체를 검색합니다. Dog.find(), Owner.find(), owner.dogs, 및 dog.owner데이터베이스에 결과가 메모리에 저장되는 후 주위의 처음을 기록했다. 그래서 :

  • 데이터베이스를 4 번 이상 가져옵니다.
  • dog.owner는 superdog.owner와 동일하지 않습니다 (별도 가져 오기).
  • dog.name은 superdog.name과 다릅니다.
  • dog와 superdog은 같은 행에 쓰려고 시도하고 서로의 결과를 덮어 씁니다. write3은 write1의 이름 변경을 취소합니다.

참조가 없으면 더 많은 페치가 있고 더 많은 메모리를 사용하며 이전 업데이트를 덮어 쓸 가능성이 있습니다.

ORM이 dogs 테이블의 1 행에 대한 모든 참조가 동일한 것을 가리켜 야한다는 것을 알고 있다고 가정하십시오. 그때:

  • fetch4는 Owner.find (1)에 해당하는 메모리에 객체가 있으므로 제거 할 수 있습니다. fetch3은 소유자가 소유 한 다른 개가있을 수 있지만 여전히 행 검색을 트리거하지 않으므로 최소한 인덱스 스캔이 발생합니다.
  • dog와 superdog은 같은 객체를 가리 킵니다.
  • dog.name과 superdog.name은 같은 객체를 가리 킵니다.
  • dog.owner와 superdog.owner는 같은 개체를 가리 킵니다.
  • write3은 write1의 변경 사항을 겹쳐 쓰지 않습니다.

즉, 참조를 사용하면 데이터베이스의 행인 단일 진리 지점 (적어도 해당 스레드 내)의 원칙을 체계화하는 데 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.