본문 바로가기
카테고리 없음

모던 자바 인 액션 스터디 1. 람다

by 손너잘 2021. 3. 4.

각자 챕터를 읽고 챕터에 맞는 문제를 제출, 스터디원은 문제를 푼다.

 

손너잘

3장 문제 나갑니다.손너잘은 람다를 공부하고 람다식의 매력에 푹 빠져버렸다. 결국 그는 악행을 저지르고 마는데, 특정한 수를 계산하는데 있어 람다식을 남발해 버리고 만 것이다. 그나마 다행인것은 자바 8에 추가된 함수형 인터페이스를 이용하여 뭔가를 만들어냈다.
이 코드를 자세히 살펴보던 갓니는 손너잘에게 역정을 내며 말했다.
"이렇게 성능이 개구진 코드를 짜면 어떡합니까? 당신 프로그래머 실격이에요"
다음은 손너잘이 짠 코드이다. MyNumber 클래스를 제외한 부분을 수정하여 프로그램의 성능을 최대한 끌어 올리고, 왜 수정된 부분에 의해 성능 하락이 발생했는지 서술하여라. (단, 람다식을 메소드로 분리하여 레거시 하게 리팩토링 하는 행위는 금지한다)추가)
아래 코드를 출제자의 시점에서 성능을 최대한 끌어 올렸을 때
기존 코드의 수행 시간 4081200ns
변경 코드의 수행 시간 3658200ns
이 걸렸다.
import java.util.function.*;
public class Main {
    static private class MyNumber {
        private int num;
        public MyNumber(int num) {
            this.num = num;
        }
        public int getNum() {
            return num;
        }
        public void plusNum() {
            num++;
        }
        public MyNumber sum(MyNumber yourNumber) {
            return new MyNumber(num + yourNumber.num);
        }
    }
    public static void main(String[] args) {
        UnaryOperator<Integer> unaryOperator1 = a -> {
            for (int i = 0; i < 100; i++) {
                a += 1;
            }
            return a;
        };
        UnaryOperator<MyNumber> unaryOperator2 = a -> {
            a.plusNum();
            return a;
        };
        BinaryOperator<MyNumber> binaryOperator = MyNumber::sum;
        Supplier<MyNumber> supplier1 = () -> new MyNumber(unaryOperator1.apply(3));
        Supplier<MyNumber> supplier2 = () -> new MyNumber(unaryOperator1.apply(5));
        MyNumber myNumber = new MyNumber(0);
        Function<Integer, Integer> function1 = a -> a + 1;
        for (int i = 0; i < 100; i++) {
            myNumber = myNumber.sum(binaryOperator.apply(supplier1.get(), supplier2.get()));
        }
        for (int i = 0; i < 100; i++) {
            unaryOperator2.apply(myNumber);
        }
        int sum = 0;
        for(int i=0; i<100; i++) {
            sum += function1.apply(myNumber.getNum());
        }
        System.out.println(sum);
    }
}

정답

더보기
// 생략    
    public static void main(String[] args) {
    	// 오토 박싱으로 인한 성능 저하
        IntUnaryOperator unaryOperator1 = a -> {
            for (int i = 0; i < 100; i++) {
                a += 1;
            }
            return a;
        };
        UnaryOperator<MyNumber> unaryOperator2 = a -> {
            a.plusNum();
            return a;
        };
        BinaryOperator<MyNumber> binaryOperator = MyNumber::sum;
        Supplier<MyNumber> supplier1 = () -> new MyNumber(unaryOperator1.applyAsInt(3));
        Supplier<MyNumber> supplier2 = () -> new MyNumber(unaryOperator1.applyAsInt(5));
        MyNumber myNumber = new MyNumber(0);
        //오토 박식으로 인한 성능 저하
        IntUnaryOperator function1 = a -> a + 1;
        for (int i = 0; i < 100; i++) {
            myNumber = myNumber.sum(binaryOperator.apply(supplier1.get(), supplier2.get()));
        }
        for (int i = 0; i < 100; i++) {
            unaryOperator2.apply(myNumber);
        }
        int sum = 0;
        for(int i=0; i<100; i++) {
            sum += function1.applyAsInt(myNumber.getNum());
        }
        System.out.println(sum);
    }

JDK 1.5부터는 오토 박싱, 언박싱이 이루어 진다. 기본적으로 기본형 타입은 박싱, 언박싱 하는 과정은 새로운 객체를 생성하는 과정이므로 성능에 크게 영향을 미친다.

 

 

 


파즈

