Java 8 Comparator 유형 추론에 의해 매우 혼동 됨


84

특히 정적 메서드 사용과 람다 식에 매개 변수 형식이 필요한지 여부와 관련하여 Collections.sort및 의 차이점을 살펴 보았습니다 . 시작하기 전에, 예 를 들어 내 문제를 극복하기 위해 메소드 참조를 사용할 수 있다는 것을 알고 있지만 여기에서 내 쿼리는 수정하고 싶은 것이 아니라 대답을 원하는 것입니다. 즉, Java 컴파일러가 이러한 방식으로 처리하는 이유 .list.sortComparatorSong::getTitle

이것이 내 발견입니다. 일부 노래가 추가 된 ArrayList유형 이 있다고 가정 Song하면 세 가지 표준 get 메소드가 있습니다.

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

다음은 작동하는 두 가지 유형의 정렬 방법에 대한 호출입니다.

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

체인을 시작하자마자 thenComparing다음이 발생합니다.

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

즉, p1더 이상 유형을 알지 못하기 때문에 구문 오류 입니다. 이 문제를 해결하기 위해 Song비교의 첫 번째 매개 변수에 유형 을 추가합니다 .

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

이제 혼란스러운 부분이 있습니다. p laylist1.sort, 즉 List의 경우 다음 thenComparing호출 모두에 대해 모든 컴파일 오류를 해결 합니다. 그러나의 Collections.sort경우 첫 번째 문제는 해결하지만 마지막 문제는 해결하지 않습니다. 나는 몇 가지 추가 호출을 테스트 했으며 매개 변수를 thenComparing입력하지 않는 한 항상 마지막 호출에 대한 오류를 표시합니다 (Song p1).

이제를 만들고 TreeSet사용하여 이것을 추가로 테스트했습니다 Objects.compare.

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

에서와 동일한 일이 발생합니다 TreeSet.에는 컴파일 오류가 없지만 Objects.compare마지막 호출에 thenComparing오류가 표시됩니다.

누구든지 이것이 왜 발생하는지 그리고 (Song p1)단순히 비교 메서드를 호출 할 때 (추가 thenComparing호출 없이 ) 사용할 필요가없는 이유를 설명해 주시겠습니까?

동일한 주제에 대한 또 다른 쿼리는 다음과 같이 할 때입니다 TreeSet.

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

Song, 비교 메서드 호출에 대한 첫 번째 람다 매개 변수에서 유형 을 제거하면 비교 호출과 첫 번째 호출에서 구문 오류가 표시 thenComparing되지만 최종 호출에는 표시되지 않습니다 thenComparing. 위에서 발생한 것과 거의 반대입니다! 다른 모든 3 예로, 즉 위해, 반면에 Objects.compare, List.sort그리고 Collections.sort그 첫번째 제거 할 때 Song모든 통화에 대해 PARAM 유형이 쇼 구문 오류를.

미리 감사드립니다.

Eclipse Kepler SR2에서 수신 한 오류의 스크린 샷을 포함하도록 편집되었습니다. 이제 이클립스에만 해당됩니다. 명령 줄에서 JDK8 자바 컴파일러를 사용하여 컴파일 할 때 정상적으로 컴파일되기 때문입니다.

Eclipse의 정렬 오류


모든 테스트에서 얻은 모든 컴파일 오류 메시지를 질문에 포함하면 도움이 될 것입니다.
Eran

1
솔직히 말해서 누군가가 소스 코드를 직접 실행하여 문제가 무엇인지 확인하는 것이 가장 쉬울 것이라고 생각합니다.
Tranquility

의 유형은 무엇입니까 t1t2Objects.compare예는? 나는 그것들을 추론하려고하는데 컴파일러의 유형 추론에 대한 내 유형 추론을 계층화하는 것은 다루기 어렵다. :-)
Stuart Marks

1
또한 어떤 컴파일러를 사용하고 있습니까?
Stuart Marks

1
여기에 두 가지 문제가 있습니다. 응답자 중 한 명이 방법 참조를 사용할 수 있다고 지적했습니다. 람다가 "명시 적으로 유형이 지정됨"및 "암시 적으로 유형이 지정된"버전으로 제공되는 것처럼 메서드 참조는 "정확한"(한 번의 오버로드) 및 "비정상적인"(여러 개의 오버로드) 버전으로 제공됩니다. 정확한 메서드 참조 또는 명시 적 람다를 사용하여 추가 입력 정보를 제공 할 수 있습니다. (명시 형 증인 및 캐스트도 사용하지만, 종종 더 큰 망치입니다 수 있습니다.)
브라이언 게츠

