수색…


소개

인터페이스interface 키워드를 사용하여 선언 할 수있는 클래스와 비슷한 참조 유형입니다. 인터페이스는 상수, 메서드 서명, 기본 메서드, 정적 메서드 및 중첩 형식 만 포함 할 수 있습니다. 메서드 본문은 기본 메서드 및 정적 메서드에만 있습니다. 추상 클래스처럼 인터페이스는 인스턴스화 될 수 없습니다. 인터페이스는 클래스에 의해서만 구현되거나 다른 인터페이스에 의해 확장 될 수 있습니다. 인터페이스는 Java에서 완전한 추상화를 달성하는 일반적인 방법입니다.

통사론

  • 공용 인터페이스 Foo {void foo (); / * 다른 메소드 * /}
  • 공용 인터페이스 Foo1은 Foo {void bar (); / * 다른 메소드 * /}
  • 공용 클래스 Foo2는 Foo, Foo1을 구현합니다. {/ * Foo와 Foo1의 구현 * /}

인터페이스 선언 및 구현

interface 키워드를 사용하는 interface 선언 :

public interface Animal {
    String getSound(); // Interface methods are public by default
}

주석 재정의

@Override
public String getSound() {
    // Code goes here...
}

이렇게하면 컴파일러가 우리가 재정의했는지 확인하고 프로그램이 새 메서드를 정의하거나 메서드 서명을 엉망으로 만들지 않도록합니다.

인터페이스는 implements 키워드를 사용하여 implements 됩니다.

public class Cat implements Animal {

    @Override 
    public String getSound() {
        return "meow";
    }
}

public class Dog implements Animal {

    @Override
    public String getSound() {
        return "woof";
    }
}

이 예제에서 Cat 클래스와 Dog 클래스는 getSound() 메서드를 정의 해야합니다 . 인터페이스의 메서드는 본질적으로 추상 메서드입니다 (기본 메서드 제외).

인터페이스 사용

Animal cat = new Cat();
Animal dog = new Dog();

System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"

다중 인터페이스 구현하기

Java 클래스는 다중 인터페이스를 구현할 수 있습니다.

public interface NoiseMaker {
    String noise = "Making Noise"; // interface variables are public static final by default

    String makeNoise(); //interface methods are public abstract by default
}

public interface FoodEater {
    void eat(Food food);
}

public class Cat implements NoiseMaker, FoodEater { 
    @Override
    public String makeNoise() {
        return "meow";
    }

    @Override
    public void eat(Food food) {
        System.out.println("meows appreciatively");
    }
}

Cat 클래스 두 인터페이스 모두에서 상속 된 abstract 메소드를 구현 해야하는 방법에 주목하십시오. 게다가, 클래스가 실제로 필요한만큼 많은 인터페이스를 구현할 수있는 방법에 주목하십시오 ( JVM 제한 으로 인해 65,535 한계가 있음 ).

NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid

Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid

노트 :

  1. 인터페이스에서 선언 된 모든 변수는 public static final
  2. 인터페이스 메소드에 선언 된 모든 메소드는 public abstract (이 명령문은 Java 7을 통해서만 유효합니다. Java 8부터는 인터페이스에 메소드가있을 수 있으며, 추상화 할 필요는 없습니다. 이러한 메소드를 기본 메소드 라고 합니다 )
  3. 인터페이스를 final 로 선언 할 수 없습니다.
  4. 둘 이상의 인터페이스가 동일한 서명을 가진 메소드를 선언하면 효과적으로 하나의 메소드로 취급되며 구현 된 인터페이스 메소드와 구분할 수 없습니다
  5. 컴파일시 각 인터페이스에 대해 해당 InterfaceName.class 파일이 생성됩니다.

인터페이스 확장하기

인터페이스는 extends 키워드를 통해 다른 인터페이스를 확장 할 수 있습니다.

public interface BasicResourceService {
    Resource getResource();
}

public interface ExtendedResourceService extends BasicResourceService {
    void updateResource(Resource resource);
}

이제 ExtendedResourceService 를 구현하는 클래스는 getResource()updateResource() 모두 구현해야합니다.

다중 인터페이스 확장

클래스와 달리 extends 키워드는 여러 인터페이스 (쉼표로 구분)를 확장하여 인터페이스를 새로운 인터페이스로 결합 할 수 있습니다

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
    Resource updateResource(Resource resource);
}

