java 8 정리3

Java 정리 2021. 5. 17. 22:18

인프런 강의 6일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. 인터페이스 기본 메소드와 스태틱 메소드 2

 - Iterable의 기본 메소드

   -> forEach()

   -> spliterator()

 

 - Collection의 기본 메소드(Collection은 Iterable의 상속받는 클래스이니 사실상 Collection = Iterable과 같다)

   -> stream() / parallelStream()

   -> removeIf(Predicate)

   -> spliterator()

 

 - Comparator의 기본 메소드

   -> reversed()

   -> thenComparing()

   -> static reverseOrder() / naturalOrder()

   -> static nullsFirst() / nullsLast()

   -> static comparing()

 

 

package me.whiteship.java8to11;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

public class App {

    public static void main(String[] args) {
        /*GreetingInterface greetingInterface = new DefaultGreeting("park");
        greetingInterface.printName();              //park 출력
        greetingInterface.printNameUpperCase();     //PARK 출력

        GreetingInterface.printAnything();      //Interface의 static 메소드 바로 사용*/

        List<String> name = new ArrayList<>();
        name.add("park");
        name.add("dong");
        name.add("hyeon");
        name.add("kim");

        //case1. foreach lambda
        name.forEach(s -> {
            //System.out.println(s);
        });

        //case2. foreach method reference
        //name.forEach(System.out::println);

        Spliterator<String> spliterator = name.spliterator();
        //case3. spliterator 사용(name 배열을 순회하면서 출력한다)
        //tryAdvance = hasNext와 같은 역할
        //while(spliterator.tryAdvance(System.out::println));


        /* spliterator.trySplit()를 사용한 split 시 배열에 대해 trySplit을 진행한 뒤 tryAdvance를 수행해야 한다.
        즉 아래 코드는 오류가 발생한다.
        Spliterator<String> spliterator = name.spliterator();
        while(spliterator.tryAdvance(System.out::println));

        Spliterator<String> spliterator1 = spliterator.trySplit();  //name 배열에 대해 split 진행
        while(spliterator1.tryAdvance(System.out::println));
        --------------------------------------------------------------
        //정상 동작 쿼리
        Spliterator<String> spliterator = name.spliterator();
        Spliterator<String> spliterator1 = spliterator.trySplit();  //name 배열에 대해 split 진행
        while(spliterator.tryAdvance(System.out::println));
        while(spliterator1.tryAdvance(System.out::println));
        */

        //name 배열에 대해 UppserCase실행 후 filer를 걸어서 P로 시작하는 값만 읽어서 count
        name.stream().map(String::toUpperCase)
                    .filter(s -> s.startsWith("P"))
                    .count();

        //k로 시작하는 문자열 제거
        name.removeIf(s -> s.startsWith("k"));
        name.forEach(System.out::println);
        /*park
        dong
        hyeon*/

        //case1. 문자열 역순으로 소팅
        name.sort(String::compareToIgnoreCase);
        name.forEach(System.out::println);
        /*dong
        hyeon
        park
        */

        //case2. 문자열 역순으로 소팅(Comparator 사용)
        Comparator<String> compareToIgnoreCase = String::compareToIgnoreCase;
        name.sort(compareToIgnoreCase.reversed());
        name.forEach(System.out::println);
        /*park
        hyeon
        dong
        */

        //case3. 문자열 역순으로 소팅 reversd() 사용 후 thenComparing으로 추가 비교
        //name.sort(compareToIgnoreCase.reversed().thenComparing());
        name.forEach(System.out::println);


    }

}

 

 

2. 스트림 API

 - Stream : sequence of elements supporting sequential and parallel aggregate operations

   -> 데이터를 담고 있는 저장소(컬렉션)이다.

   -> Function in nature, 스트림이 처리하는 데이터 소스를 변경하지 않는다.

   -> 스트림으로 처리하는 데이터는 오직 한번만 처리한다.

   -> 무제한일 수도 있다. (Sort Circuit 메소드를 사용해서 제한할 수 있다.)

   -> 중개 오퍼레이션은 근본적으로 lazy하다.

   -> 손쉽게 병렬처리할 수 있다.

 

 - 스트림 파이프라인

   -> 0 또는 다수의 중개 오퍼레이션(intermediate operation)과 한개의 종료 오퍼레이션(terminal operation)으로 구성.

   -> 스트림의 데이터 소스는 오직 터미널 오퍼레이션을 실행할 때에만 처리한다.

 

 - 중개 오퍼레이션

   -> Stream을 리턴한다.

   -> Stateless / Statefull 오퍼레이션으로 더 상세하게 구분할 수도 있다. (대부분은 Stateless지만 distinct나 sorted처럼 이전 이전 소스 데이터를 참조해야 하는 오퍼레이션은 Statefull 오퍼레이션이다)

   -> filter, map, limit, skip, sorted, ...

 

 - 종료 오퍼레이션

   -> Stream을 리턴하지 않는다.

   -> collect, allMatch, count, forEach, min, max, ...

 

