Lambda
메서드를 간단한 식으로 표현하는 방법
다른언어들은 자바와 다르게 함수자체를 파라미터로 보낼 수 있지만, 자바는 객체지향언어이므로 함수자체를 보낼수없어서 그 함수를 포함한 객체를 만들고 그 객체를 보내야 하는 불편함이 있었다.
class ABC implements Compatrator<String>{
public int compare(String first, String second){ // <- 이 함수를 보내고싶은데 그럴 수 없다.
return Integer.compare(first.length(), second.length());
}
}
ABC abc = new ABC();
Arrays.sort(string, abc); // <- 객체를 만들어서 보냄
람다 기본문법
int max(int a, int b){ // 기존방식
return a + b;
}
(int a, int b) -> {return a + b;}; // 람다
(a, b) -> {return a + b;};
(a, b) -> a+b;
- 한줄로 처리되는 바디는 {}로 감쌀필요가 없다.
- 여러줄로 처리되어야하는 바디는 {}로 감싼다. {}를 사용할때는 return문을 명시적으로 써줘야한다.
- 기본적으로 람다파라미터에는 타입을 명시해주는게 맞지만, 유추가 가능할경우에는 타입명을 생략가능하다.
- 타입유추가 가능한 람다파라미터가 1개일경우 ()로 감쌀필요가 없다.
- 람다표현식의 리턴타입은 지정하지않는다. 리턴타입은 항상 문맥으로부터 추정된다.
- 람다표현식이 어떤경우에는 값을 리턴하고, 어떤경우에는 값을 리턴하지않는것은 규칙에 어긋난다.
x -> { if(x>=0) return 1;} // <- x<0 일때는 값을리턴하지않으므로 규칙에 어긋남
- 람다표현식의 바디에서 checked Exception을 던지는 경우 해당예외가 추상메서드에 선언이 되어있어야한다.
Runnable run = () -> {
System.out.println("ab");
Thread.sleep(1000); // 2가지 방법, 1: try catch로 잡거나
// 2: 해당예외를 던질수있는 함수형인터페이스를 찾아서 람다를 대입하거나
};
함수형인터페이스
단 하나의 추상메서드만 선언된 언터페이스
Object b = new Object(){
int max(int a, int b){
return a + b;
}
}
b.max = 에러 (Object클래스는에는 max함수가 없기떄문)
이렇게 자바에서 람다식을 쓰려면 참조변수 타입은 반드시 함수형 인터페이스여야만 한다.
Func b = new Func(){
int max(int a, int b){
return a + b;
}
}
Func b = (a, b) -> a + b
여기서 Func는 반드시 함수형 인터페이스라는 말. 그니까 interface Func{ public int max(int a, int b); } <- 이렇게 선언되어있어야함. 그 인터페이스의 추상 메서드의 구현을 람다에서 하는것.
근데 인터페이스에 추상메서드를 하나 더 추가하면, max함수쪽에는 에러가나지만, 인터페이스쪽에서는 에러가 안남. 그리고 람다식으로 사용할 의도의 인터페이스인지 그냥 하나만 있는 인터페이스인지 구분이 쉽지않음. 그러므로 이 인터페이스는 람다식을 위한것이다 라고 표현해놓기위해 인터페이스 위에 @FunctionalInterface 어노테이션을 붙임. ← 어노테이션이 있을때 단일추상메서드가 아니라면 컴파일에러
(그냥 붙이는것을 권장)
메서드 레퍼런스
다른코드에 전달하려고하는 액션을 수행하는 메서드가 이미 존재할때 나타낼수있는 표현
- 객체 : 인스턴스메소드
- 클래스 : 스태틱메소드
- 클래스 : 인스턴스메소드 // System.out::println = x → System.out.println(x), // String::compareToIgnoreCase = (x, y) → x.compareToIgnoreCase(y)
생성자 레퍼런스
메서드의 이름이 new인 메서드 레퍼런스. 생성자가 한개일때는 문제가 안되지만, 여러 생성자일때는 문맥을 추론하여 맞는생성자로 알아서 넣어줌
int[] x = new int[x];
(int[]::new)
변수유효범위
람다는 람다표현식을 감싸고있는 메서드나 클래스에 있는 변수에 접근이 가능하다. 람다는 자유변수를 캡쳐하여 내부에 저장한다.
다만 이러한 변수는 변화가 일어나서는 안된다. (사실상 final인데 이너클래스도 자바8이후로는 final변수가 아닌것도 접근가능 - 원래는 final만 접근가능했는데 람다와 맞춰주기 위함)
public static void repeatMessage(String text, int count){
Runnable r = () -> {
for(int i=0; i<**count**; i++){
System.out.println(**text**);
Thread.yield();
count--; // 캡처한 변수는 변경할수 없다. 쓰레드에 안전하지 않기때문에 막아놓음
}
};
new Thread(r).start();
}
repeatMessage("Hello", 1000);
람다표현식은 3세가지로 구성
- 파라미터
- 코드 블록
- 자유변수(파라미터도 아니고 코드 내부에도 정의되어있지않은 변수)
람다 표현식의 몸체는 중첩블록과 동일한 유효범위를 가진다.
Path first = Paths.get("/usr/bin");
Comparator<String> comp = (first, second) -> Integer.compare(first.lenth(), second.length()); // first가 람다를 감싸고있는 클래스에 정의되어있으므로 에러
public class Application() {
public void doWork(){
Runnable runner = () -> { System.out.println(this.toString()); } // this는 Runnable의 객체가 아니라, Application의 객체를 나타내므로 주의
}
}
디폴트 메서드
자바8 이후로 인터페이스에 몸체를 구현할 수 있는 디폴트 메서드가 추가되었다.
Collection인터페이스에 forEach의 새로운 메서드가 추가되었다고 가정하면, 기존에 Collection을 구현하는 클래스들은 forEach를 새롭게 구현하기전에는 forEach메서드를 가져다 쓸수없다.
하지만 디폴트 메서드로 인터페이스에서 몸체까지 직접 구현해주어서 기존 클래스들은 새롭게 구현할 필요없이 구현되어있는 forEach를 가져다 쓰기만하면된다.
(실제로 Iterator인터페이스에 forEach가 디폴트 메서드로 구현되어있음)
public interface Iterable<T> {
Iterator<T> iterator(); // 실제로 하위단계에서 구현해주어야하는 메서드
default void forEach(Consumer<? super T> action) { ... } // 몸체까지 구현한 디폴트메서드, 하위단계에서 구현하면 오버라이딩됨
}
for(int i=0; i<list.size(); i++){
System.out.println(list.get(i));
}
list.forEach(System.out::println);
똑같은 메서드가 한 인터페이스에서 디폴트메서드로 정의되어있고, 슈퍼클래스나 다른 인터페이스의 메서드로도 정의되어있을때 자바는 2가지 규칙을 갖는다.
- 인터페이스들이 충돌하므로 한쪽을 선택해서 해결해야한다.
- public interface Named { long getId(); default String getName() { return "123"; } } public interface Person { long getId(); default String getName() { // 한쪽이 구현이 안되어있어도 선택해야하는건 똑같음. 둘다 구현이 안되어있으면 충돌이 안남 return "abc"; } } public class Student implements Named, Person { @Override public long getId() { // TODO Auto-generated method stub return 0; } @Override public String getName() { // return Named.super.getName(); return Person.super.getName(); // 서로 다른 인터페이스에서 디폴트메서드를 구성하고있다면, 어느 인터페이스 것을 가져올지 선택해야한다. } }
- 슈퍼클래스 상속과 인터페이스의 구현간의 충돌이 일어날때는 무조건 클래스가 우선한다. (그러므로 기존 인터페이스에 디톨트 메서드를 추가해도 기존 메서드 동작에는 아무런 영향을 주지않는다)
class Student extends Person implements Named { ... } // Person만 중요하고 Named는 디폴트메서드 제공여부와 관계없이 무시됨
- Object클래스에 있는 메서드들은 디폴트 메서드로 만들 수 없다. 예를 들어 List같은 인터페이스에서 toString과 equals들은 디폴트메서드로 만들면 아주 좋을것같지만, 모든 클래스는 Object를 상속하므로 클래스 우선 규칙으로 인하여 상속되는 시점에서 디폴트 메서드는 무시가 되어버리기 때문이다.
인터페이스 정적(static) 메서드
자바8 이후로는 인터페이스에 스테틱메서드를 추가할 수 있다. 지금까지는 일반적으로 인터페이스와 동반하는 클래스에 정적 메서드를 두었다(인터페이스 자체에 둘수가 없으니까..)
Collection/Collections, Path/Paths가 그 예이다. 하지만 자바8부터 Path 인터페이스에 직접 스태틱메서드를 추가해주면 사실상 Paths는 필요없다고함..
Comparator.comparing(Person::name) // Person객체를 Name으로 비교 <- static메서드이므로 바로 호출가능
'IT > 자바' 카테고리의 다른 글
classpath (0) | 2023.03.26 |
---|---|
ExecutorService (0) | 2023.03.26 |
Java8 람다(Lambda)를 이용한 프로그래밍 (0) | 2022.01.23 |
Java8 스트림(Stream)연산을 사용해보자 (0) | 2022.01.23 |
Java8 옵셔널(Optional<T>)객체 올바르게 사용하기 (0) | 2022.01.23 |
댓글