이 경우 ExtendedResourceService 를 구현하는 클래스는 getResource() , getAlternateResource()updateResource() 를 구현해야합니다.

Generics에서 인터페이스 사용

다른 유형의 채널 (예 : AMQP, JMS 등)간에 데이터를 게시 / 소비 할 수있는 인터페이스를 정의하려고하지만 구현 세부 사항을 전환 할 수 있기를 원한다고 가정 해 봅시다.

여러 구현에서 재사용 할 수있는 기본 IO 인터페이스를 정의합시다.

public interface IO<IncomingType, OutgoingType> {

    void publish(OutgoingType data);
    IncomingType consume();
    IncomingType RPCSubmit(OutgoingType data);

}

이제 그 인터페이스를 인스턴스화 할 수 있지만, 그 메소드에 대한 기본 구현이 없으므로 인스턴스화 할 때 구현이 필요합니다.

    IO<String, String> mockIO = new IO<String, String>() {

        private String channel = "somechannel";

        @Override
        public void publish(String data) {
            System.out.println("Publishing " + data + " to " + channel);
        }

        @Override
        public String consume() {
            System.out.println("Consuming from " + channel);
            return "some useful data";
        }

        @Override
        public String RPCSubmit(String data) {
            return "received " + data + " just now ";
        }

    };

    mockIO.consume(); // prints: Consuming from somechannel
    mockIO.publish("TestData"); // Publishing TestData to somechannel
    System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now

우리는 또한 그 인터페이스에 더 유용한 것을 할 수 있습니다, 우리가 그것을 사용하여 기본적인 RabbitMQ 함수를 감싸고 싶다고합시다 :

public class RabbitMQ implements IO<String, String> {

    private String exchange;
    private String queue;

    public RabbitMQ(String exchange, String queue){
        this.exchange = exchange;
        this.queue = queue;
    }

    @Override
    public void publish(String data) {
        rabbit.basicPublish(exchange, queue, data.getBytes());
    }

    @Override
    public String consume() {
        return rabbit.basicConsume(exchange, queue);
    }

    @Override
    public String RPCSubmit(String data) {
        return rabbit.rpcPublish(exchange, queue, data);
    }

}

마지막으로 시스템을 다시 시작한 후 내 웹 사이트 방문수를 계산 한 다음 총 방문수를 표시 할 수있는 방법으로이 IO 인터페이스를 사용하려고한다고 가정 해 봅시다. 다음과 같이 할 수 있습니다.

import java.util.concurrent.atomic.AtomicLong;

public class VisitCounter implements IO<Long, Integer> {

    private static AtomicLong websiteCounter = new AtomicLong(0);
    
    @Override
    public void publish(Integer count) {
        websiteCounter.addAndGet(count);
    }

    @Override
    public Long consume() {
        return websiteCounter.get();
    }

    @Override
    public Long RPCSubmit(Integer count) {
        return websiteCounter.addAndGet(count);
    }
    
}

이제 VisitCounter를 사용 해보자.

    VisitCounter counter = new VisitCounter();

    // just had 4 visits, yay
    counter.publish(4);
    // just had another visit, yay
    counter.publish(1);

    // get data for stats counter
    System.out.println(counter.consume()); // prints 5

    // show data for stats counter page, but include that as a page view
    System.out.println(counter.RPCSubmit(1)); // prints 6

여러 인터페이스를 구현할 때 동일한 인터페이스를 두 번 구현할 수 없습니다. 일반 인터페이스에도 적용됩니다. 따라서 다음 코드는 유효하지 않으며 컴파일 오류가 발생합니다.

interface Printer<T> {
    void print(T value);
}

// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
    @Override public void print(Double d){ System.out.println("Decimal: " + d); }
    @Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}

인터페이스의 유용성

인터페이스는 많은 경우에 매우 유용 할 수 있습니다. 예를 들어, 동물의 목록이 있었고 목록을 반복하면서 각자 소리를 출력하고 싶다고 가정 해 보겠습니다.

{cat, dog, bird}

이를 수행하는 한 가지 방법은 인터페이스를 사용하는 것입니다. 이렇게하면 모든 클래스에서 동일한 메소드를 호출 할 수 있습니다.

public interface Animal {
    public String getSound();
}

implements Animalimplements Animal 하는 모든 클래스에는 getSound() 메서드가 있어야하지만, 모두 다른 구현을 가질 수 있습니다.

public class Dog implements Animal {
    public String getSound() {
        return "Woof";
    }
}

