JAVA - 10

월요일좋아·2022년 10월 12일
0

JAVA

목록 보기
10/12

인터페이스의 역할

인터페이스란?

추상 클래스(규격 강제)의 업그레이드판
인터페이스의 구성요소는 모두 추상 메서드이다.(일반 멤버변수, 일반 멤버메서드 없음, 정적 멤버는 사용함)

인터페이스 선언

  • 인터페이스 이름 : 자바 식별자 작성 규칙에 따라 작성
  • 소스 파일 생성 : 인터페이스 이름과 대소문자가 동일한 소스 파일 생성
  • 인터페이스 선언
public interface 인터페이스명 {...}

RemoteControl.interface

public interface RemoteControl {
//    인터페이스의 멤버는 상수, 정적 멤버 메서드, 추상 메서드, 디폴트 메서드만 사용이 가능함
//    인터페이스에서 사용할 수 있는 멤버는 일반 멤버 변수와 메서드는 불가능하기 때문에 static 및 abstract 를 생략할 수 있음
//    인터페이스는 객체 사용 설명서이기 때문에 상수, 정적 메서드, 추상 메서드, 디폴트 메서드만 사용이 가능함
//    정적 메서드 및 디폴트 메서드는 자바 버전 8부터 사용이 가능해짐

//    static final 붙어야하는데 인터페이스 안에 있는건 무조건 상수로 인식하기때문에 안붙여도 됨
//    상수
    public int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;

//    추상 메서드(abstract 생량)
    void turnOn();
    void turnOff();
    void setVolume(int volume);

//    디폴트 메서드
    default void setMute(boolean mute) {
        if (mute) {
            System.out.println("무음 처리합니다.");
        }
        else {
            System.out.println("무음 해제합니다.");
        }
    }
    
//    정적 메서드
    static void changeBattery() {
        System.out.println("건전지를 교환합니다.");
    }
}

상수 필드 선언

  • 인터페이스는 상수 필드만 선언 가능
    • 데이터 저장하지 않음
    • 일반 멤버 변수 선언 불가
  • 인터페이스에 선언된 필드는 모두 public static final
    • 자동적으로 컴파일 과정에서 붙음
  • 상수명은 대문자로 작성
    • 서로 다른 단어로 구성되어 있을 경우에는 언더 바_로 연결
  • 선언과 동시에 초기값 지정
    • static { } 블록 작성 불가 - static { } 으로 초기화 불가

추상 메서드 선언

  • 인터페이스 통해 호출된 메소드는 최종적으로 객체에서 실행
    • 인터페이스의 메소드는 기본적으로 실행 블록이 없는 추상 메소드로 선언
    • public abstract를 생략하더라도 자동적으로 컴파일되정에서 붙게 됨

디폴트 메서드 선언

  • 자바 8에서 추가된 인터페이스의 새로운 멤버
[public] default 리턴타입 메소드명(매개변수, ...) {...}
  • 실행 블록을 가지고 있는 메소드
  • default 키워드를 반드시 붙여야
  • 기본적으로 public 접근제한
    • 생략하더라도 컴파일 과정에서 자동으로 붙음

정적 메서드 선언

  • 자바 8에서 추가됨

인터페이스 구현

구현 객체와 구현 클래스

  • 인터페이스의 추상 메소드에 대한 실체 메소드를 가진 객체 = 구현 객체
  • 구현 객체를 생성하는 클래스 = 구현 클래스

구현 클래스 선언

  • 자신의 객체가 인터페이스 타입으로 사용할 수 있음
    • implement 키워드로 명시

추상 메소드와 실체 메소드를 작성하는 방법

  • 오버라이딩의 규칙 그대로 적용
  • 인터페이스의 모든 추상 메소드를 재정의하는 실체 메소드를 작성해야 함
    • 일부만 재정의할 경우, 추상 클래스로 선언 + abstract 키워드 붙임
      RemoteControl.interface
public interface RemoteControl {
//    인터페이스의 멤버는 상수, 정적 멤버 메서드, 추상 메서드, 디폴트 메서드만 사용이 가능함
//    인터페이스에서 사용할 수 있는 멤버는 일반 멤버 변수와 메서드는 불가능하기 때문에 static 및 abstract 를 생략할 수 있음
//    인터페이스는 객체 사용 설명서이기 때문에 상수, 정적 메서드, 추상 메서드, 디폴트 메서드만 사용이 가능함
//    정적 메서드 및 디폴트 메서드는 자바 버전 8부터 사용이 가능해짐

//    static final 붙어야하는데 인터페이스 안에 있는건 무조건 상수로 인식하기때문에 안붙여도 됨
//    상수
    public int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;

//    추상 메서드(abstract 생량)
    void turnOn();
    void turnOff();
    void setVolume(int volume);

//    디폴트 메서드
    default void setMute(boolean mute) {
        if (mute) {
            System.out.println("무음 처리합니다.");
        }
        else {
            System.out.println("무음 해제합니다.");
        }
    }
    
//    정적 메서드
    static void changeBattery() {
        System.out.println("건전지를 교환합니다.");
    }
}

Television.class

public class Television implements RemoteControl{   // 커서 올리고 Alt + Enter : 메소드 자동 구현
    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("현재 TV 볼륨 : " + this.volume);
    }
}

Audio.class

public class Audio implements RemoteControl {

    private int volume;

    @Override
    public void turnOn() {
        System.out.println("Audio를 켭니다.");
    }

    @Override
    public void turnOff() {
        System.out.println("Audio를 끕니다.");
    }

    @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("현재 Audio 볼륨 : " + this.volume);
    }
}

RemoteControlEx.class

public class RemoteControlEx {
    public static void main(String[] args) {
//        인터페이스 타입의 객체를 생성이 불가능
//        인터페이스 타입의 변수는 생성이 가능
//        다형성에 의해서 인터페이스 타입의 변수에 자식 클래스 타입의 객체를 대입하는것이 가능함
        RemoteControl rc;
        rc = new Television();
        rc.turnOn();
        rc.setVolume(3);
        rc.setVolume(15);
        rc.turnOff();

        rc = new Audio();
        rc.turnOn();
        rc.setVolume(3);
        rc.setMute(true);
        rc.setMute(false);
        rc.setVolume(-5);
        rc.turnOn();
    }
}

익명 구현 객체

  • 명시적인 구현 클래스 작성 생략하고 바로 구현 객체를 얻는 방법
    • 이름 없는 구현 클래스 선언과 동시에 객체 생성
  • 인터페이스의 추상 메소드들을 모두 재정의하는 실체 메소드가 있어야
  • 추가적으로 필드와 메소드 선언 가능하나 익명 객체 안에서만 사용
  • 인터페이스 변수로 접근 불가 (변수.xxx 불가능)

RemoteControlEx.class - 추가

rc = new RemoteControl() {
            private int volume;
            @Override
            public void turnOn() {
                System.out.println("radio 전원을 켭니다.");
            }

            @Override
            public void turnOff() {
                System.out.println("radio 전원을 끕니다.");
            }

            @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("현대 rddio 볼륨 : " + this.volume);
            }
        };  // 세미콜론 붙여줘야함!
        rc.turnOn();
        rc.setVolume(50);
        rc.setMute(true);
        rc.turnOff();
        
        // changeBattery()는 정적 메서드 이므로 인터페이스명.changeBattery()로 호출해야 함
        // 정적멤버는 객체 만들어서 호출하는것 아님.
        RemoteControl.changeBattery();

다중 인터페이스 구현 클래스

Searchable.interface

public interface Searchable {
    void search(String url);
}

SmartTelevision.class

public class SmartTelevision implements RemoteControl, Searchable {

    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 = MAX_VOLUME;
        } else if (volume < RemoteControl.MIN_VOLUME) {
            this.volume = MIN_VOLUME;
        } else {
            this.volume = volume;
        }
        System.out.println("현재 스마트TV 볼륨 : " + this.volume);
    }

    @Override
    public void search(String url) {
        System.out.println(url + "을 검색합니다.");
    }
}

RemoteControlEx.class

public class RemoteControlEx {
    public static void main(String[] args) {
//        인터페이스 타입의 객체를 생성이 불가능
//        인터페이스 타입의 변수는 생성이 가능
//        다형성에 의해서 인터페이스 타입의 변수에 자식 클래스 타입의 객체를 대입하는것이 가능함
        RemoteControl rc;
        rc = new Television();
        rc.turnOn();
        rc.setVolume(3);
        rc.setVolume(15);
        rc.turnOff();

        rc = new Audio();
        rc.turnOn();
        rc.setVolume(3);
        rc.setMute(true);
        rc.setMute(false);
        rc.setVolume(-5);
        rc.turnOn();

        rc = new RemoteControl() {
            private int volume;
            @Override
            public void turnOn() {
                System.out.println("radio 전원을 켭니다.");
            }

            @Override
            public void turnOff() {
                System.out.println("radio 전원을 끕니다.");
            }

            @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("현대 rddio 볼륨 : " + this.volume);
            }
        };  // 세미콜론 붙여줘야함!
        rc.turnOn();
        rc.setVolume(50);
        rc.setMute(true);
        rc.turnOff();

