Tipi di Java parametrizzati (generici)

3

Considera questo codice "legacy":

public interface IPersistentCollection {
    IPersistentCollection cons(Object o);
}

Generato in Java, potrebbe diventare qualcosa del genere:

public interface IPersistentCollection<T> {
    IPersistentCollection<T> cons(T o);
}

L'aggiunta di un nuovo elemento a una raccolta Java mutevole non dovrebbe cambiare il tipo della raccolta esistente. Ma a differenza delle collezioni Java, cons () restituisce una collezione completamente nuova e immutabile, lasciando invariata la vecchia raccolta, aprendo la possibilità che potrebbe assumere un tipo diverso rispetto alla collezione originale.

  1. Ovviamente, dovresti essere in grado di avere un oggetto di tipo Ford in una collezione di Cars e ottenere una collezione di Cars indietro (covarianza). Penso che questo sia coperto dal precedente esempio generico.

  2. Se Auto e Treno estendessero entrambe una classe di veicoli, sarebbe utile poter trasportare un treno per una collezione di auto e ottenere indietro una collezione di veicoli (controvarianza). Come lo scriverei anche io? Ho pensato dichiarando nuove variabili di tipo limitate, S ed E per "Veicolo" e "Treno" in questo esempio:

    // Illegal because of <S super T>
    <S super T, E extends S> IPersistentCollection<S> cons(E o);
    
    // Simpler, but still Illegal because java disallows <S super T>
    <S super T> IPersistentCollection<S> cons(S o);
    
  3. Se vogliamo assumere che il programmatore sappia cosa stanno facendo, dovresti avere a disposizione qualcosa di totalmente estraneo a una collezione e recuperare una collezione di oggetti. Penso che questo sia il caso estremo di contravarianza, ma non sono sicuro che ci sia persino un nome per questo ("Dynamic Language" forse?).

Penso che se il # 2 fosse legale, coprirebbe i casi n. 1 e n. 3.

Le mie domande sono:

A. In che misura è possibile fare # 2 in Java?

B. Il n. 2 è possibile / più semplice in altre lingue? Scala? Haskell? ML?

C. In teoria, un sistema di tipi che preferiva la versione più specifica di S nell'esempio 2 poteva gestire una definizione come questa. Che libro posso leggere sui sistemi di tipi senza un dottorato in matematica? "Tipi e linguaggi di programmazione" di Pierce è il posto migliore in cui iniziare?

Il codice di esempio sarebbe apprezzato. Se sto usando termini come covarianza e controvarianza in modo errato, gradirei essere educatamente corretto.

    
posta GlenPeterson 30.08.2014 - 22:08
fonte

2 risposte

2

Scala List s funziona in questo modo per gli operatori :: (cons) e ++ (concat), ma non per + , :+ e +: (che va bene perché a volte ciò che vuoi). La firma del tipo assomiglia a:

::[B >: T](x: B): List[B]

// Returns a List[Any] (Any is Scala's "Object" type)
5 :: List("example")

Questo dice che B è una superclasse di T , il tipo di Lista su cui stai lavorando. Stai utilizzando un elemento B e restituendo un List di B s.

Diciamo che T è un Car e B è un Vehicle . Il parametro x può essere qualsiasi sottoclasse di Vehicle a causa della covarianza, quindi non è necessario specificare un terzo tipo. Non conosco anche il sistema di tipo Java, ma immagino che il suo equivalente (se supportato) sia:

<B super T> IPersistentCollection<B> cons(B o);
    
risposta data 30.08.2014 - 23:49
fonte
1

Angelika Langer è il mio nuovo eroe. Ha un'incredibile FAQ di Java Generics link

  1. Aveva l'intuizione " class Box<T super Number> {...} non è consentita" ... e "le congiunzioni con le dichiarazioni di metodo, i parametri di tipo con un limite inferiore potrebbero essere occasionalmente utili". Wow! A.) Non sto sprecando il mio tempo a provare l'impossibile e B.) c'è una possibilità che quello che sto cercando di fare possa essere utile!

  2. Suggerisce la soluzione alternativa per l'utilizzo di un metodo statico come segue:

    static <A, B, X extends A, Y extends B>
            B addToMap(Pair<X,Y> pair, Map<A,B> map) {
        return map.put(pair.first,pair.second);
    }
    

Sono stato in grado di applicarlo direttamente a un metodo statico nella mia interfaccia, che ha funzionato!

@SuppressWarnings("unchecked")
static <S, E extends S, T extends S> Consable<S> cons(Consable<T> cs, E e) {
    return Consable3.of((S) e, (Consable<S>) cs);
}

Per un breve periodo ho pensato che questa stessa tecnica possa essere applicata a un metodo di istanza, ma non può:

public static interface Consable<T> {
    // T is the type of our existing collection (defined above)
    // E is the new element being cons-ed with our collection
    // S is a super-type of both T and E and therefore the
    //   type of our new collection.
    <S, E extends S, T extends S> Consable<S> cons(E e);
}

...

class Animal {}
class Mammal extends Animal {}

...

// Consable<Vehicle> vehicles = cars.cons(new Train());
Consable<Animal> vehicles = cars.cons(new Mammal()); 

Commentando le cose consce ai veicoli, che compila! Eek! E corre!

$ javac -Xlint JavaConsSignature.java 
$ java JavaConsSignature
JavaConsSignature$Mammal@15db9742
JavaConsSignature$Car@6d06d69c
JavaConsSignature$Chevy@7852e922
JavaConsSignature$Chevy@4e25154f
JavaConsSignature$Chevy@70dea4e

Quindi metodo statico: buono. Metodo di istanza: cattivo. Non supporta la fluente costruzione dell'interfaccia, ma almeno puoi avere questo tipo particolare di sicurezza di tipo su un qualche tipo di metodo in Java.

    
risposta data 01.09.2014 - 04:33
fonte

Leggi altre domande sui tag