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

특히 정적 메서드 사용과 람다 식에 매개 변수 형식이 필요한지 여부와 관련하여 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의 정렬 오류



답변

첫째, 당신이 말하는 모든 예제는 참조 구현 (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>.


답변

문제는 유형 추론입니다. (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.


답변

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

첫 번째 비교 함수의 변수를 명시 적으로 캐스팅 한 다음 사용하면됩니다. 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());


답변

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

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

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


답변