Java 8 정리

함수형 인터페이스

  • 인터페이스에 메서드가 한개만 있는 상태

    • @FunctionalInterface사용
  • static메서드, default 메서드를 정의할 수 있다.

    • 구현체를 별도로 두지 않아도 됨.

    • 그렇지만 default 메서드도 구현부 에서 재정의가 가능하다.

      • default메서드가 정의 되어있는 인터페이스를 상속받는 인터페이스에서 default메서드 내용을 사용하고 싶지 않을 경우에는 상속 받는 인터페이스에서 구현된 default메서드의 내용을 지우고 선언만 해준다.
      • default메서드가 구현되어있는 인터페이스를 상속 받는 인터페이스가 있고 그 인터페이스에도 동일한 default메서드가 override되어 다른 형태로 구현되어 있을 때 두개의 인터페이스를 모두 implements하는 클래스는 어떤 default메서드가 적용될지 알 수 없기 때문에 오류가 발생하며, 오류가 발생하지 않게 하기위해서는 해당 default메서드 내용을 클래스에서 재작성 해주어야 한다.
    • 아래와 같이 상세 주석을 달아 메서드 사용자에게 사용법을 알려준다

      /**
      *      @implSpec
      *      이 구현체는.. 어쩌구..
      */
      
    • 그렇다면 이제 abstract는 사용할 필요가 없어진건가?

      • 실제로 spring내 많은 소스들이 abstract에서 interface의 default메서드로 전환 된것을 알 수 있다.(abstract클래스인 WebMvcConfigurerAdater는 deprecated되었다. spring 5.0부터는 WebMvcConfigurer를 사용하도록 가이드 된다.)
      • 그럼에도 abstract를 사용해야 하는 이유는 아래와 같다.
        • abstract에서는 멤버변수를 사용할 수 있다.
        • abstract에서는 private, protected등의 다양한 접근제어자를 사용할 수 있다.
        • 생성자로 초기화 작업이 필요할 경우 abstract를 사용해야 한다.

람다 표현식

  • 인터페이스 구현체를 직접 작성하는 익명 내부클래스 방식은 람다 표현식으로 아래와 같이 정의 가능하다

    //익명내부클래스
    Lambdainterface inter = new Lambdainterface() {
                @Override
                public void test(int n) {
                    System.out.println("test" + n);
                }
            };
      
    //람다 표현식
    Lambdainterface inter2 = n -> System.out.println("testtest" + n);
    
  • 내부 클래스에서 외부 변수를 참조하게 될 경우 기본적으로 final 처리가 된다.

    • 가급적 함수 밖에 있는 변수는 참조 하지 않도록 한다.
    int test = 0; //final 붙여주지 않아도 됨       
      
    Lambdainterface inter2 = n -> {
        System.out.println("testtest" + n);
        test++; //에러!
    };
    

java 기본 제공 FunctionalInterface

https://docs.oracle.com/javase/8/docs/api/index.html

  • Function

    //1번째 타입은 입력값, 2번재 타입은 리턴값정의
    Function<Integer, Integer> plus10 = (i) -> i + 10;
    Function<Integer, Integer> multi = (i) -> i * 2;
      
    //apply로 실행
    System.out.println(plus10.apply(1));
    System.out.println(multi.apply(2));
      
    //두개의 조합
    System.out.println(plus10.compose(multi).apply(3)); //compose안에 오는 multi를 먼저 수행하고 plus10을 수행한다.
    System.out.println(plus10.andThen(multi).apply(4)); //plus10 을 수행하고 multi를 수행한다.
      
    Function<Integer, Integer> chain = plus10.compose(multi);
    Function<Integer, Integer> chain2 = plus10.andThen(multi);
    System.out.println(chain.apply(5));
    System.out.println(chain2.apply(6));
    
  • BiFunction

    //1번째, 2번째 타입이 입력값, 3번재 타입이 리턴값
    BiFunction<Integer, Integer, String> biFunction = (i, j) -> i + "/" + j;
      
    System.out.println(biFunction.apply(1,2));
    
  • Consumer

    //1번재 타입이 입력값, 입력값을 받아 내부에서 뭔가 한다. 리턴값없음
    Consumer<Integer> printT = (i) -> System.out.println("print" + i);
    printT.accept(2);
    
  • Supplier

    //1번째 타입이 리턴값, 입력값없고, 리턴값만 있다.
    Supplier<Integer> get10 = () -> 10;
    System.out.println(get10.get());
    
  • Predicate

    //1번재 타입이 검사할 값. boolean값 리턴
    Predicate<String> isRight = (s) -> s.startsWith("test");
    Predicate<String> isRight2 = (s) -> s.startsWith("te");
    System.out.println(isRight.test("test"));
    //두개의 값을 and, or연산할 수 있다.
    System.out.println(isRight.and(isRight2).test("te"));
    System.out.println(isRight.or(isRight2).test("te"));
    
  • UnaryOperator

    //Function<Integer, Integer> plus10 = (i) -> i + 10; 과 같이 입력값과 리턴값이 같을때 사용. Function을 상속 받음
    UnaryOperator<Integer> plus10Unary = (i) -> i + 10;
    System.out.println(plus10Unary.apply(10));
    
  • BinaryOperator

    //BiFunction<Integer, Integer, Integer> biFunction = (i, j) -> i + j; 과 같이 모든 타입이 같을때 사용
    BinaryOperator<Integer> binaryOperator = (i, j) -> i + j;
    System.out.println(binaryOperator.apply(2,3));
    

