September 04, 2022
Big Data를 처리하면서 함수형 언어가 새로 뜨기 시작
int max(int a, int b) {
return a > b ? a : b;
}
(a, b) -> a > b ? a : b
->
화살표 생긴다->
를 블록{} 앞에 추가한다.int max(int a, int b) {
return a > b ? a : b;
}
위에서 아래로
(int a, int b) -> {
return a > b ? a : b;
}
;
안 붙임)(int a, int b) -> {
return a > b ? a : b
}
위에서 아래로
(int a, int b) -> a > b ? a : b
(int a, int b) -> a > b ? a : b
위에서 아래로
(a, b) -> a > b ? a : b
(a) -> a * a
는 a -> a * a
로 가능하다(int a) -> a * a
는 int a -> a * a
로 변경 불가능하다;
안 붙임, 붙이면 안됨)(int i) -> {
System.out.println(i);
}
위에서 아래로 변경가능
(int i) -> System.out.println(i)
(int a, int b) -> {return a > b ? a : b;} // OK
(int a, int b) -> return a > b ? a : b // 에러
1.
int max(int a, int b) {
return a > b ? a : b;
}
위는 아래로 변경
(a, b) -> a > b : a : b
2.
int printVar(String name, int i) {
System.out.println(name + "=" + i);
}
위는 아래로 변경
(name, i) -> System.out.println(name + "=" + i)
3.
int squre(int x) {
return x * x;
}
위는 아래로 변경
x -> x * x
4.
int roll() {
return (int)(Math.random() * 6);
}
위는 아래로 변경
() -> (int)(Math.random() * 6)
(a, b) -> a > b ? a : b
위는 사실 아래와 같다. 익명 객체임. 즉, 익명 클래스의 익명 객체임
그래서 람다식은 즉, 객체임
new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
}
Object obj = new Object() {
int max(int a, int b) {
return a > b ? a : b;
}
};
타입 obj = (a, b) -> a > b ? a : b; // 어떤 타입일까?
int value = obj.max(3, 5); // 에러. Object클래스에 max()가 없음. Object리모콘으로 호출불가
public class Test {
public static void main(String[] args) {
// Object obb = (a, b) -> a > b ? a : b; // 에러. 함수형처럼 다뤄야 함. 자세한 것은 뒤에
Object obj = new Object() { // 람다식은 객체이므로 이렇게 표현한 것. 하지만 object로 받으면 안된다.
int max(int a, int b) {
return a > b ? a : b;
}
};
int value = obj.max(3, 5); // 에러. Object타입 리모콘에는 max가 없다.
}
}
@FunctionalInterface // 애너테이션은 안 붙여도 되는데 붙이면, 붙이면 함수형 인터페이스를 올바르게 작성했는지를 체크해준다. 그래서 붙여주는 게 좋다
interface MyFunction {
public abstract int max(int a, int b);
}
MyFunction f = new MyFunction() { // 익명클래스{}의 선언과 객체 생성을 동시에
public int max(int a, int b) {
return a > b ? a : b;
}
};
그러면 아래처럼 사용가능
Object로 선언하지 않았기 때문에 가능
int value = f.max(3, 5); // OK. MyFunction에 max() 가 있음
매개변수 개수, 타입
와 반환타입
이 일치해야 함. 즉 람다(익명 객체이자 메서드)는 이름을 없앴기 때문에 상기 조건을 일치시켜야만 함수형 인터페이스의 메서드와 연결해서 사용할 수 있다(무슨 메서드인지는 알아야 하므로). 추상 메서드를 통해서 람다식을 호출하는 것MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3, 5); // 실제로는 람다식(익명 함수)이 호출됨
람다식이 익명객체, 즉 객체이므로 참조변수(f)가 필요하다.(람다식을 다루려면)
public class Test {
public static void main(String[] args) {
// MyFunction f = new MyFunction() { // 람다식. 익명객체
//
// @Override
// public int max(int a, int b) { // public붙인 이유는 오버라이딩시 접근제어자를 좁게 못 바꾸기 때문에
// return a > b ? a : b;
// }
// };
// 람다식(익명 객체) 을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다.
MyFunction f = (a, b) -> a > b ? a : b; // 람다식. 익명객체
int value = f.max(3, 5);
System.out.println("value = " + value);
}
}
@FunctionalInterface
interface MyFunction {
int max(int a, int b); // public abstract int max(int a, int b); 와 동일.
}
List<String> list = Arrays.asList("abc", "aaA", "bbb", "ddd", "aaa");
Collections.sort(list, new Comparator<String>() {
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
});
// Comparator<T> 참고
// @FunctionalInterface 생략되어 있음
interface Comparator<T> {
int compare(T o1, T o2);
}
아래로 변환 가능 (간단히)
List<String> list = Arrays.asList("abc", "aaA", "bbb", "ddd", "aaa");
Collections.sort(list, (s1, s2) -> s2.compareTo(s1));
@FunctionalInterface
interface MyFunction {
void myMethod();
}
void aMethod(MyFunction f) {
f.myMethod(); // MyFunction에 정의된 메서드 호출. 즉 람다식 호출
}
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);
aMethod(() -> System.out.println("myMethod()");
MyFunction myMethod() {
MyFunction f = () -> {}'
return f;
}
MyFunction myMethod() {
return () -> {};
}
@FunctionalInterface
interface MyFunction {
void run(); // public abstarct void run();
}
public class Test {
static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction(함수형 인터페이스) 인 메서드
f.run();
}
static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드. 즉 람다식을 반환
// MyFunction f = () -> System.out.println("f3.run()");
// return f;
// 위 2줄을 아래 1줄로 변환 가능
return () -> System.out.println("f3.run()");
}
public static void main(String[] args) {
// 1. 람다식으로 MyFunction의 run()을 구현
MyFunction f1 = () -> System.out.println("f1.run()"); // void run()과 파라미터가 없다는 것, 반환타입도 없다는 것 다 일치.
// 2. 익명클래스로 MyFunction의 run()을 구현
MyFunction f2 = new MyFunction() { //
@Override // @Override 안 붙여도 되긴 함
public void run() { // public을 반드시 붙여야 함
System.out.println("f2.run()");
}
};
// 3. MyFunction 인터페이스의 run()을 구현한 것을 반환(람다로) 하는 것을 받기
MyFunction f3 = getMyFunction();
f1.run(); // f1.run();
f2.run(); // f2.run();
f3.run(); // f3.run();
execute(f1); // f1.run(); , execute(() -> System.out.println("f1.run()")); 와 동일하다
execute(() -> System.out.println("run()")); // run()
}
}
Predicate<T>
사용예제
Predicate<String> isEmptyStr = s -> s.length() == 0;
String s = "";
if (isEmptyStr.test(s)) // if(s.length() == 0)
System.out.println("This is an empty String.");
String s
가 아니라 s
인 이유는 앞에서 Predicate<String>
이미 String인 것을 알고 있으므로if (isEmptyStr.test(s))
에서 test는 s -> s.length() == 0;
식을 말한 것이다. 이 식에 test라는 이름이 붙은 것임. 이 람다식을 사용하려면 test라는 이름을 사용해야만 하는 것예시(퀴즈)
Supplier<Integer>
f = () -> (int)(Math.Random()*100) + 1; // Integer인 이유는 반환타입이 Integer이므로. int -> Integer로 오토박싱되어서 반환(지네릭스는 primitive 타입 못 씀)Consumer<i>
f = i -> System.out.print(i + ”, ”); // 반환값 없음. 그냥 콘솔에 출력뿐. i 타입이므로 IntegerPredicate<Integer>
f = i -> i % 2 == 0; // 조건식이므로 Predicate. 원래는 Function<T, R>
처럼 Predicate<Integer, Boolean>
이라고 써야 하지만 반환타입이 항상 Boolean 이기 때문에 Boolean은 쓰지 않는다.Function<Integer, Integer>
f = i -> i / 10 * 10; // Integer를 넣으면 Integer가 반환되어서 <Integer, Integer>
Bi
가 붙는다.<T, U>
는 T, U를 받기만 할 뿐 반환은 없고, BiPredicate<T, U>
는 T, U를 받아서 Boolean을 반환하고, BiFunction<T, U, R>
은 T, U를 받아서 R을 반환한다.매개변수 3개부터는 없다. 만약 3개 이상이 필요하다면 아래처럼 직접 만들어야 한다.
T, U, V 3개를 받는 인터페이스를 아래처럼 만들어야 한다.
@FunctionalInterface
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
UnaryOperator<T>
는 단항연산자, BinaryOperator<T>
는 이항연산자는 뜻
<T>
소스코드@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t; // 항등함수
}
}
<T, R>
소스코드@FunctionalInterface
pubic interface Function<T, R> {
R apply(T t);
....
}
public class Test {
public static void main(String[] args) {
Supplier<Integer> s = () -> (int)(Math.random()*100) + 1; // 1~100난수
Consumer<Integer> c = i -> System.out.print(i + ", ");
Predicate<Integer> p = i -> i % 2==0; // 짝수인지 검사
Function<Integer, Integer> f = i -> i / 10 * 10; // i의 일의 자리를 없앤다.
List<Integer> list = new ArrayList<>();
makeRandomList(s, list); // s를 이용해 list를 1~100 랜덤값으로 채운다.
System.out.println(list);
printEvenNum(p, c, list); // 짝수를 출력
List<Integer> newList = doSomething(f, list);
System.out.println(newList);
}
static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
List<T> newList = new ArrayList<T>(list.size());
for (T i : list) {
newList.add(f.apply(i)); // 일의 자리를 없애서 새로운 list에 저장
}
return newList;
}
static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
System.out.print("[");
for (T i : list) {
if(p.test(i)) { // Predicate<Integer> p = i -> i % 2==0; // 짝수인지 검사
c.accept(i); // Consumer<Integer> c = i -> System.out.print(i + ", ");
}
}
System.out.println("]");
}
static <T> void makeRandomList(Supplier<T> s, List<T> list) {
for (int i = 0 ; i < 10 ; i++) {
list.add(s.get()); // Supplier로부터 1~100의 난수를 받아서 list에 추가
}
}
}
Predciate<Integer> p = i -> i < 100;
Predciate<Integer> q = i -> i < 200;
Predciate<Integer> r = i -> i%2 == 0;
Predciate<Integer> notP = p.negate(); // i >= 100
Predicate<Integer> all = notP.and(q).or(r); // 100 <= i && i < 200 || i % 2 == 0
Predicate<Integer> all2 = notP.and(q.or(r)); // 100 <= i && (i < 200 || i % 2 == 0)
System.out.println(all.test(2)); // true
System.out.println(all2.test(2)); // false
Predicate<String> p = Predicate.isEqual(str1); // isEquals() 은 static메서드
Boolean result = p.test(str2); // str1과 str2가 같은지 비교한 결과를 반환
boolean reuslt = Predicate.isEqual(str1).test(str2);
public class Test {
public static void main(String[] args) {
// 위에서 개념 설명은 없었지만 문제를 통해서 설명
// 함수 2개를 합치는 것. f함수와 g를 합치려면 f의 출력(Integer)과 g의 입력(Integer)가 동일해야 한다.
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16); // String s를 16진수로 변경
Function<Integer, String> g = (i) -> Integer.toBinaryString(i); // 2진수 문자열로 변경
// h는 f와 g를 합친 새로운 함수 h를 만든 것임 (String -> Integer -> String)
Function<String, String> h = f.andThen(g); // f 적용 후 g를 적용 하는 것이 andThen (함수를 2개 붙인 것)
// h2는 g와 f를 합친 새로운 함수 h2를 만든 것임 (Integer -> String -> Integer)
Function<Integer, Integer> h2 = f.compose(g); // g 적용 후 f를 적용 (즉, g.andThen(f)와 같고 andThen을 쓰는 것이 더 가독성 좋음)
System.out.println(h.apply("FF")); // "FF" -> 255 -> "11111111"
System.out.println(h2.apply(2)); // 2 -> "10" -> 16
Function<String, String> f2 = x -> x; // 항등 함수(identity function)
System.out.println(f2.apply("AAA")); // AAA가 그대로 출력됨
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate<Integer> notP = p.negate(); // i >= 100
Predicate<Integer> all = notP.and(q.or(r)); // (i>=100) && (i<200 || i%2==0)
System.out.println(all.test(150)); // true
String str1 = "abc";
String str2 = "abc";
// str1과 str2가 같은지 비교한 결과를 반환
Predicate<String> p2 = Predicate.isEqual(str1);
boolean result = p2.test(str2); // 위 2줄을 boolean result = Predicate.isEqual(str1).test(str2); 와 같다. 이것은 boolean result = str1.equals(str2); 와 역시 같다.
System.out.println(result); // True
// 위에서 String str1 = new String("abc");, String str2 = new String("abc"); 해도 결과는 똑같다. 그 말은 등가비교(==) 를 하는 것이 아니라 equals로 비교한다는 것을 의미
}
}
list.forEach(i -> System.out.print(i + ",")); // list의 모든 요소를 출력
list.removeIf(x -> x%2 == 0 || x % 3 == 0); // 2 또는 3의 배수를 제거
list.replaceAll(i -> i * 10); // 모든 요소에 10을 곱한다.
// map의 모든 요소를 {k, v} 의 형식으로 출력
map.forEach((k, v) -> System.out.println("{" + k + "," + v + "} , "));
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++) {
list.add(i);
}
// 1. list의 모든 요소를 출력. (물론 System.out.println(list); 만 해도 잘 나오긴 함.)
// 기존에는 아래처럼 코드가 길었음.
/*
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
*/
// 람다식 이용할 수 있는 메서드 덕분에 간결해짐
list.forEach(i -> System.out.println(i + ",")); // forEach는 consumer들어가니 받기만 하는 것
// 2. list에서 2 또는 3의 배수를 제거한다.
list.removeIf(x -> x%2 == 0 || x%3 == 0);
System.out.println(list);
// 3. list의 각 요소에 10을 곱한다.
list.replaceAll(i -> i*10);
System.out.println(list);
// 4. map의 모든 요소를 {k, v} 의 형식으로 출력한다.
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
// 출력
// 기존코드는 매우 길다
/*
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
*/
// 람다식 이용할 수 있는 메서드 덕분에 간결해짐
map.forEach((k, v) -> System.out.println("{" + k + "," + v + "},"));
System.out.println();
}
}
클래스이름::메서드이름
Integer method(String s) { // 그저 Integer.parseInt(String s)만 호출
return Integer.parseInt(s);
}
int result = obj.method("123");
int result = Integer.parseInt("123");
따라서 A는 아래처럼 차례로 변할 수 있다. (람다식 <—> 메서드 참조, 양방향 변환을 연습해야 한다)
우선 람다식으로 변환
Function<String, Integer> f = (String s) -> Integer.parseInt(s); // 람다식
메서드 참조로 다시 변환(람다식에서 어차피 입력값이 String인 것 아니까 (String s)
와 parseInt(s)의 (s)
는 생략해도 됨, 특히 parseInt(s)는 컴파일러가 애초에 무엇을 받는지 미리 다 알고 있으므로.
), 그래서 다 지우면 Integer.paserInt; 가 남는데 .를 ::로 문법상 약속
Function<String, Integer> f = Integer::parseInt; // 메서드 참조
public class Test {
public static void main(String[] args) {
// Function<String, Integer> f = (String s) -> Integer.parseInt(s); // 람다식
Function<String, Integer> f = Integer::parseInt; // 위의 람다식을 메서드 참조로 변환
System.out.println(f.apply("100"));
}
}
생성자에 매개변수가 없는 경우
Supplier<Myclass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
생성자에 매개변수가 있는 경우
입력값 Integer타입 i를 주고 출력이 MyClass 클래스
Function<Integer, MyClass> s = (i) -> new Myclass(i);
매개변수가 2개면 BiFunction<T, U, R>
사용하면 됨. T, U가 입력값, R이 반환값
Function<Integer, MyClass> s = MyClass::new;
람다식
배열길이(x, 입력값) 가 필요하므로 Function 써야 한다.
Function<Integer, int[]> f = x -> new int[x]; // 람다식
메서드 참조(위의 람다식을 변환)
어차피 입력값 Integer를 아니까
Function<Integer, int[]> f2 = int[]::new; // 메서드 참조
public class Test {
public static void main(String[] args) {
// Supplier은 입력X, 출력O
Supplier<MyClass> s1 = () -> new MyClass(); // 람다식(파라미터 X)
Supplier<MyClass> s2 = MyClass::new; // 메서드 참조(파라미터 X)
System.out.println(s2.get()); // MyClass mc = s2.get();로 객체 얻어서 System.out.println(mc); 한 것임
// Function은 입력O, 출력O
Function<Integer, MyClass> f1 = (i) -> new MyClass(i); // 람다식(파라미터 O)
Function<Integer, MyClass> f2 = MyClass::new; // 메서드 참조(파라미터 O)
MyClass mc = f1.apply(100);
System.out.println(f1.apply(100).iv); //100, System.out.println(mc.iv); 와 같음
// 배열. 배열을 반환(출력)하려면 배열의 길이(입력)를 줘야 하니까 Function
Function<Integer, int[]> r1 = (k) -> new int[k]; // 람다
Function<Integer, int[]> r2 = int[]::new; // 메서드 참조
System.out.println(r2.apply(100).length); // 100
}
}
class MyClass {
int iv;
MyClass(int iv) {
this.iv = iv;
}
MyClass() {}
}