[Java] 람다식

2024. 1. 28. 21:40공부 중/Java

1. Lamda 표현식

 

public class SamTest {

    SamTest(){
        Sam s = new Sam();
        InterImpl impl = new InterImpl();
        s.m1(impl);
    }

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

}
public class Sam {
    void m1 (Inter inter) {
        int res = inter.calc(1, 2);
        System.out.println(res);
    }
}
public class InterImpl implements Inter {

    @Override
    public int calc(int a, int b) {
        return a+b;
    }

}
public interface Inter {
    int calc(int a, int b);
}

고작 Inter.calc(1, 2)를 한 번 실행하기 위해서 Inter 인터페이스를 구현하는 InterImpl 클래스도 정의해야 하고 InterImpl impl 객체도 생성해야 하고 이를 s.m1()으로 전달까지 해야 한다.

 

결국 전달하고 싶은 것은 구체적으로 정의된Inter.calc() 메서드다.

 

이때 불필요한 코드를 획기적으로 줄일 수 있는 방법이 람다식이다.

 


가. Anonymous inner class

 

InterImpl처럼 단 한 번만 사용하는데 .java 파일을 만드는 것이 아까울 때?

public class SamTest {

    class InterImpl implements Inter {

        @Override
        public int calc(int a, int b) {
            return a+b;
        }

    }
    SamTest(){
        Sam s = new Sam();
        InterImpl impl = new InterImpl();
        s.m1(impl);
    }


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

}

InterImplSamTest 내부로 이동.

 

Inner class 또는 nested class라고 한다.

 

Inter impl = new InterImpl();
class InterImpl implements Inter {
    @Override
    public int calc(int a, int b) {
        return a+b;
    }
}

InterImplInter로 참조할 수 있다.

 

InterImpl은 어차피 한 번만 사용되는데 굳이 InterImpl class를 정의하는 것이 비효율적인 것 같다.

 

Inter impl = new Inter() {
    @Override
    public int calc(int a, int b) {
        return a+b;
    }
};
  • Inter()는 사실 new InterImpl(); + class InterImpl implements Inter와 같다.

 

단, 이때는 ;를 더 이상 생략할 수 없다.

 

이런 클래스를 anonymous inner class라고 한다.

 

그런데 impl도 한 번만 사용되니깐 이것도 귀찮나 보다.

 

SamTest(){
    Sam s = new Sam();
    s.m1(new Inter() {
        @Override
        public int calc(int a, int b) {
            return a+b;
        }
    });
}

그런데 이것도 길다고 생각했나 보다. 그래서 람다가 탄생했다.

 


나. 람다식

@FunctionalInterface
public interface Inter {
    int calc(int a, int b);
}
  • @FuntinalInterface
    : 인터페이스 안에 추상메서드는 단 1개만 존재해야 함.
    : 조건만 따른다면 어노테이션이 있든 없든 상관없음.

 

public class Sam {
    void m1 (Inter inter) {
        int res = inter.calc(1, 2);
        System.out.println(res);
    }
}

이미 컴파일 단계에서 매개변수의 타입과 @FunctionalInterface를 만족하는 경우 접근할 수 있는 메서드가 하나밖에 없다는 사실을 알 수 있다.

 

이 사실을 참고해 불필요한 부분을 다 줄인다.

 

// before
SamTest(){
    Sam s = new Sam();
    s.m1(new Inter() {
        @Override
        public int calc(int a, int b) {
            return a+b;
        }
    });
}
// after
SamTest(){
        Sam s = new Sam();
        s.m1((int a, int b)->{
                return a+b;
        });
    }
  • : 람다 기호
  • new Inter() { @Override public int calc 생략.

 

그런데 매개변수의 타입도 알 수 있어서 줄일 수 있다.

 

SamTest(){
    Sam s = new Sam();
    s.m1((a, b)->{return a+b;});
}

그런데 여기서도 실행문이 한 줄이면 {}과 return을 생략할 수 있다.

 

s.m1((a, b)-> a+b);

그런데 만약 전달받는 매개변수가 하나라면 ()도 생략할 수 있다.

 

//before
s.m1((a)-> a);
//after
s.m1(a -> a);

 


다. lambda 블록에서의 변수 참조

 

  • lambda 블록 밖 클래스 member 변수는 접근제한자의 제약 없이 사용가능함.
  • lambda 블록 밖 클래스의 local 변수는 final 키워드가 추가된 것을 동작 → read-only

 

 

[람다표현식]Lambda 표현식

이번 포스트에서는 본격적으로 lambda 표현식을 작성해보자. lambda 표현식 lambda를 이용한 정렬 이전 포스트(https://goodteacher.tistory.com/548)에서 이야기 했듯이 lambda 표현식이란 anonymous inner class를 이

goodteacher.tistory.com

 


라. java.util.function

 

Inter와 같이 @FuntinalInterface를 만족하는 인터페이스들은 어차피 하는 일이 비슷하다.

 

java.util.function에 자주 사용되는 함수적 표준 interface를 정의해 두었다.

 

 

  parameter return method 비고
java.lang.Runnable X X void run() Thread 용도
Consumer 계열 O X void accept(T) 소비자
Supplier 계열 X O T get() 공급자
Funtion 계열 O O T apply (R) 주로 parameter를 이용한 연산 후 원하는 타입으로 return
: 새로운 타입으로 변환
Operation 계열 O O T XXX(T) 주로 parameter를 연산해서 return
: 동일한 타입으로 연산 결과를 반환
Predicate 계열 O boolean boolean test(T) parameter를 조사해서 return 결정

 

  • 예시
//consumer 계열
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("Hello");

//supplier 계열
IntSupplier supplier = () -> {
    Random random = new Random();
    return random.nextInt(g));
};
System.out.println(supplier.getAsInt());

//function 계열
ToIntBiFunction<String, String> function = (src1, scr2) -> {
    return Integer.parseInt(src1) + Integer.parseInt(src2);
}
System.out.println(function.applyAsInt("4", "5"));

//Operator 계열
UnaryOperator<Double> operator = (x) -> {return Math.pow(x,2);}; //제곱
System.out.println(operator.apply(10.0));

//Predicate 계열
Predicate<String> predicate = (name) -> {return name.contains("Java");};
System.out.println(predicate.test("JavaScript"));

 


마. 메서드 참조

 

Method reference

 

1) Parameter method 참조

