C #과 Java의 삼항 연산자 (? :)의 차이점


94

저는 C # 초보자이고 문제가 발생했습니다. 삼항 연산자 ( ? :)를 다룰 때 C #과 Java에는 차이가 있습니다 .

다음 코드 세그먼트에서 네 번째 줄이 작동하지 않는 이유는 무엇입니까? 컴파일러는 오류 메시지를 표시합니다 there is no implicit conversion between 'int' and 'string'. 다섯 번째 줄도 작동하지 않습니다. 둘 다 List객체 죠, 그렇죠?

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

그러나 동일한 코드가 Java에서 작동합니다.

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

C #에서 누락 된 언어 기능은 무엇입니까? 있는 경우 추가되지 않는 이유는 무엇입니까?


4
당으로 msdn.microsoft.com/en-us/library/ty67wk28.aspx "first_expression 및 second_expression의 유형 중 하나를 동일해야합니다, 또는 암시 적 변환은 한 유형에서 다른 존재해야합니다."
Egor

2
나는 한동안 C #을하지 않았지만 당신이 인용 한 메시지가 그것을 말하지 않습니까? 삼항의 두 가지 분기는 동일한 데이터 유형을 반환해야합니다. int와 String을 제공합니다. C #은 int를 String으로 자동 변환하는 방법을 모릅니다. 그것에 명시적인 형식 변환을 넣어야합니다.
Jay

2
@ blackr1234 an int은 개체가 아니기 때문입니다. 원시적입니다.
Code-Apprentice

3
@ Code-Apprentice 예 그들은 실제로 매우 다릅니다 -C #에는 런타임 수정 기능이 있으므로 목록은 관련없는 유형입니다. 아, 그리고 원한다면 일반적인 인터페이스 공분산 /
반반 변성을 혼합에 던져서

3
영업의 이익을 위해 : 자바 수단에 입력 삭제한다는 ArrayList<String>ArrayList<Integer>단지가 될 ArrayList바이트 코드에. 즉 , 런타임에 정확히 동일한 유형으로 나타납니다 . 분명히 C #에서는 다른 유형입니다.
Code-Apprentice

답변:


106

관통 찾고 C 것은 # 5 언어 사양 섹션 7.14 : 조건부 연산자 우리는 다음을 볼 수 있습니다 :

  • x에 X 유형이 있고 y에 Y 유형이 있으면

    • 암시 적 변환 (§6.1)이 X에서 Y로 존재하지만 Y에서 X로가 아닌 경우 Y는 조건식의 유형입니다.

    • 암시 적 변환 (§6.1)이 Y에서 X로 존재하지만 X에서 Y로가 아닌 경우 X는 조건식의 유형입니다.

    • 그렇지 않으면 식 유형을 확인할 수 없으며 컴파일 타임 오류가 발생합니다.

즉, x와 y가 서로 변환 될 수 있는지 여부를 찾으려고 시도 하고 그렇지 않으면 컴파일 오류가 발생합니다. 우리의 경우 intstring는 컴파일되지 않도록 명시 적 또는 암시 적 변환이 없습니다.

이것을 Java 7 언어 사양 섹션 15.25 : 조건부 연산자 와 대조하십시오 .

  • 두 번째 및 세 번째 피연산자가 동일한 유형 (널 유형일 수 있음)을 갖는 경우 이것이 조건식의 유형입니다. ( 아니요 )
  • 두 번째 및 세 번째 피연산자 중 하나가 기본 유형 T이고 다른 유형이 T에 boxing 변환 (§5.1.7)을 적용한 결과 인 경우 조건식의 유형은 T입니다. ( NO )
  • 두 번째 및 세 번째 피연산자 중 하나가 널 유형이고 다른 유형이 참조 유형 인 경우 조건식의 유형은 해당 참조 유형입니다. ( 아니요 )
  • 그렇지 않고 두 번째 및 세 번째 피연산자에 숫자 유형으로 변환 할 수있는 유형 (§5.1.8)이있는 경우 다음과 같은 몇 가지 경우가 있습니다. ( NO )
  • 그렇지 않으면 두 번째 및 세 번째 피연산자는 각각 S1 및 S2 유형입니다. T1을 S1에 권투 변환을 적용한 결과 유형이고 T2를 S2에 권투 변환을 적용한 결과 유형이라고 가정합니다.
    조건식의 유형은 캡처 변환 (§5.1.10)을 lub (T1, T2) (§15.12.2.7)에 적용한 결과입니다. ( )

그리고 섹션 15.12.2.7살펴보십시오. 실제 인수를 기반으로 유형 인수 추론 우리는 호출에 사용되는 유형으로 사용할 공통 조상을 찾으려고 시도하는 것을 볼 수 있습니다 Object. Object 입니다 전화가 작동 할 수 있도록 허용 인수.


1
적어도 한 방향으로 암시 적 변환이 있어야한다고 C # 사양을 읽었습니다. 컴파일러 오류는 어느 방향으로도 암시 적 변환이없는 경우에만 발생합니다.
Code-Apprentice

