Recent Posts
Recent Comments
Link
06-30 12:53
Today
Total
관리 메뉴

삶 가운데 남긴 기록 AACII.TISTORY.COM

람다식 본문

DEV&OPS/Java

람다식

ALEPH.GEM 2022. 5. 6. 17:30

Lambda Expressions

함수형 프로그래밍은 병렬처리와 이벤트 처리에 효율적이어서, 객체지향 언어인 자바에서도 함수형 프로그래밍의 장점을 도입하기 위해 람다식이 자바 8부터 도입되었습니다.

람다식은 익명함수를 생성하기 위한 식입니다.

 

기본 문법

(타입, 매개변수) -> {실행문; }

(int a)->{System.out.println(a);}

매개변수의 타입은 자동 인식 될 수 있기 때문에 생략 가능합니다.

(a)->{System.out.println(a);}

매개변수가 한개만 있다면 소괄호도 생략 가능합니다.

실행문도 한개만 있다면 중괄호도 생략 가능합니다.

a->System.out.println(a)

만약 매개변수가 없다면 소괄호를 반드시 표기 해야 합니다.

()->System.out.println(a)

결과값이 있다면 return문을 사용할 수 있습니다.

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

중괄호에 return문만 있다면 return과 중괄호를 생략 가능합니다.

(x,y)->x+y

 

타겟 타입 Target type 

자바의 메서드는 객체의 멤버이기 때문에 독립적으로 정의할 수 없습니다.

그래서 람다식은 인터페이스 변수에 대입됩니다.

그렇다는 것은 람다식이 인터페이스의 익명구현 객체를 생성한다는 뜻입니다.

람다식은 대입될 인터페이스의 종류에 따라 작성 방법이 달라지기 때문에 람다식이 대입될 인터페이스를 람다식의 타겟 타입(target type)이라고 합니다.

 

함수적 인터페이스 @FunctionalInterface

한 개의 추상 메서드만 선언된 인터페이스만 람다식의 타겟 타입이 될 수있습니다.

이런 인터페이스를 Functional Interface라고 합니다.

어노테이션으로 @FunctionalInterface 를 붙이면 두 개 이상의 추상 메서드를 선언하면 컴파일 오류를 발생시킵니다.

public class MyFunctionalInterfaceEx {

	public static void main(String[] args) {
		MyFunctionalInterface fi;
		fi=()->{
			String str = "method call 1";
			System.out.println(str);
		};
		fi.method();
		
		fi=()->{System.out.println("method call 2");};
		fi.method();
		
		fi=()->System.out.println("method call 3");
		fi.method();
	}

}

@FunctionalInterface
interface MyFunctionalInterface {
	public void method();
}

실행 결과

method call 1
method call 2
method call 3

 

매개변수가 있는 람다식

public class ParameterEx {

	public static void main(String[] args) {
		FunctionalInterface2 fi;
		fi=(x)->{
			int result = x +5;
			System.out.println(result);
		};
		fi.method(2);
		
		fi=(x)->{System.out.println(x*5);};
		fi.method(2);
		
		fi=x->System.out.println(x/5);
		fi.method(10);
	}

}

@FunctionalInterface
interface FunctionalInterface2 {
	public void method(int x);
}

실행 결과

7
10
2

 

리턴값이 있는 람다식

public class ReturnEX {

	public static void main(String[] args) {
		FunctionalInterface3 fi;
		fi=(x,y)->{
			int result = x + y;
			return result;
		};
		System.out.println(fi.method(2, 5));
		
		fi=(x,y)->{return x+y;};
		System.out.println(fi.method(2, 5));
		
		fi=(x,y)->x+y;
		System.out.println(fi.method(2, 5));		
	}

}

@FunctionalInterface
interface FunctionalInterface3 {
	public int method(int x, int y);
}

실행 결과

7
7
7

 

클래스의 멤버 사용

익명객체의 내부 this는 익명객체의 참조이지만 람다식에서 this는 람다식을 실행한 객체의 참조입니다.

@FunctionalInterface
interface FunctionalInterface4 {
	public void method();
}

