뭐라도 끄적이는 BLOG

Java Lambda 본문

Java/Java 기본

Java Lambda

Drawhale 2023. 7. 2. 11:09

람다(java 8)

public interface Printable {
    void print(String s);
}
public class Printer implements Printable{
    @Override
    public void print(String s) {
        System.out.println(s);
    }

    public static void main(String[] args) {
        Printable prn = new Printer();
        prn.print("What is Lambda?");
    }
}

일반적으로 인터페이스는 바로 인스턴스를 생성할 수 없기 때문에 클래스로 구현한 뒤에 사용한다. 하지만 인터페이스의 함수가 적거나 이벤트와 같이 특별한 경우에만 적용된다면 익명 클래스를 이용해서 사용하기도 한다.

public class AnonymousClass {
    public static void main(String[] args) {
        Printable prn = new Printable() {
            @Override
            public void print(String s) {
                System.out.println(s);
            }
        };
        prn.print("What is Anonymous?");
    }
}

위 코드를 보면 몇가지 이정도는 없어도 되지 않을까 라는 부분 언뜻 보인다.

  1. Printable 객체를 익명 클래스로 생성하는데 new Printable()은 당연한것이니 빼도 되지 않을까?
  2. Printable 인터페이스에서 추상 메소드는 단 하나인데 굳이 함수 시그니처를 또 적을 필요가 있을까?

두가지를 생략할 수 있으나 함수 시그니처까지 사라지면 s가 매개변수라고 판단하기까진 무리다. 그래서 매개변수까진 남겨두면 아래와 같은 코드가된다.

public class LambdaClass {
    public static void main(String[] args) {
        Printable prn = (s) -> { System.out.println(s);};
        prn.print("What is Lambda?");
    }
}

이렇듯 컴파일러가 충분히 유추할 수 있는 부분을 생략하면서 완성된 식이 바로 람다식이다. 익명 클래스 보다 코드의 양이 줄어들기도 하였고 코드의 가독성이 높아지기도 한다.

※ 해당 설명은 코드의 이해를 돕기 위한 것이지 실제 익명클래스와 람다의 실행과정은 차이가 있습니다.

람다식 사용법

"->"는 람다식을 작성하는데 사용이 되는 연산자이다.

(매개변수) -> {코드};
Printable prn = null;
prn = (String s)-> { //생략 없이 작성
    System.out.println(s);
};
prn = (s)-> { // 매개변수 타입 생략
    System.out.println(s);
};
prn = s->{ // 매개변수 괄호 생략 (매개변수가 하나인경우 가능)
    System.out.println(s);
};
prn = s-> System.out.println(s);
// 실행 코드 중괄호 생략 (실행코드가 한줄일때 가능)

Sum sum = null;
sum = (a, b) -> { //반환값이 있는 경우
    return a+b;
};
sum = (a, b) -> a + b;
//반환값이 있는 경우

Printable2 prn2 = () -> System.out.println("Lambda");
// 매개변수가 없는 경우

함수형 인터페이스

인터페이스 중 추상메소드가 단 하나인 인터페이스를 함수형 인터페이스라고 한다. 람다식을 사용할때 대상이 되는 인터페이스가 추상메소드가 하나인 인터페이스이기 때문에 고유의 이름으로 부르게 되었다.

이미 정의되어 있는 함수형 인터페이스

사용자가 직접 함수형 인터페이스를 만들어 사용할 수도 있지만 Java에서는 이미 함수형 인터페이스들을 제공해주고 있다. 이러한 함수형 인터페이스들은 java.util.function 패키지에서 볼 수 있다. 다양한 함수형 인터페이스중 대표적으로 4가지만 살펴보겠다.

함수 설명
Predicate<T> boolean test(T t) 전달 인자를 근거로 참 또는 거짓을 반환
Supplier<T> T get() 메소드 호출 시 T형의 인스턴스를 반환함
Consumer<T> void accept(T t) 인스턴스를 받아들이기만 함
Function<T, R> R apply(T t) 입출력 (수학적으로 사용)

Predicate<T>

boolean test(T t);

public static int sum(Predicate<Integer> p, List<Integer lst){
	int s = 0;
	for(int n : lst){
		if(p.test(n))
			s += n;
	}
	return s;
}
...
s = sum(n -> n%2 == 0, list);
System.out.println("짝수의 합 : " + s);

