ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java-31] java8 이전 의 인터페이스
    Java 2021. 1. 7. 23:39

    Java interface

    java 인터페이스 정의 하는 방법

    인터페이스는 .java 형태로 컴파일러를 걸치면 클래스 형태로 컴파일 되기 때문에, 실질적으로 형태는 클래스와 동일하다고 할 수 있다.

    인터페이스 선언

    public interface Example{
        // do something
    }
    • 여기까지는 클래스와 정의하는 방법이 별반 다른거 없이 보일 수 있지만, 몇가지 다른 점들이 있다.
      클래스 인터페이스
      필드 상수
      생성자 -
      메소드 메소드 (자바 8 이전에는 추상 메소드)
    public interface Excample{
        //상수 
        타입 상수명 = 값;
    
        //추상 메소드
        타입 메소드명(매개변수, ...);
    }
    • 물론 여기서 더 추가가 될 수 있는 default, static, private이 있지만 이는 이 글에서 알아보도록 하자

    상수 필드(Constant Field)

    인터페이스는 객체 즉, 클래스의 사용 설명서라고 볼 수 있다. 그러므로, 런타인 동안에 데이터를 저장할 수 있는, 필드 맴버변수의 선언이 불가능 하다. 상수 필드란, 인터페이스에 고정된 값으로 있으므로, 절대 데이터를 바꿀 수 없다. 그러므로 반드시 초기값을 대입해줘야 한다.

    public interface Boo{
        String homePage = "https://www.naver.com";
        // final 이 생략되어있다고 생각하면 됨 -> final String homePage = ""; 와 같음
        default String changeHomePage(){
            homePage = "https://www.daum.net" // Error -> Cannot assign a value to final variable 'NAVER'
            return homePage;
        }
    }

    추상 메소드

    추상 메소드는 객체가 가지고 있는 메소드를 설명하는 것으로 호출할 때 어떤 매개타입이 오고, 어떤 리턴타입이 오는지에대해서 명시할 뿐이다. 예를 들어보자

    public interface Foo{
        (public abstract 생략) void sayHello();    // return 이 없으르므로 void
    
        (public abstract 생략)int sumCal(int var1, int var2); // return 값은 int
    
        (public abstract 생략) String sumString(String str1, String str2); // 2개의 string 파라미터에 리턴값은 String 
    }
    • 인터페이스의 메소드 들은 자바8 이전에 모두, 추상 메서드 이기 때문에 *public abstract 를 생략 *하고있다고 봐도 무방하다. 왜냐면 결국에 컴파일 시에, public abstact가 붙기 때문이다.

    인터페이스 구현

    인터페이스를 사용하는 방법은 크게 다음과 같다.

    image

    다음과 개발 코드에서 인터페이스를 호출하면, 인터페이스에 있는 추상 메소드를 구현하는 메소드가 있는 클래스에 메소드를 호출하여 리턴값을 반환 받는게 일반 적인 인터페이스 호출 방법이다. 우리는 이런 클래스를 인터페이스 구현(implement) 객체 라고 한다.

    구현 클래스

    • 이제 인터페이스를 정의하는 방법에 대해서 알아보았으니, 이 인터페이스를 사용하는 클래스에 대해서 알아보도록 하자, 구현 클래스는 사실 일반적인 클래스와 다른 점이 없다. 하지만, 인터페이스 타입으로 사용할 수 있음을 알기 위해서 클래스를 정의하는 부분에 명시를 해주도록 해야한다.
    public class Main implements Foo{ //인터페이스를 상속
        //code do something
    }

    구현 클래스에서는 그럼 어떤 방법으로 인턴페이스에 있는 메서드를 구현하는 것일까? 한가지 예시를 들어보도록 하자. 아주 간단한 interface 와 class의 uml 을 그려보자

    image

    • 간단한 설명을 하자면, 인터페이스를 UML 로 표현한 것이며, 실제 구현된 클래스 간의 관계를 작성한 것이다.
    • 전통적으로 Java의 인터페이스 네이밍은 ~able 로 진행한다. example : Serializable, Runnable
    • C# 에서는 l~ 로 시작 하는 네이밍이 관행이라고 한다.
    public interface Turnable {
        void turnOn(); // 전자제품을 작동한다. 
    }

    다음과 같이 인터페이스를 정의 했고, TV와 라디오 컴퓨터는 이 Turnable 인터페이스의 추상메소드의 구현체를 가지고 있어야 한다.

    public class Computer implements Turnable {
        @Override
        public void turnOn() {
            System.out.println("컴퓨터를 켭니다.");
        }
    }
    public class Radio  implements Turnable {
        @Override
        public void turnOn() {
            System.out.println("라디오를 켭니다.");
        }
    }
    public class Television  implements Turnable {
        @Override
        public void turnOn() {
            System.out.println("텔레비전을 켭니다.");
        }
    }

    이렇게 간단하게 구현할 수 있지만, 우리가 몇가지를 알아두도록 하자

    1. 구현 클래스에서 오버라이딩 하는 메소드의 접근 지시자는 public 및으로 내려갈 수 없다.

    public class Television  implements Turnable {
        @Override
        private void turnOn() { // Error 기본적으로 인터페이스의 모든 메서드는 public 접근 제한을 가지고 있기 때문에 불가능 
            System.out.println("텔레비전을 켭니다.");
        }
    }

    2. 인터페이스의 메소드를 구현하지 않았을 경우

    public abstract class Television  implements Turnable {
    /*    @Override
        public void turnOn() {
            System.out.println("텔레비전을 켭니다.");
        }*/
    }

    다음과 같이 인터페이스의 메소드를 구현하지 않았을 경우, 자동적으로 클래스는 추상 클래스가 되며, abstract 키워드를 사용하라고 명령한다.

    3. @Override 를 사용하자

    오버라이딩이라 함은, 기본적으로 메소드 재정의를 의미한다. 그럼 Override 어노테이션은 무엇일까? @Override의 역할은 제대로된 오버라이딩이 되었는가 컴파일러가 체크해주는 어노테이션이다. 왜 필요한 것일까?

    @Override 필요성

    1) 코드는 나혼자만 보지 않는다. 다른 개발자분들이 봤을 때, 이 메소드가 오버라이드 됬는지 안되있는지 확인할 수 있다.

    2) 개발을 하는 동안, 제대로 오버라이딩이 되어있는지 확인하기 위해서는 꽤나 귀찮다. 그냥 메소드 하나 재정의 하고, 매개변수 또는 리턴값, 그리고 메소드 이름이 맞는지 확인하는 과정은 꽤나 귀찮을 수 있다. 그럴땐 @Override 를 사용해보는게 Best practice 이다.

    구현 클래스 없이 사용하기

    • 인터페이스를 통해서 굳이 클래스를 상속하지 않고 사용할 수 있다. 예를 들어보자
        public static void main(String[] args) {
            Turnable tabletPc = new Turnable() {
                @Override
                public void turnOn() {
                    System.out.println("아이패드를 동작합니다.");
                }
            };
            tabletPc.turnOn(); // "아이패드를 동작 합니다."
        }

    다음과 같이 사용이 가능하다. 그럼 이런 작업을 해야할 때는 언제일까? 보통 임시작업 쓰레드를 만들 때 사용하기도 한다.

    Runnable 인터페이스

    • 물론 쓰르데에 대한 자세한 내용은 나중에 하겠지만, 간단하게 집고 넘어가서, 나중에 Runnable 에 대해 알아볼 때, 이런게 있었지 하면서 생각을 해보자
    public static void main(String[] args) throws InterruptedException {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("쓰레드 시작");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("쓰레드 종료");
                }
            };
            Thread th = new Thread(r);
            th.start();
            Thread.sleep(500);
            System.out.println("메인 쓰레드 종료");
        }
        실행 결과:
        쓰레드 시작
        메인 쓰레드 종료
        쓰레드 종료
    • 예제가 좋지는 못하지만 따로 메인쓰레드 동작 중에, 임시쓰레드를 만들어서 작성할 수 있다. 실행 결과를 보면 알 수 있듯이, 메인쓰레드 이외에 다른쓰레드가 동작하는걸 알 수 있을 것이다. 이거에 대해선 다음에 알아보도록 하자.

    interface 의 상속

    • 인터페이스를 사용 또는 설계를 하면서, interface에다가 상속을 주는일은 거의 없을 것이다. 하짐나 jdk 를 살펴보면 종종 볼 수 있는데, 이런 것도 있다 하면서 알아두면 좋을 수도 있겠다.
    public interface A{
        void method1();
    }
    
    public interface B extends A{
        void method2();
    }
    
    public class Main extends B{
        @Override
        public void method1(){
            System.out.println("call the method1"); // 인터페이스 A의 method1을 구현
        }
    
        @Override
        public void method2(){
            System.out.println("call the method2"); //인터페이스 B의 method2를 구현
        }
    }
    • 이렇게 사용할 수 있는 것을 확인할 수 있다. 즉, B는 A를 상속받은 인터페이스 인데, 이걸 implements 받는 Main 클래스는 필수적으로 A와 B 클래스의 모든 메소드를 구현하여야 한다는 것이다.

    image

    이 그림을 보고 좀 더 자세히 이해가 갔음 좋겠다.

    댓글

Designed by Tistory.