본문 바로가기
JAVA

[JAVA] - 람다식

by nam_ji 2023. 10. 17.

람다식(Lambda Expression) 정리

람다식이란?

  • 메소드를 하나의 식(expression)으로 표현한 것입니다.
    람다식으로 표현하면 메소드의 이름과 반환값이 없어지므로, 익명함수라고도 합니다.
  • 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행 가능한 함수입니다.
  • 절차형 프로그래밍, 객체지향 프로그래밍과는 사뭇 다르게 함수의 구현과 호출만으로 프로그램을 만드는 방식인 함수형 프로그래밍에서 자주 사용됩니다.
  • 자바 8부터 도입 되었으며, 람다식을 익명함수로 만드는 것인데 익명함수는 일급객체 특징을 가지고 있습니다.
  • 람다식 사용 예시 => (매개변수, ...) -> {실행문...}

익명함수란?

  • 익명함수란 말 그대로 함수의 이름이 없는 함수입니다.
    익명함수들은 공통으로 일급객체(First Class Citizen)라는 특징을 가지고 있습니다.
  • 이 일급객체란 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 개체를 가르킵니다.
    함수를 값으로 사용할 수도 있으며 파라미터로 전달 및 변수에 대입하기와 같은 연산들이 가능합니다.

일급객체란?

  • 일반적으로 다른 객체들에 적용 가능한 연산을 모두 지원하는 객체입니다.
    함수를 값으로 사용할 수도 있으며 파라미터로 전달 및 변수에 대입하기와 같은 연산들이 가능합니다.
    • 일급객체의 조건
      1. 변수나 데이터 구조 안에 담을 수 있습니다.
      2. 파라미터로 전달할 수 있습니다.
      3. 반환값으로 사용할 수 있습니다.
      4. 할당에 사용된 이름과 무관하게 고유한 구별이 가능합니다.
      5. 함수형 프로그래밍에서 함수는 1급 객체로 취급받기 때문에 함수를 파라미터로 넘기는 등의 작업이 가능한 것입니다.
        또한 우리가 일반적으로 알고 개발했던 함수들은 함수형 프로그래밍에서 정의하는 순수 함수들과는 다르다는 것을 인지해야 합니다.
package study.lambda;

import java.util.Arrays;

public class lambda1 {
	public static void main(String[] args) {
    	// 람다식 변환 예시
		int [] arr = new int [5];
		Arrays.setAll(arr, (i) -> (int)(Math.random()*5)+1); // 기본 람다식
		
		// ===== 이 람다식을 메소드로 표현하면 =====
		
		int lambda() {
			return (int)(Math.random()*5)+1;
		}
	}
}

 


람다식의 장단점

장점

  1. 코드의 간결성 - 람다를 사용하면 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현할 수 있습니다.
  2. 자연연산 수행 - 람다는 자연연산을 수행 함으로써 불필요한 연산을 최소화 할 수 있습니다.
  3. 병렬처리 가능 - 멀티쓰레드를 활용하여 병렬처리를 사용할 수 있습니다.

단점

  1. 람다식의 호출이 까다롭습니다.
  2. 람다 stream 사용 시 단순 for문 혹은 while문 사용 시 성능이 떨어집니다.
  3. 불필요하게 너무 사용하게 되면 오히려 가독성이 떨어질 수 있습니다.

람다식 작성해보기

  1. 람다식은 매개변수 화살표 (->) 함수 몸체로 이용하여 사용할 수 있습니다.
  2. 함수몸체가 단일 실행문이면 괄호 {}를 생략할 수 있습니다.
  3. 함수 몸체가 return문으로만 구성되어 있는 경우 괄호 {}를 생략할 수 없습니다.
//정상적인 유형
() -> {}
() -> 1
() -> { return 1; }

(int x) -> x+1
(x) -> x+1
x -> x+1
(int x) -> { return x+1; }
x -> { return x+1; }

(int x, int y) -> x+y
(x, y) -> x+y
(x, y) -> { return x+y; }

(String lam) -> lam.length()
lam -> lam.length()
(Thread lamT) -> { lamT.start(); }
lamT -> { lamT.start(); }


//잘못된 유형 선언된 type과 선언되지 않은 type을 같이 사용 할 수 없다.
(x, int y) -> x+y
(x, final y) -> x+y