ㅋㅋ 채워보든가 ㅋㅋ ㅋㅋ

 


나의 답

더보기
public class Main {

    static private class Fruit {
        private String name;

        public Fruit(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    public static void main(String[] args) {
        Function<String, Fruit> createFruitFunction = Fruit::new;
        Fruit orange = createFruitFunction.apply("orange");
        printNameIfOrange(orange, (Fruit f) -> f.getName().equals("orange"));
    }

    private static void printNameIfOrange(Fruit orange, Predicate<Fruit> predicate) {
        if(predicate.test(orange)) {
            System.out.println(orange.getName());
        }
    }
}

이 문제는 복수 정답이 가능하다. Predicate와 Function으로 모두 구현할 수 있기 때문이다. 하지만 기본 제공형 함수형 인터페이스를 제대로 사용하기 위해서는 Predicate를 사용하는것이 알맞을 것이다. 그렇다면 Function과 Predicate를 사용하는것에는 무슨 차이점이 존재할까?

기본적으로는 성능차이를 볼 수 있을것이다. 아래의 밴치마크를 돌려보았다.

public static void main(String[] args) {
    Predicate<Integer> predicate = i -> i == 0;
    Function<Integer, Boolean> function = i -> i == 0;

    List<Long> pre = new ArrayList<>();
    List<Long> func = new ArrayList<>();

    for (int d = 0; d < 100; d++) {
        long start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            Boolean apply = function.apply(i);
        }
        func.add(System.nanoTime() - start);

        start = System.nanoTime();
        for (int i = 0; i < 100000; i++) {
            boolean test = predicate.test(i);
        }
        pre.add(System.nanoTime() - start);
    }

    System.out.println(func.stream().mapToLong(Long::valueOf).sum() / 100);
    System.out.println(pre.stream().mapToLong(Long::valueOf).sum() / 100);

}

결과

620915
161240

 

당연히 predicate가 매우 빠르다. 박싱을 하지 않기 때문일 것 이다.

 

다른 이유로는 조합 메서드의 유무에서 차이가 날 것이다.

Predicate는 다른 Predicate와 조합할 수 있는 메서드를 존재한다. (and,  or, negate) 이러한 메서드는 Predicate의 반환형인 boolean을 더욱 잘 활용할 수 있도록 도와준다. 따라서 작성하는 메서드의 성격에 따라 적절한 함수형 인터페이스를 선택해야 할 것이다.

 

 

 


김김

제 헛짓거리 실화를 바탕으로 한 문제는요...작년 12월, 김김은 우아한 테크코스의 악명높은 3주차 프리코스 미션을 진행하며 허덕거리고 있었다. 갈수록 더러워지는 코드에 스스로 탐탁치 않았던 김김은 이윽고 메소드를 파라미터로 보내보고자 하였다. 하지만 함수형 인터페이스에 익숙하지 않았던 김김은 자바에서 기본적으로 제공하는 제네릭 함수형 인터페이스를 알지 못했고, 이윽고 함수형 인터페이스를 직접 만드는 쌩노가다를 자행, 별안간 헛수고를 했을 뿐더러 코드는 코드대로 더러워 졌던 것이다.
뒤늦게 java.util.function에 있는 함수형 인터페이스 아주 조금만 알아두면 어지간~하면 앵간 커버 가능하다는 사실을 안 김김은 내가 뭔 짓을 했나하고 현타가 왔다. 그래서 문제는,
Q. 빈칸의 번호에 자바에서 자체 제공하는 제네릭 함수형 인터페이스 이름을 알맞게 채워넣어봅시다.


나의 답

더보기
인풋 파라미터 타입 아웃풋 파라미터 타입 함수형 인터페이스
없음 T Supplier<T>
T 없음 Consumer<T>
T boolean Predicate<T>
T R Function<T, R>
T T UnaryOperater<T>

기본적인 형식을 물어보는 문제이다. Binary Functional Interface도 추가되었으면 좋았을 것 같다.


나봄

엄청난 학점 계산기를 만들던 중 if else 가 범벅인 기존의 코드가 너무 마음에 안 들었던 나봄!

String getGradeByScore(int score) {
    if(score >= 95) {
        return "A";
    }else if(score >= 90) {
        return "B";
    }else if(score >= 80) {
        return "C";
    }else if(score >= 70) {
        return "D";
    }else {
        return "F";
    }
}

곰곰이 생각하다 enum을 떠올리게 된다!

public enum Grade {
    A, B, C, D, F;
    public static Grade getGradeByScore(int score) {...}
}

위와 같은 조건 속에 getGradeByScore(int score) 를 호출하게 되면 Grade enum 을 return 하는 함수를 어떻게 구현할 수 있을까?
예 : 90을 입력하면 B라는 enum을 리턴해야한다.
힌트 : enum 생성자를 활용하자!


나의 답

더보기
public enum Grade {
    A(score -> score >= 95),
    B(score -> score >= 90),
    C(score -> score >= 80),
    D(score -> score >= 70),
    F(score -> score < 70);

