- 자바는 람다식을 위한 함수형 인터페이스가 정의되어 있어야 함
- 필요할 때마다 정의해야 하므로 번거로운 단점이 있었다.
- 자주 사용하는 인터페이스가 java.util.function 패키지에 제공하고 있어서, 자료형으로 가져다 쓰면 된다.
- 곧 배울 스트림에서 유용하게 사용
정의되어 있는 함수형 인터페이스

적용 예
더보기
ThreadInterface funcThread = (firstTime, multiplyTime) -> {Thread.sleep( (long)((firstTime+checkRandom.nextDouble())*multiplyTime));};
를
BiConsumer<Integer,Integer> funcThread = (firstTime, multiplyTime) -> {
try {
sleep( (long)((firstTime+checkRandom.nextDouble())*multiplyTime));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
};
로 바꿔서 굳이 함수형 인터페이스를 생성하지 않고 동작하도록 BiConsumer를 써서 수정해보았다.
단점은 함수명이 accept라서.. 가독성을 생각하면 오히려 전에 함수형 인터페이스를 생성한게 나았을지도 모르겠다.
사용 예시
Runnable
public class Main {
public static void main(String[] args) {
/**
* 인자 반환값 둘다 없으므로 Runnable을 사용.
*/
Runnable dogButtonFunc = () -> System.out.println("멍멍");
Runnable catButtonFunc = () -> System.out.println("야옹");
Runnable cowButtonFunc = () -> System.out.println("음메");
dogButtonFunc.run();
catButtonFunc.run();
cowButtonFunc.run();
}
}
public class Button {
private String text;
public Button(String text) { this.text = text; }
public String getText() { return text; }
private Runnable onClickListener;
public void setOnClickListener(Runnable onClickListener) {
this.onClickListener = onClickListener;
}
public void onClick () {
onClickListener.run();
}
}
/**
* Button 클래스는 Runnable을 인자로 받는 onClickListener라는 필드가 있다.
* 그래서 Runnable인스턴스를 받아서 onClieck()을 실행.
*/
System.out.println("\n- - - - -\n");
Button dogButton = new Button("강아지");
dogButton.setOnClickListener(dogButtonFunc);
Button catButton = new Button("고양이");
/**
* Runnable 인스턴스 대신 람다식 익명객체로 내부를 정의한 모습
*/
catButton.setOnClickListener(() -> {
System.out.println("- - - - -");
System.out.println(catButton.getText() + " 울음소리: 야옹야옹");
System.out.println("- - - - -");
});
dogButton.onClick();
catButton.onClick();
- Button 클래스는 내부에 Runnabler 자료형을 갖는 변수가 존재하고, set으로 Runnable객체를 받아서 세팅할 수 있다. 그래서 setOnClickListener로 객체를 세팅하고 onClick()으로 불러낼 수 있었던 것.
- catButton은 람다식으로 인스턴스를 넘긴건데, 반환값과 인자가 없어서 Runnable객체로 들어갈 수 있었던 상황.
Supplier
/**
* 인자는 없고, 반환값만 있을때 사용하는 Supplier<반환자료형>
* 내부를 보면 당연히 return 반환값의 형태로 되어 있다.
*/
Supplier<String> appName = new Supplier<String>() {
@Override
public String get() {
return "얄코오피스";
}
};
Supplier<Double> rand0to10 = () -> Math.random() * 10;
Supplier<Boolean> randTF = () -> Math.random() > 0.5;
String supp1 = appName.get();
Double supp2 = rand0to10.get();
Boolean supp3 = randTF.get();
- 인자는 없고, 반환값만 존재하는 경우 사용.
- 내부에는 return으로 되어 있다.
- Supplier다음<>사이에 반환 자료형이 꼭 들어가야 한다는 점.
Consumer, BiConsumer
/**
* 인자만 존재하는 타입
* <인자타입> 필수
*
*/
System.out.println("\n- - - - -\n");
Consumer<Integer> sayOddEven = new Consumer<Integer>() {
@Override
public void accept(Integer i) {
System.out.printf(
"%d은 %c수입니다.%n", i, "짝홀".charAt(i % 2)
);
}
};
Consumer<Button> clickButton = b -> b.onClick();
BiConsumer<Button, Integer> clickButtonNTimes = (b, n) -> {
for (int i = 0; i < n; i++) { b.onClick(); }
};
sayOddEven.accept(3);
sayOddEven.accept(4);
clickButton.accept(catButton);
clickButtonNTimes.accept(dogButton, 5);
- 인자타입만 존재하는 경우 사용한다.
- 생략시에는 인자타입이 1개이므로 ()생략 가능하다.
- BiConsumer는 인자가 2개인 케이스. 본문에서는 두번째 인자를 반복문 횟수로 사용해서 onClick이 실행되도록 한 코드.
Function, BiFunction
/**
* function은 인자값 2개 받아서 1번째는 인자, 2번째는 반환타입
* BiFunction은 인자3개 받아서 2개 인자 1개 반환타입
*/
System.out.println("\n- - - - -\n");
Function<Integer, Boolean> isOdd = i -> i % 2 == 1;
Function<String, Button> getButton = s -> new Button(s);
/**
* Unit과 Horse 클래스를 인자로 받아서 set하고 int형 변수를 리턴
*/
BiFunction<Unit, Horse, Integer> getfullHP = (u, h) -> {
h.setRider(u);
return u.hp;
};
BiFunction<String, Runnable, Button> getButtonWFunc = (s, r) -> {
Button b = new Button(s);
b.setOnClickListener(r);
return b;
};
Boolean isOdd3 = isOdd.apply(3);
Boolean isOdd4 = isOdd.apply(4);
Button goatButton = getButton.apply("염소");
Integer unitFullHP = getfullHP.apply(
new MagicKnight(Side.RED), new Horse(80)
);
/**
* 1. 오리라는 이름의 Button 객체가 생성된다.
* 2. setOClicekListener로 꽥꽦을 print하는 Runnable 객체를 세팅한다.
* 3. onClick으로 세팅되어 있던 onClicekListener를 실행시키니, 꽥꽦이 출력된다.
*/
getButtonWFunc
.apply("오리", () -> System.out.println("꽥꽥"))
.onClick();
Predicate, BiPredicate
/**
* Predicate 인자 1개받아서 boolean값 반환
* Bi : 인자 2개받아서 boolean값 반환
*/
Predicate<Integer> isOddTester = i -> i % 2 == 1;//i받아서 홀수여부 반환
Predicate<String> isAllLowerCase = s -> s.equals(s.toLowerCase());//문자열 받아서 소문자들만 있는지 검사
//숫자와 문자열 값 일치 여부
BiPredicate<Character, Integer> areSameCharNum = (c, i) -> (int) c == i;
//같은 편인지 여부 반환
BiPredicate<Unit, Unit> areSameSide = (u1, u2) -> u1.getSide() == u2.getSide();
boolean isOddT3 = isOddTester.test(3);//홀수니까 true
boolean isOddT4 = isOddTester.test(4);//false
boolean isAL1 = isAllLowerCase.test("Hello");//false
boolean isAL2 = isAllLowerCase.test("world");//true
boolean areSameCN1 = areSameCharNum.test('A', 64);//false
boolean areSameCN2 = areSameCharNum.test('A', 65);//true
boolean areSameSide1 = areSameSide.test(
new Knight(Side.RED), new Knight(Side.BLUE)
);//다른팀이므로 false
boolean areSameSide2 = areSameSide.test(
new Swordman(Side.BLUE), new MagicKnight(Side.BLUE)
);//같은팀이라서 true
UnaryOperator
UnaryOperator<Integer> square = i -> i * i;
UnaryOperator<Swordman> respawn = s -> {
if (s instanceof MagicKnight) return new MagicKnight(s.getSide());
if (s instanceof Knight) return new Knight(s.getSide());
return new Swordman(s.getSide());
};
Integer squared = square.apply(3);
Swordman respawned1 = respawn.apply(new Knight(Side.BLUE));
Swordman respawned2 = respawn.apply(new MagicKnight(Side.RED));
BiUnaryOperator
/**
* 동일한 타입의 인자2개와 반환값 1개 반환하는 인터페이스
*/
BinaryOperator<Double> addTwo = new BinaryOperator<Double>() {
@Override
public Double apply(Double i, Double j) {
return i + j;
}
};
BinaryOperator<Swordman> getWinner = (s1, s2) -> {
while (s1.hp > 0 && s2.hp > 0) {
s1.defaultAttack(s2);
s2.defaultAttack(s1);
if (s1 instanceof MagicKnight) {
((MagicKnight) s1).lighteningAttack(new Swordman[] {s2});
}
if (s2 instanceof MagicKnight) {
((MagicKnight) s2).lighteningAttack(new Swordman[] {s1});
}
}
if (s1.hp > 0) return s1;
if (s2.hp > 0) return s2;
return null;
};
var added = addTwo.apply(12.34, 23.45);
//Double added = addTwo.apply(12.34, 23.45);
Swordman winner1 = getWinner.apply(new Swordman(Side.RED), new Knight(Side.BLUE));
Swordman winner2 = getWinner.apply(new MagicKnight(Side.RED), new Knight(Side.BLUE));
Swordman winner3 = getWinner.apply(new Swordman(Side.RED), new MagicKnight(Side.BLUE));
Swordman winner4 = getWinner.apply(new MagicKnight(Side.RED), new MagicKnight(Side.BLUE));
foreach에서 사용되는 람다식 Consumer
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++)
action.accept(elementAt(es, i));
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
System.out.println("\n- - - - -\n");
// 💡 Iterable 인터페이스의 forEach 메소드
// - 곧 배울 스트림의 forEach 와는 다름 (기능은 같음)
// - Consumer를 인자로 받아 실행
// - 이터레이터를 쓸 수 있는 클래스에서 사용 가능
new ArrayList<>(
Arrays.asList("하나", "둘", "셋", "넷", "댜섯")
).forEach(s -> System.out.println(s));
- foreach는 내부를 보면 인자로 Consumer 인터페이스를 받는다.(인자 1개만 받고 반환값 없는 인터페이스)
- 그러므로 foreach안의 람다식인 인자 1개를 받아서 print하는 동작을 인자개수만큼 반복하는 코드가 되는것이다.
Map의 foreach에서 사용되는 람다식 BiConsumer
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (Node<K,V> e : tab) {
for (; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
System.out.println("\n- - - - -\n");
HashMap<String, Character> subjectGradeHM = new HashMap<>();
subjectGradeHM.put("English", 'B');
subjectGradeHM.put("Math", 'C');
subjectGradeHM.put("Programming", 'A');
// 💡 BiConsumer를 받음
subjectGradeHM.forEach(
(s, g) -> System.out.println(
"%s : %c".formatted(s, g)
)
);
- 위의 케이스와 비슷하게 2개의 인자를 받아서 처리하는 BiConsumer를 인자로 받고 있어서, subjectGradeHM.foreach( (s,g) -> ... ) 람다식코드를 통해서 출력이 가능한 것.
- foreach에 람다식을 도입한 케이스.
출처
- 사이트, 검색명 (날짜)
'얄코의 제대로 파는 자바 > 섹션9 함수형 프로그래밍' 카테고리의 다른 글
스트림 문제 풀이 (0) | 2024.08.17 |
---|---|
스트림 연산 (0) | 2024.08.16 |
스트림 (0) | 2024.08.12 |
메서드 참조 (0) | 2024.08.11 |
람다식과 함수형 인터페이스 (0) | 2024.07.28 |