Java는 객체지향 언어이기 때문에 기본적으로 함수형 프로그래밍이 불가능하다. 하지만 JDK8부터 Stream API와 람다식, 함수형 인터페이스 등을 지원하면서 Java를 이용해 함수형으로 프로그래밍할 수 있는 API 들을 제공해주고 있다.
First Class Citizon
• 변수에 값을 할당할 수 있어야 합니다.
• 함수의 파라미터로 넘겨줄 수 있어야 합니다.
• 함수의 반환값이 될 수 있어야 합니다.
https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html
[Function (Java Platform SE 8 )
docs.oracle.com](https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html)
💡 public abstract가 단 하나만 있으면 가능하며 다른 형태로 정의가 되어져 있는 것은 상관이 없다.
@FunctionalInterface
public interface PracticeLambda {
void practice(); //public abstract 생략
static void practice2() {
System.out.println("연습1");
}
default void practice3() {
System.out.println("연습2");
}
}
만약에 추상 메소드가 2개 이상이면 @functionalInterface에서 컴파일 오류가 나온다.
🦖현재의 이 코드는 Function 인터페이스를 익명객체 스타일로 구현을 했다.
public class PracticeLamdba {
public static void main(String[] args) {
Function<Integer, Integer> pricaticeTest = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer+10;
}
};
System.out.println(pricaticeTest.apply(3));
}
}
🦖이러한 익명의 느낌을 람다로 표현이 가능하다.
public class PracticeLamdba {
public static void main(String[] args) {
Function<Integer, Integer> pricaticeTest = integer -> integer+10;
System.out.println(pricaticeTest.apply(3));
}
}
→T는 입력하는 타입을 의미하며 R은 return 되는 값의 타입을 의미한다.
여기서 만약에 Funciotn 함수가 여러개 있을 때 Function의 다양한 기능을
사용할 수 있다.
public class PracticeLamdba {
public static void main(String[] args) {
Function<Integer, Integer> plus = (number) -> number+10;
Function<Integer , Integer> multiple = number2 ->number2*2;
int result=plus.compose(multiple).apply(10);
System.out.println(result);
}
더하기 기능과 곱하기 기능을 만들었고 copose를 사용을 했다.
이때 result의 값은 어떻게 나올까? —> 결과는 30이 나오게 된다.
Compose란
()의 기능을 먼저 연산을 하고 그 뒤에 첫번 째로 오는 기능을 연산을 한다.
multiple → 결과 → 결과 & plus 라고 생각을 하면 좋다.
public class PracticeLamdba {
public static void main(String[] args) {
Function<Integer, Integer> plus = (number) -> number+10;
Function<Integer , Integer> multiple = number2 ->number2*2;
int result=plus.andThen(multiple).apply(10);
System.out.println(result);
}
}
andThen은 compose와 반대로 먼저오는 기능을 연산하고 그 뒤에 () 기능을 연산을 하는 것을 의미한다.
즉. plus → 결과 → multiple을 하며 결과는 40이 나오게 된다.
public class PracticeLamdba {
public static void main(String[] args) {
BiFunction<Integer,Integer,Integer>biFunction = new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer+integer2;
}
};
int result=biFunction.apply(10 , 20);
System.out.println(result);
}
}
public class PracticeLamdba {
public static void main(String[] args) {
BiFunction<Integer,Integer,Integer>biFunction = (integer, integer2) -> integer+integer2;
int result=biFunction.apply(10 , 20);
System.out.println(result);
}
}
만약에 타입이 같으면 Function은 UnaryOperator를 사용하면 된다.
그러면 입력 , 출력값의 타입이 모두 T로 정해진다.
BiFunction<T,U,R>은 입력 출력 모두 같으면 BinaryOperator를 사용
public class PracticeLamdba {
public static void main(String[] args) {
UnaryOperator<Integer>unaryOperator = (n) -> n+10;
System.out.println(unaryOperator.apply(10));
BinaryOperator<Integer>binaryOperator=(a,b)->a+10+b;
System.out.println(binaryOperator.apply(10,20));
}
}
T타입을 입력 받아서 아무값도 리턴하지 않는 함수 인터페이스를 의미
public class PracticeLamdba {
public static void main(String[] args) {
Consumer<String>consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("내 이름은 "+s +"입니다.");
}
};
consumer.accept("김무건");
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Consumer<String>consumer = (name) -> System.out.println("내 이름은"+name+"입니다.");
consumer.accept("김무건");
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "김무건";
}
};
System.out.println("내 이름은 "+supplier.get());
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Supplier<String> supplier = () -> "김무건";
System.out.println("내 이름은 "+supplier.get());
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Predicate<Integer>predicate = new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer%2==0;
}
};
System.out.println(predicate.test(10));
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Predicate<Integer>predicate = integer -> integer%2==0;
System.out.println(predicate.test(10));
}
}
만약에 다양한 조건이 있다고 생각하면 and or를 이용하면 된다.
예를 들어서 짝수이면서 값이 4인 결과를 받고 싶다고 조건을 만들면
이 두개의 조건을 and연산을 하면 되는데
public class PracticeLamdba {
public static void main(String[] args) {
Predicate<Integer>integerPredicate = integer -> integer%2==0;
Predicate<Integer>predicate = num ->num==4;
if(integerPredicate.and(predicate).test(4)){
System.out.println("참");
}else System.out.println("거짓");
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Predicate<Integer>integerPredicate = integer -> integer%2==0;
Predicate<Integer>predicate = num ->num==4;
if(integerPredicate.or(predicate).test(4)){
System.out.println("참");
}else System.out.println("거짓");
}
}
public class PracticeLamdba {
public static void main(String[] args) {
Predicate<Integer>integerPredicate = integer -> integer%2==0;
Predicate<Integer>predicate = num ->num==4;
System.out.println(predicate.negate().test(4));
}
}
Java8에서 final이 붙지 않은 변수의 값이 변경되지 않는다면, 그 변수를 Effectively final이라고 합니다.
람다에서는 사용할 수 있는 로컬 변수는 Effectively Final만 사용이 가능하다.
public class EffectivelyFinal {
public static void main(String[] args) {
new Pint().go();
}
}
class Pint {
void go() {
int baseAnInt = 111;
Function<Integer, Integer> function = (num) -> num + baseAnInt;
System.out.println(baseAnInt+"baseAnInt");
System.out.println(function.apply(10)+"function");
}
}
위에 코드를 보면 baseAnInt는 Effectively Final이다.
자바에서는 프로그램이 값이 변경하지 않는 수를 final로 추측을 할 수 있다.
여기서 람다에서 중요한 부분은 람다식에서 참조하는 로컬 변수의 값은 변경을 할 수 없다.
이 부분은 스코프의 내용을 이해하면 더 이해하기 쉽다. ==> 이 부분은 뒤에서 더 설명
현재 간단하게 설명을 하면 GO()메소드의 스코프와 람다의 스코프가 같다. 같은 스코프에서 동일한 변수를 지정할 수 없고 이러한 부분을 밑에 코드를 보면 알 수 있다.
스코프란
먼저 스코프란 변수를 사용할 수 있는 범위
baseAnInt를 선언하고 람다식에서 사용하니 이미 스코프에서 정의가 되어져 있다고 나온다.
이것을 통해서 람다는 go메소드랑 동일한 스코프를 가지고 있다고 이해할 수 있다.
람다식에서 사용되는 변수는 해당 변수를 의미하는 것이 아닌 복사본이다.
근본적인 이유는 생명 주기가 다르기 때문이다.
하지만 람다는 고차 함수여서 람다 자체를 인자로 받고 리턴을 할 수 있기 때문에 람다의 생명주기는 여전히 실행을 하고 있을 수 있다.
즉. 로컬 변수의 생명 주기가 끝나도 람다는 로컬 변수의 값을 복사하여 가지고 있어야 한다.
https://incheol-jung.gitbook.io/docs/study/kotlin-in-action/8
[8장 고차 함수: 파라미터와 반환 값으로 람다 사용 - Incheol's TECH BLOG
코틀린 표준 라이브러리의 컬렉션 함수는 대부분 람다를 인자로 받는다. filter와 map은 인라인 함수다. 따라서 그 두 함수의 본문은 인라이닝되며, 추가 객체나 클래스 생성은 없다. 하지만 이 코
incheol-jung.gitbook.io](https://incheol-jung.gitbook.io/docs/study/kotlin-in-action/8)
로컬 변수
람다의 쓰레드
https://wakestand.tistory.com/179
[자바 변수의 스코프가 뭔말?
면접 시 많이 물어보는 것이 변수의 스코프인데 스코프가 뭔 말인지 감이 안와서 어려울 수 있는데 막상 보면 단순하다 먼저 스코프란 변수를 사용할 수 있는 범위를 얘기하는데 {} 안에서 변수
wakestand.tistory.com](https://wakestand.tistory.com/179)
기본적인 스코프의 내용은 링크를 통해서 확인이 가능하다.
이 페이지에서 설명하는 스코프는 람다에서 람다 , 로컬 클래스 , 익명 클래스에 대한 스코프를 비교하고
람다에 대한 특징을 알아보기 위해 작성을 하고 있다.
class Pint {
private int baseAnInt = 111;
class LocalClass{
int baseAnInt = 1;
void localClass(){
System.out.println(baseAnInt);//1
}
}
Consumer<Integer>consumer = new Consumer<Integer>() {
int baseAnInt=2;
@Override
public void accept(Integer integer) {
System.out.println(baseAnInt);//2
}
};
void go() {
Function<Integer, Integer> function = (baseAnInt) -> baseAnInt + baseAnInt;//111
}
}
로컬 클래스와 , 익명 클래스는 각각 baseAnInt의 값을 변경을 할 수 있지만 람다식은 변경이 불가능 하다.
이것을 통해 위에 2개와 람다식은 서로 다른 스코프를 통해 동작을 한다는 것을 알 수 있다.
이것에 대한 자세한 내용은 밑에 블로그를 참고
https://tjdtls690.github.io/studycontents/java/2022-10-24-lambda_anonymous_local_class_difference/
[[자바, Java] 람다 (Lambda) - 쉐도잉 (Shadowing)
.
tjdtls690.github.io](https://tjdtls690.github.io/studycontents/java/2022-10-24-lambda_anonymous_local_class_difference/)