    IntPredicate predicate;

    Grade(IntPredicate predicate) {
        this.predicate = predicate;
    }

    public static Grade getGradeByScore(int score) {
        return Arrays.stream(Grade.values())
                .filter(grade -> grade.predicate.test(score))
                .findAny().orElse(Grade.F);
    }
}

enum의 요소 순서가 정렬되어 있기 때문에 if - else문과 같은 형식으로 위와같은 조건을 제시할 수 있다.

만일 요소가 정렬되어있지 않았다면 조건에 하한바운드 ,상한바운드를 모두 제시해 줘야 했을 것이다.

또한 주의해야 할 점은, stream이 병렬이면 findAny는 위험하다는 것 이다. 물론 findFirst를 사용하면 되긴 한다.

 


웨지

1. 다음 인터페이스가 Functional Interface인지 아닌지 맞추고, 아니라면 그 이유에 대해 설명해주세요.
public interface myPredicate<T>(){
	test(T t);
        
	default boolean isNull(T t){
		return Objects.isNull(T);
	}
        
	default void print(T t){
		System.out.println(t);
	}
}

나의 답 1

더보기

함수형 인터페이스 이다. 함수형 인터페이스의 요건은 추상 메소드가 하나면 된다. 위 함수는 test(T t)를 추론할 수 있으며 추상 메서드가 하나 존재한다. Consumer와 같은 역할을 할 것이다. default메소드는 영향을 미치지 않는다.

소스에는 문제가 조금 있으나 정상이라는 가정하에 답하였다.

2.다음 문제 중 틀린 보기를 모두 찾아주세요!
 a. 람다 표현식은 인스턴스 이다.
 b. 원시타입으로 함수형 인터페이스를 활용하려면 랩핑 클래스를 활용해야만 한다.
 c. 같은 람다 표현식을 별개의 함수형 인터페이스에서 활용할 수 있다.
 d. 지역변수는 stack 메모리에, 람다 표현식은 heap 메모리에 저장되므로 람다표현식에선 지역변수를 활용할 수 없다.
 e. 람다 표현식의 파라미터 부분에서 언제나 타입을 생략할 수 있다.

 

나의 답

더보기

a. 람다 표현식을 인스터스라고 할 수 있을까? 람다 표현식은 람다를 나타내는 '식'일 뿐이다.

b. 기본형 인터페이스가 존재한다.

c. 맞다.

d. 람다 표현식이 구현하는 인터페이스의 구현 인스턴스가 heap에 존재하는게 더 알맞은 표현 아닐까?

e. 맞다. 자바에서 람다 표현식은 항상 대상 객체의 함수 디스크립터와 매칭되기 때문에 타입추론이 항상 가능하다.


검프

제목: 틈새 0,
리스트에  0이외의 숫자별로 정리를 해놓은 검프!
누군가 장난을 친다고 배열중 몇개의 인자를 0으로 바꿔놨다!이때 범인은 Predicate 인터페이스와 람다를 사용하여  0의 갯수를 찾아내면 그 수에 맞게 커피를 준다고한다!
뼈대는 준다. 이에맞게 찾아보자!


나의 답

더보기
public static void main(String[] args) {
    Predicate<Integer> integerFilter = i -> i == 0;
    List<Integer> rawIntegers = Arrays.asList(1, 0, 3, 0, 5, 0, 7, 8, 9);
    Integer count = findZero(rawIntegers, integerFilter);
    System.out.println("count = " + count);
}

public static <T> Integer findZero(List<T> list, Predicate<T> predicate) {
    return (int) list.stream().filter(predicate).count();
}

간단한 구현 문제.


중간곰

// 초기 코드
public class MemberManager {
    public static void main(String[] args) {
        final Names names = new Names();
        final List<String> inputNames = Arrays.asList("mediumBear", "kason", "bobi", "bobi", "bobi");
        names.addAll(inputNames);
        names.printAll(); // mediumBear kason bobi bobi bobi
    }
}
class Names {
    final List<String> names = new ArrayList<>();
    public void addAll(final List<String> values) {
        names.addAll(values);
    }
    public void printAll() {
        System.out.println("신입 교육생");
        names.forEach(System.out::println);
    }
}


나의 답

더보기

1. 간단한 구현 문제

public class MemberManager {
    public static void main(String[] args) {
        final Names names = new Names();
        final List<String> inputNames = Arrays.asList("mediumBear", "kason", "bobi", "bobi", "bobi");
        Predicate<String> bobiFilter = name -> !name.equals("bobi");
        names.addSatisfiedNames(inputNames, bobiFilter);
        names.printAll(); // mediumBear kason bobi bobi bobi
    }
}

class Names {
    final List<String> names = new ArrayList<>();