class UsingThis{
	public int outterField = 10;
	class Inner{
		int innerFiled = 20;
		void method() {
			FunctionalInterface4 fi=()->{
				System.out.println("outter1:"+outterField);
				//바깥 객체의 참조를 위해서는 클래스명.this로 참조
				System.out.println("outter2:"+UsingThis.this.outterField +"\n");
				System.out.println("inner1:"+innerFiled);
				//람다식 내부에서 this는 객체를 참조
				System.out.println("inner2:"+this.innerFiled +"\n");
			};
			fi.method();
		}
	}
}

public class ThisEx {
	public static void main(String[] args) {
		UsingThis usingThis = new UsingThis();
		UsingThis.Inner inner = usingThis.new Inner();
		inner.method();
	}
}

실행 결과

outter1:10
outter2:10

inner1:20
inner2:20

 

로컬 변수 사용

람다식은 메소드 내부에서 주로 작성되기 때문에 로컬 익명 객체를 생성한다고 봐야합니다.

람다식에서 바깥 클래스의 필드나 메소드는 제한없이 사용할 수 있습니다.

하지만 메소드의 로컬 변수를 사용하면 final 특성을 가져야 합니다.

그래서 로컬 변수를 람다식에서 읽는 것은 허용되지만 변경할 수 는 없습니다.

@FunctionalInterface
interface FunctionalInterface5 {
	public void method();
}

class UsingLocalVariable{
	// arg와 localVar는 final 속성
	void method(int arg) {
		int localVar = 40;	//이후 수정 불가
		
		FunctionalInterface5 fi=()->{
			System.out.println(arg);
			System.out.println(localVar);
		};
		fi.method();
	}
}

public class UsingLocalEx {

	public static void main(String... args) {
		UsingLocalVariable ulv = new UsingLocalVariable();
		ulv.method(20);
	}

}

실행 결과

20
40

 

표준 API의 함수적 인터페이스

자바에서 제공되는 인터페이스 중에서 한 개의 추상 메서드를 갖는 인터페이스들은 모두 람다식을 이용해 익명 객체로 표현이 가능합니다.

예를 들어 Runnable 인터페이스는 run() 메서드만 존재하기 때문에 람다식을 이용해 인스턴스를 생성할 수 있습니다.

public class RunnableEx {

	public static void main(String[] args) {
		Runnable runnable=()->{
			for(int i=0;i<5;i++) {
				System.out.println(i);
			}
		};
		Thread thread = new Thread(runnable);
		thread.start();
	}

}

 

java.util.function

java 8부터 java.util.function 패키지에 functional interface를 정의해 놓아서 람다식을 사용할 수 있게 해놓았습니다.

  • Consumer : parameter는 있고 return 값은 없음
  • Supplier : parameter는 없고 return 값은 있음
  • Function : parameter도 있고 return 값도 있음. 주로 parameter를 리턴값으로 매핑
  • Operator : parameter도 있고 return 값도 있음. 주로 parameter를 연산한 결과를 리턴
  • Predicate : parameter가 있고 return은 boolean  

 

Consumer 함수적 인터페이스

Consumer는 parameter는 있고 return 값은 없습니다.

parameter를 받아 소비하는 목적입니다.

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.ObjIntConsumer;

public class ConsumerEx {

	public static void main(String[] args) {
		//객체 T를 매개로 받음
		Consumer<String> consumer = t->System.out.println(t+"8");
		consumer.accept("JAVA");
		
		//객체 T와 U를 매개로 받음
		BiConsumer<String, String> biConsumer = (t,u)->System.out.println(t+u);
		biConsumer.accept("JAVA", "8");
		
		//double 데이터를 매개로 받음
		DoubleConsumer doubleConsumer = d->System.out.println("JAVA"+d);
		doubleConsumer.accept(8.0);
		
		//객체 T와 int값을 매개로 받음
		ObjIntConsumer<String> objIntConsumer = (t,i)->System.out.println(t+i);
		objIntConsumer.accept("JAVA", 8);
	}

}

 

Supplier 함수적 인터페이스

