8. 인터페이스
8.1인터페이스역할
인터페이스는 사전적인 의미로 두 장치를 연결하는 접속기를 말한다.
두장치를 서로 다른객체로 본다면 인터페이스는 이 두객체를 연결하는 역할을 한다.
인터페이스 뒤의 객체가 변경된다고 하더라도 객체A가 인터페이스를 통해 메소드를 호출한다면
인터페이스 뒤편의 객체 객체B혹은 객체C이다. 만약 이 두 객체의 실행결과가 다르다면 객체 A는 객체 교체로 다른 결과를 얻게 된다.
이특징으로 인해 인터페이스는 다형성 구현에 주된 기술로 이용된다. 상속을 이용해서 다형헝을 구현할수도잇지만 인터페이스를 이용해서 다형성을 구현하는 경우가 더 많다.
8.2 인터페이스와 구현클래스 선언
8.2.1 인터페이스 선언
인터페이스 선언은 class키워드 대신 interface키워드를 사용한다.
접근제한자로는 클래스와 마찬가지로 default, public을 붙일수잇다.
중괄호안에는 상수, 추상메소드, 디폴트, 정적메소드, private메소드, private정적메소드를 선언할 수 있다.
public interface RemoteControl {
//public 추상메소드
public void turnOn();
}8.2.2 구현클래스 선언
객체 A가 인터페이스의 추상메소드를 호출하면 인터페이스는 객체B의 메소드를 실행한다.
그렇다면 객체B는 인터페이스에 선언된 추상메소드와 동일한 선언부를 가진 메소드를 가지고 있어야한다.
여기서 객체B를 인터페이스를 구현한 객체라고 한다. 인터페이스에 정의된 추상메소드에 대한 실행내용이 구현되어있기 때문이다.
객체B와 구현객체는 implements 인터페이스 명을 붙여 구현하고 있음을 선언부에 명시해야한다.
public class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
}8.2.3 변수 선언과 구현객체 대입
인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있다.
인터페이스는 참조타입에 속하므로 인터페이스 변수에는 객체를 참조하지 않고 있다는 뜻으로 null을 대입할 수 있다.
인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현객체를 대입해야한다.
정확하겐 구현객체의 번지를 대입하는 것이다.
인터페이스 변수에 구현객체가 대입되엇다면 변수를 통해 인터페이스의 추상메소드를 호출할 수 있게 된다.
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn(); // TV를 켭니다.
}
}rc변수에는 Remotecontrol을 구현한 어떠한 객치든 대입이 가능하다.
만약 Audio객체가 구현객체라면 객체를 교체해서 대입할 수 있다.
public class Audio implements RemoteControl {
@Override
public void turnOn() {
System.out.println("Auido를 켭니다.");
}
}public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc = new Audio();
rc.turnOn(); //Audio를 켭니다.
}
}8.3 상수필드
인터페이스는 public static final특성을 갖는 불변의 상수필드를 멤버로 가질 수 있다.
인터페이스에 선언된 필드는 모두 상수 특성을 갖기 때문에 생략해도 자동적으로 컴파일과정에서 붙이게 되낟.
상수명은 대문자로 작성하고 다른단어는 언더바로 연결하는게 관례이다.
public interface RemoteControl {
// 상수
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
}public class RemoteControlExample {
public static void main(String[] args) {
System.out.println("리모콘 최대 볼륨: " + RemoteControl.MAX_VOLUME);
System.out.println("리모콘 최대 볼륨: " + RemoteControl.MIN_VOLUME);
}
}상수는 구현객체와 관련없는 인터페이스 소속멤버이므로 바로 접근해서 상수값을 읽을 수 잇다.
8.4 추상메소드
인터페이스는 구현클래스가 재정의해야하는 public추상 메소드를 멤버로 가질 수 있다.
추상메소드는 리턴타입 메소드명 매개변수만 기술되고 {}를 생략한다.
public abstract를 생략해도 컴파일 과정에서 자동으로 붙는다.
추상메소드는 객체 A가 인터페이스를 통해 어떻게 메소드를 호출 할 수 있는지 방법을 알려주는 역할을 한다.
인터페이스 구현객체B는 추상메소드의 실행부를 갖는 재정의된 메소드가 있어야한다.
public interface RemoteControl {
// 상수
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
// public 추상메소드
void turnOn();
void turnOff();
void setVolume(int volume);
}public class Television implements RemoteControl {
// 필드
private int volume;
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void setVolume(int volume) {
if (volume > RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if (volume < RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재볼륨: " + volume);
}
}주의할점은 인터페이스의 추상메소드는 기본적으로 pubilc접근제한을 갖기 때문에
public보다 더 낮은 접근제한으로 재정의 할 수 없다. 그래서 재정의되는 메소는 모두 public이다.
사용할땐 주입된 객체에 따라 오버라이딩된 메소드가 실행되게 된다.
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn(); // TV를 켭니다.
rc.setVolume(5); // 현재볼륨: 5
rc.turnOff(); // TV를 끕니다.
rc = new Audio();
rc.turnOn(); // Auido를 켭니다.
rc.setVolume(5); // 현재볼륨: 5
rc.turnOff(); // Auido를 끕니다.
}
}8.5 디폴트 메소드
인터페이스에는 완전한 실행코드를 가진 디폴트 메소드를 선언할 수 있다.
선언방법은 클래스 메소드와 동일한데 차이점은 default키워드가 리턴타입앞에 붙는다.
디폴트 메소드의 실행부에는 상수필드를 읽거나 추상메소드를 호출하는 코드를 작성할 수 있다.
//디폴트 인스턴스 메소드
default void setMute(boolean mute) {
if (mute) {
System.out.println("무음처리합니다.");
//추상메소드 호출하면서 상수필드 사용
setVolume(MIN_VOLUME);
} else {
System.out.println("무음 해제합니다.");
}
}디폴트 메소드는 구현객체가 필요한 메소드이다. RemoteControl의 setMute()메소드를 호출하려면 구현객체를 변수에 대입하고 호출해야한다.
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn(); // TV를 켭니다.
rc.setVolume(5); // 현재볼륨: 5
rc.setMute(true); //무음처리합니다. 현재볼륨: 0
rc.setMute(false); //무음 해제합니다.
rc.turnOff(); // TV를 끕니다.
}
}구현 클래스는 디폴트 메소드를 재정의해서 자신에게 맞게 수정할 수도 있다.
재정의시 주의할점은 public접근제한자를 반드시 붙여야하고 default키워드를 생략해야한다.
@Override
public void setMute(boolean mute) {
if (mute) {
this.memoryVolume = this.volume; //현재볼륨을 저장
System.out.println("무음처리합니다.");
setVolume(MIN_VOLUME);
} else {
System.out.println("무음 해제합니다.");
setVolume(this.memoryVolume); //해제시 원래 볼륨으로 복원
}
}public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Audio();
rc.turnOn(); // Auido를 켭니다.
rc.setVolume(5); // 현재볼륨: 5
rc.setMute(true); //무음처리합니다. 현재볼륨: 0
rc.setMute(false); //무음 해제합니다. 현재볼륨: 5
rc.turnOff(); // Auido를 끕니다.
}
}8.6 정적 메소드
인터페이스에는 정적메소드도 선언이 가능하다.
추상메소드와 디폴트메소드는 구현객체가 필요하지만 정적 메소드는 구현객체가 없어도 인터페이스만으로 호출이 가능하다.
선언방법은 클래스 정적메소드와 완전 동일하다. public을 생략하더라도 자동으로 컴파일과정에서 붙는다.
public interface RemoteControl {
// 정적메소드
static void changeBattery() {
System.out.println("리모콘 건전지를 교환합니다.");
}
}public class RemoteControlExample {
public static void main(String[] args) {
//정적메소드 호출
RemoteControl.changeBattery(); //리모콘 건전지를 교환합니다.
}
}8.7 private 메소드
인터페이스의 상수 필드, 추상메소드 , 디폴트메소드, 정적메소드는 모두 public으로 접근제한을 갖는다.
이 멤버들을 선언할때는 public을 생략하더라도 컴파일과정에서 알아서 붙어서 항상외부접근이 가능하다.
또한 인터페이스 외부에서 접근할 수 없는 private메소드 선언도 가능하다.
private메소드는 디폴스메소드안에서만 호출이 가능한반면 private정적메소드는 디폴트메소드뿐만아니라 정적메소드안에서도 호출이 가능하다.
private메소드의 용도는 디폴트와 정적메소드들의 중복코드를 줄이기 위함이다.
public interface Service {
//디폴트메소드
default void defaultMethod1() {
System.out.println("defaultMethod1 종속 코드");
//System.out.println("defaultMethod 중복 코드1");
//System.out.println("defaultMethod 중복 코드2");
defaultCommon();
}
default void defaultMethod2() {
System.out.println("defaultMethod2 종속 코드");
//System.out.println("defaultMethod 중복 코드1");
//System.out.println("defaultMethod 중복 코드2");
defaultCommon();
}
//pirvate 메소드
private void defaultCommon() {
System.out.println("defaultMethod 중복 코드1");
System.out.println("defaultMethod 중복 코드2");
}
}8.8 다중 인터페이스 구현
구현객체는 여러개의 인터페이스를 implements할 수 잇다.
구현객체가 인터페이스A와 인터페이스B를 구현하고 있다면 각각 인터페이스를 통해 구현객체를 사용할 수 있다.
implements뒤에 쉼표로 구분해 작성하고 모든 추상메소드를 재정의 해야한다.
public interface RemoteControl {
//추상메소드
void turnOn();
void turnOff();
}public interface Searchable {
//추상메소드
void search(String url);
}public class SmartTelevision implements RemoteControl, Searchable {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void search(String url) {
System.out.println(url + "을 검색합니다.");
}
}public class MultiInterfaceImplExample {
public static void main(String[] args) {
// RemoteControl에 구현객체 대입
RemoteControl rc = new SmartTelevision();
//RemoteControl 인터페이스에 선언된 추상메소드만 호출가능
rc.turnOn();
rc.turnOff();
//Searchable에 구현객체 대입
Searchable searchable = new SmartTelevision();
//Searchable 인터페이스에 선언된 추상메소드만 호출가능
searchable.search("https://www.youtube.com");
}
}8.9 인터페이스 상속
인터페이스도 다른 인터페이스를 상속할 수 있으며 클래스와는 달리 다중상속을 허용한다.
extends키워드 뒤에 상속할 인터페이스들을 나열하면된다.
자식인터페이스의 구현클래스는 자식인터페이스의 메소드 뿐만 아니라 부모 인터페이스의 모든 추상메소드를 재정의해야한다.
구현객체는 자식 및 부모 인터페이스 변수에 대입될 수 있다.
public interface InterfaceA {
//추상메소드
void methodA();
}public interface InterfaceB {
//추상메소드
void methodB();
}public interface InterfaceC extends InterfaceA, InterfaceB{
//추상메소드
void methodC();
}public class InterfaceCImpl implements InterfaceC{
@Override
public void methodA() {
System.out.println("InterfaceCImpl-methodA() 실행");
}
@Override
public void methodB() {
System.out.println("InterfaceCImpl-methodB() 실행");
}
@Override
public void methodC() {
System.out.println("InterfaceCImpl-methodC() 실행");
}
}8.10 타입변환
인터페이스의 타입변환은 인터페이스와 구현클래스 간에 발생한다.
인터페이스 변수에 구현객체를 대입하면 구현객체는 인터페이스 타입으로 자동타입변환된다.
반대로 인터페이스 타입을 구현클래스 타입으로 변환시킬 수 있는데 이때는 강제 타입변환이 필요하다.
8.10.1 자동타입변환
자동타입변환은 의미그대로 자동으로 타입변환이 일어나는 것을 말한다.
인터페이스 변수 = 구현객체
public class PromotionExample {
public static void main(String[] args) {
// 구현객체 생성
B b = new B();
C c = new C();
D d = new D();
E e = new E();
// 인터페이스 변수선언
A a;
// 변수에 구현객체 대입
a = b; // A <- B 자동타입변환
a = c; // A <- C 자동타입변환
a = d; // A <- D 자동타입변환
a = e; // A <- E 자동타입변환
}
}8.10.2 강제타입변환
강제타입변환은 캐스팅기호를 사용해서 인터페이스 타입을 구현클래스 타입으로 변환시키는 것을 말한다.
구현클래스 변수 = (구현클래스) 인터페이스 변수
구현객체가 인터페이스 타입으로 자동변환되면 인터페이스에 선언된 메소드만 사용가능하다.
public interface Vehicle {
//추상메소드
void run();
}public class Bus implements Vehicle{
//추상메소드 재정의
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
//추가메소드
public void checkFare()
{
System.out.println("승차요금을 체크합니다.");
}
}public class PromotionExample {
public static void main(String[] args) {
// 인터페이스 변수 선언과 구현객체 선언
Vehicle vehicle = new Bus();
// 인터페이스 통해서 호출
vehicle.run();
// 강제 타입 변환 후 호출
Bus bus = (Bus) vehicle;
bus.run();
bus.checkFare();
}
}8.11 다형성
7장상속에서 다형성에 대해 살펴보았다. 인터페이스또한 다형성을 구현하는 주된 기술이다.
현업에서는 상속보다는 인터페이스를 통해 다형성을 구현하는 경우가 더 많다.
객체A가 인터페이스를 통해 메소드를 호출하는데 구현된 객체 B C중 어느 객체가 대입되엇느냐에 따라 결과가 달라진다.
8.11.1 필드의 다형성
자동타입변환으로 인해서 발생할 수있다.
public interface Tire {
//추상메소드
void roll();
}public class HankookTire implements Tire{
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}public class KumhoTire implements Tire{
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}public class Car {
// 필드
Tire tire1 = new HankookTire();
Tire tire2 = new HankookTire();
// 메소드
void run() {
tire1.roll();
tire2.roll();
}
}public class CarExample {
public static void main(String[] args) {
//자동차 객체 생성
Car myCar = new Car();
//run메소드 실행
myCar.run(); //한국 타이어가 굴러갑니다.
//타이어 객체 교체
myCar.tire1 = new KumhoTire();
myCar.tire2 = new KumhoTire();
//run() 메소드 실행(다형성: 실행결과가 다름)
myCar.run(); //금호 타이어가 굴러갑니다.
}
}Tire tire1 = new HankookTire();
myCar.tire1 = new KumhoTire();
Tire tire1 = new KumhoTire();
이런식으로 바뀌는 느낌이다.
8.11.2 매개변수의 다형성
메소드 호출 시 매개값을 다양화하기 위해 상속에서는 매개변수 타입을 부모 타입으로 선언하고 호출할때는 다양한 자식객체를 대입했다.
이것은 자동타입변환때문인데 비슷한 원리로 매개변수타입을 인터페이스로 선언하면 메소드 호출시 다양한 구현객체를 대입할 수있다.
public interface Vehicle {
//추상메소드
void run();
}public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}public class Taxi implements Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}public class Driver {
void drive (Vehicle vehicle) {
vehicle.run();
}
}public class DriverExample {
public static void main(String[] args) {
// Driver 객체 생성
Driver driver = new Driver();
// Vehicle 구현객체 생성
Bus bus = new Bus();
Taxi taxi = new Taxi();
// 매개값으로 구현 객체 대입(다형성 실행 결과가 다름)
driver.drive(bus); //버스가 달립니다.
driver.drive(taxi); //택시가 달립니다.
}
}8.12 객체 타입확인
상속에서 객체 타입을 확인하기 위해 instanceof 연산자를 사용했는데 인터페이스에서도 가능하다.
public interface Vehicle {
//추상메소드
void run();
}public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void checkFare() {
System.out.println("승차요금을 체크합니다.");
}
}public class Taxi implements Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}public class InstanceofExample {
public static void main(String[] args) {
// 구현객체 생성
Taxi taxi = new Taxi();
Bus bus = new Bus();
// ride() 메소드 호출시 구현객체를 매개값으로 전달.
ride(taxi); //택시가 달립니다.
System.out.println();
ride(bus); // 승차요금을 체크합니다. / 버스가 달립니다.
}
// 인터페이스를 매개변수로 갖는 메소드
public static void ride(Vehicle vehicle) {
/*
* if(vehicle instanceof bus) { Bus bus = (Bus) vehicle; bus.checkFare(); }
*/
// 자바 12부터 사용가능
if (vehicle instanceof Bus bus) {
bus.checkFare();
}
vehicle.run();
}
}'기초단계 > JAVA' 카테고리의 다른 글
| 2023.02.20 Java복습 (0) | 2023.02.20 |
|---|---|
| 2023.02.19 Java 복습 (2) | 2023.02.19 |
| 2023.02.17-2 Java 복습 (0) | 2023.02.17 |
| 2023.02.17-1 Java 복습 (1) | 2023.02.17 |
| 2023.02.16-2 Java 복습 (1) | 2023.02.16 |