변수 shadowing

  • 아래와 같이 로컬클래스, 익명내부클래스, 람다 표현식 에서 각각 변수 참조 방식이 다르다.

    int number = 10;
      
    //shadowing 됨. 별개의 영역
    class LocalClass{
        void method(){
            int number = 11; //허용됨.
            System.out.println(number); //11출력
        }
    }
    //shadowing 됨. 별개의 영역
    Lambdainterface inter3 = new Lambdainterface() {
        @Override
        public void test(int n) {
            int number = 11;//허용됨.
            System.out.println(number); //11출력
        }
    };
      
    //shadowing되지 않음
    Lambdainterface inter4 = n -> {
        int number = 11; //에러. 최상단의 number 와 같은 레벨임. 허용되지 않음
      
    };
    
  • 위 예제에서 최 상단에 정의된 number는 변경되지 않기 때문에 effective final로 처리 된다. 하지만 아래와 같이 구분상에 변경되는 부분이 있다면 해당 변수는 final 로 처리되지 않는다.

    int number = 10; //제일 아래에서 number의 값이 변경되지 않는다면 이 변수는 effective final임
      
    class LocalClass{
        void method(){        
            System.out.println(number); //에러
        }
    }
      
    Lambdainterface inter3 = new Lambdainterface() {
        @Override
        public void test(int n) {
            System.out.println(number); //에러
        }
    };
      
      
    Lambdainterface inter4 = n -> {
        System.out.println(number); //에러
      
    };
      
    number = 11; //변수를 변경했기 때문에 이 변수는 effective final이 아님
    

