조상 클래스에서 구현이 무의미하고, 자식 클래스에서 반드시 재정의해서 사용하는 메서드가 있다.
그럴 때 해당 메서드를 추상 메서드로 조상 클래스를 추상 클래스로 정의한다.
아래 코드를 살펴보자.
package f_interface.abs;
public class Vehicle {
private int curX, curY;
public void reportPosition() {
System.out.printf("현재 위치: (%d, %d)\n", curX, curY);
}
public void addFuel() {
System.out.println("모든 운송 수단은 연료가 필요");
}
}
package f_interface.abs;
public class DieselSUV extends Vehicle {
@Override
public void addFuel() {
System.out.println("주유소에서 급유");
}
}
package f_interface.abs;
public class ElectricCar extends Vehicle {
@Override
public void addFuel() {
System.out.println("급속 충전");
}
}
여기서 Vehicle의 메서드인 addFuel()은 Vehicle 안에서 구현할 필요가 없다.
그렇다고 Vehicle에서 addFuel()을 없애면 Vehicle 단위로는 addFuel()에 접근할 수 없다.
따라서 addFuel()을 추상 메서드로 표현하여 Vehicle 안 addFuel() 구현을 생략할 수 있다.
바뀐 Vehicle 클래스 코드는 아래와 같다.
package f_interface.abs;
public abstract class Vehicle {
private int curX, curY;
public void reportPosition() {
System.out.printf("현재 위치: (%d, %d)\n", curX, curY);
}
public abstract void addFuel();
}
또한, 객체를 생성하고자 하는 자식 클래스의 추상 메서드 구현을 강제할 수 있다.
아래 사진을 살펴보자.

왼쪽 그림은 HorseCart 클래스에서 addFuel() 재정의가 필요없지만,
오른쪽 그림은 addFuel() 재정의가 반드시 필요하다.
왼쪽 그림을 따라가면, HorseCart에서 addFuel() 재정의가 반드시 필요하지만
개발자가 이를 모르고 구현하지 않아 문제가 생길 수 있다.
따라서 오른쪽 그림처럼 addFuel() 재정의를 강제하여 구현이 필요한 메서드를 개발자에게 알려줄 수 있다.
추상 클래스의 특징은 아래와 같다.
- 구현이 안 된 메서드가 있으므로 객체를 생성할 순 없다.
- 자식 참조는 가능하다. ( Vehicle v = new DieselSUV(); )
- 해당 클래스를 상속받았고, 객체를 생성하고자 하는 자식 클래스는
반드시 추상 메서드를 구현해야 한다.- 추상 메서드는 존재하나 추상 멤버 변수는 존재하지 않는다.
인터페이스의 특징은 아래와 같다.
- interface 키워드로 선언한다.
- 인터페이스 간에는 extends 키워드로 상속이 가능하며, 다중 상속이 가능하다.
- 클래스를 인터페이스에서 사용하기 위해선 implements로 받을 수 있으며,
인터페이스에서의 모든 메서드는 클래스에서 구현하여야 한다.- 컴파일 시 .interface가 아닌 .class가 확장자이다.
- 모든 멤버 변수는 public static final 이며 키워드는 생략이 가능하다.
- 모든 메서드는 public abstract 이며 키워드는 생략이 가능하다.
인터페이스를 사용하는 이유는 아래와 같다.
- abstract 메서드로 구현을 강제해 표준화한다. 따라서 손쉬운 모듈 교체를 지원한다.
- 상속한 클래스 중 관계가 없는 클래스들에게 인터페이스로 관계를 부여할 수 있다.
- 독립적인 프로그래밍으로 개발 기간을 단축할 수 있다.
1번의 예시는 아래와 같다. 아래 사진과 코드를 살펴보자.

public class PrinterTest {
public static void main(String[] args) {
PrintClient client = new PrintClient();
client.setPrinter(new DotPrinter());
client.printThis("myfile");
client.setPrinter(new LaserPrinter());
client.printThis("myfile");
}
}
main 함수 입장에선 사용할 객체만 변화시켜주면 기능을 그대로 사용할 수 있다.
2번의 예시는 아래와 같다. 아래 사진과 코드를 살펴보자.

public static void main(String[] args) {
Object[] objs = { new HandPhone(), new Camera(), new Phone(), new DigitalCamera() };
for (Object obj : objs) {
if (obj instanceof HandPhone) {
HandPhone handPhone = (HandPhone) obj;
handPhone.charge();
}
if (obj instanceof DigitalCamera) {
DigitalCamera digitalCamera = (DigitalCamera) obj;
digitalCamera.charge();
}
}
}

public class RelationShipTest {
public static void main(String[] args) {
Object[] objs = { new HandPhone(), new Camera(), new Phone(), new DigitalCamera() };
Chargeable[] chaObjs = { new HandPhone(), new DigitalCamera() };
for (Chargeable cha : chaObjs) {
cha.charge();
}
}
}
이를 통해 Interface로 클래스 간 관계를 간편하게 추가할 수 있음을 알 수 있다.
3번의 예시는 아래와 같다. 아래 사진을 살펴보자.

