2015-02-19 16 views
8

In C#/.Net è possibile unire le sequenze IEnumerable con il metodo di estensione Enumerable.Join in modo SQL "JOIN ... ON".Qual è l'equivalente dell'API Java 8 Stream per LINQ Join?

C'è qualcosa di simile in Java 8 (Stream API)? O qual è il modo migliore per simulare Enumerable.Join?

See: https://msdn.microsoft.com/en-us/library/bb534675%28v=vs.100%29.aspx

+0

Quasi sicuramente dovresti scaricare entrambi i flussi in mappe sulla chiave specificata, quindi unire direttamente le mappe. Gli stream Java non sono realmente progettati per essere combinati in alcun modo se non la concatenazione. –

risposta

1

non ho trovato alcun equivalente esistente, ma il metodo di seguito dovrebbe funzionare:

public static <Outer, Inner, Key, Result> Stream<Result> join(
     Stream<Outer> outer, Stream<Inner> inner, 
     Function<Outer, Key> outerKeyFunc, 
     Function<Inner, Key> innerKeyFunc, 
     BiFunction<Outer, Inner, Result> resultFunc) { 

    //Collect the Inner values into a list as we'll need them repeatedly 
    List<Inner> innerList = inner.collect(Collectors.toList()); 

    //matches will store the matches between inner and outer 
    final Map<Outer, List<Inner>> matches = new HashMap<>(); 

    //results will be used to collect the results in 
    final List<Result> results = new ArrayList<>(); 


    outer.forEach(o -> innerList 
      .stream() 
      //Filter to get those Inners for which the Key equals the Key of this Outer 
      .filter(i -> innerKeyFunc.apply(i).equals(outerKeyFunc.apply(o))) 
      .forEach(i -> { 
       if (matches.containsKey(o)) { 
        //This Outer already had matches, so add this Inner to the List 
        matches.get(o).add(i); 
       } else { 
        //This is the first Inner to match this Outer, so create a List 
        List<Inner> list = new ArrayList<>(); 
        list.add(i); 
        matches.put(o, list); 
       } 
      })); 

    matches.forEach((out, in) -> in.stream() 
      //Map each (Outer, Inner) pair to the appropriate Result... 
      .map(i -> resultFunc.apply(out, i)) 
      //...and collect them 
      .forEach(res -> results.add(res))); 

    //Return the result as a Stream, like the .NET method does (IEnumerable) 
    return results.stream(); 
} 

ho fatto solo un breve test del codice utilizzando i seguenti ingressi:

public static void main(String[] args) { 
    Stream<String> strings = Arrays.asList("a", "b", "c", "e", "f", "d").stream(); 
    Stream<Integer> ints = Arrays.asList(1, 2, 3, 6, 5, 4).stream(); 
    Stream<String> results = join(strings, ints, 
      Function.identity(), 
      str -> Integer.parseInt(str, 16) - 9, 
      (o, i) -> "Outer: " + o + ", Inner: " + i); 
    results.forEach(r -> System.out.println(r)); 
} 
  • I int s sono le proprie chiavi, s o nessuna trasformazione
  • Il Strings sono mappati int s secondo il loro valore esadecimale - 9
  • (Gli elementi corrispondono se i valori int sono uguali, come da default)
  • accoppiamenti sono messi in una String

La seguente (corretto) risultati vengono stampati:

Outer: a, Inner: 1 
Outer: b, Inner: 2 
Outer: c, Inner: 3 
Outer: d, Inner: 4 
Outer: e, Inner: 5 
Outer: f, Inner: 6 

più approfonditi test saranno b E 'necessario, ovviamente, ma credo che questa implementazione sia corretta. Potrebbe anche essere più efficiente, sono aperto a suggerimenti.

8

join is just syntactic sugar for Stream.flatMap() as explained in this article. Considerate questo esempio:

List<Integer> l1 = Arrays.asList(1, 2, 3, 4); 
List<Integer> l2 = Arrays.asList(2, 2, 4, 7); 

l1.stream() 
    .flatMap(i1 -> l2.stream() 
        .filter(i2 -> i1.equals(i2))) 
    .forEach(System.out::println); 

Il risultato è:

2 
2 
4 

Nell'esempio precedente, flatMap() corrisponde (INNER) JOIN mentre la filter() funzionamento del flusso nidificata corrisponde alla clausola ON.

jOOλ è una libreria che implementa innerJoin() e altri tipi di join da astrarre su questo, ad es. anche per il buffer del contenuto del flusso nel caso in cui si voglia unire due istanze Stream, invece di due istanze Collection. Con jOOλ, si sarebbe quindi scrivere:

Seq<Integer> s1 = Seq.of(1, 2, 3, 4); 
Seq<Integer> s2 = Seq.of(2, 2, 4, 7); 

s1.innerJoin(s2, (i1, i2) -> i1.equals(i2)) 
    .forEach(System.out::println); 

... che stampa (l'uscita sono tuple, che è più simile semantica semantica di SQL):

(2, 2) 
(2, 2) 
(4, 4) 

(dichiarazione di non responsabilità, io lavoro per l'azienda dietro jOOλ)

0

Sono anche venuto da C# e ho perso quella funzione. Un grande vantaggio sarebbe avere un codice leggibile esprimendo l'intenzione. Così ho scritto il mio streamjoin che funziona come C# Enumerable.Join(). Inoltre: tollera le chiavi null.

Stream<BestFriends> bestFriends = 
join(listOfPersons.stream()) 
    .withKey(Person::getName) 
    .on(listOfDogs.stream()) 
    .withKey(Dog::getOwnerName) 
    .combine((person, dog) -> new BestFriends(person, dog)) 
    .asStream();