본문 바로가기

java

[201216] lambda 및 함수적 인터페이스

 

.

 

이전 iterator 글을 쓰면서 Iteraotr interface 소스를 읽어보다가, 매개변수에 Consumer 자료형의 변수가 있는 걸 보고 궁금해서 찾아보다가 람다식에 대해 알아보고 간략하게 정리하게 되었다. 

 

.

lambda란?


JAVA8에서 추가되었으며 익명 함수 구현을 위한 것이다. 익명 함수는 적은 횟수나, 한 번만 정의되고 쓰일 함수를 말한다.

람다식을 사용하면 코드 라인 수를 줄이고 코드를 간결하게 작성할 수 있다. Stream API나, 함수적 인터페이스를 사용했을 때 더 강력하게 이용할 수 있다. 어떤 면에서는 병렬 처리나, 성능 향상의 장점도 있다고 한다.

( stackoverflow.com/questions/33492203/what-are-the-advantages-of-using-lambda-expressions-in-java-8 )

하지만 코드를 한 번에 이해하기 어렵다는 단점이 있다. 람다식 한 줄을 이해하기 위해서는 코드를 일일이 읽어봐야하는 일이 생길 수도 있다. 간결하게 작성할 수 있다는 점에선 어쩔 수 없는 일 같다. 

 

생성 


람다 식은 인터페이스의 익명 구현 객체를 생성한다. 이때, 인터페이스는 하나의 추상 메소드만 선언되어있는 상태여야 한다. 

(매개변수, ...) -> {명령문; ...}을 통해서 정의한다. 매개변수나 명령문이 하나만 있다면 (), {}를 생략할 수 있다.

 

예시)

public class Main {
            
	public static void main(String[] args) {
		
		LambdaSample lambdaSample = (a)->{System.out.println("a: " + a);};
//        LambdaSample lambdaSample2 = a->{System.out.println("a: " + a);};
//        LambdaSample lambdaSample3 = (a)->System.out.println("a: " + a);
//        LambdaSample lambdaSample4 = a->System.out.println("a: " + a);
        lambdaSample.print(1);
    }
 
    public interface LambdaSample{
    	public void print(int a);
    }

}

실행 결과)

 

함수적 인터페이스 


하나의 추상 메소드만 가지고 있는 interface를 말하며, 디폴트 메소드나 정적 메소드는 별개로 포함할 수 있다. 많이 사용되는 형태의 인터페이스를 java.util.function 패키지에서 제공하기 때문에, 위에서 사용한 예제처럼 매번 인터페이스를 만들 필요는 없다. 

 

제공하는 메소드 종류는 크게 다섯 가지로 아래와 같다.

 

Consumer 하나 혹은 그 이상의 매개변수를 받고 return값은 없다. 
Supplier 공급자, 할당의 역할을 한다.  getter의 함수적 인터페이스.
Function 하나 혹은 그 이상의 매개변수를 받고 결과를 return한다. 
Operator 매개변수에 대해 연산을 한 후 결과를 return한다. 
Predicate 매개변수에 대한 boolean 값을 test하고 return한다. 

 

 

  • Consumer

@FunctionalInterface 애노테이션이 붙어있어서 해당 인터페이스가 함수적 인터페이스임을 확인할 수 있다. 실제로도 추상 메소드 accept와, 디폴트 메소드 하나가 있다. 

 

Consumer는 소비자로 매개변수는 받지만 return 값은 없는 것이 특징이다. 위 Consumer<T> 인터페이스 외에도 다양한 Consumer 인터페이스를 제공한다. 

BiConsumer<T,U> 2개의 매개변수를 받고 return 값은 없다. 
Consumer<T> 1개의 매개변수를 받고 return 값은 없다. 
DoubleConsumer double 형의 매개변수 하나를 받고 return 값은 없다. 
IntConsumer int 형의 매개변수 하나를 받고 return 값은 없다. 
LongConsumer long 형의 매개변수 하나를 받고 return 값은 없다. 
objDoubleConsumer<T> object 형, double 형의 매개변수를 하나씩 받고 return 값은 없다. 
objIntConsumer<T> object 형, int 형의 매개변수를 하나씩 받고 return 값은 없다. 
objLongConsumer<T> object 형, long 형의 매개변수를 하나씩 받고 return 값은 없다. 

 

예제)

public class Main {
            
	public static void main(String[] args) {
		
        Consumer<Integer> consumer = (a)-> System.out.println("a: " + a) ;
        consumer.accept(5);
        
        BiConsumer<String, String> biConsumer = (a, b) -> System.out.println("a: " + a + " b: " + b);
        biConsumer.accept("java", "jdk");
        
        IntConsumer intConsumer = (a) -> System.out.println(a+5);
        intConsumer.accept(10);
        
        DoubleConsumer doubleConsumer = (a) -> System.out.println(a+5.0);
        doubleConsumer.accept(13.3);
        
        ObjIntConsumer<String> objIntConsumer = (a, b)-> System.out.println(a + Integer.toString(b));
        objIntConsumer.accept("java", 8);
    }
 
}

 

