Java 1.8 버전의 가장 큰 차이점은 람다 표현식(Lambda Expressions) 과 스트림 API(Stream API) 의 도입이다.
이러한 람다와 스트림을 이용한 표현식을 통해 JAVA를 함수형 프로그래밍과 병렬 프로그래밍을 지원하는 언어로 변화시킬수 있었다.
Java 1.8 버전의 등장이후 개발자들은 더 간결하고 직관적인 코드를 작성할 수 있게 되었으며, 병렬 처리와 함수형 스타일을 통해 성능과 가독성을 동시에 향상시킬 수 있었어 코드의 효율성, 유지보수성, 가독성 측면에서 큰 진전을 이루게 되었다.
람다 표현식
람다 표현식이란 JAVA에서 익명함수(Anonymous Function)을 구현하는 방법을 제공하는 표현식이다.
익명함수란 말그대로 '이름이 없는 함수'이며 JAVA의 익명클래스를 통해 구현할수있다.
람다식도 이러한 익명함수의 일종이라고 할수있다.
먼저 기존의 JAVA에서 지원하는 익명클래스에 대해 알아보자.
익명클래스는 메소드 내에서 클래스의 정의와 동시에 생성을 하여 인스턴스화 할수있다.
예를들어, 일회용으로 사용될 클래스라면 굳이 클래스를 재정의하고 따로 인스턴스를 생성한뒤 사용한뒤 =null 처리를 하여 JVM의 GC가 삭제하길 기다리는 것 보다 메소드 내부에서 정의와 동시에 생성하여 한번만 사용한뒤 스택에서 바로 삭제하는 것이 메모리상에서 훨씬 효율적일 것이다.
public class AnonymousClass {
interface Dog {
public void hello();
}
public static void main(String[] args) {
Dog dog = new Dog() {
// 오버라이드 - 내부클래스의 주 사용목적
public void hello() {
System.out.println("안녕");
}
};
dog.hello(); // "안녕"
}
}
위의 익명클래스에서 오버라이드한 hello()가 일종의 익명함수라고 할수있다.
이러한 익명함수를 람다식으로 간단하게 표현하면
// 익명클래스
Dog dog = new Dog() {
public void hello() {
System.out.println("안녕");
}
};
// 람다식
Dog dog = () -> System.out.println("안녕");
// 메소드가 1개 이상일 경우
interface Dog {
void hello(); // 추상 메서드 1
// default 메서드 1
default void bark() {
System.out.println("멍멍");
}
// default 메서드 2
default void sleep() {
System.out.println("자고 있다");
}
}
위와 같이 간편하게 사용할수있다.
그런데 만약 Dog 인터페이스가 hello() 메소드 뿐만아니라 또다른 bye() 메소드까지 가지고있다면 어떻게될까?
람다식의 사용조건은 함수형 인터페이스에서만 사용할수있다.
함수형 인터페이스란 단 1개의 추상메소드를 보유한 인터페이스이다.
그러므로 함수형 인터페이스가 아니라면, 익명 클래스를 이용해 오버라이드 해야한다.
만약, 람다식을 이용해 구현하고싶다면 JAVA 1.8에 새롭게 추가된 기능인 default 를 통해
해당 인터페이스에서 람다식을 이용해 구현할 메소드를 제외한 모든 메소드들을 default 처리를 해준다면 가능하다.
스트림 API
스트림 API는 자바의 Collection 자료구조들을 간결하게 선언형으로 처리할 수있으며, 멀티스레드를 구현하지않아도
병렬처리할수있게 해주는 API이다.
컬렉션을 다루면서 스트림을 사용하지않은 코드와 스트림을 사용한 코드를 비교해보자.
public class AnonymousClass {
class Animal{
String name;
int age;
public Animal(String name,int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
AnonymousClass anony = new AnonymousClass();
List<Animal> list = new ArrayList<>();
list.add(anony.new Animal("dog",10));
list.add(anony.new Animal("cat",5));
list.add(anony.new Animal("tiger",20));
list.add(anony.new Animal("turtle",60));
list.add(anony.new Animal("elephant",30));
// 방법1. 기존 방식
List<String> youngAnimal = new ArrayList<>();
for(Animal animal : list) {
if(animal.age <= 30) {
youngAnimal.add(animal.name);
}
}
Collections.sort(youngAnimal);
// 방법2. 스트림 API 이용
List<String> youngAnimal = list.stream()
.filter(animal -> animal.age <= 30) // 30세 이하 필터링
.map(animal -> animal.name) // 이름만 추출
.sorted() // 오름차순 정렬
.collect(Collectors.toList()); //
}
}
스트림의 사용방식은 크게 3가지로 나누어져있다.
1. 스트림 생성
stream() 메소드를 통해 스트림을 생성한다.
2. 중간연산
스트림 데이터를 변환하거나 필터링 하는 작업
.filter( animal -> animal.age <= 30) 스트림 데이터 구조의 animal 객체의 age가 30이하만 필터링
.map(animal -> animal.name) : 각 요소를 다른형태로 변환, 필터링된 결과에 대해 animal객체의 animal.name값만 추출
.sorted() : 결과 스트림을 정렬, Comparator를 사용하여 원하는 대로 정렬가능
3. 최종연산
생성된 스트림을 소비하여 최종결과를 생성하는 작업.
최종연산은 스트림을 종료시키며, 사용된 스트림은 재사용할수없다.
forEach(Consumer) : 결과 스트림의 각 요소에 대해 특정 작업
collect(Collector) : 결과 스트림을 원하는 형태로 수집
또한, parallelStream() 메소드 이용해 멀티스레드를 사용하여 간편하게 병렬처리를 구현할수있다.
데이터 처리작업을 여러 스레드로 분할하여 멀티 코어 CPU를 사용하여 효율적인 자료처리 작업을 구현할수있도록 해준다.
'Category > JAVA' 카테고리의 다른 글
[JAVA] 여러가지 측면에서의 인코딩 방식 (0) | 2024.08.27 |
---|---|
[Design Pattern] Adapter Pattern의 실제 사용예시 (0) | 2024.05.23 |
logback을 사용하여 log관리하기 (0) | 2024.05.10 |
Apache Commons Library (0) | 2024.05.09 |
[JAVA] 네트워크, Server와 socket (0) | 2023.11.15 |