C #과 Java가 왜 '=='의 기본값으로 참조 동등성을 사용합니까?


32

Java와 C # (및 다른 언어)이 기본적으로 동등성을 참조하는 이유를 잠시 고민해 왔습니다 ==.

내가하는 프로그래밍에서 (확실히 프로그래밍 문제의 작은 하위 집합 일뿐입니다), 참조 평등 대신 객체를 비교할 때 거의 항상 논리적 평등을 원합니다. 나는 왜 두 언어가 왜이 길을 거꾸로 돌리고 ==논리적 평등을 유지하고 .ReferenceEquals()참조 평등 을 위해 사용 하지 않는지를 생각하려고 노력했다 .

분명히 참조 평등을 사용하는 것은 구현하기가 매우 간단하고 매우 일관된 동작을 제공하지만 오늘날 내가 보는 대부분의 프로그래밍 방식과 잘 맞지 않는 것 같습니다.

논리적 비교를 구현하려는 문제를 무시하고 모든 클래스에서 구현해야한다는 것을 무시하고 싶지 않습니다. 또한 이러한 언어는 오래 전에 설계되었지만 일반적인 질문은 있음을 알고 있습니다.

내가 이것을 놓친 것에 대한 기본 설정의 주요 이점이 있습니까? 아니면 기본 동작이 논리적 평등이어야한다는 것이 합리적입니까, 그리고 참조 평등으로 다시 기본 설정하면 논리적 평등이 클래스에 존재하지 않습니다.


3
변수가 참조이기 때문에? 변수는 포인터처럼 행동하기 때문에 변수처럼 비교하는 것이 합리적입니다.
Daniel Gratzer

C #은 구조체와 같은 값 형식에 논리적 평등을 사용합니다. 그러나 "기본 논리적 평등"은 다른 참조 유형의 두 객체에 대해 무엇이어야합니까? 또는 하나가 B에서 상속 된 A 유형의 두 개체에 대해? 구조체처럼 항상 "거짓"? 동일한 객체가 두 번 참조 된 경우에도 먼저 A, B? 나에게는별로 이해가되지 않습니다.
Doc Brown

3
즉, C #에서 왜 재정의 Equals()하면의 동작을 자동으로 변경하지 않는지 묻고 ==있습니까?
svick 2016 년

답변:


29

C #은 Java가했기 때문에 그렇게합니다. Java는 연산자 오버로드를 지원하지 않기 때문에 Java가 수행되었습니다. 각 클래스마다 값 평등을 재정의해야하므로 연산자가 아니라 메서드 여야합니다. IMO 이것은 잘못된 결정이었습니다. a == b보다 쓰기와 읽기가 훨씬 쉽고 a.equals(b)C 또는 C ++ 경험이있는 프로그래머에게는 훨씬 더 자연 스럽지만 a == b거의 항상 틀립니다. 필요한 ==.equals을 사용하는 데 따른 버그는 수많은 프로그래머 시간을 낭비했습니다.


7
나는 혼란스러운 사람들만큼 연산자 과부하를지지하는 사람들이 많다고 생각하므로 절대적인 진술로 "잘못된 결정이었다"고 말하지 않을 것입니다. 예 : 내가 일하는 C ++ 프로젝트에서 우리는 ==많은 클래스에 과부하를가했으며 몇 개월 전에 몇 명의 개발자 ==가 실제로 무엇 을하고 있는지 알지 못했다는 것을 발견했습니다 . 일부 구문의 의미가 명확하지 않은 경우 항상 이러한 위험이 있습니다. 이 equals()표기법은 사용자 정의 방법을 사용하고 있으며 어딘가에서 찾아야한다고 말합니다. 결론 : 운영자 과부하는 일반적으로 공개적인 문제라고 생각합니다.
조르지오

9
Java에는 사용자 정의 연산자 오버로드 가 없다고 말하고 싶습니다 . 많은 연산자는 Java에서 이중 (과부하 된) 의미를 갖습니다. 봐 +동시에 문자열의 연접 (숫자 값)을 첨가 않는, 예를 들면.
Joachim Sauer