Suplier는 parameter는 없고 return 값은 있습니다. 

리턴값(데이터)을 공급하는 역할입니다.

import java.util.function.IntSupplier;
import java.util.function.Supplier;

public class SupplierEx {

	public static void main(String[] args) {
		Supplier<String> sup=()->{
			return "Dice: ";
		};
		String str = sup.get();

		IntSupplier intSupplier =()->{
			int num = (int)(Math.random()*6)+1;
			return num;
		};
		int num = intSupplier.getAsInt();
		
		System.out.println(str + num);		
	}

}

 

Function 함수적 인터페이스

Function 인터페이스는 parameter와 return값이 있는 apply~~~() 메소드를 갖고 있습니다.

parameter를 return 값으로 매핑(타입변환)하는 역할을 합니다.

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;

class Student{
	private String name;
	private int engScore;
	private int mathScore;
	
	public Student(String name, int engScore, int mathScore) {
		this.name = name;
		this.engScore = engScore;
		this.mathScore = mathScore;
	}
	
	public String getName() {return name;}
	public int getEngScore() {return engScore;}
	public int getMathScore() {return mathScore;}
}

public class FunctionEx {
	private static List<Student> list = Arrays.asList(
			new Student("홍길동", 90, 96), 
			new Student("고길동", 95, 93)
			);
	
	public static void printString(Function<Student, String> function) {
		for(Student student : list) {
			System.out.print(function.apply(student) + " ");
		}
		System.out.println();
	}
	
	public static void printInt(ToIntFunction<Student> function) {
		for(Student student : list) {
			System.out.print(function.applyAsInt(student) + " ");
		}
		System.out.println();
	}
	
	public static double avg(ToIntFunction<Student> function) {
		int sum = 0;
		for(Student student : list) {
			sum += function.applyAsInt(student);
		}
		double avg = (double) sum / list.size();
		return avg;
	}

	public static void main(String[] args) {
		System.out.println("[이름]");
		printString(t->t.getName());
		System.out.println("[영어]");
		printInt(t->t.getEngScore());
		System.out.println("[수학]");
		printInt(t->t.getMathScore());
		
		double engAvg = avg(s->s.getEngScore());
		System.out.println("영어평균:"+engAvg);
		double mathAvg = avg(s->s.getMathScore());
		System.out.println("수학평균:"+mathAvg);
	}

}

 

Operator 함수적 인터페이스

Operator는 Fuction과 동일하게 매개 변수와 리턴 값이 있는 apply~~~() 메소드를 가지고 있습니다.

이 메소드들은 매개 변수를 이용하여 연산을 수행 한 후 같은 데이터 타입으로 리턴값을 제공하는 역할입니다.

import java.util.function.IntBinaryOperator;

public class OperatorEx {

	private static int[] scores = {92, 97, 84};
	
	public static int maxOrMin(IntBinaryOperator operator) {
		int result = scores[0];
		for(int score : scores) {
			result = operator.applyAsInt(result, score);
		}
		return result;
	}
	
	public static void main(String[] args) {
		
		int max = maxOrMin(
			(a, b)->{
				if(a>=b) return a;
				else return b;
			});
		System.out.println("최대값:"+max);
		
		int min = maxOrMin(
			(a,b)->{
				if(a<=b) return a;
				else return b;
			});
		System.out.println("최소값:"+min);
	}

}

 

Predicate 함수적 인터페이스

Predicate 함수적 인터페이스는 매개 변수가 있고 리턴 값은 boolean인 test~~~() 메소드를 가지고 있습니다.

이 메소드들은 매개 변수 값을 조사해서 true나 false를 리턴합니다.

예) boolean test(T t) 는 객체 T를 조사해서 true나 false를 리턴합니다.

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

class Student2{
	private String name;
	private String sex;
	private int score;
	
	public Student2(String name, String sex, int score) {
		this.name = name;
		this.sex = sex;
		this.score = score;
	}
	
	public String getSex() {return sex;}
	public int getScore() {return score;}
}

