람다식과 제너릭

고승원·2022년 10월 19일
0

TIL

목록 보기
2/24
post-thumbnail

1. Interface

인터페이스는 추상메서드와 default 메서드 그리고 static 메서드를 가질 수 있다.

Interface의 특성

  • 구현을 강제한다. 인터페이스에 있는 추상메서드의 구현을 강제한다.
  • 다형성을 제공한다. 인터페이스를 통해 여러 type으로 정의 가능.
  • 결합도를 낮춘다.(DI) 클래스 내에서 직접 정하는 것이 아닌 인터페이스를 통해 의존성을 역전시킨다. 따라서 의존 관계의 화살표의 방향이 역전된다.

2. Default Method

  • java8부터 사용 가능

  • interface에 구현 메서드 작성 가능

  • override없이 사용 가능하지만 override도 가능

  • adapter 역할을 수행 가능. ?????

  • 인터페이스 추가만으로 기능을 확장할 수 있다.

//Interface
interface Flyable {
   default void fly() {
       System.out.println("FLY");
    }
}

 interface Swimmable {
     default void swim() {
         System.out.println("SWIM");
     }
 }

 interface Walkable{
     default void walk() {
         System.out.println("WALK");
     }
 }

//Class
class Swan implements Flyable, Walkable, Swimmable{ } //구현을 안해줘도 된다.
											//implements로 기능 추가

public class Main {
    public static void main(String[] args) {
        new Swan().fly();
        new Swan().walk();
        new Swan().swim(); //implements된 기능들
		}
}

static method를 가질 수 있음. → 인터페이스를 통해 메서드 사용 가능 → 함수 제공자가 된다.

//Interface
public interface Ability{
    static void sayHello() {
        System.out.println("Hello World!");
    }
}

//Class
public static void main(String[] args) {
        Ability.sayHello();
    }

3. Functional Interface

  • 추상메서드가 하나만 존재하는 인터페이스
    • default, static 메서드는 count하지 않는다.
  • @Functional Interface를 준다
  • Functional Interface에 있는 추상 메서드는 함수라고 부른다.

3-1. 인터페이스 임시 생성

  • 익명 클래스를 사용해서 인터페이스의 인스턴스를 생성하고 구현을 바로 정의
//Interface
@FunctionalInterface
public interface MySupply {
    String supply();
}

//익명 클래스
new MySupply(){
            @Override
            public String supply() {
                return "Hello World";
            }
        }.supply();

익명 클래스는 왜! 사용하는가

  • 익명 클래스(익명 객체)는 말 그대로 이름이 없는 클래스이다.
  • 이름이 없다 → 나중에 부를 일이 없다.
  • 따라서 일시적으로 한 번 사용하고 버리는 객체를 뜻한다.
  • 주로 안드로이드에서 UI, thread 작업할 때 사용한다.
  • 익명 객체는 컴파일시에 [클래스명$시퀀스.class]로 저장된다.

4. Lamda 표현식

하나의 메서드만 전달하면 좋을텐데 객체를 생성해서 override까지 해야하다니 귀찮다!

  • 익명 메서드(람다)를 사용해서 간결한 인터페이스 인스턴스 생성 방법.
  • Functional Interface에서만 사용 가능 → 왜? 추상메서드가 여러개면 생략 못함
  • 간결한 표현 가능
//익명 클래스
new MySupply(){
            @Override
            public String supply() {
                return "Hello World";
            }
        }.supply();

//Lamda
((MySupply) () -> "Hello World").supply();

4-1. 메서드 레퍼런스

  • 람다 표현식에서 입력값을 바로 사용하는 경우
  • 최종으로 적용될 메서드의 레퍼런스를 지정해 주는 표현 방식
  • ex) 사용클래스::사용메서드, String::length;, System.out::println;
//Interface
@FunctionalInterface
interface MyNumber{
    void number(int i);
}

//일반 표현식
MyNumber n = (i) -> System.out.println(i);

//메서드 레퍼런스
MyNumber n = System.out::println; //더 간결한 코드
  • 입력 값을 변경하지 말라는 표현방식이기도 하다
  • 개발자의 개입을 차단함으로써 안정성++