실행 결과)

 

 

  • Supplier

 

Supplier는 공급자로 어떠한 객체의 인스턴스를 반환하는 메소드의 역할을 한다. 매개변수는 없고 return 값만 있다. 

BooleanSupplier Bollean 값을 생성해서 return한다. 
DoubleSupplier double 값을 생성해서 return한다. 
IntSupplier int 값을 생성해서 return한다. 
LongSupplier long 값을 생성해서 return한다. 
Supplier<T> 값을 생성해서 return한다. 

 

supplier를 사용하는 이유에 대해서도 찾아봤는데, getter나 생성자를 호출해서 직접 객체를 생성하는 것에 비해서 성능이 향상되는건 아니라고 한다. 잘 모르겠지만, getter보다 supplier를 사용했을 때 더 효율적인 상황이 있는 것 같다. getter를 수행하기 전에 조건이나 추가 작업이 필요할 때가 그렇다. 이 경우에는 람다식을 사용해서 코드 중복도 줄일 수 있다. 또, lazy evaluation을 구현해야할 때 supplier를 사용할 수 있다고 한다. 이 부분은 글을 읽어도 아직 잘 와닿지 않는다. 자세한 건 예제를 통해 설명하겠다. 

 

예제)

public class Main {
            
	static class Person{
		
		String name;
		String id;
		int age;
		
		public Person(String name, String id, int age) {
			this.name = name;
			this.id = id;
			this.age = age;
		}
		
		public void print() {
			System.out.println("info) name: "+ name +" id: " + id + " age: " + age);
		}
		
	}
	
	static void getPerson(Supplier<? extends Person> supplier) {
		Person person = supplier.get();
		person.print();
		
	}
	public static void main(String[] args) {
		
        getPerson(()->new Person("david", "1234", 20));
        getPerson(()->new Person("Jenny", "3345", 25));
        
        DoubleSupplier doubleSupplier = ()->Math.random();
		System.out.println(doubleSupplier.getAsDouble());
    }
 
}

 

실행 결과)

 

getPerson 메소드는 람다식을 매개변수로 받아서 person 객체를 생성하고 정보를 출력한다. 만약 정보 출력 전에 1초를 기다리게 하고 싶다면 다음과 같이 getPerson 메소드를 수정할 수 있다. 

	static void getPerson(Supplier<? extends Person> supplier) throws Exception {
		Person person = supplier.get();
		Thread.sleep(1000); // add 
		person.print();
	}

 

 

 

 

또한, DoubleSupplier를 통해서 random의 난수를 출력하는 것 까지 확인해보았다. 

 

 

  • Function

function은 매개변수를 받아서 return 값을 매핑하는 역할을 한다. 

BiFunction<T,U,R> 'T'형, 'U'형 2개의 매개변수를 받고 'R'형 return 값을 만든다. 
DoubleFunction<R> double 형 매개변수를 받고 'R'형 return 값을 만든다. 
DoubleToIntFunction double 형 매개변수를 받고 int 형 return 값을 만든다. 
DoubleToLongFunction double 형 매개변수를 받고 long 형 return 값을 만든다. 
Function<T,R> 'T'형 매개변수를 받아서 'R'형 return 값을 만든다. 
IntFunction<R> int 형 매개변수를 받고 'R'형 return 값을 만든다. 
IntToDoubleFunction int 형 매개변수를 받고 double 형 return 값을 만든다. 
IntToLongFunction int 형 매개변수를 받고 long 형 return 값을 만든다. 
LongFunction<R> long 형 매개변수를 받고 'R'형 return 값을 만든다. 
LongToDoubleFunction long 형 매개변수를 받고 double 형 return 값을 만든다. 
LongToIntFunction long 형 매개변수를 받고 int 형 return 값을 만든다. 
ToDoubleBiFunction<T,U> 'T'형, 'U'형 2개의 매개변수를 받고 double 형 return 값을 만든다. 
ToDoubleFunction<T> 1개의 'T형' 매개변수를 받고 double 형 return 값을 만든다. 
ToIntBiFunction<T,U> 'T'형, 'U'형 2개의 매개변수를 받고 int 형 return 값을 만든다. 
ToIntFunction<T> 1개의 'T형' 매개변수를 받고 int 형 return 값을 만든다. 
ToLongBiFunction<T,U> 'T'형, 'U'형 2개의 매개변수를 받고 long 형 return 값을 만든다. 
ToLongFunction<T> 1개의 'T형' 매개변수를 받고 long 형 return 값을 만든다. 

 

예제)

public class Main {
            
	static class Person{
		
		String name;
		String id;
		int age;
		
