Java Language Utiliser des interfaces avec des génériques


Exemple

Supposons que vous souhaitiez définir une interface permettant de publier / consommer des données vers et depuis différents types de canaux (par exemple, AMQP, JMS, etc.), mais que vous souhaitiez pouvoir changer les détails d'implémentation ...

Définissons une interface IO de base pouvant être réutilisée sur plusieurs implémentations:

public interface IO<IncomingType, OutgoingType> {

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

}

Maintenant, je peux instancier cette interface, mais comme nous n'avons pas d'implémentations par défaut pour ces méthodes, il faudra une implémentation lorsque nous l'instancierons:

    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

Nous pouvons aussi faire quelque chose de plus utile avec cette interface, disons que nous voulons l'utiliser pour envelopper certaines fonctions de base de 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);
    }

}

Disons que je veux utiliser cette interface IO maintenant pour compter les visites sur mon site depuis le dernier redémarrage du système et afficher ensuite le nombre total de visites - vous pouvez faire quelque chose comme ceci:

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);
    }
    
}

Maintenant, utilisons le 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

Lorsque vous implémentez plusieurs interfaces, vous ne pouvez pas implémenter la même interface deux fois. Cela s'applique également aux interfaces génériques. Ainsi, le code suivant n'est pas valide et entraînera une erreur de compilation:

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); }
}