[WhiteShip][8주차] 인터페이스
WhiteShip Java Live Study 의 커리큘럼을 따라 개인적으로 공부한 내용입니다.
Codestates BE 42 : Study, Commercise 01 Java
Thanks to
" Codestates_SEB_BE_42 : Commercise "
강지은, 전진우
22.11.09
8주차 : 인터페이스
(코드스테이츠 백엔드 부트캠프 - TIL - 14일차에 존재하는 내용은 추가하지 않았습니다.)
- 인터페이스 정의하는 방법
- 인터페이스 구현하는 방법
목차
- 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
- 인터페이스 상속
- 인터페이스의 메소드
- 인터페이스의 기본 메소드 (Default Method), 자바 8
- 인터페이스의 static 메소드, 자바 8
- 인터페이스의 private 메소드, 자바 9
인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
✅ 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
인터페이스 레퍼런스를 통해 구현체를 사용한다는 의미
interface Player {
public void introduction(String name);
}
public class MyPlayer implements Player {
@Override
public void introduction(String name) {
System.out.println("Hello my name is "+name);
}
}
public class DevAndy {
MyPlayer player = new MyPlayer();
player.introduction("Andy");
Player player02 = player;
player02.introduction("Steve Jobs");
}
Hello my name is Andy
Hello my name is Steve Jobs
인터페이스인 Player의 인스턴스 player02를 선언하면서 참조변수로 인터페이스 구현체인 MyPlayer의 인스턴스를 참조했다.
즉 인터페이스 레퍼런스를 통한 구현체 (player02)는
인터페이스 구현클래스(MyPlayer : player)로 타입 변환이 가능하다는 뜻인듯.
⏺ 인터페이스 상속을 통한 상,하위 인터페이스 존재시
→ 하위 인터페이스로 타입 변환하면 상,하위 구현 메소드 모두 사용 가능
→ 상위 인터페이스로 타입이 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능
또다른 예제
인터페이스 타입으로 객체를 생성할 수 있다.
해당 객체에 구현 클래스로 인스턴스화 할 수 없다.
study는 인터페이스타입이고, 객체 study를 생성했지만, 인스턴스화 된것은 아니기 때문에
say() 구현클래스 메서드 사용 불가한 듯
인터페이스 상속
✅ 인터페이스 상속
인터페이스 사이에서도 상속을 쓸 수 있다.
- extends 키워드 사용
- 하위 인터페이스는, 상위 인터페이스의 메서드를 모두 구현해야한다.
- 인터페이스는 다중 상속이 가능하다
- 구현 코드의 상속이 아니기 때문에 타입 상속이라고 한다.
public interface 하위인터페이스 extends 상위인터페이스1{
}
하위 인터페이스를 구현하는 클래스는
하위인터페이스의 메소드 ~ 상위 인터페이스까지의 모든 추상 메소드에 대해
실제 메소드를 갖고 있어야 한다.
→ 구현 클래스로부터 객체 생성 후 다음과 같이 하위/상위 인터페이스 타입으로 변환이 가능함.
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
✔ 빠른 이해 돕기
(인터페이스 상속에 대하여)
stack overflow 중...
"A를 구현하지 않고 B를 구현하지 않으려면 B가 A를 확장하도록 만들 수 있습니다. "
개가 아니어도 생명체가 되는 것은 가능하지만, 생명체가 되지 않고 개가 되는것은 불가능함
-- 짖을 수 있다면, 먹을수도 있지만, 먹을수 있다고 짖을 수 있는것은 아님
위의 예시가 너무 좋은 설명이라서 가져와봤다.
그리고 위의 예시를 보면, 인터페이스 상속에 대해 다음 3가지를 생각해볼 수 있다.
1) interface는 형용사적(사물의 형태나 성질)인 의미를 갖고있는 것임을 기억하자.
즉 성질/특징으로 묶어줄 때 사용하고 구현체가 아니다.
추상적인 특징을 이야기하는 메서드만 존재할 뿐이다.
2) 하위 인터페이스 : A, 상위인터페이스 : B 라고 했을 때
인터페이스 상속은 A가 되면 당연히 B도 된다는 의미
3) 객체의 상속에서도 IS-A 관계는 성립했다. 이건 명제에 있어 필요조건과 충분 조건의 관계로 나타낼 수 있다.
A → B가 참(True)일 때, 명제 B가 참이 되기 위해 명제 A가 참일 필요가 있음
즉 A는 B가 성립하기 위한 필요조건이다.
대우 true ?
~B → ~A : True
하위 인터페이스를 필요조건 A , 상위 인터페이스를 충분조건 B 에 대입하면 된다.
이제 슬슬 이해가 될 것 같다.
직접 인터페이스 상속을 사용하는 예를 생각해보자.
((under water))
<물에서 살지 않는> <물에서 사는> 두 성질이 있고,
<발로 걸어다니는> <날개로 하늘을 나는> <헤엄치는> 세개의 성질이 있다고 가정하자
<발로 걸어다니는>특징을 가졌다면, <물에서 살지 않는>게 자연스럽다. ⭕
<날개로 하늘을 나는>특징을 가졌다면, <물에서 살지 않는>게 자연스럽다. ⭕
그러나 <물에서 살지 않는>다고 해서 무조건 <날개로 하늘을 나는> 것은 아니다. ❌
이걸 interface로 구현해보면,public class interface_extends{ public static void main(String args[]){ Person p = new Person("jieun",24); System.out.println(p.name); System.out.println(p.age); System.out.println(p.getClass()+ "는 "+ p.where()+"에 삽니다."); System.out.println(p.getClass()+"의 다리 개수는 : "+p.getNumberOfLeg()); } } interface LiveInWater{ String where(); } interface NotLiveInWater{ String where(); } interface Swim extends LiveInWater{ boolean isMammalia(); } interface Walk extends NotLiveInWater{ int getNumberOfLeg(); } interface Fly extends NotLiveInWater{ int getSpeed(); } class Person implements Walk{ String name; int age; Person(String name, int age){ this.name = name; this.age = age; } @Override public String where(){ return "Land"; } @Override public int getNumberOfLeg(){ return 2; } }
만약 하위인터페이스나 상위 인터페이스의 추상메서드가 구현이 한개라도 안되면 다음과 같은 에러가 난다.
compiler.java:8: error: cannot find symbol
System.out.println(p.getClass()+ "? "+ p.where()+"? ???.");
^
symbol: method where()
location: variable p of type Person
compiler.java:32: error: Person is not abstract and does not override abstract method where() in NotLiveInWater
class Person implements Walk{
^
2 errors
// Person은 추상이 아니며 NotLiveInWater의 추상 메서드 where()를 재정의하지 않습니다.
⏺ 왜 인터페이스가 인터페이스를 상속하는 구조를 사용할 까? (WHY)
https://stackoverflow.com/questions/13437131/java-why-interface-extends-interface
Q. 둘의 차이는 무엇일까? 실제로 코드상에선 아무런 차이가 없어보인다.
interface A{
public void method1();
}
interface B extends A{
public void method2();
}
class C implements B{
@Override public void method1(){}
@Override public void method2(){}
}
interface A{
public void method1();
}
interface B{
public void method2();
}
class C implements A, B{
@Override public void method1(){}
@Override public void method2(){}
}
A. 한 가지 큰 차이점이 있다.
첫 번째 예시는 인터페이스 B가 A를 extends (확장) 하기 때문에
앞으로 B를 구현하는 모든 클래스는 A를 자동으로 구현한다.
→ 컴파일러에게 "A가 된다는 것" 은 B가 된다는 뜻으로 알려주는것. " 모든 B는 A이다 !"
그런데 아래 코드를 보자.
class C implements B {
... you implement all of the methods you need...
...blah...
...blah...
}
A myNewA = new C();
두 번째 예에서는 A와 B의 관계가 없어서 위의 코드가 동작하지 않는다.
C가 B를 구현했는데, A 참조변수에 C인스턴스를 할당하려고 하면,
모든 B는 A이다! 라고 컴파일러에게 말하지 않았기 때문이다.
ㅏ
class ChevyVolt implements Vehicle, Drivable {}
class FordEscort implements Vehicle, Drivable {}
class ToyotaPrius implements Vehicle, Drivable {}
class ChevyVolt implements Vehicle {}
class FordEscort implements Vehicle {}
class ToyotaPrius implements Vehicle {}
interface Vehicle extends Drivable {}
위의 예제를 A, B, C에 대입하면
Drivable == A
Vehicle == B
class ChevyVolt == C
class FordEscort == C
class ToyotaPrius == C
A myNewA = new C(); //여기에 대입하면, (상속 없이는 안되는 코드 )
Drivable Toyota = new ToyotaPrius(); // 가 불가능하게된다.
//Vehicle Toyota = new ToyotaPrius(); 얘는 가능함 ..
(Vehicle) 탈것이라면 당연히 (Drivable)운전할 수있다는 참인 명제를
상속 관계를 안 넣어주면,
자동차는 탈것을 구현했지만 운전할수있다는 성질로 묶어줄 수 없어지는 것...
참인 명제에서 오류가 생긴다.
✅ 인터페이스 다중 상속
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 {...}
상위 인터페이스에 있는 메서드 중
메서드명/파라미터 형식이 같지만 리턴타입이 다른 메서드가 있다면,
둘중 어떤 것을 상속받느냐에 따라 규칙이 달라져 다중 상속이 불가능하다.
⏺ 두개 인터페이스를 구현시, 메소드 시그니쳐가 같은 케이스에서는 어떻게 해야하는지?
중복되는 인터페이스의 추상 메소드를 재정의 하여 사용할수 있음
++ 보니까 default만 문제가 생기는 것 같음
default 메서드는 인터페이스에 구현되고, 구현클래스에서 오버라이딩이 가능한(필수x) 유일한 메서드라서...!
- 기본 메서드는 어차피 구현클래스에서 오버라이딩을 필수로 해줘야 하기 때문에 메서드 시그니처가 같아도 오버라이딩된 메서드만 호출해 사용하는 것 같다.
아래는 예제
→ preJoin 메소드를 재정의 하지 않으면 컴파일 에러가 발생
→ ".super.메소드" 사용
public interface JoinMember {
default void preJoin(){
System.out.println("pre member");
}
}
public interface JoinGroup {
default void preJoin(){
System.out.println("pre group");
}
}
public class Member implements JoinMember, JoinGroup{
@Override
public void preJoin() {
JoinMember.super.preJoin();
JoinGroup.super.preJoin();
}
}
+ static 메소드면 어떻게 할까?
애초에 override가 안되기 때문에
인터페이스이름.preJoin(); 으로 사용할 수 밖에 없음.
인터페이스의 Method
인터페이스의 메서드는 public abstract 여야 하며 이를 생략할 수 있다. 단, static 메서드와 디폴트 메서드는 예외(JDK 1.8)
- 자바 8 이전의 인터페이스는 추상 메서드만 가질수 있었으나 자바8 이후
- default method 와 statatic method 를 정의할 수 있도록 변경되었다.
✅ 인터페이스의 static 메소드, 자바 8
// static 메서드 : public 은 생략가능
public static void staticMethod(){
System.out.println("this is Static Method ");
};
// static 메서드 : 내부에서 private static 메서드 사용
public static void staticMethodUsingPrivateMethod(){
System.out.println("this is Static Method with Private Method");
System.out.println(getPrivateStatic());
};
//----------------------------------------------------
// main 에서 호출 시
// paper.isThisStaticPaper(); // static 메서드이므로 불가능
PrintAble.staticMethod(); // static Method 호출
PrintAble.staticMethodUsingPrivateMethod(); // static Method 호출 : 내부에서 private static 메서드 호출
- static 메서드는 인스턴스와 관계 없는 독립적인 메서드
- 인터페이스의 static 메서드 는 접근제어자가 항상 public 이며 생략 가능
- 당연히 static 메서드는 다른 static 메서드에서 사용될 수 있지만 static 이 아닌 메서드는 다른 static 메서드에서 사용될 수 없다.
✅ 인터페이스의 기본 메소드 (Default Method), 자바 8
// default 메서드 : public 은 생략가능
public default void doNotAnythig(){
};
// default 메서드 : 내부에서 private 메서드 사용
public default void doSomeThing(){
System.out.println("doSomeThing 1 !!! -> " + getPrivateNotStatic());
}
//----------------------------------------------------
// main 에서 호출 시
paper.doNotAnythig(); // default Method 호출 : 내용 없음
paper.doSomeThing(); // default Method 호출 : 내부에서 private 메서드 호출
- 인터페이스 내부에 메서드가 추가되는 변경사항이 생긴다면 해당 인터페이스를 상속받아 구현하는 구현체는 이 메서드를 구현해줘야하는 치명적인 불편사항이 존재
- 이를 해결하기 위한 개념으로 자바8 부터 도입되었다.
- 인터페이스에 default method 로 작성된 메서드는 구현체에서 반드시 구현하지 않아도(구현체를 변경하지 않아도) 동작에 문제되지 않는다.
- default 반환형 메서드명(){} 형식으로 작성하며 메서드 바디가 있어야한다.
- default 메서드는 접근제어자가 publilc 이며 생략이 가능함
- 주의
- 새로 추가된 default 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있다.
- 이러한 두가지 상황에 대한 충돌을 해결하는 규칙이 있다.
- 여러 인터페이스의 default 메서드간의 충돌이 일어나는 경우
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야한다.
- 인터페이스의 default 메서드와 조상 클래스의 메서드 간의 충돌이 일어나는 경우
- 조상클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.
- 여러 인터페이스의 default 메서드간의 충돌이 일어나는 경우
- 단순하게는 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 하는 방법이 있다.
✅ 인터페이스의 private 메소드, 자바 9
// private 메서드
private String getPrivateNotStatic(){
return "using Private Method Not Static";
}
// private static 메서드
private static String getPrivateStatic(){
return "using Private Method Static";
}
//----------------------------------------------------
// main 에서 호출 시
// paper.getPrivateNotStatic(); // private 메서드 외부 호출 불가능
- 인터페이스 내부에서만 쓰이고 외부에서는 쓰일 수 없는 메서드
- 인터페이스 내부에서만 쓰고 싶은데 기존의 메서드는 public 이던 문제점을 개선하기위해 추가되었다. 또한 private 메서드를 사용해 인터페이스 내부에서 반복적인 코드를 줄이는것이 가능
- default method, static method, private method 에서만 사용 가능하다고 볼 수 있다.
- 당연히 static 메서드는 다른 static 메서드에서 사용될 수 있지만 static 이 아닌 메서드는 다른 static 메서에서 사용될 수 없다.
- private 반환형 메서드명(){} 형식으로 작성하며 메서드 바디를 가진다.
💻전체 코드
public class ClassTest {
interface PrintAble{
// public 과 abstract 는 생략가능
// static 메서드와 default 메서드를 제외한 모든 메서드에 public abstract 기본으로 추가
public abstract String getTitle();
int getPageNum();
// static 메서드 : public 은 생략가능
public static void staticMethod(){
System.out.println("this is Static Method ");
};
// static 메서드 : 내부에서 private static 메서드 사용
public static void staticMethodUsingPrivateMethod(){
System.out.println("this is Static Method with Private Method");
System.out.println(getPrivateStatic());
};
// default 메서드 : public 은 생략가능
public default void doNotAnythig(){
};
// default 메서드 : 내부에서 private 메서드 사용
public default void doSomeThing(){
System.out.println("doSomeThing 1 !!! -> " + getPrivateNotStatic());
}
// private 메서드
private String getPrivateNotStatic(){
return "using Private Method Not Static";
}
// private static 메서드
private static String getPrivateStatic(){
return "using Private Method Static";
}
}
// PrintAble 를 구현한 Paper 클래스
static class Paper implements PrintAble{
@Override
public String getTitle() {
return "연간보고서";
}
@Override
public int getPageNum() {
return 600;
}
}
// main
public static void main(String[] args) {
Paper paper = new Paper();
System.out.println("1) 인터페이스의 메서드를 구현한 메서드 호출");
System.out.println(paper.getTitle());
System.out.println(paper.getPageNum());
System.out.println();
System.out.println("2) default 메서드 호출");
paper.doNotAnythig(); // default Method 호출 : 내용 없음
paper.doSomeThing(); // default Method 호출 : 내부에서 private 메서드 호출
System.out.println();
System.out.println("3) static 메서드 호출");
// paper.isThisStaticPaper(); // static 메서드이므로 불가능
PrintAble.staticMethod(); // static Method 호출
PrintAble.staticMethodUsingPrivateMethod(); // static Method 호출 : 내부에서 private static 메서드 호출
System.out.println();
System.out.println("4) private 메서드 호출");
// paper.getPrivateNotStatic(); // private 메서드 외부 호출 불가능
}
}
references