public class Cat implements Animal {
    public String getSound() {
        return "Meow";
    }
}

public class Bird implements Animal{
    public String getSound() {
        return "Chirp";
    }
}

우리는 이제 세 개의 다른 클래스를 가지며, 각각은 getSound() 메소드를 가지고있다. 이 클래스의 모든 때문에 implement Animal 선언 인터페이스, getSound() 메소드를의 인스턴스 Animal 가질 수 getSound() 불려

Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();

dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"

이들 각각은 Animal 이기 때문에 우리는 동물들을리스트에 넣고 루프를 반복하며 소리를 출력 할 수 있습니다

Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
    System.out.println(animal.getSound());
}

배열의 순서가 Dog , Cat , Bird 이면 "Woof Meow Chirp" 가 콘솔에 인쇄됩니다.

인터페이스는 함수의 반환 값으로 사용할 수도 있습니다. 예를 들어, 입력이 "dog" 이면 Dog 반환하고, 입력이 "cat" 이면 Cat , "bird" 이면 Bird 반환 한 다음 해당 동물의 소리를 출력 할 수 있습니다.

public Animal getAnimalByName(String name) {
    switch(name.toLowerCase()) {
        case "dog":
            return new Dog();
        case "cat":
            return new Cat();
        case "bird":
            return new Bird();
        default:
            return null;
    }
}

public String getAnimalSoundByName(String name){
    Animal animal = getAnimalByName(name);
    if (animal == null) {
        return null;
    } else { 
        return animal.getSound();
    }
}

String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null

인터페이스는 확장성에도 유용합니다. 새 유형의 Animal 을 추가하려는 경우 수행하는 작업으로 아무 것도 변경할 필요가 없기 때문입니다.

추상 클래스에서 인터페이스 구현하기

interface 정의 된 메소드는 기본적으로 public abstract 입니다. 때 abstract class 구현 interface ,에 정의 된 모든 방법 interface 에 의해 구현 될 필요가 없습니다 abstract class . 이는 abstract 으로 선언 된 class 가 추상 메소드 선언을 포함 할 수 있기 때문입니다. 따라서 모든 인터페이스 및 / 또는 abstract class 에서 상속 된 abstract 메서드를 구현하는 것은 첫 번째 구체적인 하위 클래스의 책임입니다.

public interface NoiseMaker {
    void makeNoise();
}

public abstract class Animal implements NoiseMaker {
    //Does not need to declare or implement makeNoise()
    public abstract void eat();
}

//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
    @Override
    public void makeNoise() {
        System.out.println("Borf borf");
    }

    @Override
    public void eat() {
        System.out.println("Dog eats some kibble.");
    }
}

Java 8 이후부터는 interface 가 메서드의 default 구현을 선언 할 수 있습니다. 즉, 메서드가 abstract 이 아니므로 특정 하위 클래스는 메서드를 구현하지 않아도되지만 재정의하지 않으면 default 구현을 상속합니다.

기본 메소드

Java 8에서 소개 된 기본 메소드는 인터페이스 내부에서 구현을 지정하는 방법입니다. 이것은 인터페이스의 부분 구현을 제공하고 서브 클래스 계층을 제한함으로써 전형적인 "Base"또는 "Abstract"클래스를 피하는 데 사용될 수 있습니다.

옵저버 패턴 구현

예를 들어 Observer-Listener 패턴을 인터페이스에 직접 구현하여 구현 클래스에 더 많은 유연성을 제공 할 수 있습니다.

interface Observer {
    void onAction(String a);
}

interface Observable{
    public abstract List<Observer> getObservers();

    public default void addObserver(Observer o){
        getObservers().add(o);
    }

    public default void notify(String something ){
        for( Observer l : getObservers() ){
            l.onAction(something);
        }
    }
}

이제 Observable 인터페이스를 구현하여 모든 클래스를 "Observable"로 만들 수 있으며 다른 클래스 계층 구조의 일부가 될 수 있습니다.

abstract class Worker{
    public abstract void work();
}

public class MyWorker extends Worker implements Observable {

    private List<Observer> myObservers = new ArrayList<Observer>();
    
    @Override
    public List<Observer> getObservers() {
        return myObservers;
    }

    @Override
    public void work(){
        notify("Started work");

        // Code goes here...

        notify("Completed work");
    }
    
