람다와 스트림의 사용법(2)
날짜: 2021년 6월 10일
학습목표
Java8에 추가된 람다표현식과 스트림에 대해 알아보자. Thread코딩테스트를 풀어보려 했으나, Java8문법에 익숙해 지지 않아 기본적인 코드 흐름을 해석하기 힘들었다.
이번 포스팅에서는 코딩테스트의 문법을 해석하기위해, 람다표현식과 스트림 중 기본적인
스트림의 사용법에 대해서만 알아가는 것을 목표로 한다.
- 스트림의 사용법
- forEach
- map
- 메소드 참조
Stream
자바의 스트림은 "뭔가 연속된 정보" 를 처리할 때 사용한다.
자바에서 연속된 정보란 무엇을 뜻할까? 우리는 어떤 연속된 정보를 처리해 왔을까?
- 배열
- 컬렉션
이 연속된 정보들 이라고 할 수있다.
이 중 컬렉션을 스트림을 사용할 수 있지만, 안타깝게도 배열은 불가능하다. 하지만, 배열을 컬렉션 중 하나인 List로 변환하는 방법은 여러가지가 존재한다!
아래의 코드를 보자
Integer[] values = {1,3,5};
List<Integer> list = new ArrayList<Integer>(Arrays.asList(values));
Arrays 클래스에서 제공하는 asList() 메소드로 변환 할수도 있고,
Integer[] values2 = {1,3,5};
List<Integer> list2 = Arrays.stream(values).collect(Collectors.toList());
이 코드처럼 스트림을 이용해서 변환 할수도 있다.
그럼 본격적으로 스트림에 대해서 알아보자.
스트림의 구조
- 스트림 생성 : 컬렉션의 목록을 스트림 객체로 반환, 여기서 스트림 객체란 java.util.stream 패키지의 Stream 인터페이스를 말한다.(입출력의 Stream이 아니다) 이 stram() 메소드는 당연하게도 Collection 인터페이스에 선언되어있다.
- 중개 연산 : 생성된 스트림객체를 사용해 주개 연산 부분에서 처리한다. 하지만 이부분에서는 아무런 결과도 리턴할 수 없다.
- 종단 연산 : 중개 연산에서 작업된 내용을 결과로 받아 리턴해준다.
이 절차는 꼭 알아두자, 단 중개연산이 필수는 아니다. 즉, 0개이상의 중개연산을 포함한다.
또한 stream()은 순차적으로 데이터를 처리한다. 즉, 10개의 데이터가 있다면, 0~9번째 인덱스를 하나씩 처음부터 처리한다.
스트림에서 제공하는 연산들을 간단히 보고, 자주 쓰는 연산들에 한해서 실습해보자.
forEach() 실습
코드를 보며 이해하자. 먼저 예제에 사용될 DTO 클래스이다.
package stream8;
public class StudentDTO {
String name;
int age;
public StudentDTO(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {return name}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
}
아래코드가 위의 DTO를 이용한 forEach 예제코드이다.
public class streamExam {
public static void main(String[] args) {
streamExam exam = new streamExam();
List<StudentDTO> studentList = new ArrayList<>();
studentList.add(new StudentDTO("yoda", 43));
studentList.add(new StudentDTO("jedi", 45));
studentList.add(new StudentDTO("syth", 47));
exam.printStudentNames(studentList);
}
public void printStudentNames(List<StudentDTO> students) {
students.forEach(student -> System.out.println(student.getName()));
}
}
코드의 흐름을 이해하는것은 어렵지 않을것 이다. 자, forEach 코드부분을 자세히 보자.
students.stream().forEach(student -> System.out.println(student.getName()));
students.stream() 까지는 이해가 갈것이다. 스트림 객체를 생성하는 부분이다.
forEach()부분을 보면, student의 이름을 출력하도록 했다. 여기서 student는 students라는 List객체에 담겨있는 하나의 StudentDTO 객체를 의미한다. 향상된 for 루프문을 생각하면 쉽겠다!
즉, 아래의 코드와 똑같이 동작한다.
for(StudentDTO student : students) {
System.out.println(student.getName());
}
map() 실습
그럼 한 가지 연산이 더 추가된 아래의 코드를 보고 분석해보자.
students.stream().map(student -> student.getName()).forEach(name -> System.out.println(name));
중간에 map() 연산이 추가됬다. 위의 표를 기억하는가? map은 데이터를 특정 데이터로 변환할 때 사용한다. 즉, 위 코드에서는 student로 표기된 StudentDTO객체가, StudentDTO의 name 변수로 바뀜을 의미한다! 때문에, forEach()부분에서 student로 표기하던 것을 name으로 바꾼것을 볼수 있다.
- 람다 표현식은 Functional 인터페이스만 사용이 가능한데, forEach()에서 어떻게 썼을까?
메소드 참조
메소드 참조(method reference)는 람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해준다.
앞서 배운 forEach()예제의 출력문장은 다음과 같이 바꿀수도 있다.
forEach(System.out::println)
"::" 이라는 처음보는 연산이 나왔다.
이는 더블 콜론이라고 읽으며, Java8에서 추가되었으며, 메소드참조(Method Reference)라고 부른다.
다음과 같이 4가지 종류로 나뉜다.
각각의 참조가 어떻게 사용되는지 알아보자
static 메소드 참조
- 추후에 알고 넘어갈것!
특정 객체의 인스턴스 메소드 참조
인트턴스 참조는 System.out::println과 같이 System클래스에 선언된 out 변수가 있고, 그 out변수에 있는 println() 메소드를 호출하는 것처럼 변수에 선언된 메소드 호출 을 의미한다.
(System클래스의 out 변수는 static으로 선언된 PrintStream클래스가 할당되어있다.)
특정 유형의 임의의 객체에 대한 인스턴스 메소드 참조
- 추후에 알고 넘어갈것!
생성자 참조
- 추후에 알고 넘어갈것!
Feedback
스트림의 가장 기본적인 문법사용과, forEach,map에 대한 사용법을 익혔다. 다만, 메소드 참조에 대해서 명확히 이해가 되지는 않으나, Thread 테스트 문제를 푸는데는 무리없이 이해할 수있으므로, 여기까지만 공부한다.
알게된 점
- 스트림의 사용법
- forEach
- map
알아야 할 점
- 메소드 참조
- forEach()에서 람다표현식이 가능한 이유
References
자바의신 VOL2
'프로그래밍 > Java' 카테고리의 다른 글
Serializable(1) (0) | 2021.06.11 |
---|---|
Java I/O (0) | 2021.06.11 |
람다와 스트림의 사용법(1) (0) | 2021.06.10 |
Thread(1) (0) | 2021.06.02 |
Set과 Queue -Reboot(4) (0) | 2021.06.02 |