2009-04-10 3 views
24

ich die ViewValue Klasse definiert sich wie folgt:Java - Karte eine Liste von Objekten zu einer Liste mit Werten ihrer Eigenschaft Attribute

class ViewValue { 

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

// getters and setters for all properties 
} 

Irgendwo in meinem Code Ich brauche eine Liste von ViewValue Instanzen ein konvertieren Liste mit Werten von ID-Feldern aus dem entsprechenden ViewValue.

ich sie mit foreach-Schleife:

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

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

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

    return ids; 

}

Gibt es eine bessere Lösung für dieses Problem?

Antwort

22

EDIT: Diese Antwort basiert auf der Idee, dass Sie ähnliche Dinge für andere Entitäten und andere Eigenschaften an anderer Stelle in Ihrem Code tun müssen. Wenn Sie nur brauchen, um die Liste der ViewValues ​​in eine Liste von Longs nach ID zu konvertieren, dann bleiben Sie mit Ihrem ursprünglichen Code. Wenn Sie jedoch eine wiederverwendbare Lösung wünschen, lesen Sie weiter ...

Ich würde eine Schnittstelle für die Projektion, z.

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

Dann können Sie einfach Projektionen wie folgt definieren:

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

Und genau wie diese gelten:

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

Dann können Sie ein einzelnes generischen Umwandlungsmethode schreiben

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

(Obvi laufend Verspannung mit K & R und mehr Linien macht dagegen die Projektions Erklärung ein wenig kürzer :)

Ehrlich gesagt wäre dies alles viel schöner mit Lambda-Ausdrücke, aber nie ...

+4

Sehr Javaesque Lösung - aber nicht in einem guten Weg. Es reduziert nicht genau die Menge oder Komplexität des Codes, oder? –

+1

Ich würde sagen, es ist mehr LINQ-esque als Java-esque persönlich. Ich würde lieber den Conversion-Code erstellen und dann den Projektionsaspekt isolieren, aber wenn Sie die Conversion-Logik lieber mehrfach duplizieren möchten, ist das in Ordnung. Mit Lambda-Ausdrücken wäre es natürlich viel schöner. –

+1

Ist es nicht so, wie wir es mit Comparator in Java machen? –

2

Das auf, was Sie hängt dann tun mit dem List<Long> und die List<ViewValue>

Zum Beispiel könnten Sie eine ausreichende Funktionalität erhalten von Ihrer eigenen Liste Implementierung zu schaffen, die eine List<ViewValue> Wraps, iterator() mit einem Iterator-Implementierung, die iteriert über die ViewValues ​​Umsetzung der ID zurück.

3

Sie könnten einen Wrapper UDE:

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 
} 

obwohl das ist noch langweilige Arbeit, es Leistung verbessern könnte.

Sie könnten auch Ihre und meine Lösung generisch mit Reflektion implementieren, aber das wäre sehr schlecht für die Leistung.

Theres keine kurze und einfache generische Lösung in Java, ich fürchte. In Groovy würden Sie einfach collect() verwenden, aber ich glaube, das beinhaltet auch die Reflexion.

3

Ich habe eine kleine funktionale Bibliothek für diesen Anwendungsfall implementiert.Eine der Methoden hat diese Unterschrift:

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

die den String und verwendet Reflektion, nimmt einen Aufruf an die Eigenschaft zu erstellen, dann gibt er eine Liste von Object gesichert wo erhalten und Iterator implementiert diese Eigenschaft Anruf mit.

Die mapToProperty-Funktionen werden im Sinne einer allgemeinen Kartenfunktion implementiert, die eine Funktion als Mapper verwendet, genau wie ein anderer beschriebener Beitrag. Sehr hilfreich.

Ich schlage vor, Sie zu den grundlegenden functionl Programmierung lesen und insbesondere einen Blick auf Funktoren (Objekte, die eine Map-Funktion implementiert werden) nehmen

Edit: Reflexion wirklich nicht teuer sein muss. Die JVM hat sich in diesem Bereich stark verbessert. Stellen Sie nur sicher, dass Sie den Aufruf einmal kompilieren und wiederverwenden.

Edit2: Beispielcode

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

Sie es in einem Einzeiler mit Commons BeanUtils und Sammlungen tun konnte:
(? Warum schreiben Sie Ihren eigenen Code, wenn andere haben es für Sie getan)

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 für den Kommentar "Warum schreiben Sie Ihren eigenen Code, wenn andere es für Sie getan haben?" Dies ist ein Problem, dem wir uns täglich stellen müssen - eine Lösung für ein Problem zu finden, das bereits gelöst wurde. –

+0

PropertyUtils.getProperty (Objekt, EigenschaftName); verwendet Reflexion unter der Haube, die einen zusätzlichen Leistungsaufwand hätte. Dafür würde ich Jon Skeets Ansatz verwenden. –

+4

Das einzige, was ich nicht mag ist das Fehlen von Typ Sicherheit - wenn das 'viewValue.id' Feld umbenannt wurde, würde der Code noch kompilieren, aber zur Laufzeit fehlschlagen. –

24

Verwenden Sie Google Sammlungen. Beispiel:

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:

Auf Java 8 Sie nicht Guava brauchen. Sie können:

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

Oder einfach:

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

nächsten Update (die letzte nach Javaslang zu Vavr Namensänderung):

wert es ist Zeit über die Lösung zu erwähnen, mit Javaslang Bibliothek ( http://www.javaslang.io/) Vavr Bibliothek (http://www.vavr.io/). Nehmen wir an, dass wir unsere Liste mit echten Objekten haben:

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

Wir Transformation mit List-Klasse von Javaslang Bibliothek machen könnte (auf lange Sicht die collect ist nicht bequem laufen):

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

Aber Sie werden siehe die Macht nur mit den Javaslang Listen:

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

ich ermutige einen Blick zu nehmen verfügbare Sammlungen und neue Arten auf dieser Bibliothek (ich mag vor allem den Try-Typ). Sie finden die Dokumentation unter folgender Adresse: http://www.javaslang.io/javaslang-docs/ http://www.vavr.io/vavr-docs/.

PS. Aufgrund des Oracle und des "Java" Wortes innerhalb des Namens mussten sie den Namen der Bibliothek von javaslang in etwas anderes ändern. Sie hatten sich zu Vavr entschlossen.

14

Wir können es in einer einzigen Codezeile tun Java 8 mit

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

Für weitere Informationen: Java 8 - Streams

+0

Glücklicherweise bekommen wir die Dinge vereinfacht, während wir uns entwickeln :) –

+2

Ok, lassen Sie mich das klarstellen: Ich würde Kommentare hinzufügen, dass dies mit Java8 möglich ist, das zum Zeitpunkt des Schreibens noch nicht verfügbar war. Die Antwort ist tatsächlich ein großer Schritt vorwärts zu dem, was vor 7 Jahren geschrieben wurde. – Alex