2장: 컬렉션 사용
우리는 종종 다양한 컬렉션, 숫자, 문자열 및 객체를 사용합니다. 그것들은 어디에나 있으며, 컬렉션을 운영하는 코드가 약간 최적화될 수 있다고 해도 코드가 훨씬 더 명확해질 것입니다. 이 장에서는 람다 표현식을 사용하여 컬렉션을 조작하는 방법을 살펴봅니다. 컬렉션을 탐색하고, 컬렉션을 새 컬렉션으로 변환하고, 컬렉션에서 요소를 제거하고, 컬렉션을 병합하는 데 이를 사용합니다.
목록 탐색
목록 순회는 가장 기본적인 집합 작업이며 해당 작업은 수년에 걸쳐 약간의 변경을 거쳤습니다. 우리는 가장 오래된 버전부터 오늘날 가장 우아한 버전까지 이름을 순회하는 작은 예를 사용합니다.
다음 코드를 사용하면 변경할 수 없는 이름 목록을 쉽게 만들 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
최종 목록<String> 친구 =
Arrays.asList("브라이언", "네이트", "닐", "라주", "사라", "스콧");
System.out.println(friends.get(i));
}
다음은 목록을 탐색하고 인쇄하는 가장 일반적인 방법이지만 가장 일반적이기도 합니다.
다음과 같이 코드 코드를 복사합니다.
for(int i = 0; i < friends.size(); i++) {
System.out.println(friends.get(i));
}
나는 이런 글쓰기 방식을 마조히즘적이라고 부릅니다. 장황하고 오류가 발생하기 쉽습니다. "i<인가, i<=인가?"라고 생각해야 합니다. 이는 특정 요소에 대해 연산을 수행해야 하는 경우에만 의미가 있지만, 그런 경우에도 여전히 다음의 원칙을 준수하는 함수형 표현을 사용할 수 있습니다. 불변성 스타일에 대해서는 곧 논의하겠습니다.
Java는 또한 상대적으로 고급 구조를 제공합니다.
다음과 같이 코드 코드를 복사합니다.
컬렉션/fpij/Iteration.java
for(문자열 이름 : 친구) {
System.out.println(이름);
}
내부적으로는 이 방식의 반복이 Iterator 인터페이스를 사용하여 구현되어 hasNext 및 next 메소드를 호출합니다. 두 메서드 모두 외부 반복자이며 수행 방법과 수행하려는 작업을 결합합니다. 우리는 반복을 명시적으로 제어하여 시작 위치와 끝 위치를 알려줍니다. 두 번째 버전은 Iterator 메서드를 통해 내부적으로 이 작업을 수행합니다. 명시적 작업에서는 break 및 continue 문을 사용하여 반복을 제어할 수도 있습니다. 두 번째 버전에는 첫 번째 버전에서 몇 가지 빠진 내용이 있습니다. 컬렉션의 요소를 수정하지 않으려는 경우 이 접근 방식이 첫 번째 접근 방식보다 낫습니다. 그러나 이 두 가지 방법 모두 필수적이며 현재 Java에서는 폐기되어야 합니다. 기능적 스타일로 변경하는 데에는 몇 가지 이유가 있습니다.
1. for 루프 자체는 직렬이며 병렬화가 어렵습니다.
2. 이러한 루프는 비다형성입니다. 원하는 대로 얻을 수 있습니다. 특정 작업을 수행하기 위해 컬렉션에서 (다형성을 지원하는) 메서드를 호출하는 대신 컬렉션을 for 루프에 직접 전달합니다.
3. 디자인 관점에서 볼 때 이런 방식으로 작성된 코드는 "말하고 묻지 마세요" 원칙을 위반합니다. 반복을 기본 라이브러리에 맡기지 않고 반복을 수행하도록 요청합니다.
이제는 오래된 명령형 프로그래밍에서 내부 반복자의 보다 우아한 기능적 프로그래밍으로 전환할 때입니다. 내부 반복자를 사용한 후에는 실행을 위해 기본 메서드 라이브러리에 많은 특정 작업을 남겨 두므로 특정 비즈니스 요구 사항에 더 집중할 수 있습니다. 기본 함수는 반복을 담당합니다. 먼저 내부 반복자를 사용하여 이름 목록을 열거합니다.
Iterable 인터페이스는 JDK8에서 향상되었습니다. 여기에는 Comsumer 유형의 매개변수를 받는 forEach라는 특수 이름이 있습니다. 이름에서 알 수 있듯이 Consumer 인스턴스는 accept 메서드를 통해 전달된 객체를 소비합니다. forEach 메서드를 사용하기 위해 익숙한 익명 내부 클래스 구문을 사용합니다.
다음과 같이 코드 코드를 복사합니다.
friends.forEach(new Consumer<String>() { public void accept(최종 문자열 이름) {
System.out.println(이름) }
});
우리는 Friends 컬렉션에 대해 forEach 메서드를 호출하여 Consumer의 익명 구현을 전달했습니다. 이 forEach 메서드는 컬렉션의 각 요소에 대해 전달된 Consumer의 accept 메서드를 호출하여 이 요소를 처리할 수 있도록 합니다. 이 예에서는 이름인 값만 인쇄합니다. 이전 두 버전과 동일한 이 버전의 출력을 살펴보겠습니다.
다음과 같이 코드 코드를 복사합니다.
브라이언
네이트
닐
라주
사라
스캇
우리는 한 가지만 변경했습니다. 더 이상 사용되지 않는 for 루프를 버리고 새로운 내부 반복자를 사용했습니다. 장점은 컬렉션을 반복하는 방법을 지정할 필요가 없고 각 요소를 처리하는 방법에 더 집중할 수 있다는 것입니다. 단점은 코드가 더 장황해 보인다는 것입니다. 이는 새로운 코딩 스타일의 즐거움을 거의 없애줍니다. 다행스럽게도 이는 쉽게 변경할 수 있으며, 여기서 람다 표현식과 새로운 컴파일러의 힘이 발휘됩니다. 한 가지 더 수정하여 익명 내부 클래스를 람다 식으로 바꾸겠습니다.
다음과 같이 코드 코드를 복사합니다.
friends.forEach((최종 문자열 이름) -> System.out.println(이름));
이렇게 하면 훨씬 나아 보입니다. 코드는 적지만 먼저 이것이 무엇을 의미하는지 살펴보겠습니다. forEach 메서드는 목록의 요소에 대해 작업을 수행하기 위해 람다 식이나 코드 블록을 받는 고차 함수입니다. 각 호출에서 컬렉션의 요소는 name 변수에 바인딩됩니다. 기본 라이브러리는 람다 식 호출 활동을 호스팅합니다. 표현식 실행을 지연하기로 결정하고, 적절한 경우 병렬 계산을 수행할 수 있습니다. 이 버전의 출력도 이전 버전과 동일합니다.
다음과 같이 코드 코드를 복사합니다.
브라이언
네이트
닐
라주
사라
스캇
내부 반복자 버전이 더 간결합니다. 또한 이를 사용하면 각 요소를 순회하는 대신 각 요소의 처리에 더 집중할 수 있습니다. 이는 선언적입니다.
그러나 이 버전에는 결함이 있습니다. forEach 메서드가 실행되기 시작하면 다른 두 버전과 달리 이 반복을 중단할 수 없습니다. (물론 이를 수행하는 다른 방법도 있습니다). 따라서 이러한 작성 방법은 컬렉션의 각 요소를 처리해야 할 때 더 일반적으로 사용됩니다. 나중에 루프 프로세스를 제어할 수 있는 몇 가지 다른 기능을 소개하겠습니다.
람다 표현식의 표준 구문은 매개변수를 () 안에 넣고 유형 정보를 제공하며 쉼표를 사용하여 매개변수를 구분하는 것입니다. 우리를 해방시키기 위해 Java 컴파일러는 자동으로 유형 추론을 수행할 수도 있습니다. 물론, 타입을 쓰지 않는 것이 더 편리합니다. 일이 줄어들고 세상은 더 조용해집니다. 다음은 매개변수 유형을 제거한 이전 버전입니다.
다음과 같이 코드 코드를 복사합니다.
friends.forEach((이름) -> System.out.println(이름));
이 예에서 Java 컴파일러는 컨텍스트 분석을 통해 이름 유형이 String임을 알고 있습니다. 이는 호출된 메소드 forEach의 시그니처를 살펴본 다음 매개변수의 기능적 인터페이스를 분석합니다. 그런 다음 이 인터페이스의 추상 메소드를 분석하고 매개변수의 수와 유형을 확인합니다. 이 람다 표현식이 여러 매개변수를 수신하는 경우에도 유형 추론을 수행할 수 있지만 이 경우 모든 매개변수는 람다 표현식에서 매개변수 유형을 가질 수 없으며 매개변수 유형을 전혀 작성해야 하거나 작성하는 경우에는 작성해야 합니다. 전체.
Java 컴파일러는 단일 매개변수가 있는 람다 표현식을 특별히 처리합니다. 유형 추론을 수행하려는 경우 매개변수 주위의 괄호를 생략할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
friends.forEach(이름 -> System.out.println(이름));
여기에는 작은 주의 사항이 있습니다. 유형 추론에 사용되는 매개변수는 최종 유형이 아닙니다. 유형을 명시적으로 선언하는 이전 예에서는 매개변수도 final로 표시했습니다. 이렇게 하면 람다 식의 매개 변수 값을 변경할 수 없습니다. 일반적으로 매개변수의 값을 수정하는 것은 나쁜 습관이며, 이로 인해 BUG가 발생하기 쉬우므로 final로 표시해 두는 것이 좋은 습관이다. 불행하게도 유형 추론을 사용하려면 컴파일러가 더 이상 우리를 보호하지 않기 때문에 매개변수를 수정하지 않고 규칙을 따라야 합니다.
여기까지 오기까지 많은 노력이 필요했는데, 이제는 코드의 양이 조금 줄어들었습니다. 그러나 이것이 아직 가장 간단한 것은 아닙니다. 마지막 미니멀리스트 버전을 시도해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
friends.forEach(System.out::println);
위 코드에서는 메소드 참조를 사용합니다. 전체 코드를 메소드 이름으로 직접 바꿀 수 있습니다. 다음 섹션에서 이에 대해 자세히 살펴보겠습니다. 하지만 지금은 Antoine de Saint-Exupéry의 유명한 인용문을 떠올려 보겠습니다. 완벽함은 더할 수 있는 것이 아니라 더 이상 뺄 수 없는 것입니다.
람다 표현식을 사용하면 컬렉션을 간결하고 명확하게 탐색할 수 있습니다. 다음 섹션에서는 삭제 작업 및 컬렉션 변환을 수행할 때 이렇게 간결한 코드를 작성할 수 있는 방법에 대해 설명하겠습니다.