public class PredicateEx {
	private static List<Student2> list = Arrays.asList(
		new Student2("홍길동", "남자", 90),
		new Student2("신사임당", "여자", 90),
		new Student2("고길동", "남자", 95),
		new Student2("허난설헌", "여자", 92)
	);
	
	public static double avg(Predicate<Student2> predicate) {
		int count = 0, sum = 0;
		for(Student2 student : list) {
			if(predicate.test(student)) {
				count++;
				sum += student.getScore();
			}
		}
		return (double) sum /count;		
	}

	public static void main(String[] args) {
		double maleAvg = avg(t->t.getSex().equals("남자"));
		System.out.println("남자평균:"+maleAvg);
		double femaleAvg = avg(t->t.getSex().equals("여자"));
		System.out.println("여자평균:"+femaleAvg);
	}

}

 

andThen(), compose()

함수적 인터페이스에도 default, static 메소드를 가지고 있을 수 있습니다.

Consumer, Function, Operator 에도 andThen(),과 compose()를 가지고 있습니다.

두 개의 함수적 인터페이스를 순차적으로 연결해 첫번째 처리 결과를 두번째 매개 변수로 제공해서 최종 결과값을 얻을 때 사용합니다.

두 메소드의 차이는 처리하는 순서의 차이가 있습니다.

A.andthen(B)는 A처리 후 B를 처리하며 A.compose(B)는 B처리 후 A를 처리합니다.

import java.util.function.Consumer;
import java.util.function.Function;

class Member{
	private String name;
	private String id;
	private Address address;
	public Member(String name, String id, Address address) {
		this.name = name;
		this.id = id;
		this.address = address;
	}
	public String getName() {return name;}
	public String getId() {return id;}
	public Address getAddress() {
		return address;
	}
}

class Address{
	private String country;
	private String city;
	public Address(String country, String city) {
		this.country = country;
		this.city = city;
	}
	
	public String getCountry() {return country;}
	public String getCity() {return city;}
}

public class ConsumerAndThenEx {

	public static void main(String[] args) {
		Consumer<Member> consumerA = (m)->{
			System.out.println("consumerA: "+m.getName());
		};
		Consumer<Member> consumerB = (m)->{
			System.out.println("consumerB: "+m.getId());
		};
		Consumer<Member> consumerAB = consumerA.andThen(consumerB);
		consumerAB.accept(new Member("홍길동", "hong", null));
		
		Function<Member, Address> functionA;
		Function<Address, String> functionB;
		Function<Member, String> functionAB;
		String city;
		
		functionA = (m)->m.getAddress();
		functionB = (a)->a.getCity();
		
		functionAB = functionA.andThen(functionB);
		city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "부산")));
		System.out.println("도시:"+city);
		
		functionAB = functionB.compose(functionA);
		city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "서울")));
		System.out.println("도시:"+city);
	}

}

 

and(), or(), negate() 디폴트 메소드

Predicate 는 boolean을 다루기 때문에 and(),or(),negate() 디폴트 메소드를 가지고 있습니다.

import java.util.function.IntPredicate;

public class PredicateEx2 {

	public static void main(String[] args) {
		//2의 배수 검사
		IntPredicate predicateA = a->a%2 == 0;
		//3의 배수 검사
		IntPredicate predicateB = (a)->a%3 == 0;
		
		IntPredicate predicateAB;
		boolean result;
		
		predicateAB = predicateA.and(predicateB);
		result = predicateAB.test(9);
		System.out.println("9는 2와 3의 공배수 입니까? "+result);
		
		predicateAB = predicateA.or(predicateB);
		result = predicateAB.test(9);
		System.out.println("9는 2또는 3의 배수 입니까? "+result);
		
		predicateAB = predicateA.negate();
		result = predicateAB.test(9);
		System.out.println("9는 홀수 입니까? "+result);
		
	}

}

 

isEqual() 정적 메소드

import java.util.function.Predicate;

public class PredicateIsEqualEx {