    public static void main(String[] args) {    
        MyWorker w = new MyWorker();
       
        w.addListener(new Observer() {
            @Override
            public void onAction(String a) {
                System.out.println(a + " (" + new Date() + ")");
            }
        });
        
        w.work();
    }
}

다이아몬드 문제

Java 8의 컴파일러는 클래스가 동일한 서명을 가진 메소드를 포함하는 인터페이스를 구현할 때 발생하는 다이아몬드 문제 를 인식합니다.

이를 해결하기 위해서는 구현 클래스가 공유 메서드를 재정의하고 자체 구현을 제공해야합니다.

interface InterfaceA {
    public default String getName(){
        return "a";
    }
}

interface InterfaceB {
    public default String getName(){
        return "b";
    }
}

public class ImpClass implements InterfaceA, InterfaceB {

    @Override
    public String getName() {    
        //Must provide its own implementation
        return InterfaceA.super.getName() + InterfaceB.super.getName();
    }
    
    public static void main(String[] args) {    
        ImpClass c = new ImpClass();
        
        System.out.println( c.getName() );                   // Prints "ab"
        System.out.println( ((InterfaceA)c).getName() );     // Prints "ab"
        System.out.println( ((InterfaceB)c).getName() );     // Prints "ab"
    }
}

컴파일 할 수없는 다른 반환 유형을 가진 동일한 이름과 매개 변수를 가진 메서드를 갖는 문제가 여전히 있습니다.

기본 방법을 사용하여 호환성 문제 해결

기본 메소드 구현은 인터페이스가 여러 클래스에 의해 사용되는 기존 시스템의 인터페이스에 메소드가 추가되는 경우 매우 편리합니다.

전체 시스템이 손상되지 않도록 인터페이스에 메소드를 추가 할 때 기본 메소드 구현을 제공 할 수 있습니다. 이렇게하면 시스템은 여전히 ​​컴파일되고 실제 구현은 단계적으로 완료 될 수 있습니다.


자세한 내용은 기본 메서드 항목을 참조하십시오.

인터페이스의 수식어

Oracle Java Style Guide에는 다음과 같이 언급되어 있습니다.

수정자가 암시적일 때 작성하지 않아야합니다.

(컨텍스트와 실제 Oracle 문서에 대한 링크는 Oracle Official Code Standard의 수정 자를 참조하십시오.)

이 스타일 지침은 특히 인터페이스에 적용됩니다. 다음 코드 스 니펫을 살펴 보겠습니다.

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

    public static void staticMethod() { ... }
    public default void defaultMethod() { ... }
}

변수

모든 인터페이스 변수는 암시 적으로 public (모두 액세스 가능), static (인터페이스 이름으로 액세스 가능) 및 final (선언 중에 초기화해야 함) 수정자를 갖는 암시 적으로 상수 입니다.

public static final int VARIABLE = 0;

행동 양식

  1. 구현제공하지 않는 모든 메소드는 암시 적으로 publicabstract 입니다.
public abstract void method();
Java SE 8
  1. static 또는 default 수식자를 가지는 모든 메소드 는 구현을 제공해야하며 암묵적으로 public 되어야합니다 .
public static void staticMethod() { ... }

위의 모든 변경 사항이 적용되면 다음과 같이 처리됩니다.

interface I {
    int VARIABLE = 0;
    
    void method();

    static void staticMethod() { ... }
    default void defaultMethod() { ... }
}

제한된 형식 매개 변수 강화

제한된 형식 매개 변수를 사용하면 제네릭 형식 인수에 대한 제한을 설정할 수 있습니다.

class SomeClass {

}

class Demo<T extends SomeClass> {

}

그러나 형식 매개 변수는 단일 클래스 형식에만 바인딩 할 수 있습니다.

인터페이스 유형은 이미 바인딩이있는 유형에 바인딩 될 수 있습니다. 이 작업은 & 기호를 사용하여 수행됩니다.

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

이렇게하면 여러 유형에서 파생되도록 형식 인수가 필요할 수있는 바인드가 강화됩니다.

여러 인터페이스 유형을 유형 매개 변수에 바인딩 할 수 있습니다.

class Demo<T extends SomeClass & FirstInterface & SecondInterface> {

}

그러나주의해서 사용해야합니다. 다중 인터페이스 바인딩은 일반적으로 코드 냄새 의 징조이며, 다른 유형의 어댑터 역할을하는 새로운 유형을 만들어야한다고 제안합니다.

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow