- 람다식을 알아보기 전 람다의 기본이 되는 인터페이스에 대해 다시 한 번 알아보겠다.
- 인터페이스 구현 방법
- MyInterface.java
public interface MyInterface {
public void print();
}
- 일반적인 인터페이스 선언 방식이다.
1. implements 키워드로 클래스 선언
- MyClass.java
class MyClass1 implements MyInterface {
@Override
public void print() {
System.out.println("MyClass2");
}
}
class MyClass2 implements MyInterface {
@Override
public void print() {
System.out.println("MyClass2");
}
}
public class Class {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
MyClass2 myClass2 = new MyClass2();
myClass1.print();
myClass2.print();
}
}
- implements를 통해 인터페이스를 상속 받고 클래스 구현체를 작성한 것을 볼 수 있다.
- 클래스의 메서드를 사용하기 위해 객체를 생성하고 인터페이스의 구현체인 print() 메서드를 각각 호출 한 것을 볼 수 있다.
2. 익명 클래스 사용
.....
public class Class {
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
MyClass2 myClass2 = new MyClass2();
myClass1.print();
myClass2.print();
MyInterface mi = new MyInterface() {
@Override
public void print() {
System.out.println("익명 클래스로 구현");
}
};
}
}
- 익명 클래스는 일반적으로 클래스 선언과 동시에 객체를 생성한다.
- 결국 인터페이스도 구현체가 있다면 객체를 생성할 수 있기 때문에 참조형이 인터페이스인 변수에 인터페이스 객체를 생성함과 동시에 구현체를 작성하는 방법이다.
3. 선언, 생성, 호출을 한번에 처리
.....
(new MyInterface(){
@Override
public void print() {
System.out.println("선언, 생성, 호출을 한번에 처리");
}
}).print();
- 매개변수
- 인터페이스 타입으로 매개변수가 선언된 경우를 살펴보겠다.
public class Class {
//----------------------------
public static void test(MyInterface mi) {
mi.print();
}
//----------------------------
public static void main(String[] args) {
MyClass1 myClass1 = new MyClass1();
MyClass2 myClass2 = new MyClass2();
myClass1.print();
myClass2.print();
MyInterface mi = new MyInterface() {
@Override
public void print() {
System.out.println("익명 클래스로 구현");
}
};
(new MyInterface(){
@Override
public void print() {
System.out.println("선언, 생성, 호출을 한번에 처리");
}
}).print();
//-------------------------------
test(myClass1);
test(mi);
//-------------------------------
}
}
- 위의 코드는 test()의 매개변수로 인터페이스를 받는 것을 볼 수 있다.
- 아래 test() 메서드의 경우 파라미터로 인터페이스 구현체인 myClass1과 익명 클래스 방식을 이용한 인터페이스 객체 mi를 파라미터로 넘긴 것을 확인 할 수 있다.
- 리턴 타입
public class Class {
public static MyInterface test2() {
MyInterface mi = new MyInterface() {
@Override
public void print() {
System.out.println("test2() 메서드에서 반환된 MyInterface");
}
};
return mi;
}
MyInterface mi2 = test2();
mi2.print();
}
}
- 리턴 타입을 인터페이스로 받는 경우, 위 코드처럼 test2() 메서드 안에 익명 클래스로 인터페이스를 구현한 객체가 있기 때문에 반환이 가능하다.
- 람다식 사용하기
- 람다식 기본
public static MyInterface test3() {
return new MyInterface() {
@Override
public void print() {
System.out.println("Hello");
}
};
}
MyInterface m = test3();
m.print();
- 기존의 인터페이스는 위와 같이 익명 클래스 사용방식을 이용하여 인터페이스를 구현했다.
MyInterface m2 = () -> System.out.println("Hello");
m2.print();
- 람다식을 이용하면 위의 복잡한 코드가 아래와 같이 깔끔하게 작성 가능한 것을 볼 수 있다.
- new MyInterface() -> () / test(), print() 메서드 사라짐 / 구현체만 남음
- 기본 문법
() -> 명령문;
() -> {
명령문1;
명령문2;
명령문3;
};
- 람다식 매개변수
(변수명) -> 명령문;
(변수명1, 변수명2, ... , 변수명n) -> 명령문;
// () 안에 타입을 지정하고 싶다면 다음처럼 할 수도 있다.
(타입 변수명) -> 명령문;
class Verify1 implements Verify {
@Override
public boolean check(int n) {
return (n % 2) == 0;
}
}
public class Class {
public static void main(String[] args) {
Verify v1 = new Verify1();
System.out.println(v1.check(10));
Verify v2 = (n) -> (n * 2) == 20;
System.out.println(v2.check(10));
}
}
- 람다식 블록
// 람다식 본문이 한 줄일 때
() -> 명령문;
// 람다식 본문이 여러 줄일 때
() -> {
명령문1;
명령문2;
return 값;
};
interface NumberFunc {
int func(int n);
}
public class Test05 {
public static void main(String[] args) {
NumberFunc sum = (n) -> {
int result = 0;
for (int i = 0; i <= n; i++) {
result += i;
}
return result;
};
System.out.println("1부터 10까지의 합 : " + sum.func(10));
System.out.println("1부터 10까지의 합 : " + sum.func(100));
}
}
- 메서드 참조
- 메서드 참조 방식은 람다식의 구현체가 길어 질 경우 코드의 가독성이 떨어질 수 있기 때문에, 인터페이스 메서드를 특정 클래스의 메서드를 통해 구현 시키는 방식이다.
- 단, 메서드 참조 방식은 반환 타입, 매개변수가 동일해야만 사용할 수 있다.
- 인터페이스 객체 생성
클래스명::메서드명 - static 메서드로 선언했을 때
참조변수명::메서드명 - 인스턴스 메서드로 선언했을 때
- static 메서드 참조
interface StringFunc {
String modify(String s);
}
public class Test10 {
static String func(String s) {
String result = "";
char c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if(c==',')
result += " ";
else result += c;
}
return result;
}
public static void main(String[] args) {
StringFunc sf = Test10::func;
String str = "Korea,Australia,China,Germany,Spain,Turkey";
String result = sf.modify(str);
System.out.println(result);
}
}
- Test10::func는 Test10 클래스의 func() 메서드를 참조하겠다는 의미이다.
- 참조 메서드의 경우 주의 할 점은 람다식을 구현하는 메서드의 리턴 타입과 매개변수는 함수형 인터페이스의 추상 메서드와 동일해야 한다는 점이다.
- 인스턴스 메서드 참조
String func1(String s) {
String result = "";
char c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
if(c==',')
result += " ";
else result += c;
}
return result;
}
public static void main(String[] args) {
Test10 obj = new Test10();
StringFunc sf1 = obj::func1;
String str1 = "Korea,Australia,,Germany,Spain,";
String result1 = sf1.modify(str1);
System.out.println(result1);
}
}
- 인스턴스 메서드의 경우 클래스의 객체를 생성해 준 다음 메서드 참조하는 방식으로 사용해야 한다.
- 함수형 인터페이스 API
- 기본적인 함수형 인터페이스는 java.util.function 패키지에서 제공한다.
인터페이스 | 메서드 | 설명 |
Fuction<T,R> | R apply(T) | T 타입 인자 처리 후 R 타입 값 반환 |
Predicate<T> | boolean test(T) | T 타입 인자 처리 후 boolean 값 반환 |
Consumer<T> | void accept(T) | T 타입 인자 처리(반환값 없음) |
Suppiler<T> | T get() | 인자 없이 처리 후 T 타입 값 반환 |
- Function
- 인터페이스 : Function<T, R>
- 메서드 : R apply(T)
- apply() 메서드는 매개변수 T와 리턴 타입 R로 선언되어 있다.
package ch14;
import java.util.function.Function;
public class Test14 {
public static void main(String[] args) {
Function<String, Integer> func = (s) -> {
int cnt = 0;
String[] word = s.split(" ");
cnt = word.length;
return cnt;
};
int wordCnt = func.apply("고개를 들어 별들을 보라 당신 발만 내다 보지말고");
System.out.println("단어 수 : " + wordCnt);
}
}
- Ramda.java
@FunctionalInterface
interface MyFunction {
void run(); // public abstract void run();
}
public class Ramda {
static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
f.run();
}
static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드
// MyFunction f = () -> System.out.println("f3.run()");
// return f;
return () -> System.out.println("f3.run()");
}
public static void main(String[] args) {
// 람다식으로 MyFunction의 run()을 구현
MyFunction f1 = () -> System.out.println("f1.run()");
MyFunction f2 = new MyFunction() { // 익명클래스로 run()을 구현
@Override
public void run() { // public을 반드시 붙여야 함
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute(() -> System.out.println("run()"));
}
}
- ExFunction.java
import java.util.function.Function;
public class ExFunction {
public static void main(String[] args) {
Function<Integer, Integer> f1 = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer / 10 * 10;
}
};
Function<Integer, Integer> f2 = i -> i / 10 * 10;
int inputValue = 25;
int result1 = f1.apply(inputValue);
int result2 = f2.apply(inputValue);
System.out.println("f1("+inputValue +") = "+result1);
System.out.println("f2("+inputValue +") = "+result2);
}
}
- Predicate
- 인터페이스 : Predicate<T>
- 메서드 : boolean test(T)
- test() 메서드는 매개변수 T와 리턴 타입은 boolean으로 선언되어 있다.
public class Test15 {
public static void main(String[] args) {
Predicate<Integer> func = (n) -> n % 2 == 0;
if(func.test(123))
System.out.println("짝수입니다");
else
System.out.println("홀수입니다.");
}
}
- Consumer
- 인터페이스 : Consumer<T>
- 메서드 : void accept(T)
- accept() 메서드는 매개변수 T와 리턴 타입은 void로 선언되어 있다.
public class Test16 {
public static void main(String[] args) {
Consumer<Date> date = (d) -> {
String s = new SimpleDateFormat("YY-MM-dd").format(d);
System.out.println(s);
};
date.accept(new Date());
}
}
- Supplier
- 인터페이스 : Supplier<T>
- 메서드 : T get()
- get() 메서드는 전달받은 인자가 없고 리턴 타입은 T로 선언되었다.
public class Test17 {
public static void main(String[] args) {
Supplier<String> day = () -> new SimpleDateFormat("E요일").format(new Date());
String result = day.get();
System.out.println(result);
}
}
'Programming > JAVA' 카테고리의 다른 글
JAVA - Stream API (1) (스트림 생성 방법) (0) | 2023.06.08 |
---|---|
JAVA - 열거형 (0) | 2023.06.07 |
JAVA - Optional (0) | 2023.06.05 |
JAVA - Reflection API (0) | 2023.06.01 |
JAVA - 람다 (기본) (0) | 2023.05.18 |