List<String> name = new ArrayList<>();
        name.add("park");
        name.add("dong");
        name.add("hyeon");
        name.add("kim");

        //Stream<String> stringStream
        name.stream().map((s) -> {
            System.out.println(s);      //map은 중개 오퍼레이션이기 때문에 터미널 오퍼레이션이 오기전까지 실행되지 않는다. 즉 해당 sout은 출력되지 않는다!
            return s.toUpperCase();
        }); //해당 상태는 종료 오퍼레이션 없이 끝난 스트림이다.(선언만 된 상태이다)

        List<String> collect = name.stream().map((s) -> {
            System.out.println(s);      //map은 중개 오퍼레이션이기 때문에 터미널 오퍼레이션이 오기전까지 실행되지 않는다. 즉 해당 sout은 출력되지 않는다!
            return s.toUpperCase();
        }).collect(Collectors.toList());//종료 오퍼레이션인 collect 추가했기 때문에 처리 진행

        //return이 upperCase이므로 sout 당시는 소문자가 출력되고, return값인 collect 변수 출력 시 대문자가 출력된다.
        name.forEach(System.out::println);      //대문자 출력

        //병렬 스트림
        List<String> collect1 = name.parallelStream().map((s)->{
            System.out.println(s + " " + Thread.currentThread().getName()); //ForkJoinPool을 통해 다른 thread에서 실행됨을 확인 가능
            return s.toUpperCase();
        }).collect(Collectors.toList());
        collect1.forEach(System.out::println);

 

 

package me.whiteship.java8to11;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class App {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(2, "spring data jpa", true));
        springClasses.add(new OnlineClass(3, "spring mvc", false));
        springClasses.add(new OnlineClass(4, "spring core", false));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        List<OnlineClass> javaClasses = new ArrayList<>();
        javaClasses.add(new OnlineClass(6, "The Java, Test", true));
        javaClasses.add(new OnlineClass(7, "The Java, Code manipulation", true));
        javaClasses.add(new OnlineClass(8, "The Java, 8 to 11", false));

        List<List<OnlineClass>> keesunEvents = new ArrayList<>();
        keesunEvents.add(springClasses);
        keesunEvents.add(javaClasses);

        System.out.println("spring 으로 시작하는 수업");
        springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .forEach(oc -> System.out.println(oc.getId()));

        System.out.println("close 되지 않은 수업");
        springClasses.stream()
                .filter(Predicate.not(OnlineClass::isClosed))//  filter(oc -> !oc.isClosed())와 같은 의미이다.
                .forEach(oc -> System.out.println(oc.getId()));

        System.out.println("수업 이름만 모아서 스트림 만들기");
        //map이라는 중개 오퍼레이션을 사용해야함
        springClasses.stream()
                .map(oc -> oc.getTitle())       //input은 onlineClass로 들어오나 output은 다른 type으로 변경가능
                //.forEach(s -> System.out.println(s));   //getTitle이 String이기 때문에 들어온 String 출력
                .forEach(System.out::println);          //method preference

        System.out.println("두 수업 목록에 들어있는 모든 수업 아이디 출력");
        //keesunEvents는 리스트 2개를 모아놓은 리스트이고, 이 2개를 합쳐서 1개의 스트림으로 만들어야한다. -> Flat 시킨다
        keesunEvents.stream()
                .flatMap(Collection::stream)        //리스트를 flat 시켜 1개의 스트림으로 만들음
                .forEach(oc -> System.out.println(oc.getId())); //input이 collection stream으로 들어감


        System.out.println("10부터 1씩 증가하는 무제한 스트림 중에서 앞에 10개 빼고 최대 10개 까지만");
        //Stream의 iterator를 가지고 구현 가능
        Stream.iterate(10, i -> i + 1)      //10부터 1씩 증가하는 무제한 스트림 (중개 오퍼레이션이기 때문에 iterate만 있어도 별다른 실행이 되지 않는다.
                .skip(10)
                .limit(10)
                .forEach(System.out::println);

        System.out.println("자바 수업 중에 Test가 들어있는 수업이 있는지 확인");
        //Stream의 Match로 구현 가능
        boolean test = javaClasses.stream().anyMatch(oc -> oc.getTitle().contains("Test"));
        System.out.println(test);

        System.out.println("스프링 수업 중에 제목에 spring이 들어간 제목만 모아서 List로 만들기");
        //case1. filter 먼저 수행 (filter에서 onlineClass로 비교 수행)
        List<String> spring = springClasses.stream()
                .filter(oc -> oc.getTitle().contains("spring"))  //filter을 먼저 해도 되고, map을 먼저 해도 됨. 다만 어떤 것을 먼저 하느냐에 따라 지나가는 타입이 달라지므로 구현에 주의!
                .map(OnlineClass::getTitle)
                .collect(Collectors.toList());

        //case2. map 먼저 수행 (filter에서 String 으로 비교 수행)
        List<String> spring1 = springClasses.stream()
                .map(OnlineClass::getTitle)
                .filter(t -> t.contains("spring"))
                .collect(Collectors.toList());
    }

}

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리4  (0) 2021.05.19
java 8 정리2  (0) 2021.05.13
java 8 정리1  (0) 2021.05.12