1
물론 이것은 사양에서 직접 인용하기 때문에 여전히 가장 완전한 대답입니다.
Code-Apprentice

이것은 실제로 Java 5에서만 변경되었습니다 (내 생각에는 6 일 수도 있습니다). 그 전에 Java는 C #과 똑같은 동작을했습니다. 열거 형이 구현되는 방식으로 인해 C #보다 Java에서 더 많은 놀라움이 발생했을 것입니다.
Voo

@Voo : 참고로, 자바 5와 Java 6은 동일한 사양 버전 (이 Java 언어 사양 은 확실히 6. 자바로 변경하지 않은, 그래서, 제 3 판)을
ruakh

86

주어진 대답은 좋습니다. 이 C # 규칙은보다 일반적인 디자인 지침의 결과라고 덧붙일 것입니다. 여러 선택 항목 중 하나에서 식의 유형을 추론하라는 요청을 받으면 C #은 그중 가장 고유 한 항목을 선택 합니다 . 즉, C #에 "Giraffe, Mammal, Animal"과 같은 선택 항목을 제공하면 상황에 따라 가장 일반적인 동물 (동물)을 선택하거나 가장 구체적인 기린을 선택할 수 있습니다. 그러나 실제로 주어진 선택 사항 중 하나를 선택해야합니다 . C #은 "내 선택은 고양이와 개 사이에 있으므로 동물이 최선의 선택이라고 추론 할 것입니다"라고 말하지 않습니다. 그것은 주어진 선택이 아니기 때문에 C #은 그것을 선택할 수 없습니다.

삼항 연산자의 경우 C #은 더 일반적인 유형의 int 및 string을 선택하려고하지만 더 일반적인 유형은 아닙니다. 개체와 같이 처음에 선택되지 않은 유형을 선택하는 대신 C #은 유형을 유추 할 수 없다고 결정합니다.

또한 이것이 C #의 또 다른 디자인 원칙과 일치한다는 점에 유의합니다. 문제가 발생하면 개발자에게 알려주십시오. 그 언어는 "나는 당신이 의미하는 바를 추측하고 내가 할 수만 있다면 뒤죽박죽 이겠지"라고 말하지 않습니다. 언어는 "당신이 여기에 헷갈리는 것을 썼다고 생각합니다. 그리고 그것에 대해 말씀 드리겠습니다."

또한 C #은 변수 에서 할당 된 값으로 추론하는 것이 아니라 다른 방향으로 추론합니다 . C #은 "객체 변수에 할당하고 있으므로 표현식을 객체로 변환 할 수 있어야합니다. 따라서 확실히 할 것입니다"라고 말하지 않습니다. 오히려 C #은 "이 식에는 형식이 있어야하며 형식이 개체와 호환되는지 추론 할 수 있어야합니다."라고 말합니다. 표현식에 유형이 없으므로 오류가 발생합니다.


6
나는 왜 갑작스러운 공분산 / 반반 변성에 관심이 있는지 궁금해하는 첫 번째 문단을 지나갔고, 두 번째 문단에 대해 더 동의하기 시작했고, 누가 답을 썼는지 보았습니다. 예를 들어 '기린'을 정확히 얼마나 사용하는지 알아 본 적이 있습니까? 정말 기린을 좋아하시 나봐요.
Pharap

21
기린은 굉장합니다!
Eric Lippert

9
@Pharap : 진지하게 기린을 많이 사용하는 교육 학적 이유가 있습니다. 우선, 기린은 전 세계적으로 잘 알려져 있습니다. 둘째, 그것은 일종의 재미있는 말이고, 그들은 너무 이상해 보여서 기억에 남는 이미지입니다. 셋째, 같은 비유에서 "기린"과 "거북이"를 사용하는 것은 "이 두 클래스가 기본 클래스를 공유 할 수 있지만 매우 다른 특성을 가진 매우 다른 동물"임을 강력하게 강조합니다. 유형 시스템에 대한 질문에는 유형이 논리적으로 매우 유사한 예가있어 직관을 잘못된 방식으로 유도하는 경우가 많습니다.
Eric Lippert

7
@Pharap : 즉, 일반적으로 "고객 송장 공급자 공장 목록을 송장 공급자 공장 목록으로 사용할 수없는 이유는 무엇입니까?"와 같은 질문입니다. 그리고 당신의 직감은 "네, 그것들은 기본적으로 같은 것입니다"라고 말하지만 "왜 기린으로 가득 찬 방을 포유류로 가득 찬 방으로 사용할 수 없습니까?" 그런 다음 "포유류로 가득 찬 방에는 호랑이가있을 수 있기 때문에 기린으로 가득 찬 방에는 할 수 없기 때문에"라고 말하는 것이 훨씬 더 직관적 인 비유가됩니다.
Eric Lippert

6
@Eric 나는 원래 int? b = (a != 0 ? a : (int?) null). 측면에 연결된 모든 질문함께이 질문 도 있습니다 . 링크를 계속 따라 가면 상당수가 있습니다. 이에 비해 Java가 수행하는 방식과 관련하여 실제 문제에 직면 한 사람은 들어 본 적이 없습니다.
BlueRaja-Danny Pflughoeft