        // changeBattery()는 정적 메서드 이므로 인터페이스명.changeBattery()로 호출해야 함
        // 정적멤버는 객체 만들어서 호출하는것 아님.
        RemoteControl.changeBattery();


        ㅡㅡㅡㅡㅡ다중 인터페이스 구현 클래스 ㅡㅡㅡㅡㅡ
        SmartTelevision stv = new SmartTelevision();
        stv.turnOn();
        stv.setVolume(30);
        stv.setMute(true);
        stv.setVolume(-50);
        stv.search("kakaotalk");
        stv.turnOff();
    }
}


인터페이스 사용

추상 메소드 사용

디폴트 메소드 사용

  • 인터페이스만으로는 사용 불가
    • 구현 객체가 인터페이스에 대입되어야 호출할 수 있는 인스턴스 메소드
  • 모든 구현 객체가 가지고 있는 기본 메소드로 사용
    • 필요에 따라 구현 클래스가 디폴트 메소드 재정의해 사용

정적 메소드 사용

  • 인터페이스로 바로 호출 가능
  • 인터페이스명.정적메소드명();

타입변환과 다형성

다형성

  • 하나의 타입에 여러 가지 객체 대입해 다양한 실행 결과를 얻는 것
  • 다형성을 구현하는 기술
    • 상속 또는 인터페이스의 자동 타입 변환(Promotion)
    • 오버라이딩(OVerriding)
  • 다형성의 효과
    • 다양한 실행 결과를 얻을 수 있음
    • 객체를 부품화시킬 수 있어 유지보수 용이(메소드의 매개변수로 사용)

Tirei.interface

public interface Tirei {
    public void roll();
}

HankookTire2.class

public class HankookTire2 implements Tirei {
    @Override
    public void roll() {
        System.out.println("한국 타이어가 굴러갑니다.");
    }
}

KumhoTire2.class

public class KumhoTire2 implements Tirei {
    @Override
    public void roll() {
        System.out.println("금호 타이어가 굴러갑니다.");
    }
}

Car4.class

public class Car4 {
    Tirei frontLeftTire = new HankookTire2();
    Tirei frontRightTire = new HankookTire2();
    Tirei backLeftTire = new HankookTire2();
    Tirei backRightTire = new HankookTire2();

    void run() {
        frontLeftTire.roll();
        frontRightTire.roll();
        backLeftTire.roll();
        backRightTire.roll();
    }
}

Car4Ex.class

public class Car4Ex {
    public static void main(String[] args) {
        Car4 myCar = new Car4();

        myCar.run();
        myCar.frontLeftTire = new KumhoTire2();
        myCar.frontRightTire = new KumhoTire2();

        myCar.run();
    }
}

자동 타입 변환

인터페이스 배열로 구현한 객체 관리

매개 변수의 다형성

  • 매개 변수의 타입이 인터페이스인 경우
    • 어떠한 구현 객체도 매개값으로 사용 가능
    • 구현 객체에 따라 메소드 실행결과 달라짐

강제 타입 변환

  • 인터페이스 타입으로 자동 타입 변환 후, 구현 클래스 타입으로 변환
    • 필요성: 구현 클래스 타입에 선언된 다른 멤버 사용하기 위해
  • 예제

객체 타입 확인(instanceof 연산자)

  • 강제 타입 변환 전 구현 클래스 타입 조사

Car5.class

public class Car5 {
    Tirei[] tires = new Tirei[4];

//    방법 1 : 배열 선언과 동시에..
//    Tirei[] tires = {
//            new HankookTire2(),
//            new HankookTire2(),
//            new HankookTire2(),
//            new HankookTire2(),
//    }

//    방법 2 : 생성자에서 for문 사용
    public Car5() {
        for (int i=0; i < tires.length; i++) {
            tires[i] = new HankookTire2();
        }
    }

    void run() {
        for (Tirei item : tires) {
            item.roll();
        }
    }
}