java 8 정리2

Java 정리 2021. 5. 13. 21:09

인프런 강의 5일차.

 - 더 자바, Java 8 (백기선 강사님)

 

1. 메소드 레퍼런스

 - 람다가 하는 일이 기존 메소드 또는 생성자를 호출하는 거라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현가능

 - 메소드 레퍼런스 참조 방법

스태틱 메소드 참조 타입::스태틱 메소드
특정 객체의 인스턴스 메소드 참조 객체 레퍼런스::인스턴스 메소드
임의 객체의 인스턴스 메소드 참조 타입::인스턴스 메소드
생성자 참조 타입::new

    -> 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다

    -> 리턴값 또는 생성한 객체는 람다의 리턴값이다.

package me.whiteship.java8to11;

import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class App {

    public static void main(String[] args) {
        UnaryOperator<String> hi = (s) -> "hi " + s;
        UnaryOperator<String> staticMethodHi = Greeting::hi;        //hi와 staticMethodHi는 동일한 결과를 같는다.

        Supplier<Greeting> newGreeting = Greeting::new;     //Supplier는 선언하더라도 아무런 일이 발생하지 않는다.
        //newGreeting.get() 해야 뭐라도 만들어짐.

        Greeting greeting = new Greeting();
        UnaryOperator<String> hello = greeting::hello;
        System.out.println(hello.apply("Park"));

        Function<String, Greeting> parkGreeting = Greeting::new;        //Supplier와 똑같은 Greeting::new 이지만 다른 생성자를 참조한다.
        Greeting park = parkGreeting.apply("park");
        System.out.println(park.getName());

        String[] names = {"park", "whiteship", "toby"};

        //case1. names를 정렬시킬 때 특정한 조건(compare에 구현된 조건)으로 정렬
        /*Arrays.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return 0;
            }
        }*/
        //case2. new Comparator를 람다로 치환
        Arrays.sort(names, (o1, o2) -> 0);

        //case3. case2를 메소드 레퍼런스로 가져다 사용
        Arrays.sort(names, String::compareToIgnoreCase);

        System.out.println(Arrays.toString(names));

    }

}

 

2. 인터페이스 기본 메소드와 스태틱 메소드

 - 기본 메소드(Default Method)

    -> 메소드 또는 생성자의 매개변수로 람다의 입력값을 받는다   

    -> 해당 인터페이스를 구현한 클래스를 깨트리지 않고 새 기능을 추가할 수 있다.

        * 컴파일 에러는 아니지만 구현체에 따라 런타임 에러가 발생할 수 있다.

        * 반드시 문서화 할 것. (@ImplSpec 자바독 태그 사용)

    -> Object class가 제공하는 기능 (equals, hasCode)는 기본 메소드로 제공할 수 없다.

        * 구현체가 재정의해야 한다.

    -> 본인이 수정할 수 있는 인터페이스에만 기본 메소드를 제공할 수 있다.

    -> 인터페이스를 상속받는 인터페이스에서 다시 추상 메소드로 변경할 수 있다.

    -> 인터페이스 구현체가 재정의 할 수도 있다.

 

 - 스태틱 메소드

    -> 해당 타입 관련 헬터 또는 유틸리티 메소드를 제공할 때 인터페이스에 스태틱 메소드를 제공할 수 있다.

 