A팀은 클라이언트를 위한 UI를 개발하기 위해 계산 로직 결과가 필요하다.
이때, 호출하는 함수 이름과 리턴 타입만 대략적으로 구현해두면
B팀이 계산 로직을 구현할때까지 기다릴 필요없이 대략적으로 UI를 구현할 수 있다.
그리고 B팀이 계산 로직을 구현하면 클래스만 교체하여 전체 기능을 만들 수 있다.
default 메서드는 인터페이스 안 메서드에 default 키워드를 붙여 선언한다.
아래 코드를 살펴보자.
class OldisButGoodies implements Aircon {
@Override
public void makeCool() {
}
}
public class StaticDefaultMethod {
public static void main(String[] args) {
Aircon aircon = new OldisButGoodies();
aircon.makeCool();
}
}
여기서 무풍 에어컨이 나와 dry 기능을 추가한다고 가정해보자.
interface Aircon {
void makeCool();
void dry();
}
이처럼 구현하면 OldisButGoodies 클래스도 dry 기능을 구현해야 한다.
하지만 dry 메서드를 아래처럼 구현해보자.
interface Aircon {
void makeCool();
default void dry() {
System.out.println("건조 중입니다.");
}
}
이처럼 구현하면 OldisButGoodies 클래스에 dry 기능을 구현할 필요가 없다.
정리하면, default 메서드로 기존 클래스의 관리가 편해지기 때문에 default 메서드를 지원한다.
default 메서드를 사용할 땐 이름 충돌을 주의하자.
- 인터페이스의 default 메서드와 조상 클래스의 메서드가 이름이 충돌하면
조상 클래스의 메서드가 우선된다.- 인터페이스의 default 메서드와 다른 인터페이스의 메서드가 이름이 충돌하면
해당 클래스에서 의무적으로 메서드를 오버라이딩한다.
static 메서드를 사용하면 Interface의 이름으로도 메서드를 접근할 수 있다.
static 메서드의 사용 예제는 아래와 같다.
interface Aircon {
static void info() {
System.out.println("냉매를 이용한다던가..");
}
}
public class StaticDefaultMethod {
public static void main(String[] args) {
Aircon.info();
}
}
클래스 선언에서 멤버 변수의 타입 정의를 피하기 위해 Generic을 사용한다. 아래 코드를 살펴보자.
class NormalBox {
private int some;
public int getSome() {
return some;
}
public void setSome(int some) {
this.some = some;
}
}
class ObjectBox {
private Object some;
public Object getSome() {
return some;
}
public void setSome(Object some) {
this.some = some;
}
}
class GenericBox<T> {
private T some;
public T getSome() {
return some;
}
public void setSome(T some) {
this.some = some;
}
}
public static void main(String[] args) {
// 1번 경우
NormalBox normalBox = new NormalBox();
normalBox.setSome(1);
int noSome = normalBox.getSome();
// 2번 경우
ObjectBox objectBox = new ObjectBox();
objectBox.setSome(1);
if (objectBox.getSome() instanceof Integer) {
Integer objIntegerSome = (Integer) objectBox.getSome();
int objIntsome = objIntegerSome;
}
objectBox.setSome("abc");
if (objectBox.getSome() instanceof String) {
String objStringSome = (String) objectBox.getSome();
}
// 3번 경우
GenericBox<Integer> genericBox1 = new GenericBox<>(); // <> 안에는 객체만 넣을 것
genericBox1.setSome(1);
int geIntegerSome = genericBox1.getSome();
GenericBox<String> genericBox2 = new GenericBox<>();
genericBox2.setSome("abc");
String geStringSome = genericBox2.getSome();
}
1번 경우 : 멤버 변수 some에 int만 넣을 수 있다.
2번 경우 : 멤버 변수 some에 어떤 객체든 넣을 수 있지만, 항상 타입 체크를 해줘야 한다.
3번 경우 : 객체를 생성할 때 멤버 변수 some의 타입을 정할 수 있다.
<>에 구체적인 타입 외에도 넣을 수 있는 Generic Type이 있다.
- Generic Type <?> : 타입에 제한이 없다.
- Generic Type <? extends T> : T 또는 T를 상속받은 타입들만 사용 가능하다.
- Generic Type <? super T> : T 또는 T의 조상 타입만 사용 가능하다.
사용 예시는 아래와 같다.
class Person {}
Class SpiderMan extends Person {}
Class PersonBox<T> {}
public class WildTest {
public void wildTest() {
PersonBox<Object> pObj = new PersonBox<>();
PersonBox<Person> pPer = new PersonBox<>();
PersonBox<SpiderMan> pSpi = new PersonBox<>();
// 1번 경우
PersonBox<?> pAll = pPer;
pAll = pObj;
pAll = pSpi;
// 2번 경우
PersonBox<? extends Person> pChildPer = pPer;
pChildPer = pSpi;
// 3번 경우
PersonBox<? super Person> pSuperPer = pPer;
pSuperPer = pObj;
}
}
1번 경우 : Object, Person, SpiderMan 클래스 모두 넣을 수 있다.
2번 경우 : Person, SpiderMan 클래스만 넣을 수 있다.
3번 경우 : Object, Person 클래스만 넣을 수 있다.