specifically how to leverage it to write the Comparator and sort a Collection.
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)));
}
``
람다를 쓰면서 익명 내부 클래스를 건너뛰고,
단순하고 기능적인 의미론으로 동일한 결과를 얻을 수 있다
(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을 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)));
}
``
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)));
}
``
스트림을 사용할 수도 있습니다.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)));
}
``
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.