package me.whiteship.java8to11;

import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class App {

    public static void main(String[] args) {
        GreetingInterface greetingInterface = new DefaultGreeting("park");
        greetingInterface.printName();              //park 출력
        greetingInterface.printNameUpperCase();     //PARK 출력

        GreetingInterface.printAnything();      //Interface의 static 메소드 바로 사용
    }

}


package me.whiteship.java8to11;

public interface GreetingInterface {

    void printName();

    //case1. 만약 printName()만 존재하는 인터페이스에 새로 추상 메소드를 추가할 경우 해당 인터페이스를 구현한 모든 클래스는 컴파일 에러가 발생한다(추가된 메소드를 구현하지 않았기 때문!)
    //void printNameUpperCase();

    //case2. 헌데 모든 클래스에 새로 추가되는 추상 메소드를 구현하지 않아도 컴파일 에러를 발생시키지 않는 방법은 Default 메소드로 선언하는 것이다.
    /*default void printNameUpperCase(){
        System.out.println("default Greeting");
    }*/

    /**
     * @implSpec
     * 이 구현체는 getName()으로 가져온 문자열을 대문자로 바꿔 출력한다.
     * 기본 메소드는 구현체가 모르게 추가된 기능이기 때문에 null로 온 경우 런타임 익셉션이 발생할 수 있다.(문서화 필수!)
     */
    //case3. 아래와 같은 형태로도 사용이 가능하다.
    default void printNameUpperCase(){
        System.out.println(getName().toUpperCase());
    }

    //static 메소드
    static void printAnything(){
        System.out.println("GreetingInterface");
    }

    String getName();
}


package me.whiteship.java8to11;

public interface Bar {
    //GreetingInterface가 제공하는 기본 구현체를 제공하고 싶지 않을 경우 Bar에서 해당 구현체를 다시 추상 메소드로 구현하면 된다 extends GreetingInterface
    //void printNameUpperCase() ;
    //아무것도 선언하지 않을 시 기본 오버라이딩
    //만약 Bar와 GreetingInterface에 같은 이름의 기본 구현체가 있고, 이를 implements하는 경우 에러가 발생한다.
    default void printNameUpperCase(){
        System.out.println("BAR");
    }
}


package me.whiteship.java8to11;

public class DefaultGreeting implements GreetingInterface, Bar{
    //Bar를 추가한 경우 두 인터페이스에 같이 구현된 기본 구현체인 printNameUpperCase 충돌이 발생한다.
    //충돌이 발생하는 경우 DefaultGreeting에서 직접 기본 구현체를 오버라이딩해서 구현해야 한다.

    String name;

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

    @Override
    public void printName() {
        System.out.println(this.name);
    }

    //GreetingInterface, Bar에 printNameUpperCase라는 기본 구현체가 있으므로 오버라이딩 해야 컴파일 오류가 발생하지 않음
    @Override
    public void printNameUpperCase() {

    }

    @Override
    public String getName() {
        return this.name;
    }
}

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리4  (0) 2021.05.19
java 8 정리3  (0) 2021.05.17
java 8 정리1  (0) 2021.05.12

java 8 정리1

Java 정리 2021. 5. 12. 21:43

인프런 강의 4일차. 

 - 더 자바, Java 8 (백기선 강사님)

 

1. 함수형 인터페이스

 - 추상 메소드를 딱 하나만 가지고 있는 인터페이스

 - SAM (Single Abstract Method) 인터페이스

 - @FunctionInterface 애노테이션을 가지고 있는 인터페이스

 

2. 람다 표현식

 - 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.

 - 코드를 줄일 수 있다.

 - 메소드 매개변수, 리턴타입, 변수로 만들어 사용할 수도 있다.

 

3. 자바에서 함수형 프로그래밍

 - 함수를 First Class Object로 사용할 수 있다.

 - 순수 함수(Pure Function) 

    -> 사이드 이펙트 만들 수 없다. (함수 밖에 있는 값을 변경하지 못한다)

    -> 상태가 없다. (함수 밖에 정의되어 있는)

 - 고차 함수(High-Order Function)

    -> 함수가 함수를 매개변수로 받을 수 있고, 함수를 리턴할 수도 있다.

 - 불변성

 