답변:


105

첫째, 당신이 말하는 모든 예제는 참조 구현 (JDK 8의 javac)을 사용하여 오류가 잘 컴파일됩니다. 또한 IntelliJ에서도 잘 작동하므로보고있는 오류는 Eclipse에만 해당됩니다.

귀하의 근본적인 질문은 "연결을 시작할 때 작동이 중지되는 이유"입니다. 그 이유는 람다 식과 제네릭 메서드 호출이 메서드 매개 변수로 나타날 때는 폴리 식 (그 유형은 상황에 따라 다름)이지만 메서드 수신자 식으로 대신 나타날 때는 그렇지 않기 때문입니다.

네가 얘기 할 때

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

의 유형 인수 comparing()및 인수 유형 둘 다에 대해 해결할 충분한 유형 정보가 p1있습니다. comparing()호출의 서명에서 목표 형식을 가져옵니다 Collections.sort, 그래서 알려져있다 comparing()을 반환해야합니다 Comparator<Song>, 따라서 p1해야합니다 Song.

그러나 연결을 시작할 때 :

Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));

이제 우리는 문제가 있습니다. 복합 표현식 comparing(...).thenComparing(...)의 대상 유형 이라는 것을 알고 Comparator<Song>있지만 체인에 대한 수신자 표현식 comparing(p -> p.getTitle())은 일반 메소드 호출이고 다른 인수에서 유형 매개 변수를 유추 할 수 없기 때문에 운이 좋지 않습니다. . 이 식의 유형을 모르기 때문에 thenComparing메서드 등 이 있는지 알 수 없습니다 .

이 문제를 해결하는 방법에는 여러 가지가 있으며, 모두 더 많은 유형 정보를 주입하여 체인의 초기 객체를 올바르게 입력 할 수 있습니다. 다음은 욕구 감소와 침입 증가의 대략적인 순서입니다.

  • 와 같은 정확한 메서드 참조 (오버로드가없는 참조)를 사용합니다 Song::getTitle. 그러면 comparing()호출에 대한 유형 변수를 추론 할 수있는 충분한 유형 정보가 제공되므로 유형을 지정하고 체인 아래로 계속 진행합니다.
  • 예제에서했던 것처럼 명시 적 람다를 사용합니다.
  • comparing()호출에 대한 유형 증인을 제공하십시오 Comparator.<Song, String>comparing(...)..
  • 수신자 표현식을로 캐스트하여 캐스트가있는 명시 적 대상 유형을 제공하십시오 Comparator<Song>.

13
해결 방법 / 솔루션을 제공하는 대신 실제로 OP "컴파일러가 이것을 추론 할 수없는 이유"에 실제로 응답하는 경우 +1.
Joffrey 2014-06-27

귀하의 답변 브라이언에 감사드립니다. 그러나 여전히 답이없는 것을 발견했습니다. List.sort가 Collections.sort와 다르게 작동하는 이유는 전자가 매개 변수 유형을 포함하기 위해 첫 번째 람다 만 필요하지만 후자는 마지막으로도 필요합니다. 그 다음 5 개의 thenComparing 호출을 비교하고 마지막 thenComparing에 (Song p1)을 넣어야합니다. 또한 내 원래 게시물에서 모든 매개 변수 유형을 제거했지만 thenComparing에 대한 마지막 호출은 괜찮지 만 나머지는 그렇지 않은 TreeSet의 맨 아래 예제를 볼 수 있습니다. 따라서 이것은 다르게 동작합니다.
Tranquility

3
@ user3780370 아직 Eclipse 컴파일러를 사용하고 있습니까? 귀하의 질문을 올바르게 이해했다면이 동작을 보지 못했습니다. (a) JDK 8의 javac로 시도하고 (b) 여전히 실패하면 코드를 게시 할 수 있습니까?
Brian Goetz

