람다 Comparison

최종윤·2023년 1월 15일

자바

목록 보기
3/6

specifically how to leverage it to write the Comparator and sort a Collection.

람다없이 basic 정렬

java8 전에는 collection을 정렬하기 위해 정렬하는데 쓰이는 Comparator에 대한 익명 Inner class를 생성해야했습니다.
``
new Comparator() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
}

``

human entities List를 정렬하기 위한 코드
``
@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);

Collections.sort(humans, new Comparator<Human>() {
    @Override
    public int compare(Human h1, Human h2) {
        return h1.getName().compareTo(h2.getName());
    }
});
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));

}
``

람다로 basic 정렬

람다를 쓰면서 익명 내부 클래스를 건너뛰고,
단순하고 기능적인 의미론으로 동일한 결과를 얻을 수 있다
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());

정렬하는 테스트 코드
``
@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);

humans.sort(
  (Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));

assertThat(humans.get(0), equalTo(new Human("Jack", 12)));

}
``
java8에 java.util.List에 새로 추가된 sort API를 사용하여 정렬했습니다.
그 전에는 Collections.sort API를 사용했습니다.

Type 정의 없이 basic 정렬

람다식에서 type을 parameter의 type을 정의하지 않아도 compiler가 추론할 수 있기 때문에 표현식을 더 간단히 할 수 있습니다.
(h1, h2) -> h1.getName().compareTo(h2.getName())

``
@Test
public void
givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {

List<Human> humans = Lists.newArrayList(
  new Human("Sarah", 10), 
  new Human("Jack", 12)
);

humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));

assertThat(humans.get(0), equalTo(new Human("Jack", 12)));

}
``

정적 메서드에 대한 참조를 사용하여 정렬

다음으로 정적 방법을 참조하여 람다 식을 사용하여 정렬을 수행할 것입니다.

먼저 compareByName 메서드를 정의합니다그런 다음 비교기의 비교 방법과 정확히 동일한 서명을 가진 나이<휴먼 > 개체:

public static int compareByNameThenAge(Human lhs, Human rhs) { if (lhs.name.equals(rhs.name)) { return Integer.compare(lhs.age, rhs.age); } else { return lhs.name.compareTo(rhs.name); } }
humans.sort method를 호출
humans.sort(Human::compareByNameThenAge);

The end result is a working sorting of the collection using the static method as a Comparator:
``
@Test
public void
givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {

List<Human> humans = Lists.newArrayList(
  new Human("Sarah", 10), 
  new Human("Jack", 12)
);

humans.sort(Human::compareByNameThenAge);
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));

}
``

추출된 대조군 정렬

또한 인스턴스 메서드 참조와 Comparator.comparing 메서드를 사용하여 비교 가능 함수를 추출하고 생성함으로써 비교 논리 자체를 정의하는 것을 피할 수 있습니다.

getter getName()을 사용하여 람다 식을 작성하고 목록을 이름별로 정렬합니다:

``
@Test
public void
givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() {

List<Human> humans = Lists.newArrayList(
  new Human("Sarah", 10), 
  new Human("Jack", 12)
);

Collections.sort(
  humans, Comparator.comparing(Human::getName));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));

}

``

역순 정렬

JDK 8은 대조군을 역전시키는 도우미 방식도 도입했다. 우리는 그것을 우리의 종류를 바꾸기 위해 빠르게 사용할 수 있다
``
@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);

Comparator<Human> comparator
  = (h1, h2) -> h1.getName().compareTo(h2.getName());

humans.sort(comparator.reversed());

Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));

}
``

여러 조건으로 정렬

비교 람다 표현식이 이렇게 간단할 필요는 없습니다. 이름별로 먼저 엔터티를 정렬한 다음 연령별로 정렬하는 등 보다 복잡한 표현식을 작성할 수도 있습니다:
``
@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 12),
new Human("Sarah", 10),
new Human("Zack", 12)
);

humans.sort((lhs, rhs) -> {
    if (lhs.getName().equals(rhs.getName())) {
        return Integer.compare(lhs.getAge(), rhs.getAge());
    } else {
        return lhs.getName().compareTo(rhs.getName());
    }
});
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));

}
``

여러 조건으로 정렬 – 구성

처음에는 이름별로 정렬하고 다음에는 연령별로 정렬하는 동일한 비교 로직을 Comparator에 대한 새로운 구성 지원에서도 구현할 수 있습니다.

JDK 8부터는 여러 비교기를 연결하여 보다 복잡한 비교 논리를 구축할 수 있습니다:

``
@Test
public void
givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {

List<Human> humans = Lists.newArrayList(
  new Human("Sarah", 12), 
  new Human("Sarah", 10), 
  new Human("Zack", 12)
);

humans.sort(
  Comparator.comparing(Human::getName).thenComparing(Human::getAge)
);

Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));

}
``

Stream.sorted()으로 List 정렬

Java 8의 Stream sorted() API를 사용하여 Collection을 정렬할 수도 있습니다.

Comparator에서 제공하는 순서뿐만 아니라 자연 순서를 사용하여 스트림을 정렬할 수 있습니다. 이를 위해 sorted() API의 두 가지 오버로드 변형이 있습니다:

sorted() – 자연스러운 순서를 사용하여 스트림의 요소를 정렬합니다. 요소 클래스는 비교 가능한 인터페이스를 구현해야 합니다.
sorted(Comparator<.? super T> comparator) – 비교기 인스턴스를 기준으로 요소를 정렬합니다
자연 순서로 정렬() 방법을 사용하는 방법의 예를 살펴보겠습니다:

``
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List letters = Lists.newArrayList("B", "A", "C");

List<String> sortedLetters = letters.stream().sorted().collect(Collectors.toList());
assertThat(sortedLetters.get(0), equalTo("A"));

}
``
Now let's see how we can use a custom Comparator with the sorted() API:

``
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName());

List<Human> sortedHumans = 
  humans.stream().sorted(nameComparator).collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));

}
We can simplify the above example even further if we use the Comparator.comparing() method:
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));

List<Human> sortedHumans = humans.stream()
  .sorted(Comparator.comparing(Human::getName))
  .collect(Collectors.toList());
  
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));

}
``

Stream.sorted() 역순으로 목록 정렬

스트림을 사용할 수도 있습니다.sorted(정렬) - 컬렉션을 역순으로 정렬합니다.

먼저 정렬된() 메서드를 Comparator.reverseOrder()와 결합하여 목록을 역자연 순서로 정렬하는 방법의 예를 살펴보겠습니다:
``
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List letters = Lists.newArrayList("B", "A", "C");

List<String> reverseSortedLetters = letters.stream()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());
  
assertThat(reverseSortedLetters.get(0), equalTo("C"));

}
``
Now let's see how we can use the sorted() method and a custom Comparator:

``
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator reverseNameComparator =
(h1, h2) -> h2.getName().compareTo(h1.getName());

List<Human> reverseSortedHumans = humans.stream().sorted(reverseNameComparator)
  .collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));

}
``

Note that the invocation of compareTo is flipped, which is responsible for the reversing.

Finally, let's simplify the above example by using the Comparator.comparing() method:
``
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));

List<Human> reverseSortedHumans = humans.stream()
  .sorted(Comparator.comparing(Human::getName, Comparator.reverseOrder()))
  .collect(Collectors.toList());

assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));

}
``

Sort collection containing null

So far, we implemented our Comparators in a way that they can't sort collections containing null values. That is, if the collection contains at least one null element, then the sort method throws a NullPointerException:
``
@Test(expected = NullPointerException.class)
public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE() {
List humans = Lists.newArrayList(null, new Human("Jack", 12));

humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));

}
The simplest solution is to handle the null values manually in our Comparator implementation:
@Test
public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast() {
List humans = Lists.newArrayList(null, new Human("Jack", 12), null);

humans.sort((h1, h2) -> {
    if (h1 == null) {
        return h2 == null ? 0 : 1;
    }
    else if (h2 == null) {
        return -1;
    }
    return h1.getName().compareTo(h2.getName());
});

Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));

}
``
Here we're pushing all null elements towards the end of the collection. To do that, the comparator considers null to be greater than non-null values. When both are null, they are considered equal.

Additionally, we can pass any Comparator that is not null-safe into the Comparator.nullsLast() method and achieve the same result:

``
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() {
List humans = Lists.newArrayList(null, new Human("Jack", 12), null);

humans.sort(Comparator.nullsLast(Comparator.comparing(Human::getName)));

Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));

}
``
Similarly, we can use Comparator.nullsFirst() to move the null elements towards the start of the collection:

``
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() {
List humans = Lists.newArrayList(null, new Human("Jack", 12), null);

humans.sort(Comparator.nullsFirst(Comparator.comparing(Human::getName)));

Assert.assertNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNotNull(humans.get(2));

``
It's highly recommended to use the nullsFirst() or nullsLast() decorators, as they're more flexible and readable.

profile
https://github.com/jyzayu

0개의 댓글