구현을 강제한다는건 java에서 implements로 상속받으면 해당 interface의 기능을 전부 사용해야 한다.
interface Hello {
void hi();
void bye();
}
class Hi implements Hello {
@Override
public void hi() { System.out.println("Hello World"); }
@Override
public void bye() { System.out.println("Bye World"); }
}
다형성을 제공한다는 것은 객체가 여러 형태로 존재할 수 있게 해준다는 것이다.
아래 코드와 같이 Floppy란 객체는 Dog면서도 Friend의 역할을 하는 것이다.
interface Friend {
void isFriend();
};
interface Dog {
void name();
};
class Floppy implments Friend, Dog {
@Override
public void isFriend() { System.out.println("Yes"); }
@Override
public void name() { System.out.println("Floppy"); }
}
interface Animal {}
interface Dog {}
interface Cat {}
Dog animal = Dog();
Cat animal = Cat();
-----------------------
Animal animal = Dog();
animal = Cat();
위 코드처럼 animal 필드가 구상체(Dog, Cat)과 결합하면 구상체에 의존할 수 밖에 없게 된다. 하지만 추상체(Animal)와 결합하면 추상체에 좀 더 다양한 형을 받을 수 있게 된다.
pblic Animal findAnimal(String name) {
if(name.equals("dog"){ return new Dog(); }
else { return new Cat(); }
}
Animal myFriend = findAnimal("dog");
여기서 Dependency Injection을 볼 수 있다. findAnimal 메서드는 외부에서 오는 값에 따라 다른 객체를 반환한다. 이를 의존성을 외부로 주입받는다고 하여 DI라 부른다.
자바 8부터는 인터페이스도 구현체를 가지게 된다. 메서드 앞에 default만 붙이면 끝이다.
interface test {
default void dm() { System.out.println("Hello World"); }
}
상속받는 객체들이 공통적으로 수행하는 것을 default method로 만들어 중복 구현을 하지 않아도 수행할 수 있도록 한다.
interface Sound {
default void hello() { System.out.println("Hello"); }
void laugh();
}
class Human implements sound{
@Override
public void laugh() { System.out.println("Hahaha"); }
}
class Dog implements sound{
@Override
public void laugh() { System.out.println("???"); }
}
Sound sound = new Human();
sound.hello();
sound = new Dog();
sound.hello();
위 코드와 같이 Sound 인터페이스에 있는 hello는 Dog, Human 두 객체 모두 같은 기능을 수행하기 때문에 default method로 만들었다.
@FunctionalInterface
interface test {
void test();
default void hello() { System.out.println("Hello"); }
static void bye() { System.out.println("Bye"); }
}
추상 메서드가 하나만 존재하는 인터페이스다. 위 코드처럼 static, default 메서드는 얼마가 있든 상관없다.
Test t = new Test {
@Override
public void test() { System.out.println("test"); }
};
t.hello();
위와 같이 익명클래스는 상속받은 클래스가 없이 인터페이스 생성자만으로도 구현할 수 있게 해준다.
Test t = () -> { System.out.println("test");
위에 있는 익명클래스 코드를 람다 표현식을 사용하면 1줄로 줄일 수 있다. 람다 표현식은 Funtional Interface에 구현해야 할 추상메서드가 하나 뿐인 점을 이용하여 익명 메서드를 만들어 바로 사용할 수 있게 해준다.
public sattic void filterNums(int m, Predicate<Integer> p, Consumer<Integer> c) {
for (int i = 0; i < max; i++) {
if (p.test(i)) c.accept(i);
}
}
filterNums(10, i -> i > 0, i -> System.out.println(i));
filterNums(10, i -> i > 0, System.out::println);
함수를 실행하는 두 코드는 전부 같은 기능을 수행한다. :: 이란 녀석이 붙으면 메소드 레퍼런스 표현 방식을 쓴 것. 이는 어짜피 i를 받아서 i로 출력하니깐 줄여서 쓰기 위해 사용한 것 처럼 보이지만 다른 이유도 있다 한다.
입력값을 변경하지 말라는 표시이기도 하다. 만약 인수인계받고 i를 받아 i를 출력하는 로직이 이상해보여서 i+1를 출력하는 로직으로 바꾼다면 원래 개발했던 의도와 다르게 작동할 수 있다. 이렇듯 개발자의 추가 의도 개입을 차단하는 역할도 한다.