2009-04-10 12 views
24

ho la classe ViewValue definiti come segue:Java - mappare un elenco di oggetti da una lista con i valori della loro proprietà attributi

class ViewValue { 

private Long id; 
private Integer value; 
private String description; 
private View view; 
private Double defaultFeeRate; 

// getters and setters for all properties 
} 

Da qualche parte nel mio codice ho bisogno di convertire un elenco di istanze ViewValue ad un lista contenente i valori dei campi id dal corrispondente ViewValue.

lo faccio usando ciclo foreach:

List<Long> toIdsList(List<ViewValue> viewValues) { 

    List<Long> ids = new ArrayList<Long>(); 

    for (ViewValue viewValue : viewValues) { 
     ids.add(viewValue.getId()); 
    } 

    return ids; 

}

Esiste un approccio migliore a questo problema?

risposta

22

MODIFICA: questa risposta si basa sull'idea che è necessario eseguire operazioni simili per entità diverse e proprietà diverse altrove nel codice. Se solo solo è necessario convertire l'elenco di ViewValues ​​in un elenco di ID per ID, quindi attenersi al codice originale. Se si desidera una soluzione più riutilizzabile, tuttavia, continua a leggere ...

Vorrei dichiarare un'interfaccia per la proiezione, ad es.

public interface Function<Arg,Result> 
{ 
    public Result apply(Arg arg); 
} 

Quindi è possibile scrivere una sola metodo di conversione generico:

public <Source, Result> List<Result> convertAll(List<Source> source, 
    Function<Source, Result> projection) 
{ 
    ArrayList<Result> results = new ArrayList<Result>(); 
    for (Source element : source) 
    { 
     results.add(projection.apply(element)); 
    } 
    return results; 
} 

Quindi è possibile definire le proiezioni semplice come questo:

private static final Function<ViewValue, Long> ID_PROJECTION = 
    new Function<ViewValue, Long>() 
    { 
     public Long apply(ViewValue x) 
     { 
      return x.getId(); 
     } 
    }; 

e applicarlo proprio come questo:

List<Long> ids = convertAll(values, ID_PROJECTION); 

