본문 바로가기
얄코의 제대로 파는 자바/섹션9 함수형 프로그래밍

java.util.function 패키지

by 백엔드 개발자 2024. 8. 4.
  • 자바는 람다식을 위한 함수형 인터페이스가 정의되어 있어야 함
    • 필요할 때마다 정의해야 하므로 번거로운 단점이 있었다.
    • 자주 사용하는 인터페이스가 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