    public void addAll(final List<String> values) {
        names.addAll(values);
    }

    public void printAll() {
        System.out.println("신입 교육생");
        names.forEach(System.out::println);
    }

    public void addSatisfiedNames(List<String> inputNames, Predicate<String> predicate) {
        addAll(inputNames.stream()
                .filter(predicate)
                .collect(toList())
        );
    }
}

2. 간단한 구현 문제. 위 코드에서 main만 수정하였다.

    public static void main(String[] args) {
        final Names names = new Names();
        final List<String> inputNames = Arrays.asList("mediumBear", "kason", "bobi", "bobi", "bobi");
        Predicate<String> bobiKsonFilter = name -> !name.equals("bobi") && !name.equals("kason");
        names.addSatisfiedNames(inputNames, bobiKsonFilter);
        names.printAll(); // mediumBear kason bobi bobi bobi
    }

라이언

쉬운거 가염..
라이언은 코딩왕국의 동물의 왕이다. 라이언은 어느날 초콜릿을 먹고싶어 옆집 아저씨 찰리의 초콜렛 공장으로 놀러갔다. 라이언은 찰리의 초콜렛이 너무 맛있었다. 그래서 집에 오는길에 찰리에게 초콜렛을 받아왔다. 착한 라이언은 초콜렛을 부하들한테 나눠주려고 하는데, 동물들은 멍청해서 순서를 정해주지 않으면 싸움이 일어났다. 싸움이 일어나지 않기 위해 초콜릿 받는 순서를 정해주기러 하였다. 라이언은 지능순으로, 지능이 같다면 힘이 쌘 순서대로 초콜렛을 나눠주기러 결심하였다.
????????에 들어갈 코드를 작성하시오. (라이언은 읽기 싫은 코드는 안읽는다, 메서드 참조 단축 표현을 사용하여 가독성이 좋은 코드를 작성해주시오)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class AnimalsSort 
{
    public static void main(String[] args) 
    {
        ArrayList<Animal> animals = getAnimals();
        Comparator<Animal> compareByStrength = Comparator.?????????
        Collections.sort(animal, compareByStrength);
    }
    private static ArrayList<Animal> getAnimals() 
    {
        ArrayList<Employee> list = new ArrayList<>();
        list.add( new Animal("pobi", 9999, 1) );
        list.add( new Animal("brown", 7788, 4) );
        list.add( new Animal("cu", 7788, 5) );
        list.add( new Animal("middlebear", 326, 8) );
        list.add( new Animal("smallbear", 326, 42) );
        list.add( new Animal("bigbear", 326, 27) );
        list.add( new Animal("pikachu", 35, 999) );
        return list;
    }
}
public class Animal
{
    private String name;
    private int intelligenceRate;
    private int musclePower;
    public Animal(String name, int intelligenceRate, int musclePower){
      this.name = name;
      this.intelligenceRate = intelligenceRate;
      this.musclePower = musclePower;
    }
    public String getName(){
      return name;
    }
    public int getIntelligenceRate(){
      return intelligenceRate;
    }
    public int getMusclePower(){
      return musclePower;
    }
}

나의 답

더보기
Comparator<Animal> compareByStrength = Comparator
                .comparingInt(Animal::getIntelligenceRate)
                .thenComparing(Animal::getMusclePower)
                .reversed();

찰리

public static void main(String[] args) {
    makeRandomNumbers(() -> (int) (Math.random() * 100) + 1);
}
아래는 람다 표현식의 형식 확인 과정을 무작위로 나열한 것입니다.

a. makeRandomNumbers 메서드는 단일 인자의 파라미터로 ??? 형식을 기대합니다.
b. makeRandomNumbers 메서드의 호출을 확인합니다.
c. ㅁㅁㅁ 메서드는 int(또는 Integer) 를 반환하는 함수 디스크립터를 묘사합니다.
d. ???는 ㅁㅁㅁ라는 한개의 추상 메서드를 정의하는 함수형 인터페이스 입니다.

1. ??? 에 들어갈 함수형 인터페이스와 ㅁㅁㅁ에 들어갈 메서드를 맞춰주세요!
2. 위의 형식 확인 과정을 올바르게 정렬해주세요!

나의 답

더보기

1. ㅁㅁㅁ : Integer get() or int getAsInt(),  ???: Supplier<Integer> or IntSupplier

2. b-a-d-c

 


다니

안녕하세요 여러분~~~! 다들 주무시겠지만,, 마지막으로 퀴즈 제출합니다! 
Q.평화로운 우테코 동물원에는 갈색곰 중간곰, 기린 파즈, 사자 라이언이 지내고 있습니다. 
각 동물은 동일한 날짜에 동물원에 들어왔는데, 어느덧 머무른 지 1달이 되었습니다.
동물들은 동물원에서 매일매일 스트레스를 받는데, 동물마다 스트레스 받는 정도가 다릅니다.
다들 아시다시피 동물은 스트레스에 매우 취약한데요.
그래서 동물원에서는 매달 스트레스를 가장 많이 받은 동물을 잠시 야생으로 보냅니다.
새로운 달을 맞아 이번에도 야생에서 한 달 간 머무를 동물을 선택하게 됐습니다.
그런데 이런! 사육사가 지난 달에 너무 바빠서 어떤 동물이 가장 스트레스가 많은지 확인하지 못했는데요.
사육사는 여러분의 힘을 빌려 이 문제를 해결하고자 합니다!
사육사를 돕기 위해 @@@ 이곳의 코드를 작성해주세요!
public class Application {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("파즈", "중간곰", "라이언");
        List<Animal> animals = names.stream()
                @@@
                .collect(Collectors.toList());
        List<Integer> stress = animals.stream()
                @@@
                @@@;
        Map<String, Integer> animalsWithStress = new HashMap<>();
        for (int i = 0; i < names.size(); i++) {
            animalsWithStress.put(names.get(i), stress.get(i));
        }
        List<Map.Entry<String, Integer>> animalsWithStressGroup = new LinkedList<>(animalsWithStress.entrySet());
        animalsWithStressGroup.sort(Map.Entry.comparingByValue());
        Collections.reverse(animalsWithStressGroup);
        System.out.println("이번 달에 야생에서 머무를 수 있는 동물은!! " + animalsWithStressGroup.get(0).getKey());
    }
}
class Animal {
    private final String name;
    private final int stress;
    private final @@@ * 7304 / 3;
    private final @@@ * 4800 + 3;
    private final @@@ * 5302 / 2;
    public Animal(String name) {
        this.name = name;
        this.stress = 30;
    }
    public int stress() {
        if (name.equals("파즈")) {
            return this.giraffe.@@@(stress);
        }
        if (name.equals("중간곰")) {
            return this.bear.@@@(stress);
        }
        return this.lion.@@@(stress);
    }
}

