1. interface의 기능 2. default Method 3. Functional Interface 4. Lambda 표현식 |
1. Interface의 기능
(1) 구현을 강제
인터페이스 : 모든 메서드가 추상 메서드로 구성된 클래스
한 클래스에 여러개의 implements를 가질 수 있음
구현 부분이 없으므로 implement 후에 꼭 구현 부분을 구현해야 함
(2) 다형성을 제공
(3) 결합도를 낮추는 효과 (의존성을 역전)
//1.
Main m = new Main();
m.myRun();
m.yourRun();
//2.
MyRunnable mm = new Main();
mm.myRun();
//mm.yourRun();
//3.
YourRunnable mmm = new Main();
//mmm.myRun();
mmm.yourRun();
두 가지 모두 implements 했을 때 다양한 Type을 사용할 수 있음
Type에 따라서 사용할 수 있는 메서드에 제한이 생김
호스트 코드 : 함수 실행 부분
구현 코드 : 함수 구현 부분
- interface에 따라 달라지는 인스턴스를 호스트 코드에서 만들지 않고, 호스트 코드에서는 Type 지정만 한 후,
서브 클래스에게 new 인스턴스를 만드는 것을 맡기는 패턴을 factory 패턴이라고 한다.
- UserService에 매개변수를 전달한 후 인스턴스를 생성함으로써 의존성을 외부(실행 부분)에 맡길 수 있게 된다.
- 여기서 의존성이란 어떤 구현체 인스턴스를 생성할 것인지에 대한 권한을 외부에 의존한다는 의미
- 만약 Card, Cash 인터페이스에 따라 서브 클래스에서 객체를 따로따로 생성해서 필요에 따라 인스턴스를 생성하게 만든 경우는 결합도가 강한 것(구상체랑 결합하게 되니까)
public class UserService implements Buy {
private Buy buy;
public UserService(Buy buy) {
this.Buy = buy;
/*
UserService를 실행할 때 매개변수로 Type을 지정한 후 private 함수에서 인스턴스를 생성한다.
여기서 생성된 인스턴스의 buy()메서드를 실행하게되면 인스턴스의 형에 따라서 구현체 메서드드가 실행된다.
*/
}
@Override
public void buy() {
buy.buy();
}
}
- 추상체랑 결합하는 경우는 결합도가 낮아졌다고 할 수 있음(private Buy buy)
- 이렇게 외부에서 주입하는 경우를 의존성을 외부로부터 전달받았다고 함
- => 의존성을 주입받았다
==> 의존성 주입, Dependency Injection, DI
* 다시 정리!!
- UserService가 구상체인 buyCard, buyCarsh에 의존하고 있으면 강한 결합이다.
- 하지만 UserService가 buyCard나 buyCarsh에 의존하긴 하는데, Buy이라는 인터페이스를 통해 의존하는 것.
- buyCard, buyCarsh 인터페이스는 Buy 인터페이스에 의존하고 있다.(Buy의 buy() 메서드를 오버라이드 함)
- UserService는 Buy 인터페이스에 의존하고 있다.(UserService 또한 Login의 login() 메서드를 오버라이드 함)
- 즉, 원래대로라면 선형으로 UserService -> .. -> Card, Cash 로 이루어져야하는 Dependency가
UserService -> Login <- Kakao,Naver 이런 형태로 Inversion 되었다.
- 이전 강의에서 배운 5가지 원칙 중 DIP (의존 역전 부분) 이 이런 의미에 해당한다.
- 즉, 구상체에 의존하지 말고 추상체에 의존해서 의존성을 역전하라!!!
2. default Method
* Java 8 이상부터 기능 개선이 있었음
* 인터페이스가 구현체를 가질 수 있게 됨
* static 메서드를 가질 수 있게 됨 -> 함수 제공자가 되었음
- interface 클래스에서 static 메소드를 구현할 수 있음
default 메서드
- interface는 추상 메서드로만 이루어진 클래스였음
- 추상 메서드가 아니라는 이야기는 구현체가 들어간다는 의미
- interface의 추상 메서드 앞에 default를 붙이게 되면 구현부 작성 가능
- default 메서드는 override 해주지 않아도 일반 함수와 똑같이 사용이 가능하다.
- override 하는 경우에도 마찬가지로 default 함수가 아니라 override 한 함수가 실행됨.
즉, 구현하지 않아도 기본 기능이 이미 구현되어있어서 새로운 기능이 필요할 때만 구현하는 것이라 생각하면 됨
Adapter 역할 수행
- 인터페이스를 implements 해서 사용하면 쓰지 않는 method가 생긴다. 그럴 때 Adapter를 사용한다.
- Adapter 클래스를 생성하고 여기에 원하는 인터페이스를 implemets 한 후 메서드를 구현한다.
그리고 실제로 쓸 class에서 Adapter를 extends(상속)한다.
- 이런 방식으로 구성할 경우 인터페이스를 사용하면서 그 안에서 원하는 함수만 구현할 수 있다.
- but, 상속은 1개만 가능하기 때문에 이미 다른 Object를 상속을 받고 있다면 다른 객체를 상속받을 수 없다.
어쩔 수 없이 빈 method를 사용할 수밖에 없다.
- 다른 object를 상속받고 있어도 interface에서 원하는 method만 사용할 방법이 있을까?
--> defaul 메서드를 사용하면 된다!
이 경우에는 더 이상 Adapter는 필요 없어졌다.
인터페이스 추가만으로 기능을 확장 가능
- 기존의 방법 : 서로 다른 클래스에서 공통 기능의 method를 사용하는 경우 각각 구현해 줘야 함
- default 메서드 : 공통 기능은 default로 미리 구현할 수 있으므로 좀 더 깔끔해짐
- 기능을 추가하고 싶을 때 interface를 추가하는 것 만으로 기능을 확장할 수 있음.
(인터페이스마다 기능을 분리해 놨기 때문에)
* static 메서드를 가질 수 있게 되었습니다. : 함수 제공자가 되었습니다.
- interface 클래스에서 static 메소드를 구현할 수 있음
* 클래스 안에서 구현되어있는 클래스에 종속된 함수 : 메소드
3. Functional Inaterface
- 추상메서드가 하나만 존재하는 인터페이스 (함수형 인터페이스)
- default 나 static 메소드가 있어도 상관없다.
- @FunctionalInterface을 달아준다.
- FunctionalInterface에 있는 추상 메서드를 함수라고 부른다.
- 맨 밑에 있는 미리 정의된 함수형 인터페이스(FunctionalInterface)에서 추상 메서드를 함수라고 부른다.
@FunctionalInterface
interface MyMap {
//추상 메소드. 1개만 존재하므로 FunctionalInterface 애노테이션 추가
void map();
//default 메소드. implements 해서 수정 가능
default void sayHello(){
System.out.println("Hello World");
}
// static 메소드. 수정 불가능
static void sayBye(){
System.out.println("Bye World");
}
}
3.1. 인터페이스 임시 생성
- 익명 클래스를 사용해서 인터페이스의 인스턴스를 생성하고 구현 부분을 바로 정의한다.
클래스를 익명으로 바로 생성하고 사용할 수 있을까??
1. 익명이기 때문에 class 명을 따로 적지 않고 바로 인터페이스명을 통해 선언함
2. 내부에서 사용할 함수를 바로 오버 라이딩하여 정의
3. new로 class의 인스턴스를 바로 생성하고 사용한다.
//일반적인 클래스 정의 방법
class XXX implements MySupply {
@Override
public String supply(){
return "hi";
}
}
// 이 클래스를 **익명으로** **바로** 생성하고 사용할 수 있을까??
/*
1. 익명이기 때문에 class 명을 따로 적지 않고 바로 인터페이스명을 통해 선언함
2. 내부에서 사용할 함수를 바로 오버라이딩하여 정의
3. new 로 class의 인스턴스를 바로 생성하고 사용한다.
*/
//익명클래스
new MySupply() {
@Override
public String supply(){
return "hi";
}
}.supply();
MyRunnable r = new MySupply() {
@Override
public String supply(){
return "hi";
}
};
r.run();
4. Lambda 표현식
- 익명 메서드를 사용해서 간결한 인터페이스 인스턴스 생성 방법.
- FunctionalInterface에서 가능한 방법
- 간결하게 표현이 가능한다.
//기존의 익명 클래스 구현 방법
MyRunnable r1 = new MyRunnable() {
@Override
public void run(){
//핵심 구현 부분
System.out.println("hello world");
}
};
1. 핵심 구현 부분을 제외하고 나머지 부분의 코드는 예상할 수 있기 때문에 생략
2. FunctionalInterface에서만 사용하기 때문에 어떤 함수를 쓰는지 또한 예상할 수 있으므로 생략한다.
2. ()를 쓰고 구현 부분을 쓰기 전 -> 표시 필수
//익명 메서드
MyRunnable r2 = () -> { System.out.println("hello world"); };
// MyRunnable r2 = () -> System.out.println("hello world");
r2.run();
윗부분의 익명 클래스와 같은 기능을 수행한다.
4.1 메서드 레퍼런스
- 람다 표현식에서 입력되는 값을 변경 없이 바로 사용하는 경우
- 최종으로 적용될 메서드의 레퍼런스를 지정해 주는 표현 방식.
- 입력값을 변경하지 말라는 표현방식
- 개발자의 개입을 차단함으로써 안정성을 얻을 수 있다.
//메서드 래퍼런스 방식
Mymapper m = String::length;
//Mymapper m = (str) -> str.length(); //익명 메서드
MyConsumer c = System.out::println;
//MyConsumer c = (str) -> System.out.println(str); //익명 메서드
에시와 같이 input 매개변수를 변경하지 않고 바로 사용하기 때문에 수정하는 부분이 존재하지 않는다.
- 타입 T
interface MyClass<T> : 제네릭 인터페이스. T 자리에 변수의 Type을 매개변수로 입력함
@FunctionalInterface
public interface MySupplier<T>{
T supply();
//String supply();
}
@FunctionalInterface
public interface MyMapper<IN, OUT>{ //순서는 반드시 Input, Output으으로 해야됨
OUT map(IN s);
//int map(String s);
}
...
MySupplier<String> s = () -> "Hello World";
MyMapper<String,Integer> m = String::length; //primitive 타입은 generic에 사용될 수 없다.
** primitive 타입은 generic에 사용될 수 없다.
- primitive는 reference가 아니기 때문에 안됨!
이미 자바에서 만들어둔 인터페이스가 있다!
- 함수형 인터페이스
- (FunctionalInterface)
- Consumer<T> // void accept(T t);
- Function<T,R> //R apply(T s);
- BiFunction<T,U,R> //R apply(T t,U u); //input이 2개인 경우
- Supplier<T> //T get();
- Predicate<T> //boolean test(T t);
'TIL' 카테고리의 다른 글
[TIL] 210809 (0) | 2021.08.10 |
---|---|
[TIL] 210806 (0) | 2021.08.09 |
[TIL] 210805 (0) | 2021.08.06 |
[TIL] 210803 (0) | 2021.08.05 |
[TIL] 210802 (0) | 2021.08.04 |