2014-11-07 21 views
31

bir değer döndüren toplayıcı Bu işlevsel programlama ve akışlar hakkında biraz yeşilim, ama bildiğim küçük bir şey çok yararlı oldu!Java 8 Sadece tek bir değer varsa

Ben birkaç kez bu durumu vardı geldim

:

List<SomeProperty> distinctProperties = someList.stream() 
    .map(obj -> obj.getSomeProperty()) 
    .distinct() 
    .collect(Collectors.toList()); 

if (distinctProperties.size() == 1) { 
    SomeProperty commonProperty = distinctProperties.get(0); 
    // take some action knowing that all share this common property 
} 

Ne istiyorum gerçekten geçerli:

Optional<SomeProperty> universalCommonProperty = someList.stream() 
    .map(obj -> obj.getSomeProperty()) 
    .distinct() 
    .collect(Collectors.singleOrEmpty()); 

ben singleOrEmpty şey sadece kombine olarak yanında diğer durumlarda yararlı olabilir düşünüyorum distinct ile. Ben bir n00b uber oldugunda Java Koleksiyonlar Framework'ü yeniden icat ettim, çünkü orada oldugunu bilmiyordum, bu yüzden hatalarimi tekrarlamamaya çalisiyorum. Java, bu singleOrEmpty bir şeyi yapmak için iyi bir yolla geliyor mu? Yanlış mı formüle ediyorum?

Teşekkürler!

DÜZENLEME: İşte, distinct vakası için bazı örnek veriler. Eğer map adım yok sayarsanız:

Optional<SomeProperty> universalCommonProperty = someList.stream() 
    .map(obj -> obj.getSomeProperty()) 
    .distinct() 
    .collect(Collectors.singleOrEmpty()); 

[]  -> Optional.empty() 
[1] -> Optional.of(1) 
[1, 1] -> Optional.of(1) 
[2, 2] -> Optional.of(2) 
[1, 2] -> Optional.empty() 

benim türlerini berbat veya eski kodu var olduğunda buna ihtiyacım bulabilirsiniz. Hızlı bir şekilde "Bu koleksiyonun tüm öğeleri bu mülkü paylaşıyor, bu yüzden şimdi bu paylaşılan özelliği kullanarak biraz harekete geçebilirim" diyebileceğimiz gerçekten çok güzel. Başka bir örnek, bir kullanıcının çeşitli öğeleri çok seçtiği ve tüm bunlar için geçerli olan bir şeyi (eğer bir şey varsa) görmeye çalıştığınızdır.

EDIT2: Örneğim yanıltıcı ise özür dilerim. Anahtar, singleOrEmpty'dur. Genelde bir distinct ürününü öne çıkardığımı fark ettim, ancak başka bir çeşit filter kolayca olabilir.

Optional<SomeProperty> loneSpecialItem = someList.stream() 
    .filter(obj -> obj.isSpecial()) 
    .collect(Collectors.singleOrEmpty()); 

[special]   -> Optional.of(special) 
[special, special] -> Optional.empty() 
[not]    -> Optional.empty() 
[not, special]  -> Optional.of(special) 
[not, special, not] -> Optional.of(special) 

EDIT3: Ben singleOrEmpty motive yerine sadece kendi başına bunun için sorarak berbat düşünüyorum.

SomeProperty distinctProperty = Iterables.getOnlyElement(
     someList.stream() 
       .map(obj -> obj.getSomeProperty()) 
       .distinct() 
       .collect(Collectors.toList())); 

IllegalArgumentException birden fazla değer yoksa yükseltilecek: Eğer Guava kullanarak sakıncası yoksa

Optional<Int> value = someList.stream().collect(Collectors.singleOrEmpty()) 
[]  -> Optional.empty() 
[1] -> Optional.of(1) 
[1, 1] -> Optional.empty() 
+0

@Ned Twigg Nazik olmak ve somelist içeriğini gönderebilir vardır Yani senin sorunun yeniden üretilebilir? –

+0

C# 'in LINQ'unda bu,' SingleOrDefault 'ile benzerdir. –

cevap

13
sadece ilk iki elemanları değerlendirir

"Hacky" çözelti:

.limit(2) 
    .map(Optional::ofNullable) 
    .reduce(Optional.empty(), 
     (a, b) -> a.isPresent()^b.isPresent() ? b : Optional.empty()); 

bazı temel açıklaması:

tek eleman [1] -> harita [İsteğe bağlı (1) ] -> azaltmak

"Empty XOR Present" yields Optional(1) 

= İsteğe bağlı (1)

İki elemanın [1, 2] '-> harita için [İsteğe bağlı (1), isteğe bağlı (2)] -> azaltmaktadır:

