Java Language
람다 식
수색…
소개
람다 식은 표현식을 사용하여 단일 메서드 인터페이스를 구현하는 명확하고 간결한 방법을 제공합니다. 이것들을 사용하면 생성하고 유지 보수해야하는 코드의 양을 줄일 수 있습니다. 익명 클래스와 비슷하지만, 타입 정보는 가지고 있지 않습니다. 유형 유추가 필요합니다.
메소드 참조는 표현식이 아닌 기존 메소드를 사용하여 기능적 인터페이스를 구현합니다. 그들은 또한 람다 가족에 속합니다.
통사론
- () -> {return expression; } // 값을 반환하는 함수 본문으로 0을 반환합니다.
- () -> expression // 위의 선언에 대한 단축 표현; 표현식에는 세미콜론이 없습니다.
- () -> {function-body} // 람다 식에서 작업을 수행하기위한 부작용.
- parameterName -> expression // 단일 - 성 람다 식. 인수가 하나 뿐인 람다 식에서는 괄호를 제거 할 수 있습니다.
- (type parameterName, Type secondParameterName, ...) -> expression // 매개 변수가 왼쪽에 나열된 표현식을 평가하는 lambda
- (parameterName, secondParameterName, ...) -> expression // 매개 변수 이름에 대한 매개 변수 유형을 제거하는 약식. 지정된 매개 변수 목록 크기가 예상되는 기능적 인터페이스의 크기 중 하나와 일치하는 컴파일러에서 추론 할 수있는 컨텍스트에서만 사용할 수 있습니다.
람다 식을 사용하여 컬렉션 정렬
목록 정렬
자바 (8) 이전에, 그것을 구현하기 위해 필요했다 java.util.Comparator
목록 (1)를 정렬 할 때 익명 (또는 이름) 클래스와 인터페이스를 :
List<Person> people = ...
Collections.sort(
people,
new Comparator<Person>() {
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
}
);
Java 8부터는 익명 클래스를 람다 식으로 대체 할 수 있습니다. 컴파일러가 자동으로 유추하기 때문에 매개 변수 p1
과 p2
의 유형을 생략 할 수 있습니다.
Collections.sort(
people,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);
이 예제는 Comparator.comparing
과 ::
(이중 콜론) 기호를 사용하여 표현 된 메서드 참조 를 사용하여 단순화 할 수 있습니다.
Collections.sort(
people,
Comparator.comparing(Person::getFirstName)
);
정적 가져 오기를 사용하면 더 간결하게 표현할 수 있지만 전반적인 가독성이 향상되는지 여부는 논쟁의 여지가 있습니다.
import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));
이 방법으로 만들어진 비교기도 함께 연결할 수 있습니다. 예를 들어 이름을 다른 사람과 비교 한 후 이름이 같은 사람이 있으면 thenComparing
메서드도 성으로 비교합니다.
sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));
1 - Collections.sort (...)는 List
부속 유형 인 콜렉션에서만 작동합니다. Set
및 Collection
API는 요소의 순서를 의미하지 않습니다.
지도 정렬
유사한 방식으로 HashMap
의 항목을 HashMap
으로 정렬 할 수 있습니다. LinkedHashMap
가 타겟으로서 사용되지 않으면 안됩니다. 통상의 HashMap
의 키는 순서 붙일 수 없습니다.
Map<String, Integer> map = new HashMap(); // ... or any other Map class
// populate the map
map = map.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
(k, v) -> k, LinkedHashMap::new));
Java lambdas 소개
기능 인터페이스
람다 (Lambdas)는 단 하나의 추상 메소드를 가진 인터페이스 인 기능적 인터페이스에서만 작동 할 수 있습니다. 기능 인터페이스에는 default
또는 static
메소드가 여러 개있을 수 있습니다. (이러한 이유 때문에 때때로 Single Abstract Method Interfaces 또는 SAM Interfaces라고도 함).
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
기능 인터페이스를 선언 할 때 @FunctionalInterface
주석을 추가 할 수 있습니다. 이것은 특별한 영향을 미치지 않지만,이 주석이 기능적이지 않은 인터페이스에 적용되면 인터페이스가 변경되지 않아야한다는 것을 상기시키는 역할을하는 컴파일러 오류가 생성됩니다.
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
반대로, 이것은 하나 이상의 추상적 인 메소드를 가지고 있기 때문에 기능적인 인터페이스가 아닙니다 :
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one should
// be considered as lambda?
}
또한 메소드가 없으므로 기능적 인터페이스가 아닙니다.
interface BlankFoo2 { }
다음 사항에 유의하십시오. 네가 가지고 있다고 가정 해보자.
interface Parent { public int parentMethod(); }
과
interface Child extends Parent { public int ChildMethod(); }
그런 다음 두 가지 지정된 메소드가 있으므로 Child
는 기능 인터페이스가 될 수 없습니다 .
Java 8은 또한 java.util.function
패키지에 많은 일반 템플릿 기능 인터페이스를 제공합니다. 예를 들어, 내장 인터페이스 Predicate<T>
는 형식 T
의 값을 입력하고 boolean
반환하는 단일 메서드를 래핑합니다.
람다 식
람다 식의 기본 구조는 다음과 같습니다.
fi
는 FunctionalInterface
를 구현하는 익명 클래스와 비슷한 클래스의 싱글 톤 인스턴스를 보유 할 것이고, 한 메소드의 정의는 { System.out.println("Hello"); }
. 즉 위의 내용은 다음과 거의 동일합니다.
FunctionalInterface fi = new FunctionalInterface() {
@Override
public void theOneMethod() {
System.out.println("Hello");
}
};
람다는 람다에서 this
, super
또는 toString()
과 같은 표현식의 의미가 새롭게 생성 된 객체가 아니라 할당이 이루어지는 클래스를 참조하기 때문에 람다는 익명 클래스와 "거의 동등"합니다.
함수 인터페이스에는 하나의 추상 메소드가 있어야하기 때문에 람다를 사용할 때 메소드의 이름을 지정할 수는 없지만 그렇게해야 할 필요는 없습니다. Java가이를 오버라이드하므로
람다 유형이 확실하지 않은 경우 (예 : 오버로드 된 메소드) 람다에 캐스트를 추가하여 컴파일러에 유형을 지정하십시오. 예를 들면 다음과 같습니다.
Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true
함수 인터페이스의 단일 메소드가 매개 변수를 사용하면 이들의 로컬 공식 이름은 람다의 대괄호 사이에 나타나야합니다. 매개 변수 유형을 선언 할 필요가 없으며 매개 변수 유형을 인터페이스에서 가져 왔기 때문에 리턴 할 수 있습니다 (원하는 경우 매개 변수 유형을 선언하는 것은 오류가 아 U니다). 따라서이 두 예제는 동일합니다.
Foo2 longFoo = new Foo2() {
@Override
public int bar(boolean baz) {
return baz ? 1 : 0;
}
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };
함수가 하나의 인수 만 갖는 경우 인수 주위의} 호를 생략 할 수 있습니다.
Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay
암시 적 반환 값
람다 안에있는 코드가 명령문이 아닌 Java 표현식 이면 표현식의 값을 반환하는 메소드로 처리됩니다. 따라서 다음 2 개는 동일합니다.
IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };
로컬 변수 액세스 (클로저 값)
람다는 익명 클래스에 대한 구문 상 축약 형이므로, 둘러싸는 범위에서 로컬 변수에 액세스하는 것과 동일한 규칙을 따릅니다. 변수는 final
로 취급되어야하며 람다 내부에서는 수정되지 않아야합니다.
IntUnaryOperator makeAdder(int amount) {
return (x) -> (x + amount); // Legal even though amount will go out of scope
// because amount is not modified
}
IntUnaryOperator makeAccumulator(int value) {
return (x) -> { value += x; return value; }; // Will not compile
}
이런 식으로 변화하는 변수를 감쌀 필요가 있다면, 변수의 복사본을 유지하는 정규 객체가 사용되어야합니다. 람다식이 있는 Java Closure 에서 자세히 읽으십시오 .
람다 수용
람다는 인터페이스의 구현이기 때문에 메소드가 람다를 받아들이도록하기 위해 특별히 할 필요는 없습니다 : 함수 인터페이스를 취하는 모든 함수는 람다를 받아 들일 수 있습니다.
public void passMeALambda(Foo1 f) {
f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));
람다 식의 유형
람다 식은 그 자체로는 특정 유형을 가지고 있지 않습니다. 매개 변수의 유형 및 수와 반환 값의 유형이 일부 유형 정보를 전달할 수있는 것은 사실이지만 이러한 정보는 할당 할 수있는 유형 만 제한합니다. lambda는 다음 중 한 가지 방법으로 기능 인터페이스 유형에 할당 될 때 유형을 수신합니다.
- 기능 유형에 직접 지정 (예 :
myPredicate = s -> s.isEmpty()
- 기능적 유형을 가진 매개 변수로 전달합니다
stream.filter(s -> s.isEmpty())
예 :stream.filter(s -> s.isEmpty())
- 함수 유형을 반환하는 함수에서 반환합니다 (예 :
return s -> s.isEmpty()
- 기능 유형 (예 :
(Predicate<String>) s -> s.isEmpty()
기능 유형에 대한 그러한 할당이 이루어지기 전까지는 람다는 명확한 유형을 가지고 있지 않습니다. 설명하기 위해, 람다 표현식 o -> o.isEmpty()
고려하십시오. 동일한 λ 식은 다양한 기능 유형에 할당 될 수 있습니다.
Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();
이것들이 할당되었으므로, 람다 표현식이 동일하게 보였지만 서로 할당 될 수는 없지만 표시된 예제는 완전히 다른 유형입니다.
방법 참조
메서드 참조를 사용하면 익명의 람다 식 대신 인수로 전달할 수있는 호환 가능한 기능 인터페이스를 준수하는 미리 정의 된 정적 또는 인스턴스 메서드를 사용할 수 있습니다.
모델이 있다고 가정합니다.
class Person {
private final String name;
private final String surname;
public Person(String name, String surname){
this.name = name;
this.surname = surname;
}
public String getName(){ return name; }
public String getSurname(){ return surname; }
}
List<Person> people = getSomePeople();
인스턴스 메소드 참조 (임의의 인스턴스로)
people.stream().map(Person::getName)
등가 람다 :
people.stream().map(person -> person.getName())
이 예에서는 Person
유형의 getName()
인스턴스 메소드에 대한 메소드 참조가 전달됩니다. 콜렉션 유형으로 알려져 있기 때문에 나중에 알려진 메소드의 메소드가 호출됩니다.
인스턴스 메소드 참조 (특정 인스턴스로)
people.forEach(System.out::println);
System.out
은 PrintStream
의 인스턴스이므로,이 특정 인스턴스에 대한 메소드 참조가 인수로 전달됩니다.
등가 람다 :
people.forEach(person -> System.out.println(person));
정적 메서드 참조
또한 스트림을 변환하기 위해 정적 메소드에 대한 참조를 적용 할 수 있습니다.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)
이 예제에서는 String
유형에 대한 정적 valueOf()
메서드에 대한 참조를 전달합니다. 따라서 컬렉션의 인스턴스 객체는 valueOf()
인수로 전달됩니다.
등가 람다 :
numbers.stream().map(num -> String.valueOf(num))
생성자에 대한 참조
List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)
컬렉션 요소를 수집하는 방법을 보려면 컬렉션 요소를 스트림으로 수집을 읽어보십시오.
Integer
형의 단일의 String 인수 생성자가 인수로서 제공된 캐릭터 라인을 지정해 정수를 구축하기 위해서 여기서 사용되고 있습니다. 이 경우 문자열이 숫자를 나타내는 한 스트림은 정수로 매핑됩니다. 등가 람다 :
strings.stream().map(s -> new Integer(s));
컨닝 지
메소드 참조 형식 | 암호 | 동등한 람다 |
---|---|---|
정적 메소드 | TypeName::method | (args) -> TypeName.method(args) |
비 정적 메소드 (인스턴스 *에서 ) | instance::method | (args) -> instance.method(args) |
비 정적 메소드 (인스턴스 없음) | TypeName::method | (instance, args) -> instance.method(args) |
생성자 ** | TypeName::new | (args) -> new TypeName(args) |
배열 생성자 | TypeName[]::new | (int size) -> new TypeName[size] |
* instance
는 instance
에 대한 참조로 평가되는 모든 표현식이 될 수 있습니다 getInstance()::method
예 : getInstance()::method
, this::method
** TypeName
이 비 정적 인 내부 클래스 인 경우, 생성자 참조는 외부 클래스 인스턴스의 범위 내에서만 유효합니다
다중 인터페이스 구현하기
때로는 둘 이상의 인터페이스를 구현하는 람다 표현식을 원할 수 있습니다. 이는 마커 인터페이스 (예 : java.io.Serializable )가 추상 메소드를 추가하지 않기 때문에 주로 유용합니다.
예를 들어, 당신은 만들 TreeSet
정의와 Comparator
다음 직렬화 및 네트워크를 통해 보낼 수 있습니다. 사소한 접근법 :
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
비교기의 람다가 Serializable
구현하지 않기 때문에 작동하지 않습니다. 교차 타입을 사용하고이 람다를 직렬화 할 필요가 있음을 명시 적으로 지정하여이 문제를 해결할 수 있습니다.
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
거의 모든 것이 직렬화 가능 해야하는 Apache Spark 와 같은 프레임 워크를 사용하는 경우와 같이 교차 유형을 자주 사용하는 경우 빈 인터페이스를 만들어 대신 코드에서 사용할 수 있습니다.
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
이렇게하면 전달 된 비교자가 직렬화 될 수 있습니다.
람다와 실행 주위 패턴
간단한 시나리오에서 lambdas를 FunctionalInterface로 사용하는 몇 가지 좋은 예가 있습니다. 람다가 개선 할 수있는 일반적인 사용 사례는 Execute-Around 패턴입니다. 이 패턴에는 유스 케이스 별 코드를 둘러싼 여러 시나리오에 필요한 표준 설정 / 분해 코드 세트가 있습니다. 몇 가지 일반적인 예는 파일 io, 데이터베이스 io, try / catch 블록입니다.
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
그런 다음이 메서드를 람다 (lambda)와 함께 호출하면 다음과 같이 보일 수 있습니다.
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
이것은 I / O 작업에만 국한되지 않습니다. 유사한 설정 / 해체 작업을 사소한 변형으로 적용 할 수있는 모든 시나리오에 적용 할 수 있습니다. 이 패턴의 주요 이점은 코드를 재사용하고 DRY (반복하지 말 것)를 시행하는 것입니다.
자신의 함수 인터페이스로 람다 식 사용하기
Lambda는 단일 메소드 인터페이스에 대한 인라인 구현 코드와 일반 변수로 수행 한 것처럼 전달할 수있는 기능을 제공합니다. 우리는 그것들을 기능적 인터페이스라고 부릅니다.
예를 들어, 익명 클래스에 Runnable을 작성해, Thread를 기동하면 (자) 다음과 같이됩니다.
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
이제 위와 같이하면 사용자 정의 인터페이스가 있다고 가정 해 보겠습니다.
interface TwoArgInterface {
int operate(int a, int b);
}
당신의 코드에서이 인터페이스의 구현을 제공하기 위해 어떻게 lambda를 사용합니까? 위에 표시된 Runnable 예제와 동일합니다. 아래의 드라이버 프로그램을 참조하십시오 :
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}
`return`은 외부 메소드가 아니라 람다에서만 리턴합니다.
return
메서드는 람다에서만 반환되며 외부 메서드에서는 반환되지 않습니다.
스칼라와 코 틀린과는 다른 점에 유의하십시오!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
등의 내장 구조에서와 같이 자신의 언어 구조를 작성하려고 할 때 예기치 않은 동작이 발생할 수 있습니다 for
루프가 return
다르게 작동합니다 :
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
Scala와 Kotlin에서 demo
와 demo
demo2
는 모두 0
인쇄합니다. 그러나 이것은 더 일관성이 없습니다 . Java 접근 방식은 리팩토링과 클래스 사용과 일관성이 있습니다. 즉 코드 맨 위의 return
이며 아래 코드는 동일하게 작동합니다.
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
따라서, 자바 return
더 클래스 메소드와 리팩토링과 일치하지만,에 이하 for
와 while
내장 매크로,이 특별한 남아있다.
이 때문에 다음 두 가지는 Java에서 동일합니다.
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
게다가, Java에서 try-with-resources를 사용하는 것이 안전합니다.
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
before accept() after close()
인쇄 할 것입니다. Scala와 Kotlin 의미에서, try-with-resources는 닫히지 않을 것이지만, before accept()
인쇄 될 것이다.
람다식이있는 Java Closure.
람다 클로저는 람다식이 범위를 둘러싼 (글로벌 또는 로컬) 변수를 참조 할 때 만들어집니다. 이 작업을 수행하는 규칙은 인라인 메서드 및 익명 클래스의 규칙과 동일합니다.
람다 안에서 사용되는 엔 클로징 스코프의 로컬 변수 가 final
이어야합니다. Java 8 (람다를 지원하는 가장 초기 버전)을 사용하면 외부 컨텍스트에서 final
선언 할 필요는 없지만 그렇게 처리해야합니다. 예 :
int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n
변수의 값이 변경되지 않는 한 유효합니다. 람다 내부 또는 외부에서 변수를 변경하려고하면 다음과 같은 컴파일 오류가 발생합니다.
"람다 식에서 참조 된 지역 변수는 최종적 이거나 효과적 인 최종 변수 여야합니다."
예 :
int n = 0;
Runnable r = () -> { // Using lambda
int i = n;
// do something
};
n++; // Will generate an error.
람다 내에서 변경 변수를 사용해야하는 경우 일반적인 방법은 변수의 final
사본을 선언하고 사본을 사용하는 것입니다. 예를 들어
int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
int i = k;
// do something
};
n++; // Now will not generate an error
r.run(); // Will run with i = 0 because k was 0 when the lambda was created
당연히 람다의 본문에는 원래 변수의 변경 내용이 표시되지 않습니다.
Java는 true closure를 지원하지 않습니다. 자바 람다는 인스턴스화 된 환경에서 변경 사항을 볼 수있는 방식으로 생성 할 수 없습니다. 환경을 관찰하거나 환경을 변경하는 클로저를 구현하려면 일반 클래스를 사용하여 시뮬레이션해야합니다. 예 :
// Does not compile ...
public IntUnaryOperator createAccumulator() {
int value = 0;
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
위의 예제는 이전에 설명한 이유로 컴파일되지 않습니다. 다음과 같이 컴파일 오류를 해결할 수 있습니다.
// Compiles, but is incorrect ...
public class AccumulatorGenerator {
private int value = 0;
public IntUnaryOperator createAccumulator() {
IntUnaryOperator accumulate = (x) -> { value += x; return value; };
return accumulate;
}
}
문제는 인스턴스가 기능적이어야하며 상태를 IntUnaryOperator
인터페이스의 디자인 계약을 IntUnaryOperator
한다는 것입니다. 그러한 클로저가 기능 객체를 받아들이는 내장 함수에 전달되면 충돌 또는 잘못된 동작이 발생할 수 있습니다. 변경 가능한 상태를 캡슐화하는 클로저는 일반 클래스로 구현되어야합니다. 예를 들어.
// Correct ...
public class Accumulator {
private int value = 0;
public int accumulate(int x) {
value += x;
return value;
}
}
람다 - 리스너 예제
익명의 클래스 청취자
Java 8 이전에는 다음 코드와 같이 익명 클래스를 사용하여 JButton의 클릭 이벤트를 처리하는 것이 일반적입니다. 이 예제는 btn.addActionListener
의 범위 내에서 익명 수신기를 구현하는 방법을 보여줍니다.
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
람다 청취자
ActionListener
인터페이스는 하나의 actionPerformed()
메서드 만 정의하기 때문에 함수 인터페이스이므로 람다 식을 사용하여 상용구 코드를 대체 할 수 있습니다. 위의 예제는 다음과 같이 람다 식을 사용하여 다시 작성할 수 있습니다.
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
전통적인 스타일의 람다 스타일
전통 방식
interface MathOperation{
boolean unaryOperation(int num);
}
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = new MathOperation() {
@Override
public boolean unaryOperation(int num) {
return num%2 == 0;
}
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
람다 스타일
- 클래스 이름과 기능적 인터페이스 본문을 제거하십시오.
public class LambdaTry {
public static void main(String[] args) {
MathOperation isEven = (int num) -> {
return num%2 == 0;
};
System.out.println(isEven.unaryOperation(25));
System.out.println(isEven.unaryOperation(20));
}
}
- 선택적 형식 선언
MathOperation isEven = (num) -> {
return num%2 == 0;
};
- 매개 변수가 단일 매개 변수 인 경우 매개 변수 주위에 선택적 괄호
MathOperation isEven = num -> {
return num%2 == 0;
};
- 선택적 중괄호 (function body에 한 줄만있는 경우)
- 함수 본문에 한 줄만있는 경우 선택적 return 키워드
MathOperation isEven = num -> num%2 == 0;
람다와 메모리 활용
Java lambda는 클로저이기 때문에 둘러싸는 어휘 범위의 변수 값을 "캡처"할 수 있습니다. 모든 lambdas가 아무것도 캡처하지는 않지만 s -> s.length()
와 같은 단순한 lambdas는 아무 것도 포착하지 않으며 stateless 라 불리는 - 람다 캡처에는 캡처 된 변수를 저장하는 임시 객체가 필요합니다. 이 코드 스 니펫에서 lambda () -> j
는 캡처 람다이며 평가 될 때 객체가 할당되도록 할 수 있습니다.
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1000000000; i++) {
int j = i;
doSomethingWithLambda(() -> j);
}
}
new
키워드가 스 니펫의 아무 곳에 나 나타나지 않기 때문에 즉시 명확하지 않을 수도 있지만이 코드는 () -> j
람다 식의 인스턴스를 나타 내기 위해 1,000,000,000 개의 개별 개체를 만드는 경향이 있습니다. 그러나 Java 1 의 향후 버전에서는이를 최적화 하여 런타임시 람다 인스턴스가 재사용되거나 다른 방식으로 표현 될 수 있음에 유의해야합니다.
1 - 예를 들어 Java 9에서는 Java 빌드 시퀀스에 선택적 "링크"단계가 도입되어 전역 최적화를 수행 할 수있는 기회를 제공합니다.
람다 식과 술어를 사용하여 목록에서 특정 값 가져 오기
Java 8부터 람다 식과 술어를 사용할 수 있습니다.
예 : 람다 식과 술어를 사용하여 목록에서 특정 값을 가져옵니다. 이 예에서 모든 사람은 18 세 이상인 경우 사실로 인쇄됩니다.
사람 클래스 :
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() { return age; }
public String getName() { return name; }
}
java.util.function.Predicate 패키지의 내장 인터페이스 Predicate는 boolean test(T t)
메소드가있는 기능적 인터페이스입니다.
사용 예 :
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Jeroen", 20));
personList.add(new Person("Jack", 5));
personList.add(new Person("Lisa", 19));
print(personList, p -> p.getAge() >= 18);
}
private static void print(List<Person> personList, Predicate<Person> checker) {
for (Person person : personList) {
if (checker.test(person)) {
System.out.print(person + " matches your expression.");
} else {
System.out.println(person + " doesn't match your expression.");
}
}
}
}
print(personList, p -> p.getAge() >= 18);
메서드는 필요한 식을 정의 할 수있는 람다 식을 사용합니다 (Predicate는 매개 변수로 사용되기 때문에). 검사기의 검사 방법은이 표현식이 올바른지 여부를 확인합니다 : checker.test(person)
.
이것을 다른 것으로 쉽게 변경할 수 있습니다. 예를 들어 print(personList, p -> p.getName().startsWith("J"));
. 이 사람의 이름이 "J"로 시작하는지 확인합니다.