메서드 레퍼런스

  • FunctionalInterface에서 사용되는 parameter, return값이 일치하는 형태이면 메서드 레퍼런스를 사용할 수 있다.

  • parameter를 전달받고 무언가를 return 하는 메서드는 아래와 같이 사용가능하다.

    class Test{
        public String hello(String name){
            return "hello " + name;
        }
      
        public static String hi(String name){
            return "hello " + name;
        }
    }
      
    public class LambdaTest {
        public static void main(String[] args) {
              
            //람다 표현식
            UnaryOperator<String> lamb = name -> "hello" + name;
            System.out.println(lamb.apply("kkang"));
              
            //메서드 레퍼런스(static 메서드 사용)
            UnaryOperator<String> methodRef = Test::hi;
            System.out.println(methodRef.apply("kkang"));
      
            //메서드 레퍼런스(인스턴스 메서드 사용)
            Test test = new Test();
            UnaryOperator<String> methodRef2 = test::hello;
            System.out.println(methodRef2.apply("kkang"));
            ....
    
  • 생성자 메서드 레퍼런스

    class Test{
      
        public Test(){
      
        }
      
        public Test(String name){
      
        }
    }
      
    public class LambdaTest {
        public static void main(String[] args) {
      
            //인자가 없는 생성자
            Supplier<Test> supplier = Test::new;
            Test newTest = supplier.get();
      
            //인자가 있는 생성자
            Function<String, Test> function = Test::new;
            Test newTest2 = function.apply("kkang");
      
    
  • 임의 객체의 인스턴스 메서드 참조

    String[] arrays = {"test", "test3", "test2"};
    Arrays.sort(arrays, String::compareToIgnoreCase);
            //예전..
    //        Arrays.sort(arrays, new Comparator<String>() {
    //            @Override
    //            public int compare(String o1, String o2) {
    //                return 0;
    //            }
    //        });
    System.out.println(Arrays.toString(arrays));
    

기본 메서드

  • Iterable 의 기본메서드

    • forEach
    //인터페이스 메서드
    List<String> names = new ArrayList<>();
    names.add("test1");
    names.add("test3");
    names.add("test2");
      
    names.forEach(s-> System.out.println(s));
    //위 코드는 아래와 같이 변환
    names.forEach(System.out::println);
      
    
    • spliterator
      • stream api의 기본이 되는 요소이다. 각 배열들을 부분적으로 쪼개준다.
    List<String> names = new ArrayList<>();
    names.add("test1");
    names.add("test3");
    names.add("test2");
    names.add("test5");
      
    //리스트를 쪼갠다.
    Spliterator<String> spliterator = names.spliterator();
    //절반을 쪼갠다.
    Spliterator<String> spliterator2 = spliterator.trySplit();
    while(spliterator.tryAdvance(System.out::println));
    System.out.println("===================");
    while(spliterator2.tryAdvance(System.out::println));
    
  • Collection의 기본메서드

    • stream, parallelStream : 아래에서 자세히.
    • spliterator
    • removeIf
    List<String> names = new ArrayList<>();
    names.add("test1");
    names.add("test3");
    names.add("test2");
    names.add("test5");
      
    //조건에 맞는 요소 삭제
    names.removeIf(s -> s.startsWith("test1"));
    names.forEach(System.out::println);
    
  • Comparator

    • reversed
    • thenComparing
    • static reverseOrder
    • static naturalOrder
    • static nullsFirst
    • static nullsLast
    • static comparing

Optional

  • 값이 들어있을 수도, 없을 수도 있는 컨테이너

    private String test;
          
    public Optional<String> get(){
        return Optional.ofNullable("test");
    }
      
    public void test(){
        get().ifPresent(p -> test = p);
    }
    
  • 주의
    • 리턴값으로만 사용한다.(메소드의 매개변수 타입, 맵의 키, 인스턴스필드 타입으로 쓰지말자)
    • Optional을 리턴하는 메소드에서 null을 리턴하지말자(Optional 자체에 대해 null 체크를 또 해야 하니.. ifPresent같은 메서드를 사용하는것이 Optional의 이점인데 이를 활용하지 못한다.)
    • 프리미티브 값을 다룰때는 OptionalInt, OptionalLong등을 사용한다.
      • 일반 Optional을 사용할 경우 boxing, unboxing이 내부적으로 일어나게 되는데 이는 성능에 안좋은 영향을 끼친다.
    • Collection, Map, Stream Array, Optional은 Optional로 감싸지 말자
      • 이미 그 객체가 null체크에 대한 방법을 제공한다.
  • API
    • 생성(static method)
      • of : 값이 null이면 exception
      • ofNullable : null허용
      • empty : 빈 Optional (값이 없을때 null을 리턴하지 말고 emtpy를 리턴해라)
    • 값 확인
      • isPresent : 값이 있으면 Consumer를 통해 뭔가를 한다.
      • isEmpty(java 11 ~) : boolean리턴
    • 값 가져오기
      • get : 값을 가져온다.(없다면 exception발생)
        • get을 바로 쓰지말고 ifPresent를 사용해 값이 있을 경우에 대한 처리를 한다.
      • orElse(T) : null이면 새로운 클래스를 만들어(T) 리턴
      • orElseGet(Supplier) : null이면 새로운 클래스를 동적으로 만들어 리턴. lazy하게 생성
      • orElseThrow(Supplier) : null이면 exception throw. 바로 사용해도 되고 Supplier를 통해 던지고 싶은 exception을 직접 적어줘도 된다.
    • filter
      • filter : optional안에 담긴값을 가지고 filter를 건다. 리턴값은 Optional이다.
    • 값 변환
      • map : 변환된 값이 Optional에 담겨 리턴된다.
      • flatMap : 이중으로 Optional이 감싸여 있는 경우, 즉 Optional<Optional<String>>과 같은 형태일때 내부 String을 밖으로 꺼내어 주는 역할.

Annotation

  • 중복해서 적용할 수 있다.

    • @Repeatable(AnnotationTestContainer.class)
      • AnnotationTest 클래스보다 범위가 같거나 넓어야 한다.
    public @interface AnnotationTestContainer{
        AnnotaionTest[] value();
    }
      
    @AnnotaionTest(name="table")
    @AnnotaionTest(name="chair")
    @AnnotaionTest(type="date")
    public class App{
          
    }
      
    //사용
    //배열로 넘어온다.
    AnnotationTest[] test = App.class.getAnntotationsByType(AnnotationTest.class);
          
    //컨테이너를 사용하는 방법
    AnnotationTestContainer cont = App.class.getAnnotation(AnnotationTestContainer.class);
    Arrays.stream(cont.value()).forEach(c -> ......)
        
    
  • 타입 선언부에 사용할 수 있다.

    • @Target(ElementType.TYPE_USE) : TYPE_PARAMETER까지 포함한다.

    • @Target(ElementType.TYPE_PARAMETER)

    • 제너릭 타입, 변수타입, 매개변수 타입, 예외타입

      static class MMM<@AnnotationTest T>{
      	public NoteBook<@AnnotationTest String> get(@AnnotationTest String test) throws @AnnotationTest Exception  {
                  
          }  
      }