24

제네릭 부분에 관하여 :

two > six ? new List<int>() : new List<string>()

C #에서 컴파일러는 오른쪽 식 부분을 일반적인 형식 으로 변환 하려고 합니다. 이후 List<int>List<string>두 가지 구성 유형이 있습니다, 하나는 다른 변환 할 수 없습니다.

Java에서 컴파일러는 변환하는 대신 공통 상위 유형찾으려고 하므로 코드 컴파일에는 암시 적 와일드 카드 사용 및 유형 삭제가 포함됩니다 .

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

컴파일 유형 ArrayList<?>(실제로 는 공통 일반 상위 유형이므로 사용 컨텍스트에 따라 ArrayList<? extends Serializable>또는 ArrayList<? extends Comparable<?>>일 수 있음 ) 및 원시 런타임 유형 ArrayList(공통 원시 상위 유형이므로)이 있습니다.

예를 들어 (직접 테스트) ,

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED

실제로도 Write(two > six ? new List<object>() : new List<string>());작동하지 않습니다.
blackr1234

2
@ blackr1234 정확히 : 다른 답변이 이미 말한 것을 반복하고 싶지 않았지만 삼항 식은 유형으로 평가해야하며 컴파일러는 둘 중 "가장 낮은 공통 분모"를 찾으려고 시도하지 않습니다.
Mathieu Guindon

2
실제로 이것은 유형 삭제에 관한 것이 아니라 와일드 카드 유형에 관한 것입니다. 의 소거 ArrayList<String>ArrayList<Integer>ArrayList(원시 형)이지만,이 삼원 연산자 유추 유형은 ArrayList<?>(와일드 타입). 보다 일반적으로 Java 런타임이 유형 삭제를 통해 제네릭을 구현 하는 것은 표현식 의 컴파일 시간 유형에 영향을 미치지 않습니다 .
meriton

1
@vaxquis 감사합니다! 나는 자바 제네릭 ;-) 저를 혼동 C #을 사람이야
마티유 Guindon

2
사실, 유형 two > six ? new ArrayList<Integer>() : new ArrayList<String>()IS ArrayList<? extends Serializable&Comparable<?>>당신이 유형의 변수에 할당 할 수 있습니다 의미 ArrayList<? extends Serializable>뿐만 아니라 유형의 변수 ArrayList<? extends Comparable<?>>만 물론, ArrayList<?>동등하다, ArrayList<? extends Object>뿐만 아니라 작동합니다.
Holger

6

Java 및 C # (및 대부분의 다른 언어)에서 표현식의 결과에는 유형이 있습니다. 삼항 연산자의 경우 결과에 대해 평가되는 두 개의 가능한 하위 표현식이 있으며 둘 다 동일한 유형을 가져야합니다. Java의 경우 autoboxing int을 통해 변수를로 변환 할 수 있습니다 Integer. 이제 둘 다 Integer에서 String상속 받으 므로 Object간단한 축소 변환을 통해 동일한 유형으로 변환 할 수 있습니다.

반면에 C #에서 an int은 기본 형식이며 string또는 다른 object.


답변 해주셔서 감사합니다. 오토 박싱은 제가 현재 생각하고있는 것입니다. C #은 오토 박싱을 허용하지 않습니까?
blackr1234

2
개체에 대한 int를 복싱하는 것이 C #에서 완벽하게 가능하기 때문에 이것이 정확하다고 생각하지 않습니다. 여기서 차이점은 C #이 허용 여부를 결정할 때 매우 느긋한 접근 방식을 취한다는 것입니다.
Jeroen Vannevel

9
이것은 C #에서와 마찬가지로 자동 복싱과 관련이 없습니다. 차이점은 C #은 공통 상위 유형을 찾으려고 시도하지 않는 반면 Java는 Java 5 이후에만 가능합니다. 두 개의 사용자 정의 클래스 (둘 다 객체에서 분명히 상 속됨)로 시도하면 어떤 일이 발생하는지 확인하여 쉽게 테스트 할 수 있습니다. 또한에서 int로의 암시 적 변환이 있습니다 object.
Voo

3
그리고 조금 더 nitpick에 :에서 변환 Integer/String에가 Object, 그것은 :-) 정반대 축소 변환입니다하지 않습니다
Voo

1
Integer그리고 String또한 구현 SerializableComparable그들 중 하나에 할당뿐만 아니라, 예를 작동 할 수 있도록 Comparable<?> c=condition? 6: "6";또는 List<? extends Serializable> l = condition? new ArrayList<Integer>(): new ArrayList<String>();법적 자바 코드입니다.
Holger

5

이것은 매우 간단합니다. string과 int 사이에는 암시 적 변환이 없습니다. 삼항 연산자는 동일한 유형을 갖기 위해 마지막 두 피연산자가 필요합니다.

시험:

Write(two > six ? two.ToString() : "6");

11
문제는 Java와 C #의 차이점입니다. 이것은 질문에 대한 답이 아닙니다.
Jeroen Vannevel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.