14
a == bC는 사용자 정의 연산자 오버로드를 지원하지 않기 때문에 C 경험이있는 프로그래머에게 더 자연스러운 방법은 무엇입니까? (예를 들어, 문자열을 비교하는 C의 방법입니다 strcmp(a, b) == 0하지 a == b.)
svick

이것은 기본적으로 내가 생각한 것이지만, 더 많은 경험이있는 사람들에게 내가 분명한 것을 놓치고 있지 않은지 확인하도록 부탁했습니다.
지퍼

4
@ svick : C에는 문자열 유형이나 참조 유형이 없습니다. 문자열 작업은을 통해 수행됩니다 char *. 두 포인터가 동일한 지 비교하는 것은 문자열 비교와 동일하지 않습니다.
케빈 클라인

15

짧은 대답 : 일관성

그러나 귀하의 질문에 올바르게 대답하기 위해 한 걸음 물러서서 프로그래밍 언어에서 평등의 의미 에 대한 문제를 살펴 보는 것이 좋습니다 . 다양한 언어로 사용되는 적어도 가지 다른 가능성이 있습니다.

  • 참조 평등 : a와 b가 같은 객체를 참조하면 a = b가 참임을 의미합니다. a와 b의 모든 속성이 동일하더라도 a와 b가 다른 객체를 참조하면 사실이 아닙니다.
  • 얕은 동등성 : a와 b가 참조하는 객체의 모든 속성이 동일한 경우 a = b가 참임을 의미합니다. 두 객체를 나타내는 메모리 공간을 비트 단위로 비교하면 얕은 동등성을 쉽게 구현할 수 있습니다. 참조 평등은 얕은 평등을 의미합니다.
  • 깊은 평등 : a와 b의 각 속성이 동일하거나 매우 같으면 a = b가 참임을 의미합니다. 깊은 평등은 참조 평등과 얕은 평등에 의해 암시됩니다. 이런 의미에서 깊은 평등은 가장 약한 형태의 평등이며 기준 평등은 가장 강합니다.

이 세 가지 유형의 평등은 구현하기 편리하기 때문에 자주 사용됩니다. 세 개의 평등 검사는 모두 컴파일러에 의해 쉽게 생성 될 수 있습니다 (심도 평등의 경우 컴파일러는 태그 비트를 사용하여 무한 루프를 방지해야합니다. 순환 참조가 있습니다). 그러나 또 다른 문제가 있습니다.이 중 어느 것도 적절하지 않을 수 있습니다.

사소한 시스템에서 객체의 평등은 종종 깊은 평등과 참조 평등 사이의 무언가로 정의됩니다. 특정 상황에서 두 객체를 동일한 것으로 간주할지 여부를 확인하기 위해 일부 속성은 메모리의 위치에 따라 다른 속성과 깊은 평등으로 비교해야하지만 일부 속성은 완전히 다른 것으로 허용 될 수 있습니다. 우리가 정말로 좋아하는 것은 종종 문학 의미 론적 평등 에서 불리는“제 4의 평등” 입니다. 우리의 영역에서 상황이 동일하면 동일합니다. =)

그래서 우리는 당신의 질문으로 돌아갈 수 있습니다 :

내가 이것을 놓친 것에 대한 기본 설정의 주요 이점이 있습니까? 아니면 기본 동작이 논리적 평등이어야한다는 것이 합리적입니까?

어떤 언어로든 'a == b'를 쓸 때 무엇을 의미합니까? 이상적으로는 항상 동일해야합니다. 의미 적 평등. 그러나 그것은 불가능합니다.

주요 고려 사항 중 하나는 적어도 숫자와 같은 간단한 유형의 경우 동일한 값을 지정한 후에 두 변수가 동일 할 것으로 예상합니다. 아래를보십시오 :

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