		public Person(String name, String id, int age) {
			this.name = name;
			this.id = id;
			this.age = age;
		}
		
		public String getIdAndName() {
			return "name: " + name + " id: " + id;
		}
		
		public void print() {
			System.out.println("info) name: "+ name +" id: " + id + " age: " + age);
		}
		
	}
	
	public static void main(String[] args) throws Exception {
		
        Function<Person, String> function1 = (p)->{
        	return p.getIdAndName();
        };
        
        Person p = new Person("david", "1234", 20);
        System.out.println(function1.apply(p));
        
        DoubleToIntFunction doubleToIntFunction = (d)->(int)d;
        System.out.println(doubleToIntFunction.applyAsInt(5.0));
        
        IntToDoubleFunction intToDoubleFunction = (i)->(double)i;
        System.out.println(intToDoubleFunction.applyAsDouble(10));
    }
 
}

 

 

실행 결과)

 

 

 

  • Operator

 function 처럼 매개변수와 return 값이 있는 건 같으나, operator는 연산을 수행한 후 그 결과를 return한다는 차이가 있다. 

BinaryOperator<T> 같은 타입의 2개 매개변수를 피연산자로, 같은 타입의 연산 결과를 return 한다. 
DoubleBinaryOperator double 형의 2개 매개변수를 피연산자로, double 형의 연산 결과를 return 한다. 
DoubleUnaryOperator double 형의 1개 매개변수를 피연산자로, double 형의 연산 결과를 return 한다. 
IntBinaryOperator int 형의 2개 매개변수를 피연산자로, int 형의 연산 결과를 return 한다. 
intUnaryOperator int 형의 1개 매개변수를 피연산자로, int 형의 연산 결과를 return 한다. 
LongBinaryOperator long 형의 2개 매개변수를 피연산자로, long 형의 연산 결과를 return 한다. 
LongUnaryOperator long 형의 1개 매개변수를 피연산자로, long 형의 연산 결과를 return 한다. 
UnaryOperator 1개 매개변수를 피연산자로, 같은 타입의 연산 결과를 return 한다. 

 

예제)

public class Main {
      
	public static void main(String[] args) throws Exception {
		
		IntBinaryOperator intBinaryOperator = (a, b)->{
					return a + b;
				};
		System.out.println(intBinaryOperator.applyAsInt(3, 5));
		
		IntUnaryOperator intUnaryOperator = (a) -> a * 5;
		System.out.println(intUnaryOperator.applyAsInt(2));
		
		DoubleBinaryOperator doubleBinaryOperator = (a, b) -> a - b;
		System.out.println(doubleBinaryOperator.applyAsDouble(2.34, 1.29));
				
    }
 
}

 

실행 결과)

 

 

  • Predicate

Predicate는 매개변수를 통해서 boolean 값을 return하는, 어떠한 값을 예측하는 역할을 한다. 

BiPredicate<T,U> 2개의 매개변수로부터 boolean 값을 return 한다. 
DoublePredicate 하나의 double 형 매개변수로부터 boolean 값을 return 한다. 
IntPredicate 하나의 int 형 매개변수로부터 boolean 값을 return 한다. 
LongPredicate 하나의 long 형 매개변수로부터 boolean 값을 return 한다. 
Predicate 1개의 매개변수로부터 boolean 값을 return 한다. 

 

예제)

public class Main {
      
	public static void main(String[] args) throws Exception {
		
		Predicate<String> predicate = (s)->s.equals("lion");
		System.out.println(predicate.test("lion"));
		System.out.println(predicate.test("java"));
		
		IntPredicate intPredicate = (i)->i>10;
		System.out.println(intPredicate.test(5));
		System.out.println(intPredicate.test(15));
				
    }
 
}

 

 

실행 결과)

 

 

정리


lambda의 기초적인 정의와 사용법, 그리고 함수적 인터페이스에 관해서 공부하고 정리해보았다. 생각보다 글을 작성하는데 오래 걸렸는데, 예제를 만들어보며 더 빠르게 이해할 수 있게 된 것 같다. 

기회가 된다면 andThen, compose같은 메소드나 stream, lazy evaluation같은 개념도 정리해보고 싶다. 

 

 

 

 

 

- 참고 자료 

palpit.tistory.com/670

en.wikipedia.org/wiki/Anonymous_function#Java

tourspace.tistory.com/3?category=788398

docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

shlee0882.tistory.com/195

stackoverflow.com/questions/40244571/when-we-should-use-supplier-in-java-8/40244843

sas-study.tistory.com/106

 

 

 

 

'java' 카테고리의 다른 글

[210116] java comparable & comparator  (0) 2021.01.16
[210112] java collection  (0) 2021.01.12
[201211] default method  (0) 2020.12.11
[201209] java iterator  (0) 2020.12.09
[201019] spring AOP pointcut annotation  (0) 2020.10.19