Java에서 컬렉션 반복

컬렉션이있을 때마다 해당 컬렉션의 항목을 체계적으로 단계별로 진행할 수있는 메커니즘이 필요합니다. 일상적인 예로, 다양한 TV 채널을 반복 할 수있는 TV 리모컨을 고려해보십시오. 마찬가지로 프로그래밍 세계에서는 소프트웨어 개체 컬렉션을 체계적으로 반복하는 메커니즘이 필요합니다. Java에는 인덱스 (배열 반복 용), 커서 (데이터베이스 쿼리 결과 반복 용), 열거 형 (이전 버전의 Java) 및 반복기 (최신 버전의 Java) 등 다양한 반복 메커니즘이 포함되어 있습니다 .

반복자 패턴

반복자 일부 동작이 각 요소에 대해 수행되면서 순차적으로 액세스되는 컬렉션의 모든 요소를 허용하는 메커니즘이다. 본질적으로 반복자는 캡슐화 된 개체 컬렉션에 대해 "루핑"하는 수단을 제공합니다. 반복기 사용의 예는 다음과 같습니다.

  • 디렉토리 ( 일명 폴더)의 각 파일을 방문 하여 이름을 표시하십시오.
  • 그래프의 각 노드를 방문하여 주어진 노드에서 도달 할 수 있는지 확인합니다.
  • 대기열의 각 고객을 방문하여 (예 : 은행의 라인 시뮬레이션) 고객이 얼마나 오래 기다렸는지 확인하십시오.
  • 컴파일러의 추상 구문 트리 (파서에 의해 생성됨)에서 각 노드를 방문하여 의미 검사 또는 코드 생성을 수행합니다. (이 컨텍스트에서 방문자 패턴을 사용할 수도 있습니다.)

반복자의 사용에는 특정 원칙이 적용됩니다. 일반적으로 여러 순회를 동시에 진행할 수 있어야합니다. 즉, 반복자는 중첩 루프 의 개념을 허용해야합니다.. 반복자는 또한 반복 행위 자체가 컬렉션을 변경해서는 안된다는 점에서 비파괴 적이어야합니다. 물론 컬렉션의 요소에 대해 수행되는 작업은 일부 요소를 변경할 수 있습니다. 반복자가 컬렉션에서 요소를 제거하거나 컬렉션의 특정 지점에 새 요소를 삽입하는 것을 지원할 수도 있지만 이러한 변경 사항은 반복의 부산물이 아니라 프로그램 내에서 명시 적이어야합니다. 어떤 경우에는 다른 순회 방법을 가진 반복자가 필요합니다. 예를 들어 트리의 사전 주문 및 사후 순회 또는 그래프의 깊이 우선 및 너비 우선 순회가 있습니다.

복잡한 데이터 구조 반복

처음에는 FORTRAN의 초기 버전에서 프로그래밍하는 방법을 배웠습니다. 여기서 유일한 데이터 구조화 기능은 어레이였습니다. 인덱스와 DO 루프를 사용하여 배열을 반복하는 방법을 빠르게 배웠습니다. 거기에서 레코드 배열을 시뮬레이션하기 위해 공통 인덱스를 여러 배열로 사용하는 아이디어에 대한 정신적 도약에 불과했습니다. 대부분의 프로그래밍 언어에는 배열과 유사한 기능이 있으며 배열에 대한 간단한 루핑을 지원합니다. 그러나 최신 프로그래밍 언어는 목록, 집합,지도 및 트리와 같은 더 복잡한 데이터 구조도 지원합니다. 여기서 기능은 공용 메서드를 통해 사용할 수 있지만 내부 세부 정보는 클래스의 개인 부분에 숨겨져 있습니다. 프로그래머는 반복자의 목적인 내부 구조를 노출하지 않고 이러한 데이터 구조의 요소를 탐색 할 수 있어야합니다.

반복기 및 Gang of Four 디자인 패턴