Predicate<T>인터페이스들

  • IntPredicate
  • LongPredicate
  • DoublePredicate
  • BiPredicate<T, U>

Predicate<Integer>를 IntPredicate로 대체할 수 있다. 불필요한 박싱과 언박싱 과정을 줄일수 있다.

Supplier<T>

T get();

public static List<Integer> makeIntList(Supplier<Integer> s, int n){
	List<Integer> list = new ArrayList<>();
	for(int i=0; i<n; i++)
		list.add(s.get());
	return list;
}
...
Supplier<Integer> spr = () -> {
	Random rand = new Random(); //난수를 생성해 담는다.
	return rand.nextInt(50);
}
List<Integer> list = makeIntList(spr,5);

Supplier인터페이스들

  • IntSupplier
  • LongSupplier
  • DoubleSupplier
  • BooleanSupplier

Consumer<T>

void accept(T t);

class ConsumerDemo {
	public static void main(String[] args){
		Consumer<String> c = s-> System.out.println(s);//출력이라는 결과를 보인다.
		c.accept(".....");
	}
}

Consumer<T> 인터페이스들

  • IntConsumer
  • ObjIntConsumer<T>
  • LongConsumer
  • ObjLongConsumer<T>
  • DoubleConsumer
  • ObjDoubleConsumer<T>
  • BiConsumer<T, U>

Function<T,R>

R apply(T t);

class FunctionDemo{
	public static void main(String[] args){
		Function<String, Integer> f = s -> s.length();
		System.out.println(f.apply("test"));
	}
}
  • IntToDoubleFunction
  • .....
  • IntUnaryOperator → 둘다 Int
  • ....
  • BiFunction<T, U, R>
  • IntFunction<R> → 매개 변수를 Int
  • ToIntFunction<T> →반환을Int
  • UnaryOperator<T> → T apply(T t)
  • BinaryOperator<T> → T apply(T t1, T t2)

등등 함수형 인터페이스 들은 어느정도 규칙을 가지고 선언되어 있다.

Variable Capture

Java 람다식은 특정 상황에서 람다 본문 외부에서 선언된 지역 변수의 값을 캡처할 수 있다.

public interface MyFactory {
    public String create (char[] chars);
}
public class LocalCapture {
    public static void main(String[] args) {
        String local = "test";
        MyFactory myFactory = (chars)-> local + new String(chars);
    }
}

람다 본문의 외부에 선언된 local 지역 변수를 참조하고있다. 이경우 local 변수는 처음으로 값을 할당 받은 이후 값을 변경하지 않아야 한다. 만일 값을 변경하게 되면 람다 본문에서 컴파일 에러가 발생한다.

보통 이런 일을 막기위해 final 키워드로 상수로 선언해준다.

값이 처음으로 할당된 이후 변경될 가능성이 없다면 컴파일러에서 final과 동일하게 취급하기 때문에 가능하다.

메소드, 생성자 레퍼런스

메소드를 간결하게 표현할 수 있는 방법이다. 일반 함수를 람다 형태로 사용할 수 있도록 해준다.

static 메소드 참조

public interface Finder {
    public int find(String s1, String s2);
}
public class MyClass {
    public static int doFind(String s1, String s2){
        return s1.lastIndexOf(s2);
    }
}
Finder finder = MyClass::doFind;

매개변수 참조

Finder finder1 = String::indexOf;

위 메소드 참조는 아래 람다식과 동일하다.

Finder finder = (s1, s2) -> s1.indexOf(s2);

인스턴스 메소드 참조

public interface Deserializer {
    public int deserialize (String s);
}
public class StringConverter {
    public int convertToInt(String s){
        return Integer.valueOf(s);
    }
}
StringConverter stringConverter = new StringConverter();
Deserializer des = stringConverter::convertToInt;

생성자 참조

public interface Factory {
    public String create(char[] chars);
}
Factory factory = String::new;

위 메소드 참조는 아래 람다식과 동일하다.

Factory factory = chars -> new String(chars);
반응형

'Java > Java 기본' 카테고리의 다른 글

Java Generic  (0) 2023.07.02
Java I/O  (0) 2023.07.02
Java Annotation  (0) 2023.07.02
Java Enum  (0) 2023.07.02
Java Thread  (0) 2023.07.02