함수형 인터페이스
- 한 개의 추상 메소드를 가지고 있는 인터페이스
- 자바의 람다식은 함수형 인터페이스로만 접근이 되기에 필요하다.
- 선언하여 변수처럼 할당시키거나, 다른 메소드들에 변수를 이용해 값을 전달하는 형태의 식을 가진다.
[선언 에]
@FunctionalInterface --> 어노테이션을 선언
private interface user_name{
public void [자료형] user_name(매개인자,,,);
}
- 표현식 사용 (구현하는 메소드의 인수)-> {처리} 형식
- 표현식 특징: 람다 표현식은 익명으로 처리되어 이름이 없다.
- 람다 표현식은 compiler가 context에 맞는 타입을 유추하기 때문에 return type이 없다.
- throws가 없다.
- 표현식 자체가 generic이 될 수 없어 type 파라미터를 선언할 수 없다.
- Example
ex01) 매개 인자로 su를 받아 출력하는 구문
(int su) ->{ System.out.println(su) }
ex02) 자료형이 생략 가능하다.
su -> {System.out.println(su)}
ex03) 매개인자가 하나일 경우 (), {} 가 생략 가능하다.
su -> System.out.println(su)
ex04) 매개인자가 없는 경우 () 빈괄호만 사용하여 명령 실행할 수 있다.
() -> {System.out.println('A')}
ex05) 매개인자 두개 이상이면 자료형 생략 하여 실행할 수 있다.
(a,b) -> {return a+b}
ex06) 매개인자 두 개 이상이면 {} 와 return 키워드 생략할 수 있다.
(a,b) -> {a+b}
@FunctionalInterface // My_function에 있는 메소드를 람다식으로 사용하겠다.
private interface My_Function{
public void say(String name);//추상 메소드 하나만 선언(functionalInterface에서는 하나만)
}
public static void main(String[] args) {
My_Function func= (name)-> System.out.println("Hello," +name);
func.say("홍길동");
}
위의 경우 name 안에 Hello name이라는 것을 담아 준 후 선언한 func로 say 함수에 값을 넣어주면 "Hello, 홍길동" 이라는 값을 받을 수 있다.
@FunctionalInterface
interface Func{
public int calc(int a,int b);
}
public static void main(String[] args) {
Func add=(int a, int b) ->{return a+b;};
Func sub=(int a, int b)->{return a-b;};
Func mul=(a,b)->{return a*b;};
Func div=(a,b)->{System.out.println(a+ "/"+ b);return a/b;};
System.out.println(add.calc(100,50));
System.out.println(sub.calc(100, 50));
System.out.println(mul.calc(100, 50));
System.out.println(div.calc(100, 50));
}
functionalInterface를 활용한 사칙 연산이다. Func라는 인터페이스를 기반으로 사용될 함수들을 정의해 두고, 람다 형식으로 값을 계산하여 출력해 보았다.
기본 함수형 인터페이스 java.util.function 참조
- Runnable
- 인자를 받지 않고 리턴 값도 없는 인터페이스이다.
public interface Runnable {
public abstract void run();
}
Runnable runnable = () -> System.out.println("run anything!");
runnable.run();
// 결과
// run anything!
함수형 인터페이스 별로 run()과 같은 실행 메소드 이름이 다르므로 확인하고 사용하여야 한다.
- Supplier
- 추상 메소드 get()이 정의되어 있으며, 각 메소드는 정의된 자료형을 리턴하도록 하여 컬렉션 객체를 사용시 적용하면 코드를 간결하게 활용할 수 있다.
- 인자를 받지 않고 T 타입의 객체를 반환한다.
public interface Supplier<T> {
T get();
}
Supplier<String> getString = () -> "코딩코딩!";
String str = getString.get();
System.out.println(str);
// 결과
// 코딩코딩!
해당 경우 String 형태를 T로 잡았기에 get을 하게 될 경우 String 형태의 객체를 반환한다.
- Consumer
- Consumer<T> 는 T타입의 객체를 인자로 받고 리턴 값은 존재하지 않는다.
- 값을 받아 처리하지만 반환 값을 리턴하지 않는 void형 function과 흡사하다.
- BiConsumer<T,U> : void accept(T t, U u)의 경우 T와 U를 받아서 side effect만 발생시키고 결과 값은 똑같이 존재하지 않는다.
Consumer<String> c = s-> System.out.println(s);
c.accept("abc");
Consumer<String> c1 = s-> System.out.println("c1="+s);
Consumer<String> c2 = s-> System.out.println("c2="+s);
Consumer<String> c_res = c1.andThen(c2);
c_res.accept("abc");
//결과
//abc
//=============
//c1=abc
//c2=abc
accept() 메소드를 사용하며, andThen을 이용하면 두 개 이상의 Consumer를 연속적으로 실행할 수 있다.
- Function
- apply() 메소드를 활용하여 람다로 사용시 자주 활용되나.
- apply() 메소드의 리턴 값이 R이기 떄문에 중괄호 {} 의 리턴 값이 R의 객체가 되도록 한다.
- andThen(), compose() 메소드는 function을 받아서 function을 리턴한다.
- Function<T,R>의 경우 R apply(T t)의 형식을 가지며 매개인자 T를 받아서 R로 리턴한다.
- BiFunction<T,U,R>의 경우 인자로 T와 U를 받아서 R로 리턴한다.
- UnaryOperator<T> : T apply(T t)의 경우 Function<T,T>을 상속 받아서 동일한 인수로 리턴한다.
- BinaryOperator<T> : T apply(T t1, T t2)의 경우 BiFunction<T,U,R>을 상속 받아서 동일한 타입으로 리턴한다.
- compose()는 두개의 function을 조합하여 새로운 function 객체를 만들어 주는 메소드이다. but AndThen()과 순서가 반대임을 명시해야 한다.
[타입선언]
package java.util.function
@FunctionalInterface
public interface Function<T,R>{
R apply(T t);
default<V> Function<T,V> andThen(Function<? super R, ? extend V> after);
default<V> Function<V,R> compose(Function<? super V, ? extend T> before);
static<T> Function<T,T> identity();
}
ex) Function<Integer, Integer> a1= e-> e*2;
Function<Integer, Integer> a1= e-> e*e;
a1.compose(a2).apply(2);//숫자 2를 e로 전달한 후에 a2, a1 순으로 수행 -> 8
a1.andThen(a2).apply(2);// 숫자 2를 e로 전달 후에 a1, a2 순으로 수행 -> 16
Function<Integer, String> my_fun=(i)->Integer.toString(i);
//인티저 값을 스트링으로 바꾼다.
System.out.println(my_fun.apply(100).length());
System.out.println(my_fun.apply(10000).length());
//결과
//3
//5
BiFunction<String, String, String> bi=(x,y)->{
return x+y;
};
Function <String,String> f= x->x+"!";
System.out.println(bi.andThen(f).andThen(f).apply("Getting Start", " java"));
//결과
//Getting Start java!!
- Predicate
- 조건 집합을 정의하고 지정된 객체의 조건 충족 유무에 따라 true/ false로 리턴하는 메소드를 가진다.
- Predicate<T>의 경우 T에 대해서 true/ false를 리턴한다.
- test()메소드를 사용한며 and() , or() 메소드와 함께 사용할 수 있다.
- isEqual()은 static 메소드로 인자로 전달되는 객체와 같은지 확인하는 객체를 만들 수 있다.
@FunctionalInterface
public interface Predicate<T>{
boolean test(T, t);
default Predicate<T> and(Predicate<? super T> other);
static <T> Predicate<T> isEqual(Object targetRef);
default Predicate<T> negate();
default Predicate<T> or(Predicate<? super T> other);
}
Predicate<Integer> isBiggerThanFive = num -> num > 5;
System.out.println("10 is bigger than 5? -> " + isBiggerThanFive.test(10));
// 결과
// 10 is bigger than 5? -> true
Predicate<String> isEquals = Predicate.isEqual("Google");
isEquals.test("Google");
// 결과
// true
람다 기초 활용
- jdk 8부터는 메소드와 생성자를 직접 참조할 수 있다.
- C++의 :: 연산자를 활용한다.
- 생성자 참조의 경우 class_name::new
- 메소드 참조의 경우
- static 일떄, class_name::method_name
- non-static일때, instance_name::method_name
// 이름과 나이를 관리하는 MyConstruct 라는 클래스를 선언해서 생성자 참조를 통해
// 값을 출력하는 소스 코드를 작성해보자.
class MyConstruct{
String name;
int age;
public MyConstruct() {
name="길동";
age=21;
}
public MyConstruct(String name) {
this.name=name;
age=25;
}
public MyConstruct(String name, int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "MyConstruct [name=" + name + ", age=" + age + "]";
}
}
//functional Interface 이용해서 생성자
public class ConstRefTest {
public static void main(String[] args) {
Supplier<MyConstruct> func=MyConstruct::new; //생성하는 방법
//매개인자 없는 기본 생성자가 생성된다.
MyConstruct res=func.get();
System.out.println(res);
//매개 인자 하나만 받아서 생성하는 경우 이걸 사용한다. MyConstruct로 리턴하기 위한 Function
Function<String,MyConstruct> func1=MyConstruct::new;
System.out.println(func1.apply("Dominica_kim"));
BiFunction<String,Integer,MyConstruct> func2=MyConstruct::new;
System.out.println(func2.apply("pearl", 26));
//결과
//MyConstruct [name=길동, age=21]
//MyConstruct [name=Dominica_kim, age=25]
//MyConstruct [name=pearl, age=26]
}
}
default/ String / String, int 형태의 생성자를 만들어 두었기에
- default의 경우 supplier를 이용하여 get으로 출력
- String의 경우 String 형태의 값을 넣어 MyConstruct 형태의 값을 뽑아야 하므로 Function<T,R>를 활용
- String, int의 경우 Function<T,U,R> 을 활용하여 값을 출력하였다.
해당 반환 값은 객체를 반환하나 Class에서 toString을 override 해놓았기에 원하는 결과값을 얻을 수 있었다.
//ConstRefTest의 생성자 참조 형식을 순수하게 람다형식으로 변환
public class ConstRefTest02 {
public static void main(String[] args) {
Supplier<MyConstruct> func=()->new MyConstruct(); //생성하는 방법
//매개인자 없는 기본 생성자가 생성된다.
MyConstruct res=func.get();
System.out.println(res);
//매개 인자 하나만 받아서 생성하는 경우 이걸 사용한다. MyConstruct로 리턴하기 위한 Function
Function<String,MyConstruct> func1=(name)->new MyConstruct(name);
System.out.println(func1.apply("Dominica_kim"));
BiFunction<String,Integer,MyConstruct> func2=(name,age)-> new MyConstruct(name,age);
System.out.println(func2.apply("장재성", 26));
}
}
위 코드의 경우 앞선 코드를 순수한 람다 형식으로 바꿔준 것이다.
'JAVA' 카테고리의 다른 글
Java Collectors의 메소드 정리(for 람다) (0) | 2021.09.19 |
---|---|
JAVA 스트림(Stream) 정리 (0) | 2021.09.17 |
JavaFX GUI활용한 프로그램 개발 (0) | 2021.08.12 |
JAVA 클래스와 객체 (0) | 2021.08.10 |
java와 javac 버전이 다를 때 해결법(Window) (0) | 2021.07.20 |