Sayısal olarak değerlendirilmeyen ara akış işlemleri


33

Görünüşe göre Java'nın akış işlemlerini bir akış hattına nasıl dönüştürdüğünü anlamakta sorun yaşıyorum.

Aşağıdaki kodu yürütürken

public
 static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

Konsol yalnızca yazdırır 4. StringBuilderNesne halen değerine sahiptir "".

Filtre işlemini eklediğimde: filter(s -> true)

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .filter(s -> true)
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

Çıktı şu şekilde değişir:

4
1234

Bu gereksiz gibi görünen filtre işlemi, oluşturulan akış boru hattının davranışını nasıl değiştirir?


2
İlginç !!!
uneq95

3
Bunun uygulamaya özgü bir davranış olduğunu düşünürdüm; belki de birinci akışın bilinen bir boyutu olduğu, ancak ikincisinin olmadığı ve büyüklüğün ara işlemlerin yürütülüp yürütülmediğini belirlediği için.
Andy Turner

İlginin dışında, filtreyi ve haritayı ters çevirirseniz ne olur?
Andy Turner

Haskell'de biraz programladıktan sonra, burada devam eden biraz tembel değerlendirme gibi kokuyor. Bir google araması geri döndü, bu akışların gerçekten biraz tembellik var. Durum böyle olabilir mi? Filtre olmadan, java yeterince akıllıysa, eşlemeyi yürütmeye gerek yoktur.
Frederik

@AndyTurner Tersine döndüğünde bile aynı sonucu verir
uneq95

Yanıtlar:


39

count()Terminal operasyon, JDK benim sürümünde aşağıdaki kodu çalıştırarak biter:

if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
    return spliterator.getExactSizeIfKnown();
return super.evaluateSequential(helper, spliterator);

filter()Operasyonların boru hattında bir işlem varsa, başlangıçta bilinen akışın boyutu artık bilinemez (çünkü filterakışın bazı öğelerini reddedebilir). Böylece ifblok yürütülmez, ara işlemler yürütülür ve böylece StringBuilder değiştirilir.

Öte yandan, yalnızca map()boru hattındaysanız, akıştaki öğe sayısının ilk öğe sayısıyla aynı olacağı garanti edilir. Böylece if bloğu yürütülür ve ara işlemler değerlendirilmeden boyut doğrudan döndürülür.

Lamda'nın map()belgelerde tanımlanan sözleşmeyi ihlal ettiğini unutmayın : müdahale etmeyen, vatansız bir işlem olması gerekiyordu, ancak vatansız değil. Bu nedenle, her iki durumda da farklı bir sonuca sahip olmak hata olarak kabul edilemez.


Çünkü flatMap()elemanların sayısını değiştirmek mümkün olabilir, olduğu o (şimdi tembel) başlangıçta istekli sebebi? Yani, alternatifi , mevcut haliyle sözleşmeyi ihlal ederse forEach()ayrı olarak kullanmak ve saymak olacaktır map(), sanırım.
Frederik

3
FlatMap ile ilgili olarak, sanmıyorum. AFAIK, çünkü başlangıçta istekli yapmak daha basitti. Evet, yan etkiler üretmek için map () ile bir akış kullanmak kötü bir fikirdir.
JB Nizet

4 1234Ek filtre kullanmadan veya map () işleminde yan etkiler üretmeden tam çıktıya nasıl ulaşılacağına dair bir öneriniz var mı?
atalantus

1
int count = array.length; String result = String.join("", array);
JB Nizet

1
ya da gerçekten bir StringBuilder kullanmak istiyorsanız forEach kullanabilirsiniz ya da kullanabilirsinizCollectors.joining("")
njzk2

19

In jdk-9 açıkça java docs belgelenmiştir

Yan etkilerin ortaya çıkması da şaşırtıcı olabilir. ForEach ve forEachOrdered terminal işlemleri hariç olmak üzere, akış uygulaması hesaplama sonucunu etkilemeden davranışsal parametrelerin yürütülmesini optimize edebildiğinde, davranış parametrelerinin yan etkileri her zaman yürütülemeyebilir. (Belirli bir örnek için sayma işleminde belgelenen API notuna bakın .)

API Notu:

Bir uygulama, sayımı doğrudan akış kaynağından hesaplayabiliyorsa akış boru hattını (sıralı olarak veya paralel olarak) yürütmemeyi seçebilir. Bu gibi durumlarda kaynak unsurlar geçilmeyecek ve ara işlemler değerlendirilmeyecektir. Hata ayıklama gibi zararsız durumlar dışında kesinlikle önerilmez, yan etkileri olan davranışsal parametreler etkilenebilir. Örneğin, aşağıdaki akışı göz önünde bulundurun:

 List<String> l = Arrays.asList("A", "B", "C", "D");
 long count = l.stream().peek(System.out::println).count();

Akış kaynağı, bir Liste tarafından kapsanan elemanların sayısı bilinir ve ara işlem, gözetleme, akışa eleman enjekte etmez veya akımdan eleman çıkarmaz (flatMap veya filtre işlemleri için olduğu gibi). Böylece sayı Listenin boyutudur ve boru hattını yürütmeye ve bir yan etki olarak liste öğelerini yazdırmaya gerek yoktur.


0

.Map bunun için değildir. "Bir şey" akışını "Başka bir şey" akışına dönüştürmek için kullanılması gerekiyordu. Bu durumda, harici bir Stringbuilder'a bir dize eklemek için map kullanıyorsunuz. Ardından, her biri orijinal Stringbuilder'a bir sayı ekleyerek harita işlemi tarafından oluşturulan bir "Stringbuilder" akışına sahipsiniz.

Akışınız gerçekte akışta eşlenmiş sonuçlarla hiçbir şey yapmaz, bu nedenle adımın akış işlemcisi tarafından atlanabileceğini varsaymak son derece mantıklıdır. Haritanın işlevsel modelini bozan işi yapmak için yan etkilere güveniyorsunuz. Bunu yapmak için forEach kullanarak daha iyi hizmet edersiniz. Sayımı tamamen ayrı bir akış olarak yapın veya forEach'ta AtomicInt kullanarak bir sayaç koyun.

Filtre, onu artık her içerik öğesinde dikkat çekici bir şey yapmak zorunda olduğundan akış içeriğini çalıştırmaya zorlar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.