이 경우 두 진술에서 모두 'a는 b'입니다. 다른 것은 미쳤을 것입니다. 대부분의 언어는이 규칙을 따릅니다. 따라서 단순한 유형 (일명 값)을 사용하여 의미 적 평등을 달성하는 방법을 알고 있습니다. 객체를 사용하면 완전히 다를 수 있습니다. 아래를보십시오 :

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

우리는 첫 번째 'if'가 항상 사실이라고 기대합니다. 그러나 두 번째 'if'에서 무엇을 기대하십니까? 정말 달려 있습니다. 'DoSomething'이 a와 b의 (의미 적) 평등을 바꿀 수 있습니까?

시맨틱 평등의 문제점은 컴파일러가 객체에 대해 자동으로 생성 할 수 없으며 할당에서 분명하지 않다는 것 입니다. 사용자가 의미 적 동등성을 정의 할 수있는 메커니즘이 제공되어야합니다. 객체 지향 언어에서이 메커니즘은 상속 된 메소드입니다 : equals . OO 코드를 읽으면 모든 클래스에서 메소드가 정확히 동일한 구현을 기대하지는 않습니다. 우리는 상속과 오버로드에 익숙합니다.

그러나 연산자도 동일한 동작을 기대합니다. 'a == b'가 표시되면 모든 상황에서 동일한 유형의 동등 함 (위의 4부터)을 예상해야합니다. 따라서 일관성유지하기 위해 언어 설계자는 모든 유형에 대해 참조 평등을 사용했습니다. 프로그래머가 메소드를 대체했는지 여부에 의존해서는 안됩니다.

추신 : 언어 Dee는 Java 및 C #과 약간 다릅니다. equals 연산자는 간단한 유형의 경우 얕은 평등을 의미하고 사용자 정의 클래스의 의미 적 평등을 의미합니다 (= 사용자와 거짓말하는 = 연산을 구현해야 함-기본값은 제공되지 않음). 단순 유형의 경우 얕은 평등은 항상 시맨틱 평등이므로 언어는 일관성이 있습니다. 그러나 지불하는 가격은 equals 연산자가 기본적으로 사용자 정의 유형에 대해 정의되어 있지 않다는 것입니다. 당신은 그것을 구현해야합니다. 때로는 지루합니다.


2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Java의 언어 디자이너는 객체의 참조 평등과 프리미티브의 시맨틱 평등을 사용했습니다. 이것이 올바른 결정인지, 또는이 결정이 ==객체의 의미 론적 동등성을 위해 과부하되는 것을 허용 하는 것보다 더 "일관 적"이라는 것은 분명하지 않습니다 .
Charles Salvia

그들은 프리미티브에 대해서도 "동등한 참조 평등"을 사용했습니다. "int i = 3"을 사용하면 숫자에 대한 포인터가 없으므로 참조를 사용할 수 없습니다. "일종의"기본 유형 인 문자열을 사용하면 더 분명합니다. ".intern ()"또는 직접 할당 (String s = "abc")을 사용하여 == (참조 평등)을 사용해야합니다.
Hbas

1
추신 : C #은 문자열과 일치하지 않았습니다. 이 경우 IMHO는 훨씬 좋습니다.
Hbas

@CharlesSalvia : 자바, 경우 ab동일한 유형의 표현입니다 a==b여부를 테스트 ab같은 일을 누릅니다. 그중 하나가 객체 # 291에 대한 참조를 보유하고 다른 하나는 객체 # 572에 대한 참조를 보유하는 경우 동일한 것을 보유하지 않습니다. 객체 # 291과 # 572 의 내용 은 동일 할 수 있지만 변수 자체는 서로 다른 것을 보유합니다.
supercat

2
@CharlesSalvia 그것은 당신 a == b이 무엇을 보고 알 수있는 방식으로 설계되었습니다 . 마찬가지로 a.equals(b)과부하가 발생하는 것을 보고 추정 할 수 있습니다 equals. 만약 a == b전화 a.equals(b)(구현 된 경우)가 참조 또는 콘텐츠에 의해 비교입니까? 기억 나지 않습니까? 클래스 A를 확인해야합니다. 코드가 무엇인지 확실하지 않으면 코드를 더 이상 빨리 읽을 수 없습니다. 서명이 같은 메소드가 허용 된 것처럼, 호출되는 메소드는 현재 범위가 무엇인지에 따라 다릅니다. 그러한 프로그램은 읽을 수 없습니다.
Neil