Car4Ex.class (추가)

import package2.C;

public class Car4Ex {
    public static void main(String[] args) {
        Car4 myCar = new Car4();

        myCar.run();
        myCar.frontLeftTire = new KumhoTire2();
        myCar.frontRightTire = new KumhoTire2();

        myCar.run();

        /////////// 추가 ///////////
        Car5 car5 = new Car5();
        car5.run();
        car5.tires[2] = new KumhoTire2();
        car5.tires[3] = new KumhoTire2();

        car5.run();
    }
}

Driver2.class

public class Driver2 {
    public void drive(Vehicle2 vehicle2) {
        vehicle2.run();
    }
}

Vehicle2.interface

public interface Vehicle2 {
    void run(); // 인터페이스에서는 public 생략 가능
}

Driver2Ex.class

import package1.B;
import package2.D;

public class Driver2Ex {
    public static void main(String[] args) {

        System.out.println("각각 객체를 생성하여 실행");
        Bus2 bus2 = new Bus2();
        Taxi2 taxi2 = new Taxi2();

        bus2.run();
        taxi2.run();

        System.out.println("\n익명 객체를 통한 구현");
//        오류 : 인터페이스타입은 객체생성 불가능
//      Vehicle2 vehicle2 = new Vehicle2();
//        익명객체 생성해서 사용하면 됨
        Vehicle2 vehicle2 = new Vehicle2() {
            @Override
            public void run() {
                System.out.println("탈것이 달립니다.");
            }
        };  // 익명객체 생성시 중괄호 끝에 세미콜론!
        vehicle2.run();

        System.out.println("\n부모 인터페이스 타입의 변수에 자식 객체를 대입");
        vehicle2 = bus2;
        vehicle2.run();
        vehicle2 = taxi2;
        vehicle2.run();

        System.out.println("\n인터페이스 변수를 매개변수로 사용 시");
        Driver2 driver2 = new Driver2();
        driver2.drive(bus2);
        driver2.drive(taxi2);


        System.out.println("\n자동 타입변환, 강제 타입변환, instanceof\n");
        /////// 자동 타입 변환, 부모인 Vehicle2 인터페이스 타입의 변수에 자식인 Bus2클래스 타입의 객체를 대입 ///////
        Vehicle2 vehicle21 = new Bus2();
        vehicle21.run();

//        오류 발생 : 데이터 타입이 다르므로 강제 타입변환을 시도해야 함
//        Bus2 bus21 = vehicle21;
        Bus2 bus21 = (Bus2) vehicle21;  // 강제 타입 변환
        bus21.run();
        
//        강제 타입 변환의 조건 : 원본이 자식 클래스 타입의 객체일때 해당 클래스 타입으로 변환 시 가능 -> instanceof 로 확인가능
//        원본 클래스 객체 타입이 맞는지 확인을 하기 위해서는 instanceof 연산자를 사용해야 함
        Vehicle2 vehicle22 = new Bus2();
        vehicle22.run();

//        런타임 에러 발생
//        Taxi2 taxi22 = (Taxi2) vehicle22;
//        taxi22.run();

        if (vehicle22 instanceof Taxi2) {
            Taxi2 taxi22 = (Taxi2) vehicle22;
            taxi22.run();
            System.out.println("정상적으로 Taxi2 클래스 타입으로 변경 했습니다.");
        }
        else {
            System.out.println("Taxi2 클래스 타입으로 변경할 수 없습니다.");
        }
    }
}

인터페이스 상속

인터페이스간 상속 가능

  • 하위 인터페이스 구현 클래스는 아래 추상 메소드를 모두 재정의해야

    • 하위 인터페이스의 추상 메소드
    • 상위 인터페이스1의 추상 메소드
    • 상위 인터페이스2의 추상 메소드
  • 인터페이스 자동 타입 변환

    • 인터페이스끼리 다형성이 먹힘
    • 해당 타입의 인터페이스에 선언된 메소드만 호출 가능

인터페이스의 상속 예제

인터페이스는 인터페이스끼리 다중 상속이 가능하다.

InterfaceA, InterfaceB, InterfaceC

public interface InterfaceA {
    public void methodA();
}

public interface InterfaceB {
    public void methodB();
}


public interface InterfaceC extends InterfaceA, InterfaceB{
    public void methodC();
}

ImplC.class

public class ImplC implements InterfaceC {

