2015-09-14 27 views
20

Ho un enigma che mi ha indotto a riflettere se esistono classi standard Java che implementano Iterable<T> senza implementare anche Collection<T>. Sto implementando un'interfaccia che mi richiede di definire un metodo che accetti uno Iterable<T>, ma l'oggetto che sto usando per supportare questo metodo richiede uno Collection<T>.Esistono classi standard Java che implementano Iterable senza implementare Collection?

Questo mi ha fatto fare un po 'di codice molto sensato che fornisce alcuni avvisi non controllati quando compilati.

public ImmutableMap<Integer, Optional<Site>> loadAll(
     Iterable<? extends Integer> keys 
) throws Exception { 
    Collection<Integer> _keys; 
    if (keys instanceof Collection) { 
     _keys = (Collection<Integer>) keys; 
    } else { 
     _keys = Lists.newArrayList(keys); 
    } 

    final List<Site> sitesById = siteDBDao.getSitesById(_keys); 
    // snip: convert the list to a map 

Cambiare la mia collezione risultante di utilizzare la più generified Collection<? extends Integer> tipo non elimina l'avvertimento incontrollato per quella linea. Inoltre, non posso modificare la firma del metodo per accettare uno Collection anziché uno Iterable perché in quel momento non sostituisce più il metodo super e non viene richiamato quando necessario.

C'è doesn't seem to be a way around questo problema cast-or-copy: altre domande sono state poste qui e altrove e sembra profondamente radicata nei sistemi di cancellazione generica e di tipo Java. Ma sto chiedendo invece se ci sono mai delle classi che possono implementare Iterable<T> che non implementano anche Collection<T>? Ho dato un'occhiata allo Iterable JavaDoc e sicuramente tutto quello che mi aspetto di passare alla mia interfaccia sarà in realtà una raccolta. Mi piacerebbe usare una classe pre-scritta in-the-wild, invece, sembra molto più probabile che sia passata come parametro e renderebbe l'unità test molto più preziosa.

Sono certo che il pezzo di cast-or-copy che ho scritto funzioni con i tipi che sto usando per il mio progetto a causa di alcuni test unitari che sto scrivendo. Ma mi piacerebbe scrivere un test unitario per alcuni input che è un iterable ma non è una raccolta e finora tutto ciò che sono riuscito a realizzare è l'implementazione di un'implementazione di una classe fittizia.


Per i curiosi, il metodo sto implementando è Guava di CacheLoader<K, V>.loadAll(Iterable<? extends K> keys) e il metodo di supporto è un JDBI istanziata oggetto di accesso ai dati, che richiede una raccolta da utilizzare come il tipo di parametro per l'interfaccia @BindIn. Penso di essere nel giusto pensando che questo sia tangente alla domanda, ma nel caso qualcuno voglia provare il pensiero laterale sul mio problema. Sono consapevole che potrei semplicemente biforcare il progetto JDBI e riscrivere l'annotazione @BindIn per accettare un iterabile ...

+5

Per rispondere alla domanda nel titolo: Sì [ 'ServiceLoader'] (https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) per esempio. – aioobe

+1

Cosa c'è di sbagliato nel fare una copia, cioè creare una nuova collezione? Dovresti copiare solo riferimenti, non creare nuovi oggetti. – VGR

+0

Niente di terribilmente sbagliato, ma stavo cercando un modo per evitare di fare assegnazioni. Come suggerisce il nome, ['Lists.newArrayList'] (http://grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/r06/com/google/common/collect/Lists .java # Lists.newArrayList% 28java.lang.Iterable% 29) può allocare un nuovo array, potenzialmente 'log (n)' volte in quanto non ottiene una dimensione prima di allocare l'array di supporto. (Anche se guardo di nuovo il codice Guava, ho appena realizzato che il mio 'instanceof then cast' è ridondante.) –

risposta

0

Dopo aver letto le ottime risposte e forniti documenti, ho curiosato in un paio di classi e ho trovato quello che sembra essere il vincitore, sia in termini di linearità per il codice di prova e per un titolo di domanda diretto. principale ArrayList implementazione di Java contiene questo gioiello:

public Iterator<E> iterator() { 
    return new Itr(); 
} 

Dove Itr è una classe interna privata con altamente ottimizzato, implementazione personalizzata di Iterator<E>. Sfortunatamente, Iterator non implementa direttamente Iterable, quindi se voglio eseguire il clacson nel mio metodo helper per testare il percorso del codice che non esegue il cast, devo inserirlo nella mia classe junk che implementa Iterable (e non Collection) e restituisce Itr. Questo è un modo pratico per trasformare facilmente una raccolta in una Iterable senza dover scrivere il codice di iterazione da solo.

In una nota finale, la mia versione finale del codice non fa nemmeno il cast stesso, perché lo Lists.newArrayList di Guava fa esattamente quello che stavo facendo con il rilevamento del tipo di runtime nella domanda.

@GwtCompatible(serializable = true) 
public static <E> ArrayList<E> More ...newArrayList(Iterable<? extends E> elements) { 
    checkNotNull(elements); // for GWT 
    // Let ArrayList's sizing logic work, if possible 
    if (elements instanceof Collection) { 
    @SuppressWarnings("unchecked") 
    Collection<? extends E> collection = (Collection<? extends E>) elements; 
    return new ArrayList<E>(collection); 
    } else { 
    return newArrayList(elements.iterator()); 
    } 
} 
11

Anche se non esiste una classe che soddisfi immediatamente le tue esigenze e sia intuitiva per i lettori del tuo codice di prova, tu può facilmente creare una classe anonima che è facile da capire:

static Iterable<Integer> range(final int from, final int to) { 
    return new Iterable<Integer>() { 
     public Iterator<Integer> iterator() { 
      return new Iterator<Integer>() { 
       int current = from; 
       public boolean hasNext() { return current < to; } 
       public Integer next() { 
        if (!hasNext()) { throw new NoSuchElementException(); } 
        return current++; 
       } 
       public void remove() { /*Optional; not implemented.*/ } 
      }; 
     } 
    }; 
} 

Demo.

Questa implementazione è anonimo, e non implementa Collection<Integer>. D'altra parte, produce una sequenza enumerabile di numeri interi non vuota, che puoi controllare completamente.

10

di rispondere alla domanda di cui al titolo:

Esistono classi standard Java che implementano Iterable senza implementare Collection?

Da testo:

Se mai ci sono classi che possono implementare Iterable<T> che non lo fanno anche attuare Collection<T>?

Risposta:

vedere la seguente pagina javadoc: https://docs.oracle.com/javase/8/docs/api/java/lang/class-use/Iterable.html

Ogni sezione che dice Classes in XXX that implement Iterable, elencherà Java classi standard che implementano l'interfaccia. Molti di questi non implementano Collection.

10

kludgy, sì, ma penso che il codice di

Collection<Integer> _keys; 
if (keys instanceof Collection) { 
    _keys = (Collection<Integer>) keys; 
} else { 
    _keys = Lists.newArrayList(keys); 
} 

è perfettamente sana. L'interfaccia Collection<T> estende Iterable<T> e non è possibile implementare la stessa interfaccia con 2 diversi parametri di tipo, quindi non è possibile che una classe possa implementare Collection<String> e Iterable<Integer>, ad esempio.

La classe Integer è definitiva, quindi la differenza tra Iterable<? extends Integer> e Iterable<Integer> è in gran parte accademica.

Nel loro insieme, gli ultimi 2 paragrafi dimostrare che se qualcosa è sia un Iterable<? extends Integer> e Collection, deve essere un Collection<Integer>. Pertanto il tuo codice è sicuro di essere sicuro. Il compilatore non può essere sicuro di questo in modo da poter eliminare l'avviso scrivendo

@SuppressWarnings("unchecked") 

sopra l'istruzione. Dovresti anche includere un commento dall'annotazione per spiegare perché il codice è sicuro.

Per quanto riguarda la domanda se ci sono classi che implementano Iterable ma non Collection, come altri hanno sottolineato la risposta è sì. Comunque penso che quello che stai veramente chiedendo sia se ci sia un qualche punto nell'avere due interfacce. Molti altri hanno chiesto questo. Spesso, quando un metodo ha un argomento Collection (ad es addAll() potrebbe, e forse dovrebbe, essere un Iterable.

Modifica

@Andreas ha fatto notare nei commenti che Iterable stato introdotto solo in Java 5, considerando Collection stato introdotto in Java 1.2, e la maggior parte dei metodi esistenti prendendo un Collection non poteva essere adattati a prendere un Iterable per ragioni di compatibilità

+0

Capisco che questo può essere dimostrato di funzionare, ma queste sono tutte cattive pratiche. In particolare, '@ SuppressWarnings' presuppone che tutti i futuri programmatori che mantengono questo metodo non commettano mai un errore. – VGR

+1

@VGR Ecco perché ho detto di includere un commento per spiegare perché è ok. '@SuppressWarnings (" unchecked ")' è usato in tutto il JDK, ed è per la situazione in cui è possibile dimostrare che qualcosa è sicuro, ma il compilatore non può - esattamente questa situazione.Quali sono le altre cattive pratiche? –

+0

Il cast non controllato. È solo "perfettamente sano" in senso logico, e questa sarà probabilmente la prima cosa da rompere quando qualcun altro interviene per modificare il codice in qualche modo. Lo paragonerei al fatto di dire "Non ho bisogno di una cintura di sicurezza perché sono un buon guidatore". – VGR

5

in API di base, gli unici tipi che sono Iterable ma non Collection. -

interface java.nio.file.Path 

interface java.nio.file.DirectoryStream 
interface java.nio.file.SecureDirectoryStream 

class java.util.ServiceLoader 

class java.sql.SQLException (and subclasses) 

Probabilmente questi sono tutti progetti scadenti.

+1

Non riesco a vedere come alcuni di questi rappresentano "cattivi progetti", potresti spiegare ulteriormente? –

+1

@ ıɯɐƃoʇǝızuǝʞ - "discutibilmente" :) Per me, 'for (x: sqlEx)' è sbagliato perché il significato non è immediatamente chiaro. Sarebbe meglio progettarlo come 'for (x: sqlEx.causes())' – ZhongYu

+0

Utilità di quelle classi a parte, riutilizzare qualcosa in modo non intuitivo chiamato per codice di test sarebbe un cattivo progetto. +1 per enumerare le scelte. –

1

Come accennato nella risposta @bayou.io s', un tale implementazione per Iterable è il nuovo Path classe per l'attraversamento del file system introdotto in Java 7.

Se vi capita di essere su Java 8, Iterable è stato equipaggiato con (vale a dire dato un default metodo) attenzione spliterator() (paga alla sua Attuazione Nota), che consente di utilizzare in combinazione con StreamSupport:

public static <T> Collection<T> convert(Iterable<T> iterable) { 
    // using Collectors.toList() for illustration, 
    // there are other collectors available 
    return StreamSupport.stream(iterable.spliterator(), false) 
         .collect(Collectors.toList()); 
} 

Questo avviene in th e leggera spesa per il fatto che qualsiasi argomento che è già un'implementazione Collection passa attraverso un'operazione di streaming e raccolta non necessaria. Probabilmente dovresti usarlo solo se il desiderio di un approccio standardizzato JDK supera il potenziale impatto in termini di prestazioni, rispetto al cast originale o ai metodi basati su Guava, il che è verosimile dato che stai già usando lo CacheLoader di Guava.

Per testare il tutto, si consideri questo frammento e di campionamento in uscita:

// Snippet 
System.out.println(convert(Paths.get(System.getProperty("java.io.tmpdir")))); 
// Sample output on Windows 
[Users, MyUserName, AppData, Local, Temp] 
Problemi correlati