람다식 예제

  • 인자 x, y를 받아들여 x,y의 계산식을 리턴하는 람다식 만들기
package study.lambda;

// 인자 x, y를 받아들여 사칙연산 리턴하는 람다식 만들기
// 함수형 인터페이스
@FunctionalInterface
interface MyFunction {
	int calc(int x, int y);
}

public class Lambda2 {
	
	public Lambda2() {
		MyFunction f = new MyFunction() {
			
			@Override
			public int calc(int x, int y) {
				return x + y;
			}
		};
		System.out.println("f의 결과 : " + f.calc(100, 100));
		
		MyFunction f1 = (x, y) -> { return x+y;};
		System.out.println("f1의 결과 : " + f1.calc(10, 90));
		
		MyFunction f2 = (x, y) -> {return x-y;};
		System.out.println("f2의 결과 : " + f2.calc(90, 10));
	}
	
	public static void main(String[] args) {
		new Lambda2();
	}
}

package study.lambda;

//인자 x, y를 받아들여 사칙연산 리턴하는 람다식 만들기
//함수형 인터페이스
@FunctionalInterface
interface MyFunction1 {
	int calc1(int x);
}

@FunctionalInterface
interface MyFunction2 {
	int calc2(int x, int y);
}

public class Lambda3 {
	public Lambda3() {
		MyFunction1 Square1 = (x) -> { return x * x; };
		System.out.println("Square1의 결과 : " + Square1.calc1(2));
		
		MyFunction1 Square2 = (x) -> { return x * x * x; };
		System.out.println("Square2 의 결과 : " + Square2.calc1(4));
		
		MyFunction2 Square3 = (x, y) -> { return x * y; };
		System.out.println("Square3의 결과 : " + Square3.calc2(6, 8));
	}

	public static void main(String[] args) {
		new Lambda3();
	}
}

  • 매개변수 없는 람다식 만들기
package study.lambda;

// 매개변수가 없는 람다식 만들기
// 함수형 인터페이스
@FunctionalInterface
interface MyFunction3 {
	void print();
}

public class Lambda4 {
	public Lambda4 () {
		MyFunction3 print = () -> {
			System.out.println("Lambda Developer");
		};
		print.print();
	}
	public static void main(String[] args) {
		new Lambda4();
	}
}

  • 메소드 인자로 매개변수 전달
package study.lambda;

// 메소드 인자로 람다식 전달
// 함수형 인터페이스
@FunctionalInterface
interface MyFunction4 {
	int calc (int x, int y);
}

public class Lambda5 {
	// 메소드 정의 (메소드의 인자로 람다식 전달)
	static void printMultiply (int x, int y, MyFunction4 f) {
		System.out.println("실행 결과 : " + f.calc(x, y));
	}
	
	public Lambda5() {
		printMultiply(50, 50, (x, y) -> { return x * y; });
	}
	
	public static void main(String[] args) {
		new Lambda5();
	}
}

  • 제네릭을 이용한 함수형 인터페이스
package study.lambda;

// 제네릭을 이용한 함수형 인터페이스
@FunctionalInterface
interface MyFunction5 <T> {
	void println(T x);
}

public class Lambda6 {
	
	public Lambda6() {
		MyFunction5<String> f1 = x -> {
			System.out.println(x.toString());
		};
		f1.println("String Generic 사용한 람다식");
		f1.println("Lambda Developer");
		
		MyFunction5<Integer> f2 = x -> {
			System.out.println(x.toString());
		};
		f2.println(Integer.valueOf(100));
	}
	public static void main(String[] args) {
		new Lambda6();
	}
}


Stream API이란

  • Stream이란 다양한 데이터를 표준화된 방법으로 다루기 위한 라이브러리입니다.
    자바 8부터 추가된 Stream API는 다음과 같이 구성됩니다.
example.stream().filter(x -> x < 2).count
  • stream() <- 스트림 생성
    filter <- 중간 연산 (스트림 변환) - 연속으로 수행 가능합니다.
    count <- 최종 연산 (스트림 사용) - 마지막에 단 한 번만 사용 가능합니다.

Stream의 특징

  1. Stream은 데이터를 변경하지 않습니다.
  2. Stream은 1회용 입니다.
  3. Stream은 지연 연산을 수행합니다.
  4. Stream은 병렬 실행이 가능합니다.