5. 제너릭

특정 타입을 미리 지정하지 않고, 필요에 의해 지정할 수 있도록 Generic 타입으로 지정하여
클래스 내부에서 타입을 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다.

제너릭은 reference타입만 사용 가능하다. primitive 불가능

장점

  • 잘못된 타입이 들어오는 것을 컴파일 단계에서 방지할 수 있다.
  • 클래스 외부에서 타입을 지정해주기 때문에 관리하기가 편하다.
  • 코드의 재사용성이 높아진다.

사용법

타입설명
<T>Type
<E>Element
<K>Key
<V>Value
<N>Number

위 표는 제너릭 타입의 통상적인 룰이다. 물론 위 표대로 사용하지 않아도 된다.

@FunctionalInterface
public interface MyMapper<IN, OUT> {
    OUT map(IN in);
}

사용법

  1. 클래스 및 인터페이스
class ClassName <T> { }
interface InterfaceName <T> { }

위와같이 이름 다음으로 선언하며 제너릭 타입은 해당 블럭안에서만 유효하다.

1-1. 제너릭 클래스

class ClassName <T> { 
	private T variable;  //제너릭 타입 변수
	
	void set(T variable) {  //제너릭 파라미터 메서드
		this.variable = variable;
	}
	T get() {    //제너릭 타입 반환
		return variable;
	}
}

public class Main{
	public static void main(String[] args){
		ClassName<String> s = new ClassName<String>();
		ClassName<Integer> i = new ClassName<Integer>();

		s.set("10");
		i.set(10);
	}
}

위 코드를 실행하면 타입변환이 잘 되어 출력된다

  1. 제너릭 메서드

클래스에서 지정한 제너릭 유형과 별도로 메서드에서 독립적인 제너릭 유형을 선언하여 사용할 수 있다.

class ClassName <E> {
	<T> T method(T t) {
        return t;
    }
}

public class Main{
	public static void main(String[] args){
		ClassName<Integer> i = new ClassName<Integer>();
			
		System.out.println("i.method(3).getClass().getName() = " + i.method(3).getClass().getName());
    System.out.println("i.method(\"3\").getClass().getName() = " + i.method("3").getClass().getName());
	}
}

원리는 같다.

그럼 static 메서드는 프로그램 실행시에 메모리에 적재되는데 어떻게 타입을 얻어올까?

→ 클래스와 static 메서드가 같은 제너릭을 사용하면 클래스 객체가 생성되기전에는 제너릭이 지정되지 않는다.

타입 제한

제너릭에도 타입을 제한할 수 있다.

<IN extends Child> : Child와 Child의 자손들만 IN에 들어올 수 있다.

<IN super Parent> : Parent와 Parent의 부모들만 IN에 들어올 수 있다.

IN을 생략하고 ?를 쓴다면 본인이 기준이 된다.

<? extends IN> : IN과 IN의 자손만 들어올 수 있다.

<? super IN> : IN과 IN의 부모만 들어올 수 있다.

참고

https://st-lab.tistory.com/153#recentComments

정리

인터페이스에는 추상메서드, default메서드, static메서드를 넣을 수 있다.

추상메서드가 하나인 인터페이스를 FunctionalInterface라고 한다.

FunctionalInterface는 람다식으로 객체 선언 없이 메서드만 가져와서 사용이 가능하다.

람다식에서 입력값을 그대로 사용하게 된다면 메서드 레퍼런스를 사용해 더욱 코드를 짧게할 수 있다.

익명 객체/메서드 클래스 또는 메서드를 여러번 한 번만 사용할 경우 클래스/메서드의 이름을 지정하지 않고 구현만해 단발성으로 사용한다.

익명 객체/메서드는 호출할 수 없다.

주로 안드로이드에서 쓰이며 UI, thread 작업에 사용한다.

제너릭은 특정 타입으로 지정하지 않고 외부 또는 사용자가 지정해서 사용할 수 있도록 도와준다.

제너릭은 reference타입만 지정이 가능하다.

profile
봄은 영어로 스프링

0개의 댓글