@BrianGoetz이 제안에 감사드립니다. 방금 javac를 사용하여 명령 창에서 컴파일했으며 말한대로 컴파일됩니다. Eclipse 문제인 것 같습니다. JDK8을 위해 특별히 제작 된 Eclipse Luna로 아직 업데이트하지 않았으므로이 문제가 해결되기를 바랍니다. 실제로 Eclipse에서 무슨 일이 일어 났는지 보여주는 스크린 샷이 있지만 여기에 게시하는 방법을 모릅니다.
Tranquility

2
당신이 의미하는 것 같아요 Comparator.<Song, String>comparing(...).
shmosel

23

문제는 유형 추론입니다. (Song s)첫 번째 비교에를 추가 comparator.comparing하지 않으면 입력 유형을 알지 못하므로 기본값은 Object입니다.

다음 세 가지 방법 중 하나로이 문제를 해결할 수 있습니다.

  1. 새로운 Java 8 메서드 참조 구문 사용

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. 각 비교 단계를 로컬 참조로 가져옵니다.

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    편집하다

  3. Comparator에서 반환 된 유형 강제 적용 (입력 유형과 비교 키 유형이 모두 필요함)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

"마지막" thenComparing구문 오류가 오해의 소지가 있다고 생각합니다 . 그것은 실제로 전체 체인의 유형 문제입니다. 최종 반환 유형이 일치하지 않는 경우이기 때문에 체인의 끝을 구문 오류로 표시하는 컴파일러 일뿐입니다.

동일한 캡처 유형을 수행해야하지만 분명히 그렇지 않기 때문에 왜 List더 나은 추론 작업을 수행 하는지 잘 모르겠습니다 Collection.


솔루션에 대해서는 알고 ArrayList있지만 Collections체인의 첫 번째 호출에 Song매개 변수 가있는 경우 이유는 무엇 입니까?
Sotirios Delimanolis 2014-06-26

4
답장을 보내 주셔서 감사합니다. 내 게시물을 읽으면 "시작하기 전에 Song :: getTitle과 같은 메소드 참조를 사용하여 문제를 극복 할 수 있다는 것을 알고 있지만 여기에서 내 질문은 그다지 많지 않습니다. 내가 고치고 싶은 것, 그러나 내가 대답하고 싶은 것, 즉 자바 컴파일러가 왜 이런 식으로 그것을 처리 하는가. "
Tranquility

람다 식을 사용할 때 컴파일러가 그런 식으로 작동하는 이유에 대한 답변을 원합니다. 그것은 compare (s-> s.getArtist ())를 받아들이지 만 예를 들어 .thenComparing (s-> s.getDuration ())을 연결하면 두 호출에 대해 구문 오류가 발생하고 명시 적 유형을 추가하면 비교 호출, 예를 들어 compare ((Song s)-> s.getArtist ()) 그러면 해당 문제가 수정되고 List.sort 및 TreeSet의 경우 추가 매개 변수 유형을 추가하지 않고도 모든 추가 컴파일 오류를 해결합니다. 마지막 thenComparing 여전히 실패은, Collections.sort 및 Objects.compare 예
평온

1

이 컴파일 시간 오류를 처리하는 또 다른 방법 :

첫 번째 비교 함수의 변수를 명시 적으로 캐스팅 한 다음 사용하면됩니다. org.bson.Documents 개체 목록을 정렬했습니다. 샘플 코드를보세요

Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder())
                       .thenComparing(hist -> (Date) hist.get("promisedShipDate"))
                       .thenComparing(hist -> (Date) hist.get("lastShipDate"));
list = list.stream().sorted(comparator).collect(Collectors.toList());

0

playlist1.sort(...) 재생 목록 1의 선언에서 비교기로 "잔물결"하는 형식 변수 E에 대한 노래 경계를 만듭니다.

에는 Collections.sort(...)그러한 경계가 없으며 첫 번째 비교기 유형의 추론만으로는 컴파일러가 나머지를 추론하기에 충분하지 않습니다.

나는 당신이에서 "올바른"동작을 얻을 것이라고 생각하지만 당신을 위해 Collections.<Song>sort(...)그것을 테스트하기 위해 자바 8이 설치되어 있지 않다.


안녕하세요, 그래요 컬렉션을 추가하는 것이 맞습니다. <Song>은 마지막 thenComparing 호출에 대한 오류를 제거합니다.
Tranquility
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.