	public static void main(String[] args) {
		Predicate<String> predicate;
		
		predicate = Predicate.isEqual(null);
		System.out.println("null, null: "+ predicate.test(null));
		
		predicate = Predicate.isEqual("Java");
		System.out.println("null, Java: "+ predicate.test(null));
		
		predicate = Predicate.isEqual(null);
		System.out.println("Java, null: "+ predicate.test("Java"));
		
		predicate = Predicate.isEqual("Java");
		System.out.println("Java, Java: "+ predicate.test("Java"));
		
		predicate = Predicate.isEqual("Java");
		System.out.println("java, Java: "+ predicate.test("java"));
	}

}

 

minBy(), maxBy()

BinaryOperator<T> 함수적 인터페이스는 minBy()와 maxBy() 정적 메소드를 제공합니다.

이 두 메소드는 매개변수로 제공되는 Comparator를 이용해서 최대T와 최소T를 얻는 BinaryOperator<T>를 리턴합니다.

 

메소드 참조

Method References 는 메소드를 참조해서 매개변수나 리턴타입을 알아내어 람다식에서 불필요한 매개 변수를 제거하는 목적입니다.

정적(static) 메소드를 참조하는 경우 클래스 이름뒤에 :: 기호를 붙이고 정적 메소드 이름을 기술하면 됩니다.

인스턴스 메소드인 경우 인스턴스 객체를 생성한 다음 인스턴스 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 기술합니다.

import java.util.function.IntBinaryOperator;

class Calculator{
	public static int staticMethod(int x, int y) {
		return x+y;
	}
	
	public int instanceMethod(int x, int y) {
		return x+y;
	}
}

public class MethodReferencesEx {

	public static void main(String[] args) {
		IntBinaryOperator operator;
		
		operator = (x, y)->Calculator.staticMethod(x, y);
		System.out.println("결과1: "+operator.applyAsInt(1, 2));
		operator = Calculator :: staticMethod;
		System.out.println("결과2: "+operator.applyAsInt(3, 4));
		
		Calculator obj = new Calculator();
		operator = (x,y)->obj.instanceMethod(x, y);
		System.out.println("결과3: "+operator.applyAsInt(5, 6));
		operator = obj :: instanceMethod;
		System.out.println("결과4: "+operator.applyAsInt(7, 8));
	}

}

 

매개 변수의 메소드 참조

import java.util.function.ToIntBiFunction;

public class ArgumentRefEx {

	public static void print(int order) {
		if(order<0) {
			System.out.println("사전순으로 먼저 옵니다.");
		}else if(order == 0) {
			System.out.println("동일합니다.");
		}else {
			System.out.println("사전순으로 나중에 옵니다.");
		}
	}
	
	public static void main(String[] args) {
		ToIntBiFunction<String, String> function;
		
		function = (a, b)->a.compareToIgnoreCase(b);
		print(function.applyAsInt("Java", "JAVA"));
		
		function = String :: compareToIgnoreCase;
		print(function.applyAsInt("Java", "JAVA"));
	}

}

 

생성자 참조

import java.util.function.BiFunction;
import java.util.function.Function;

class Member2{
	private String name;
	private String id;
	
	public Member2() {
		System.out.println("Member2() 실행");
	}
	
	public Member2(String id) {
		System.out.println("Member2(String id) 실행");
		this.id = id;
	}
	
	public Member2(String name, String id) {
		System.out.println("Member2(String name, String id) 실행");
		this.name = name;
		this.id = id;
	}
	
	public String getId() {return id;}
}

public class ConstrutorEx {

	public static void main(String[] args) {
		Function<String, Member2> function1 = Member2 :: new;
		Member2 member1 = function1.apply("hong");	//인수가 1개인 생성자
		
		BiFunction<String, String, Member2> function2 = Member2 :: new;
		Member2 member2 = function2.apply("홍길동", "hong");	//인수가 2개인 생성자
	}

}

 

 

 

 

 

 

728x90

'DEV&OPS > Java' 카테고리의 다른 글

Set 컬렉션  (0) 2022.05.09
List 컬렉션  (0) 2022.05.09
Generic  (0) 2022.04.21
Thread Pool  (0) 2022.04.20
JAVA Thread  (0) 2022.04.19