(Obvi neamente utilizzando K & R rinforzo e linee più lunghe rende la dichiarazione di proiezione un po 'più corto :)

Francamente tutto questo sarebbe molto più bello con le espressioni lambda, ma non importa ...

+4

soluzione Molto Javaesque - ma non in senso buono. Non riduce esattamente la quantità o la complessità del codice, vero? –

+1

Direi che è più LINQ-esque che Java-esque personalmente. Preferisco creare il codice di conversione e quindi isolare l'aspetto di proiezione, ma se preferisci duplicare la logica di conversione di volte, va bene. Sarebbe molto più carino con le espressioni lambda, ovviamente. –

+1

Non è qualcosa che facciamo con Comparator in Java? –

2

Questo dipende da ciò che si poi fare con il List<Long>, e il List<ViewValue>

Per esempio si potrebbe ottenere una funzionalità sufficiente di creare una propria implementazione List che avvolge una List<ViewValue>, implementando iterator() con un'implementazione iteratore che scorre sopra le ViewValues, restituendo l'id.

3

Si potrebbe ude un wrapper:

public class IdList impements List<Long> 
{ 
    private List<ViewValue> underlying; 

    pubic IdList(List<ViewValue> underying) 
    { 
     this.underlying = underying; 
    } 

    public Long get(int index) 
    { 
     return underlying.get(index).getId() 
    } 

    // other List methods 
} 

anche se questo è ancora più noioso il lavoro, si potrebbe migliorare le prestazioni.

Si potrebbe anche implementare genericamente la propria soluzione e la mia utilizzando il riflesso, ma questo sarebbe molto negativo per la perforazione.

Theres non è una soluzione generica semplice e corta in Java. In Groovy, useresti semplicemente collect(), ma credo che coinvolga anche la riflessione.

3

Ho implementato una piccola libreria funzionale per questo caso.Uno dei metodi ha questa firma:

<T> List<T> mapToProperty(List<?> objectList, String property, Class<T> returnType) 

che prende la corda e utilizza la reflection per creare una chiamata alla proprietà allora restituisce una lista sostenuta dal ObjectList in cui ottenere e iteratore implementato utilizzando questa chiamata proprietà.

Le funzioni mapToProperty sono implementate in termini di una funzione di mappa generale che accetta una funzione come un mappatore, proprio come un altro post descritto. Molto utile.

Vi suggerisco di leggere su programmazione di base functionl e, in particolare, dare un'occhiata a funtori (oggetti che implementano una funzione di mappa)

Edit: Riflessione in realtà non deve essere costoso. La JVM è migliorata molto in questo settore. Assicurati di compilare una volta l'invocazione e riutilizzarla.

Edit2: Codice di esempio

public class MapExample { 
    public static interface Function<A,R> 
    { 
     public R apply(A b); 
    } 

    public static <A,R> Function<A,R> compilePropertyMapper(Class<A> objectType, String property, Class<R> propertyType) 
    { 
     try { 
      final Method m = objectType.getMethod("get" + property.substring(0,1).toUpperCase() + property.substring(1)); 

      if(!propertyType.isAssignableFrom(m.getReturnType())) 
       throw new IllegalArgumentException(
        "Property "+property+" on class "+objectType.getSimpleName()+" is not a "+propertyType.getSimpleName() 
       ); 

      return new Function<A,R>() 
      { 
       @SuppressWarnings("unchecked") 
       public R apply(A b) 
       { 
        try { 
         return (R)m.invoke(b); 
        } catch (Exception e) { 
         throw new RuntimeException(e); 
        } 
       }; 
      }; 

     } catch (Exception e) { 
      throw new RuntimeException(e); 
     } 
    } 

    public static <T1,T2> List<T2> map(final List<T1> list, final Function<T1,T2> mapper) 
    { 
     return new AbstractList<T2>() 
     { 
      @Override 
      public T2 get(int index) { 
       return mapper.apply(list.get(index)); 
      } 

      @Override 
      public int size() { 
       return list.size(); 
      } 
     }; 
    } 

    @SuppressWarnings("unchecked") 
    public static <T1,T2> List<T2> mapToProperty(List<T1> list, String property, Class<T2> propertyType) 
    { 
     if(list == null) 
      return null; 
     else if(list.isEmpty()) 
      return Collections.emptyList(); 

     return map(list,compilePropertyMapper((Class<T1>)list.get(0).getClass(), property, propertyType)); 
    } 
} 
31

Si potrebbe farlo in un one-liner utilizzando Commons BeanUtils e Collezioni:
(? Il motivo per cui scrivere il proprio codice quando altri lo hanno fatto per voi)

import org.apache.commons.beanutils.BeanToPropertyValueTransformer; 
import org.apache.commons.collections.CollectionUtils; 

... 

List<Long> ids = (List<Long>) CollectionUtils.collect(viewValues, 
             new BeanToPropertyValueTransformer("id")); 
+3

+1 per il commento "perché scrivi il tuo codice quando altri lo hanno fatto per te?" Questo è un problema che affrontiamo quotidianamente, dovendo creare una soluzione a un problema che è già stato risolto. –

+0

PropertyUtils.getProperty (oggetto, propertyName); utilizza la riflessione sotto il cofano che avrebbe un costo extra in termini di prestazioni. Per quello vorrei usare invece l'approccio di Jon Skeet. –

+4

L'unica cosa che non mi piace è la mancanza di sicurezza del tipo: se il campo 'viewValue.id' è stato rinominato, il codice si compilerebbe ma non funzionerà in fase di esecuzione. –

24

Utilizzare raccolte Google. Esempio:

Function<ViewValue, Long> transform = new Function<ViewValue, Long>() { 
     @Override 
     public Long apply(ViewValue from) { 
      return from.getId(); 
     } 
    }; 
    List<ViewValue> list = Lists.newArrayList(); 
    List<Long> idsList = Lists.transform(list, transform); 

UPDATE:

su Java 8 non occorre Guava. È possibile:

import com.example.ViewValue; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

Function<ViewValue, Long> transform = ViewValue::getId; 
List<ViewValue> source = new ArrayList<>(); 
List<Long> result = source.stream().map(transform).collect(Collectors.toList()); 

O semplicemente:

List<ViewValue> source= new ArrayList<>(); 
List<Long> result = source.stream().map(ViewValue::getId).collect(Collectors.toList()); 

prossimo aggiornamento (l'ultima dopo Javaslang al cambio di nome Vavr):

Attualmente vale la pena di menzionare circa la soluzione con Biblioteca Javaslang ( http://www.javaslang.io/) Libreria Vavr (http://www.vavr.io/). Supponiamo che abbiamo la nostra lista con gli oggetti originali:

List<ViewValue> source = newArrayList(new ViewValue(1), new ViewValue(2), new ViewValue(2)); 

potremmo fare trasformazione con classe List dalla biblioteca Javaslang (sul lungo periodo la raccolta non è conveniente):

List<Long> result = io.vavr.collection.List.ofAll(source).map(ViewValue::getId).toJavaList(); 

Ma si vuole vedere la potenza con solo le liste Javaslang:

io.vavr.collection.List<ViewValue> source = javaslang.collection.List.of(new ViewValue(1), new ViewValue(2), new ViewValue(3)); 
io.vavr.collection.List<Long> res = source.map(ViewValue::getId); 

incoraggio a dare un'occhiata collezioni disponibili e nuovi tipi su quella libreria (mi piace soprattutto il tipo di prova). La documentazione è disponibile al seguente indirizzo: http://www.javaslang.io/javaslang-docs/ http://www.vavr.io/vavr-docs/.

PS. A causa dell'Oracolo e della parola "Java" all'interno del nome, dovevano cambiare il nome della libreria da javaslang a qualcos'altro. Avevano deciso di Vavr.

14

Possiamo farlo in una sola riga di codice utilizzando Java 8

List<Long> ids = viewValues.stream().map(ViewValue::getId).collect(Collectors.toList()); 

Per maggiori informazioni: Java 8 - Streams

+0

Fortunatamente stiamo ottenendo le cose semplificate mentre stiamo sviluppando :) –

+2

Ok lasciatemelo dire: vorrei aggiungere commenti, che questo è abilitato con Java8 che non era disponibile al momento della scrittura. La risposta è in realtà un grande passo in avanti verso ciò che è stato scritto 7 anni fa. – Alex

Problemi correlati