Gang of Four (아래 참조)에 따르면 Iterator 디자인 패턴 은 동작 패턴이며, 핵심 아이디어는 "목록 [ ed. think collection ] 객체 에서 액세스 및 순회에 대한 책임을지고 이를 반복기에 넣는 것입니다. 목적." 이 기사는 반복기가 실제로 사용되는 방식만큼 반복기 패턴에 관한 것이 아닙니다. 패턴을 완전히 다루려면 반복기 설계 방법, 설계 참여자 (객체 및 클래스), 가능한 대안 설계 및 다양한 설계 대안의 장단점을 논의해야합니다. 실제로 반복기가 어떻게 사용되는지에 초점을 맞추고 싶지만 일반적으로 반복기 패턴과 디자인 패턴을 조사하기위한 몇 가지 리소스를 알려 드리겠습니다.

  • 디자인 패턴 : Erich Gamma, Richard Helm, Ralph Johnson 및 John Vlissides (Gang of Four 또는 단순히 GoF라고도 함)가 작성한 재사용 가능한 객체 지향 소프트웨어 요소 (Addison-Wesley Professional, 1994)는 학습을위한 결정적인 리소스입니다. 디자인 패턴에 대해. 이 책은 1994 년에 처음 출판되었지만 40 개가 넘는 인쇄물이 있다는 사실에서 알 수 있듯이 여전히 고전으로 남아 있습니다.
  • University of Maryland Baltimore County의 강사 인 Bob Tarr는 반복자 패턴에 대한 소개를 포함하여 디자인 패턴에 대한 강의를위한 훌륭한 슬라이드 세트를 가지고 있습니다.
  • David Geary의 JavaWorld 시리즈 Java Design Patterns 는 Singleton, Observer 및 Composite 패턴을 포함하여 Gang of Four 디자인 패턴을 많이 소개합니다. 또한 JavaWorld에서 Jeff Friesen의 최근 세 부분으로 구성된 디자인 패턴 개요에는 GoF 패턴에 대한 가이드가 포함되어 있습니다.

활성 반복기 대 수동 반복기

반복을 제어하는 ​​사람에 따라 반복기를 구현하는 두 가지 일반적인 접근 방식이 있습니다. 를 들어 활성 반복자 (라고도 명시적인 반복자 또는 외부 반복자 ), 클라이언트는 클라이언트가, 반복자를 생성 할 때 모든 요소가 방문한 경우 다음 요소로 진행, 테스트보고를 알 수 있다는 점에서 반복을 제어, 등등. 이 접근 방식은 C ++와 같은 언어에서 일반적이며 GoF 책에서 가장 주목받는 접근 방식입니다. Java의 반복기는 다른 형태를 취했지만 활성 반복기를 사용하는 것이 본질적으로 Java 8 이전의 유일한 실행 가능한 옵션이었습니다.

A에 대한 수동적 반복기 (도 알려져 암시 반복기 , 내부 반복기 또는 반복기 콜백 ), 반복자 자체가 반복을 제어한다. 클라이언트는 본질적으로 반복자에게 "컬렉션의 요소에 대해이 작업을 수행"한다고 말합니다. 이 접근 방식은 익명 함수 또는 클로저를 제공하는 LISP와 같은 언어에서 일반적입니다. Java 8이 출시되면서이 반복 방식은 이제 Java 프로그래머에게 합리적인 대안이되었습니다.

Java 8 명명 체계

Windows (NT, 2000, XP, VISTA, 7, 8, ...)만큼 나쁘지는 않지만 Java의 버전 기록에는 여러 명명 체계가 포함되어 있습니다. 시작하려면 Java 표준 버전을 "JDK", "J2SE"또는 "Java SE"라고해야합니까? Java의 버전 번호는 1.0, 1.1 등 매우 간단하게 시작되었지만 모든 것이 Java (또는 JDK) 5라는 상표가 붙은 버전 1.5로 변경되었습니다. Java의 초기 버전을 언급 할 때는 "Java 1.0"또는 "Java 1.1 "이지만 Java의 다섯 번째 버전 이후에는"Java 5 "또는"Java 8 "과 같은 구문을 사용합니다.

To illustrate the various approaches to iteration in Java, I need an example of a collection and something that needs to be done with its elements. For the initial part of this article I'll use a collection of strings representing names of things. For each name in the collection, I will simply print its value to standard output. These basic ideas are easily extended to collections of more complicated objects (such as employees), and where the processing for each object is a little more involved (like giving each highly rated employee a 4.5 percent raise).

Other forms of iteration in Java 8

I'm focusing on iterating over collections, but there are other, more specialized forms of iteration in Java. For example, you might use a JDBC ResultSet to iterate over the rows returned from a SELECT query to a relational database, or use a Scanner to iterate over an input source.

Iteration with the Enumeration class

In Java 1.0 and 1.1, the two primary collection classes were Vector and Hashtable, and the Iterator design pattern was implemented in a class called Enumeration. In retrospect this was a bad name for the class. Do not confuse the class Enumeration with the concept of enum types, which didn't appear until Java 5. Today both Vector and Hashtable are generic classes, but back then generics were not part of the Java language. The code to process a vector of strings using Enumeration would look something like Listing 1.

