본문 바로가기

TIL

[TIL] 210804

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가 buyCardbuyCarsh에 의존하긴 하는데, 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