4. 단축키

 -> Alt + Enter : 익명 내부 클래스를 람다로 바꿔주는 단축키(IntelliJ)

package me.whiteship.java8to11;

@FunctionalInterface
public interface RunSomething {

    //추상 메소드가 1개만 있다면 함수형 인터페이스
    //다른 구현된 메소드가 여러개이더라도 추상화 메소드가 1개라면 함수형 인터페이스
    // @FuncationalInterface 추가 시 함수형 인터페이스로 취급되어 추상 메소드 1개만 선언이 가능하다.
    //void doIt();
    int doIt(int number);

    //static method 정의 가능
    static void printName(){
        System.out.println("dhpark");
    }

    //default method 정의 가능
    default void printAge(){
        System.out.println("32");
    }

}


package me.whiteship.java8to11;

public class Foo {

    public static void main(String[] args) {

        //익명 내부 클래스 anonymous inner class
        /*
        RunSomething runSomething = new RunSomething() {
            @Override
            public void doIt() {
                System.out.println("Hello");
            }
        };

        //Alt + Enter : 익명 내부 클래스를 람다 표현식으로 바꿔주는 자동완성 기능
        RunSomething runSomething = () -> System.out.println("Hello");
        runSomething.doIt();
         */
        /*
          함수형 인터페이스를 인라인으로 구현한 오브젝트라고 볼 수 있다.
          이러한 형태는 java가 객체지향 언어이기 때문에 아래 스텝으로 처리된다.
          1. System.out.println("Hello");를 runSomething 객체에 할당
          2. runSomething 객체를 메소드 파라미터에 전달
          3. doIt() 메소드 실행
          위와 같은 호출을 First Class Object로 사용한 것으로 본다.
         */


        /*
          순수 함수 : 입력받은 값이 동일한 경우 결과 값이 같아야 한다.(수학적인 의미)
          아래와 같은 경우 Pure하다 라고 볼 수 없다. (상태 값에 의존한다 라고 볼 수 있다)
           - Case1. 함수가 함수 바깥의 변수를 가져다 사용하는 경우
           - Case2. 외부의 값을 변경하려는 경우
         */
        //1. 순수 함수
        RunSomething runSomething1 = (number) -> {
            return number + 10;
        };
        System.out.println(runSomething1.doIt(1));

        //2. 순수 함수가 아닌 경우
        int baseNumber1 = 10;    //함수 바깥의 변수
        RunSomething runSomething2 = new RunSomething() {
            int baseNumber2 = 20;    //함수 바깥의 변수

            @Override
            public int doIt(int number) {
                baseNumber2++;  //Case2. 외부의 값을 변경하려는 경우
                return number + baseNumber1;    //Case1. 함수 바깥의 값을 가져다 사용하는 경우
            }
        };
    }
}

 

5. Java가 기본으로 제공하는 함수형 인터페이스

 - Java.lang.function 패키지

 - 자바에서 미리 정의해둔 자주 사용할만한 함수 인터페이스

 - Function<T, R>

 - BiFunction<T, U, R>

 - Consumer<T>

 - Supplier<T>

 - Predicate<T>

 - UnaryOperator<T>

 - BinaryOperator<T>

 

6. Function<T, R>

 - T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스

   -> R apply(T t)

 - 함수 조합용 메소드

   -> andThen

   -> compose

 

7. BiFunction<T, U, R>

 - Function<T, R>와 유사하나, 입력값을 2개 받는다.

 - 두 개의 값 (T, U)를 받아서 R 타입을 리턴하는 함수

    -> R apply(T t, U u)

 

8. Consumer<T>

 - T 타입을 받아서 아무것도 리턴하지 않는 함수 인터페이스

   -> void Accept(T t)

 - 함수 조합용 메소드

   -> andThen

 

9. Supplier<T>

 - T 타입의 값을 제공하는 함수 인터페이스

   -> T Get()

 

10. Predicate<T>

 - T 타입의 값을 받아서 boolean을 리턴하는 함수 인터페이스

   -> boolean test(T t)

 - 함수 조합용 메소드

   -> And

   -> Or

   -> Negate

 

11. UnaryOperator<T>

 - Function<T, R>의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스

 

12. BinaryOperator<T>

 - BiFunction<T, U, R>의 특수한 형태로, 동일한 타입의 입력값 두개를 받아 리턴하는 함수 인터페이스

 

package me.whiteship.java8to11;

import java.util.function.*;

public class Foo {