나의 답

더보기
public class Application {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("파즈", "중간곰", "라이언");
        List<Animal> animals = names.stream()
                .map(Animal::new)
                .collect(Collectors.toList());
        List<Integer> stress = animals.stream()
                .map(Animal::stress)
                .collect(Collectors.toList());

        Map<String, Integer> animalsWithStress = new HashMap<>();
        for (int i = 0; i < names.size(); i++) {
            animalsWithStress.put(names.get(i), stress.get(i));
        }
        List<Map.Entry<String, Integer>> animalsWithStressGroup = new LinkedList<>(animalsWithStress.entrySet());
        animalsWithStressGroup.sort(Map.Entry.comparingByValue());
        Collections.reverse(animalsWithStressGroup);
        System.out.println("이번 달에 야생에서 머무를 수 있는 동물은!! " + animalsWithStressGroup.get(0).getKey());
    }
}
class Animal {
    private final String name;
    private final int stress;
    private final IntUnaryOperator giraffe = stress -> stress * 7304 / 3;
    private final IntUnaryOperator bear = stress -> stress * 4800 + 3;
    private final IntUnaryOperator lion = stress -> stress * 5302 / 2;
    public Animal(String name) {
        this.name = name;
        this.stress = 30;
    }
    public int stress() {
        if (name.equals("파즈")) {
            return this.giraffe.applyAsInt(stress);
        }
        if (name.equals("중간곰")) {
            return this.bear.applyAsInt(stress);
        }
        return this.lion.applyAsInt(stress);
    }
}

댓글