Java 8 정리
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을 직접 적어줘도 된다.
- get : 값을 가져온다.(없다면 exception발생)
- filter
- filter : optional안에 담긴값을 가지고 filter를 건다. 리턴값은 Optional이다.
- 값 변환
- map : 변환된 값이 Optional에 담겨 리턴된다.
- flatMap : 이중으로 Optional이 감싸여 있는 경우, 즉
Optional<Optional<String>>
과 같은 형태일때 내부 String을 밖으로 꺼내어 주는 역할.
- 생성(static method)
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 -> ......)
- @Repeatable(AnnotationTestContainer.class)
-
타입 선언부에 사용할 수 있다.
-
@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 { } }
-