Stream의 종류

Stream<T> 법용 Stream
IntStream 값 타입이 Int인 Stream
LongStream 값 타입이 Long인 Stream
DoubleStream 값 타입이 Double인 Stream

Stream의 중간 연산 명령어

Stream < T > distinct Stream의 요소 중복 제거
Stream < T > sorted () Stream 요소 정렬
Stream < T > filter (Predicate < T > predicate) 조건에 충족하는 요소를 Stream으로 생성
Stream < T > limit (long maxSize) maxSize까지의 요소를 Stream으로 생성
Stream < T > skip (long n) 처음 n개의 요소를 제외하는 Stream 생성
Stream < T > peek (Consumer < T > action) T타입 요소에 맞는 작업 수행
Stream < R > flatMap (Function < T, stream<? extends R>> 
Tmapper)
T타입 요소를 1:N의 R타입 요소로 변환하여 스트림 생성
Stream < R > map (Function <? super T, ? extends R>
mapper
입력 T타입을 R타입 요소로 변환한 스트림 생성
Stream mapToInt(), mapToLong(), mapToDouble() 만약 map Type이 숫자가 아닌 경우 변환하여 사용

Stream의 최종 연산 명령어

 void forEach(Consumer <? super T> action)  Stream 의 각 요소에 지정된 작업 수행
 long count()  Stream 의 요소 개수
 Optional < T > sum (Comparator <? super T> comparator)  Stream 의 요소 합
 Optional < T > max (Comparator <? super T> comparator)  Stream 요소의 최대 값
 Optional < T > min (Comparator <? super T> comparator)  Stream 요소의 최소 값
 Optional < T > findAny()  Stream 요소의 랜덤 요소
 Optional < T > findFirst()  Stream 의 첫 번째 요소
 boolean allMatch(Pradicate < T > p)  Stream 의 값이 모두 만족하는지 boolean 반환
 boolean anyMatch(Pradicate < T > p)  Stream 의 값이 하나라도 만족하는지 boolean 반환
 boolean noneMatch(Pradicate < T > p)  Stream 의 값이 하나라도 만족하지않는지 boolean 반환 
 Object[] toArray()  Stream 의 모든 요소를 배열로 반환
 reduce 연산  Stream 의 요소를 하나씩 줄여가며 계산한다.
 - Optional < T > reduce(Binary Operator<T> accumulator)
 - T reduce ( T identity, BinaryOperator<T> accumulator)
 - <U> U reduce (U indentity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
 - .reduce((x,y) -> x > y ? x : y );
 - .reduce(1, (x,y) -> x * y);
 - .reduce(0.0,
   (val1, val2) -> Double.valueOf(val1 + val2 / 10),
   (val1, val2) -> val1 + val2);

 

 collector 연산  Stream의 요소를 수집하여 요소를 그룹화 하거나 결과를 담아 반환하는데 사용한다.
 - Collectors.toList()
 - Collectors.toSet()
 - Collectors.toMap()
 - Collectors.groupingBy
 - Collectors.partioningBy
 - Collectors.summarizingInt()

Stream API 예제

package study.lambda;

import java.util.stream.IntStream;

public class Stream1 {
	public static void main(String[] args) {
		IntStream.range(1, 11).filter(i -> i % 2 == 0).forEach(System.out::println);
	}
}

package study.lambda;

import java.util.stream.IntStream;

public class Stream2 {
	public static void main(String[] args) {
		// 0 ~ 1000까지의 값 중 500 이상 & 짝수 & 5의 배수인 수의 합을 구하라
		System.out.println(
				IntStream.range(0, 1001)
				.skip(500)
				.filter(i -> i % 2 == 0)
				.filter(i -> i % 5 == 0)
				.sum());
	}
}

'JAVA' 카테고리의 다른 글

[JAVA] - JPA란 / Hibernate란  (0) 2023.11.10
[JAVA] - 문자열을 정수로 변환 (String to Int)  (0) 2023.10.18
[JAVA] - Comparable과 Comparator의 차이  (0) 2023.10.16
[JAVA] - 배열  (0) 2023.09.29
[JAVA] - 자바 기본 용어 2  (0) 2023.09.27