    public static void main(String[] args) {
        //1. 클래스를 만들어서 함수형 프로그래밍
        /*
        public class Plus10 implements Function<Integer, Integer> {
            @Override
            public Integer apply(Integer integer) {
                return integer + 10;
            }
        Plus10 plus10 = new Plus10();
        System.out.println(plus10.apply(1));
         */

        //2. 람다 형식으로 함수형 프로그래밍1 (apply)
        /*Function<Integer, Integer> plus10 = (i) -> i+10;
        System.out.println(plus10.apply(2));*/

        //2. 람다 형식으로 함수형 프로그래밍2 (compose, andThen)
        // Compose : 가지고 뒤에 오는 함수 적용하여 계산 후 src의 입력 값으로 사용
        Function<Integer, Integer> plus10 = (i) -> i+10;
        Function<Integer, Integer> multiply2 = (i) -> i * 2;

        //plus10.compose(multiply2);  //multiply2 적용 후 plus10 하겠다는 의미
        Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
        System.out.println(multiply2AndPlus10.apply(2)); // 10 + (2 * 2)

        // andThen : 앞의 값 계산 후 뒤의 입력 값으로 사용 (compose와 반대)
        Function<Integer, Integer> plus10AndMultiply2 = plus10.andThen(multiply2);
        System.out.println(plus10AndMultiply2.apply(2)); // (2 + 10) * 2


        Consumer<Integer> printT = (i) -> System.out.println(i);
        printT.accept(10);      //10 출력

        Supplier<Integer> get10 = () -> 15;
        System.out.println(get10.get());    //15 출력

        Predicate<String> startsWithPark = (s) -> s.startsWith("Park");
        //Predicate<Integer> isEven = (i) -> i%2 == 0;
        System.out.println(startsWithPark.test("Park"));


        // 같은 타입의 인풋, 아웃풋인 경우 UnaryOperator를 써서 좀 더 깔끔하게 처리 가능
        //Function<Integer, Integer> plus10 = (i) -> i+10;
        UnaryOperator<Integer> unaryPlus10 = (i) -> i+10;

        // BiFunction은 3개의 타입이 전부 다를 때 사용하고, BinaryOperator 는 3개의 타입이 전부 같을 때 사용한다.
    }
}

 

14. 람다 표현식

package me.whiteship.java8to11;

import java.util.function.*;

public class Foo {

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.run();
    }

    private void run() {
        final int  baseNumber = 10;

        // 로컬 클래스
        class LocalClass {
            void printBaseNumber(){
                int baseNumber = 11;    //섀도잉
                System.out.println(baseNumber); // 11 출력
            }
        }


        // 익명 클래스
        Consumer<Integer> integerConsumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer baseNumber) {        //baseNumber 변수는 파라미터로 넘어온 baseNumber를 사용하게 됨 = 섀도잉
                System.out.println(baseNumber); //익명 클래스에서 로컬 변수 참조하는 법
            }
        };

        // 람다
        IntConsumer printInt = (i) -> {
            System.out.println(i + baseNumber);
        };

        printInt.accept(30);

        /*
         3개 클래스 모두 공통적으로 로컬 변수를 참조할 수 있다.
         java8 부터 final을 생략할 수 있는 경우가 있는데 해당 변수가 사실상 final인 경우 생략이 가능하다.
         사실상 fianl 의미 : 변수 선언 후 값을 변경하는 로직이 없는 경우 (= effective fianl)

         람다가 다른 부분은 다른 2개 클래스와 다르게 섀도잉이 불가능하다.
          -> 로컬 클래스나 익명 클래스에서 선언한 변수들은 해당 클래스 바깥 혹은 Nested 클래스에서 같은 이름의 변수 사용이 가능하다(덮어써짐 = 섀도잉)
          즉 람다는 섀도잉이 안되니 람다의 스콥은 람다를 감싸고 있는 클래스와 동일하다
          그렇기 때문에 람다에서 참조 가능한 로컬 변수는 fianl과 effective final 만 사용이 가능하다.(아닐 시 컴파일 에러)

            IntConsumer printInt = (baseNumber) -> {        //람다는 섀도잉이 안되므로 파라미터 이름을 baseNumber로 넘겨주어도 사용이 불가능하다(컴파일 에러 발생)
                System.out.println(baseNumber);
            };
         */
    }
}

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리4  (0) 2021.05.19
java 8 정리3  (0) 2021.05.17
java 8 정리2  (0) 2021.05.13