"Empty XOR Present" yields Optional(1) 
"Optional(1) XOR Optional(2)" yields Optional.Empty 

= Optional.Empty

Burada olan tam testcase: XOR fikir ve üzeri testCase katkıda bulunmuştur Ned (OP) için

public static <T> Optional<T> singleOrEmpty(Stream<T> stream) { 
    return stream.limit(2) 
     .map(Optional::ofNullable) 
     .reduce(Optional.empty(), 
      (a, b) -> a.isPresent()^b.isPresent() ? b : Optional.empty()); 
} 

@Test 
public void test() { 
    testCase(Optional.empty()); 
    testCase(Optional.of(1), 1); 
    testCase(Optional.empty(), 1, 1); 
    testCase(Optional.empty(), 1, 1, 1); 
} 

private void testCase(Optional<Integer> expected, Integer... values) { 
    Assert.assertEquals(expected, singleOrEmpty(Arrays.stream(values))); 
} 

Kudos!

+0

Yalnızca geçerli içeriği tek bir öğe olacak bir HashSet toplamak yerine bir akımı kısa devre ile sonlandırmak mümkün mü? –

+0

@NedTwigg bu kesinlikle sizin girişinizin bir sorusu, bu yüzden bir '# ayırım' yaptığınız durumda tahmin ediyorum? –

+2

@NedTwigg Limit (2) .reduce () 'çözümünü düşünüyordum, ancak her seviyedeki düşüşün yanlış olması için onu takmak gibi görünüyor. –

7

, sen Iterables.getOnlyElement kodunuzu sarın, bu nedenle böyle bir şey olmazdı ya da değer yok, varsayılan değeri olan bir version da var.

+0

Bağlantı bozuk – pisaruk

4

Kolayca kendi Böyle kullanabilirsiniz Collector

public class AllOrNothing<T> implements Collector<T, Set<T>, Optional<T>>{ 



@Override 
public Supplier<Set<T>> supplier() { 
    return() -> new HashSet<>(); 
} 



@Override 
public BinaryOperator<Set<T>> combiner() { 
    return (set1, set2)-> { 
     set1.addAll(set2); 
     return set1; 
    }; 
} 

@Override 
public Function<Set<T>, Optional<T>> finisher() { 
    return (set) -> { 
     if(set.size() ==1){ 
      return Optional.of(set.iterator().next()); 
     } 
     return Optional.empty(); 
    }; 
} 

@Override 
public Set<java.util.stream.Collector.Characteristics> characteristics() { 
    return Collections.emptySet(); 
} 

@Override 
public BiConsumer<Set<T>, T> accumulator() { 
    return Set::add; 
} 

} 

yazabilirsiniz:

Optional<T> result = myStream.collect(new AllOrNothing<>()); 

İşte oluyor senin örneğin test veri koşmak dışarı yazdırır

public static void main(String[] args) { 
    System.out.println(run()); 

    System.out.println(run(1)); 
    System.out.println(run(1,1)); 
    System.out.println(run(2,2)); 
    System.out.println(run(1,2)); 
} 

private static Optional<Integer> run(Integer...ints){ 

    List<Integer> asList = Arrays.asList(ints); 
    System.out.println(asList); 
    return asList 
       .stream() 
       .collect(new AllOrNothing<>()); 
} 

[] 
Optional.empty 
[1] 
Optional[1] 
[1, 1] 
Optional[1] 
[2, 2] 
Optional[2] 
+0

Yalnızca geçerli içeriği tek bir öğe olacak bir HashSet toplamak yerine bir akımı kısa devre ile sonlandırmak mümkün mü? –

+0

@NedTwigg evet, ama onu takip eden kendi 'spliterator'ınızı yazmanız gerekir. – dkatzel

19

Bu, bir küme oluşturmanın ek yüküne neden olur, ancak basittir ve önce akışı() önce ayırmayı unutsanız bile doğru şekilde çalışır.

static<T> Collector<T,?,Optional<T>> singleOrEmpty() { 
    return Collectors.collectingAndThen(
      Collectors.toSet(), 
      set -> set.size() == 1 
        ? set.stream().findAny() 
        : Optional.empty() 
    ); 
} 
+0

Önünüzde ayrı bir yere koyduğunuz için şık bir çözüm, ancak sorumun kökünün 'singleOrEmpty 'nasıl uygulanacağıdır. –

+0

@NedTwigg Anladığımdan emin değilim. Bu tüm birim testlerinizi geçecek. Stream.of (1,2) .collect (singleOrEmpty()) -> Optional.empty() '' Stream.of (1) .collect (singleOrEmpty()) -> İsteğe bağlı (1) 'vb. – Misha

