@FunctionalInterface
interface OperatorInterface{
int oper(int a, int b);
}
JVM메모리구조
class ,interface ,enum,array(reference type)같은 경우 heap영역에 저장되고 스택에는 해당 주소값이 저장됨
이 과정에서 람다 캡처링 같은 경우 지역변수를 람다가 가져와 사용하는 경우 상수 값으로 고정되는 이유이다. 스택에 쌓이기 때문에 해당 메소드가 실행된 후에는 모든 지역변수의 할당은 해제된다. 그럼에도 불구하고 람다는 더 이상 메모리에 존재하지 않는 지역변수의 값을 아무런 문제 없이 사용할 수있다. 그러한 이유는 원본 지역변수를 복제하여 사용하기 때문이다.
주의점: 위와 같은경우 지역변수는 스태틱 영역이기 때문에 복사해서 사용, 하지만 static으로 선언된 필드나 인스턴스 필드의 경우 람다 내부에 접근제약 존재x 힙 영역 혹은 메서드 영역에 저장되어 있기 때문.
제약 조건이 없다고 좋은것만은 아님
class LambdaCapturingTest {
private int instanceField = 100;
private int instanceField2 = 0; // 람다 내부에서 사용되는 인스턴스 필드의 값을 재할당하여도 컴파일 에러가 발생하지 않는다.
void lambdaCapturing_fieldDoesNotHaveToBeFinal() { Supplier r = () -> this.instanceField;
System.out.println(r.get()); // 100 this.instanceField = 200; // 컴파일 에러가 발생하지 않는다.
System.out.println(r.get()); // 200
}
// 람다 내부에서 인스턴스 필드의 값을 직접 재할당할 수 있다.
void lambdaCapturing_canChangeFieldValueFromInsideOfLambda() {
// 컴파일 에러가 발생하지 않는다.
Consumer changeFieldValue = (a) -> this.instanceField2 += a; // 1~100의 값을 순차적으로 instanceField2에 더하며 재할당
IntStream.rangeClosed(1, 100) .forEach(changeFieldValue::accept);
System.out.println(this.instanceField2); // 5050
}
}
병렬 스트림 내부에서 호출할 때(병렬 스트림은 대량의 데이터를 여러 그룹으로 쪼개어 병렬적 처리)
병렬 처리과정에서 데이터가 어떤 값으로 변하는지 정확히 파악할 수없다(디버깅하기 힘듦)
var a=10;
var funA=function(n){
alert(n);
}
코드의 간결성, 람다를 사용하면 코드를 줄일 수있기 때문에 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현 가능
지연연산 수행 가능
만약 강아지의 이미지를 찾아 저장하는 코드를 작성한다고 생각하자.
```java
public List<Dogimg> searchDog(List<Dogimg> images){
List<Dogimg> result=new ArrayList<>();
for(Dogimg dog : images){
if(dog.equals(dog))
result.add(dog)
}
return result;
}
```
이처럼 리스트등과 같은 방법을 통해 강아지 이미지를 저장하려 할것이다.
그렇다면 고양이라면?
``` java
public List<Catimg> searchCat(List<Catimg> images){
List<Catimg> result=new ArrayList<>();
for(Catimg cat : images){
if(cat.equals(cat))
result.add(cat)
}
return result;
}
```
고양이 이미지를 저장하는 메소드를 생성하게 될 것이다.
만약 고양이의 종류 예를들어 샴 고양이, 점박이 고양이 등 다양한 요구사항을 필요로 하게된다면?
우리는 매번 요구사항에 따라 새로운 메소드를 만들어내고 유지보수하기 힘들것이다.
## 이러한 문제를 해결하기 위한 방법 **추상화**
위의 코드 같은 경우 만약 새로운 요구사항을 원한다면 코드를 새로 생성하게 될것이다.
## 이것은 DRY(Do not Repeat Yourself) 법칙을 위배하게 된다!!
소스코드에서 동일한 코드가 반복이 된다는 것은 잠재적인 버그의 위협을 증가시킨다.
반복되는 코드 내용이 변경될 필요가 있는경우, 모든 코드에 찾아가서 수정해야한다.
이 과정에서 실수가 발생한다면 버그가 발생할 것이고 프로젝트 규모가 커질 수록 반복되는 코드로 인해 유지보수 오버헤드도 커진다. 그렇기 때문에 반복되는 코드를 줄이는 습관을 가져야한다.
## 동작 파라미터화 개념
- 아직 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미.
- 코드 블럭의 실행은 나중으로 미룸
- 결과적으로 코드 블럭에 따라 메서드의 동작이 파라미터화 한다.
- 메서드가 다양한 동작을 받아서 내부적으로 다양한 동작을 수행할 수있도록 하는 것
- 앞서 했던 행동들을 참, 거짓으로 반환하는 방법이있다(ex.고양이 몸무게가 50이상인가?, 강아지가 말티즈인가?)
- 참,거짓을 반환하는 함수를 Predicate라고한다
-
```java
public interface CatPredicate {
boolean test(Cat cat);
}
```
다음과 같은 방법을 통해 조건에 맞는 결과를 참, 거짓으로 판별할 수 있다.
```java
public class CatHeavyWeightPredicate implements CatPredicate {
@Override
public boolean test(Cat cat) {
return cat.weight() > 10;
}
}
```
이와 같은 방법을 이용하여 동작을 파라미터화 시켜, 메소드가 다양한 동작을 수행할 수 있게한다.
가독성과 유연성이 향상되고 속성에 관한 모든 변화에 대응이 가능하다.
**하지만 새로운 동작을 원할 때 마다 인터페이스를 구현해서 인스턴스화 해야하나?**
이것을 좀 더 편리하게 해주기 위한 것 바로 람다(익명 클래스) 이다.
밑에 하나의 예시를 통해 람다가 탄생한 마지막 방법을 설명하겟다.
``` java
List<Cat> ShamCats=filter(inventory,new CatPredicate()){
public boolean test(Cat a){
return Big.equals(a.getWeight());
}
}
```
⇒
``` java
List<Cat>result=Catfilter(inventory,(Cat cat)->Big.equals(cat.getWeight));
```
다음 처럼 람다로 한줄로 표현할수있다.
## 람다
- 익명함수를 단순화 시킨것