Integer[] nums = {1,7,3,4,5};
Arrays.sort(nums, (num1, num2) -> num1.compareTo(num2)); // instance method
Arrays.sort(nums, Integer::compareTo);

 

2) Class method 참조

Integer[] nums = {1,7,3,4,5};
Arrays.sort(nums, (num1, num2) -> Integer.compare(num1, num2)); // static method
Arrays.sort(nums, Integer::compare);

 

3) instance method 참조

List<String> names = new ArrayList<>(Arrays.asList("Hello", "Java", "World"));
names.forEach(item -> names.add(item));
names.forEach(names::add);

names.forEach(item -> System.out.println(item));
names.forEach(System.out::println);

 

4) 생성자 참조

Supplier<StringBuffer> s1 = () -> new StringBuffer();
Supplier<StringBuffer> s1 = StringBuffer::new;

Function<String, StringBuffer> f1 = (init) -> new StringBuffer();
Supplier<String, StringBuffer> f1 = StringBuffer::new;

파라미터의 개수와 타입에 따라서 알맞은 생성자 호출.

 

모두 다 외우진 못해도 보고 이해할 수 있어야 한다.

 

궁금하면 Method reference 찾아보자.

 


2. Arrays.sort()

 

Java에서 제공하는 배열을 관리하는 유틸리티.

import java.util.Arrays;

public class ATest {

    public ATest() {
        int[] arr = {1,5,8,2,4};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }

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

}
  • Arrays.sort(arr); : arr 배열을 오름차순으로 정렬.
  • Arrays.toString(arr); : arr 배열을 문자열로 변환.

 


가. 배열을 Comparator로 정렬하기

 

Comparable을 구현하지 않은 타입을 정렬하고 싶거나 원하는 방식으로 정렬하기 위해선 별도의 Comparator를 구현해야 한다.

 

Comparator를 직접 구현해서 정렬하는 연습을 해보자.

 

public static <T> void sort(T[] a, Comparator<? super T> c)TClass type이 들어가야 한다. (int[]와 같은 배열도 들어가긴 한다. Reference type이면 가능한 건가?)

 

그래서 int의 Wrapper class인 Integer로 배열을 만들어야 한다.

 

Integer[] arr = {3,1,7,9,4,8};
Arrays.sort(arr, null); // Comparator가 아직 없음.
class MyComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        // Integer -> int 자동으로 형변환        // Auto unboxing
        return o1 - o2; // 오름
        //return -(o1 - o2); // 내림
    }
}

Comparator를 구현.

  • return o1 - o2;
    : Integer -> int로 auto unboxing
    : 오름차순
  • return -(o1 - o2);
    : 내림차순

 

하지만 o1 - o2는 오버플로우가 발생할 수 있다.

 

System.out.println(Integer.MAX_VALUE - Integer.MIN_VALUE);-1

Comparator myComparator = new Comparator<Integer>(){
    @Override
    public int compare(Integer o1, Integer o2) {
        return Integer.compare(o1, o2);
    }
};

Integer.compare(o1, o2)를 사용하자.

 

public class BTest {

    MyComparator myComparator = new MyComparator();
    class MyComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    }

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, myComparator);

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}
/*
[1, 3, 4, 7, 8, 9]
*/

 


나. 람다식으로 만들기

public class BTest {

    Comparator myComparator = new Comparator<Integer>(){
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, myComparator);

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}

MyComparatoranonymous inner class로 만든다.

 

사실 여기까지는 대부분의 ide에서 자동완성을 지원해 줌.

 

public class BTest {

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        }); 

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}

어차피 사용할 수 있는 메서드가 하나라서 new Comparator<Integer>() { @Override public int compare를 생략할 수 있다.

 

public class BTest {

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, (Integer o1, Integer o2)->{
                return Integer.compare(o1, o2);
            }); 

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}

Integer{ };을 생략할 수 있다.

 

public class BTest {

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, (o1, o2)-> Integer.compare(o1, o2)); 

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}

Integer.compare()는 static method라서 Class method 참조가 가능하다.

 

메서드 참조가 발생해서 반복되는 (o1, o2) 생략한다.

 

public class BTest {

    BTest(){
        Integer[] arr = {3,1,7,9,4,8};
        Arrays.sort(arr, Integer::compare); 

        System.out.println(Arrays.toString(arr));
    }
    public static void main(String[] args) {
        new BTest();
    }
}

 


'공부 중 > Java' 카테고리의 다른 글

[Java] File IO  (0) 2024.01.29
[Java] Collection  (0) 2024.01.28
[Java] 예외 처리  (0) 2024.01.28
[Java] String  (0) 2024.01.22
[Java] 인터페이스  (0) 2024.01.22