+0

@ NedTwigg sizin için ayrı olacak, çünkü tüm şeyleri bir 'Set' içine topladınız. –

3

RxJava'nın benzer işlevleri var in its single() operator. tek bir öğe yayan sonra, aksi takdirde yerine sadece olurdu

bir istisna (veya varsayılan öğeyi iade), söz konusu öğeyi döndürmek Observable tamamlanıncaya eğer

single( ) ve singleOrDefault( )

Bir Optional, ve bir Collector olmasını tercih ederim.

1

başka toplayıcı yaklaşım:

Toplayıcılar:

public final class SingleCollector<T> extends SingleCollectorBase<T> { 
    @Override 
    public Function<Single<T>, T> finisher() { 
     return a -> a.getItem(); 
    } 
} 

public final class SingleOrNullCollector<T> extends SingleCollectorBase<T> { 
    @Override 
    public Function<Single<T>, T> finisher() { 
     return a -> a.getItemOrNull(); 
    } 
} 

SingleCollectorBase:

public abstract class SingleCollectorBase<T> implements Collector<T, Single<T>, T> { 
    @Override 
    public Supplier<Single<T>> supplier() { 
     return() -> new Single<>(); 
    } 

    @Override 
    public BiConsumer<Single<T>, T> accumulator() { 
     return (list, item) -> list.set(item); 
    } 

    @Override 
    public BinaryOperator<Single<T>> combiner() { 
     return (s1, s2) -> { 
      s1.set(s2); 
      return s1; 
     }; 
    } 

    @Override 
    public Set<Characteristics> characteristics() { 
     return EnumSet.of(Characteristics.UNORDERED); 
    } 
} 

Tek:

public final class Single<T> { 

    private T item; 
    private boolean set; 

    public void set(T item) { 
     if (set) throw new SingleException("More than one item in collection"); 
     this.item = item; 
     set = true; 
    } 

    public T getItem() { 
     if (!set) throw new SingleException("No item in collection"); 
     return item; 
    } 

    public void set(Single<T> other) { 
     if (!other.set) return; 
     set(other.item); 
    } 

    public T getItemOrNull() { 
     return set ? item : null; 
    } 
} 

public class SingleException extends RuntimeException { 
    public SingleException(String message) { 
     super(message); 
    } 
} 

eksik paralle olsa testler ve örnek kullanımları, l testleri. aşağıdaki gibi

public final class SingleTests { 

    @Test 
    public void collect_single() { 
     ArrayList<String> list = new ArrayList<>(); 
     list.add("ABC"); 

     String collect = list.stream().collect(new SingleCollector<>()); 
     assertEquals("ABC", collect); 
    } 

    @Test(expected = SingleException.class) 
    public void collect_multiple_entries() { 
     ArrayList<String> list = new ArrayList<>(); 
     list.add("ABC"); 
     list.add("ABCD"); 

     list.stream().collect(new SingleCollector<>()); 
    } 

    @Test(expected = SingleException.class) 
    public void collect_no_entries() { 
     ArrayList<String> list = new ArrayList<>(); 

     list.stream().collect(new SingleCollector<>()); 
    } 

    @Test 
    public void collect_single_or_null() { 
     ArrayList<String> list = new ArrayList<>(); 
     list.add("ABC"); 

     String collect = list.stream().collect(new SingleOrNullCollector<>()); 
     assertEquals("ABC", collect); 
    } 

    @Test(expected = SingleException.class) 
    public void collect_multiple_entries_or_null() { 
     ArrayList<String> list = new ArrayList<>(); 
     list.add("ABC"); 
     list.add("ABCD"); 

     list.stream().collect(new SingleOrNullCollector<>()); 
    } 

    @Test 
    public void collect_no_entries_or_null() { 
     ArrayList<String> list = new ArrayList<>(); 

     assertNull(list.stream().collect(new SingleOrNullCollector<>())); 
    } 

} 
6

bunun için bir Collector oluşturmak için daha kısa bir yol olduğu:

Collectors.reducing((a, b) -> null); 

indirgeyici toplayıcı ilk değerini kaydetmek ve ardışık geçişte, mevcut çalışma değerini geçmesi ve olacak lambda ifadesine yeni değer. Bu noktada, null her zaman iade edilebilir, çünkü bu sadece kaydedilecek olan ilk değerle çağrılmayacaktır.

koduna bu takma:

Optional<SomeProperty> universalCommonProperty = someList.stream() 
    .map(obj -> obj.getSomeProperty()) 
    .distinct() 
    .collect(Collectors.reducing((a, b) -> null));