    @Override
    public void methodA() {
        System.out.println("구현 클래스 C - 메서드A 실행");
    }
    @Override
    public void methodB() {
        System.out.println("구현 클래스 C - 메서드B 실행");

    }

    @Override
    public void methodC() {
        System.out.println("구현 클래스 C - 메서드C 실행");
    }
}

ImplEx.class

public class ImplEx {
    public static void main(String[] args) {
        System.out.println("구현체 implC의 객체 impl은 모든 메서드 사용 가능");
        ImplC impl = new ImplC();

        impl.methodA();
        impl.methodB();
        impl.methodC();

        System.out.println("\nInterfaceA의 변수에 대입 시 ");
        InterfaceA ifA = impl;
        ifA.methodA();  // 메서드A 만 사용 가능

        System.out.println("\nInterfaceB의 변수에 대입 시 ");
        InterfaceB ifB = impl;
        ifB.methodB();  // 메서드B 만 사용 가능

        System.out.println("\nInterfaceC의 변수에 대입 시 ");
        InterfaceC ifC = impl;
        impl.methodA();
        impl.methodB();
        impl.methodC(); // 메서드 A, B, C 모두 사용 가능
    }
}

디폴트 메소드와 인터페이스 확장

디폴트 메소드와 확장 메소드 사용하기

MyInterface.interface

public interface MyInterface {
    public void method1();
    
//    해당 interface 를 구현하는 구현체가 여러 개가 존재할 경우에 구현체의 수정 없이 기능을 추가하고자 하는 경우에 사용하는 것이 디폴트 메서드임
    default void method2() {
        System.out.println("dMyInterface의 method2() 실행");
    }
}

MyClassA.class

public class MyClassA implements MyInterface{


    @Override
    public void method1() {
        System.out.println("MyClassA의 method1() 실행");
    }
//    메서드2번 오버라이드 안해도 됨(default메서드라서), 해도 상관X
    /*@Override
    public void method2() {
        System.out.println("MyClassA의 method2() 실행");
    }*/
}

MyclassB.class

public class MyClassB implements MyInterface{
    @Override
    public void method1() {
        System.out.println("MyClassB의 method1() 실행");
    }

    @Override
    public void method2() {
        System.out.println("MyClassB의 method2() 실행");
    }
}

DefaultMethodEx.class

public class DefaultMethodEx {
    public static void main(String[] args) {
        MyInterface m1 = new MyClassA();
        m1.method1(); // 추상 메서드 : 오버라이딩
        m1.method2(); // 디폴트 메서드

        MyInterface m2 = new MyClassB();
        m2.method1(); // 추상 메서드 : 오버라이딩
        m2.method2(); // 추상 메서드 : 오버라이딩
    }
}

디폴트 메소드가 있는 인터페이스 상속

  • 부모 인터페이스의 디폴트 메소드를 자식 인터페이스에서 활용 방법
    • 디폴트 메소드를 단순히 상속만 받음
    • 디폴트 메소드를 재정의(오버라이드)해서 실행 내용을 변경
    • 디폴트 메소드를 추상 메소드로 재선언

디폴트 메소드를 추상 메소드로 재선언

  1. 인터페이스가 상속받아서 추상 메소드로 재정의해서 사용

(MyInterface, MyClassA, MyClassB)

MyInterface2.interface

public interface MyInterface2 extends MyInterface{// 인터페이스끼리 상속은 extends 사용

//    부모 인터페이스인 MyInterface에서 상속받은 멤버 메서드 중 디폴트 메서드인 method2() 를 오버라이딩하여 추상 메서드로 변환
    @Override
    void method2();

    void method3(); // MyInterface2 전용 메서드3
}

MyClassC.class

public class MyClassC implements MyInterface2{

//    MyInterface 에서 상속해준 추상 메서드
    @Override
    public void method1() {
        System.out.println("MyClassC의 method1() 실행");
    }

//    MyInterface 에서 디폴트 메서드로 상속해줬으나 MyInterface2에서 추상 메서드로 오버라이딩한 메서드
//    MyInterface2를 구현하는 구현체는 반드시 해당 메서드를 구현해야 함.
    @Override
    public void method2() {
        System.out.println("MyClassC의 method2() 실행");
    }

//    MyInterface 에서 전용으로 생성한 추상메서드
    @Override
    public void method3() {
        System.out.println("MyClassC의 method2() 실행");
    }
}

0개의 댓글