0

이 두 언어가 왜이 경로를 거꾸로 돌리고 == 논리적 평등을 유지하고 참조 평등을 위해 .ReferenceEquals ()를 사용하는 대신이 경로를 사용했는지 이유를 생각하려고했습니다.

후자의 접근 방식은 혼란 스러울 수 있기 때문입니다. 치다:

if (null.ReferenceEquals(null)) System.out.println("ok");

이 코드가 인쇄되어야합니까 "ok", 아니면 NullPointerException?


-2

Java 및 C #의 경우 객체 지향적이라는 이점이 있습니다.

A로부터 보기의 성능 지점 - 쉽게 쓰기 코드도 빨리해야한다 : OOP는 객체가 매우 커질 수 있음을 고려하여, 참조 평등을 확인하는 것이 빠를 것, 다른 객체에 의해 표현 될 논리적으로 별개의 요소 의도 때문이다.

A로부터 뷰의 논리적 점 - (. 예를 널 (null) == null이 논리적으로 해석하는 방법이 경우에 경우 다를 수 있습니까?) 다른 개체의 평등 평등에 대한 개체의 속성에 비교으로 명백한으로 할 필요가 없습니다.

나는 그것이 "참조 평등보다 항상 논리적 평등을 원한다"는 당신의 관찰이라고 생각합니다. 언어 디자이너들 사이의 합의는 아마도 반대 일 것입니다. 광범위한 프로그래밍 경험이 없기 때문에 개인적으로 이것을 평가하기가 어렵다는 것을 알게되었습니다. 대략, 나는 최적화 알고리즘에서 참조 평등을 더 사용하고 데이터 세트를 처리하는 데 논리적 평등을 더 사용합니다.


7
참조 평등은 객체 지향과 관련이 없습니다. 실제로 반대 방향입니다. 객체 지향의 기본 속성 중 하나는 동일한 동작을 가진 객체를 구별 할 수 없다는 것입니다. 한 객체는 다른 객체를 시뮬레이션 할 수 있어야합니다. (결국 OO는 시뮬레이션을 위해 발명되었습니다!) 참조 평등을 사용하면 동일한 동작을 가진 두 개의 서로 다른 객체를 구별 할 수 있으며 시뮬레이션 된 객체와 실제 객체를 구별 할 수 있습니다. 따라서 Reference Equality 객체 방향을 손상 시킵니다. OO 프로그램 Reference Equality를 사용 해서는 안됩니다 .
Jörg W Mittag 2018 년

@ JörgWMittag : 객체 지향 프로그램을 올바르게 수행하려면 객체 X의 상태가 Y (잠재적으로 일시적인 상태)와 같은지 여부를 묻는 방법과 객체 X에게 Y와 같은지 여부를 묻는 방법이 있어야합니다. [X는 상태가 영원히 Y와 동일하다는 것이 보장되는 경우에만 Y와 같습니다]. 동등성과 상태 평등에 대해 별도의 가상 방법을 사용하는 것이 좋지만 많은 유형의 경우 참조 부등식은 동등하지 않음을 의미하며 가상 메소드 디스패치에 시간을 투자하여이를 증명할 이유가 없습니다.
supercat

-3

.equals()변수를 내용으로 비교합니다. 그 대신 ==내용으로 객체를 비교합니다 ...

객체를 사용하는 것이 더 정확합니다 .equals()


3
당신은 잘못된 가정을합니다. .equals ()는 .equals ()가 코딩 한 모든 작업을 수행합니다. 일반적으로 내용에 의한 것이지만 반드시 그럴 필요는 없습니다. 또한 .equals ()를 사용하는 것이 더 정확하지 않습니다. 그것은 당신이 성취하려고하는 것에 달려 있습니다.
지퍼
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.