Listing 1. Using enumeration to iterate over a vector of strings

 Vector names = new Vector(); // ... add some names to the collection Enumeration e = names.elements(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); System.out.println(name); } 

Iteration with the Iterator class

Java 1.2 introduced the collection classes that we all know and love, and the Iterator design pattern was implemented in a class appropriately named Iterator. Because we didn't yet have generics in Java 1.2, casting an object returned from an Iterator was still necessary. For Java versions 1.2 through 1.4, iterating over a list of strings might resemble Listing 2.

Listing 2. Using an Iterator to iterate over a list of strings

 List names = new LinkedList(); // ... add some names to the collection Iterator i = names.iterator(); while (i.hasNext()) { String name = (String) i.next(); System.out.println(name); } 

Iteration with generics and the enhanced for-loop

Java 5 gave us generics, the interface Iterable, and the enhanced for-loop. The enhanced for-loop is one of my all-time-favorite small additions to Java. The creation of the iterator and calls to its hasNext() and next() methods are not expressed explicitly in the code, but they still take place behind the scenes. Thus, even though the code is more compact, we are still using an active iterator. Using Java 5, our example would look something like what you see in Listing 3.

Listing 3. Using generics and the enhanced for-loop to iterate over a list of strings

 List names = new LinkedList(); // ... add some names to the collection for (String name : names) System.out.println(name); 

Java 7 gave us the diamond operator, which reduces the verbosity of generics. Gone were the days of having to repeat the type used to instantiate the generic class after invoking the new operator! In Java 7 we could simplify the first line in Listing 3 above to the following:

 List names = new LinkedList(); 

A mild rant against generics

The design of a programming language involves tradeoffs between the benefits of language features versus the complexity they impose on the syntax and semantics of the language. For generics, I am not convinced that the benefits outweigh the complexity. Generics solved a problem that I did not have with Java. I generally agree with Ken Arnold's opinion when he states: "Generics are a mistake. This is not a problem based on technical disagreements. It's a fundamental language design problem [...] The complexity of Java has been turbocharged to what seems to me relatively small benefit."

Fortunately, while designing and implementing generic classes can sometimes be overly complicated, I have found that using generic classes in practice is usually straightforward.

Iteration with the forEach() method

Before delving into Java 8 iteration features, let's reflect on what's wrong with the code shown in the previous listings–which is, well, nothing really. There are millions of lines of Java code in currently deployed applications that use active iterators similar to those shown in my listings. Java 8 simply provides additional capabilities and new ways of performing iteration. For some scenarios, the new ways can be better.

The major new features in Java 8 center on lambda expressions, along with related features such as streams, method references, and functional interfaces. These new features in Java 8 allow us to seriously consider using passive iterators instead of the more conventional active iterators. In particular, the Iterable interface provides a passive iterator in the form of a default method called forEach().

A default method, another new feature in Java 8, is a method in an interface with a default implementation. In this case, the forEach() method is actually implemented using an active iterator in a manner similar to what you saw in Listing 3.

Collection classes that implement Iterable (for example, all list and set classes) now have a forEach() method. This method takes a single parameter that is a functional interface. Therefore the actual parameter passed to the forEach() method is a candidate for a lambda expression. Using the features of Java 8, our running example would evolve to the form shown in Listing 4.

Listing 4. Iteration in Java 8 using the forEach() method

 List names = new LinkedList(); // ... add some names to the collection names.forEach(name -> System.out.println(name)); 

Note the difference between the passive iterator in Listing 4 and the active iterator in the previous three listings. In the first three listings, the loop structure controls the iteration, and during each pass through the loop, an object is retrieved from the list and then printed. In Listing 4, there is no explicit loop. We simply tell the forEach() method what to do with the objects in the list — in this case we simply print the object. Control of the iteration resides within the forEach() method.

Iteration with Java streams

이제 단순히 목록에 이름을 인쇄하는 것보다 약간 더 복잡한 작업을 수행하는 것을 고려해 봅시다. 예를 들어, 문자 A로 시작하는 이름의 수를 세고 싶다고 가정합니다 . 더 복잡한 로직을 람다 표현식의 일부로 구현하거나 Java 8의 새로운 Stream API를 사용할 수 있습니다. 후자의 접근 방식을 취합시다.