String.chars () neden Java 8'de bir ints akışıdır?


198

Java 8'de, karakter kodlarını temsil eden bir s ( ) String.chars()akışı döndüren yeni bir yöntem vardır . Sanırım birçok insan bunun yerine burada bir s akışı beklerdi . API'yı bu şekilde tasarlama motivasyonu neydi?intIntStreamchar


4
@RohitJain Belirli bir akış demek istemedim. Eğer CharStreamyoksa, eklemek için sorun ne olurdu?
Adam Dyga

5
@AdamDyga: Tasarımcılar, ilkel akışları 3 tiple sınırlayarak sınıfların ve yöntemlerin patlamasını önlemeyi seçtiler, çünkü diğer türler (char, kısa, şamandıra) herhangi bir önemli olmadan daha büyük eşdeğeri (int, double) ile temsil edilebilir performans cezası.
JB Nizet

3
@JBNizet Anlıyorum. Ama yine de sadece birkaç yeni sınıfı kurtarmak için kirli bir çözüm gibi geliyor.
Adam Dyga

9
@JB Nizet: Bana öyle geliyor ki , tüm akış aşırı yüklemeleri ve tüm fonksiyon arayüzleri göz önüne alındığında bir arayüz patlaması var ...
Holger

5
Evet, sadece üç ilkel akış uzmanlığında bile bir patlama var. Sekiz ilkelin hepsinde akış uzmanlığı olsaydı ne olurdu? Bir felaket mi? :-)
Stuart Marks

Yanıtlar:


218

Diğerlerinin de belirttiği gibi, bunun arkasındaki tasarım kararı, yöntemlerin ve sınıfların patlamasını önlemekti.

Yine de, kişisel olarak bunun çok kötü bir karar olduğunu düşünüyorum ve yapmak istemedikleri göz önüne alındığında CharStream, bunun yerine makul, farklı yöntemler chars()olduğunu düşünmeliyim:

  • Stream<Character> chars(), bu, bazı hafif performans cezalarına sahip olacak bir kutu karakterleri akışı sağlar.
  • IntStream unboxedChars()performans kodu için kullanılır.

Ancak , şu anda neden bu şekilde yapıldığına odaklanmak yerine, bu cevabın Java 8 ile aldığımız API ile bunu yapmanın bir yolunu göstermeye odaklanması gerektiğini düşünüyorum.

Java 7'de böyle yapardım:

for (int i = 0; i < hello.length(); i++) {
    System.out.println(hello.charAt(i));
}

Ve Java 8'de bunu yapmak için makul bir yöntem olduğunu düşünüyorum:

hello.chars()
        .mapToObj(i -> (char)i)
        .forEach(System.out::println);

Burada bir IntStreamlambda aracılığıyla bir nesneye alıyorum ve eşleştiriyorum i -> (char)i, bu otomatik olarak bir kutuya yerleştirecek Stream<Character>ve sonra istediğimizi yapabiliriz ve yine de yöntem referanslarını artı olarak kullanabiliriz.

Unutmayın bunu gerçi gerekir yapmak mapToObjunutur ve kullanımı halinde map, o zaman hiçbir şey şikayet edecek, ancak yine de bir ile sona erecek IntStreamve bunu yerine karakterleri temsil eden dizeleri tamsayı değerlerini yazdırır neden merak bıraktığı olabilir.

Java 8 için diğer çirkin alternatifler:

Birinde kalarak IntStreamve sonuçta yazdırmak isteyerek, yazdırma için artık yöntem referanslarını kullanamazsınız:

hello.chars()
        .forEach(i -> System.out.println((char)i));

Ayrıca, kendi yönteminize yöntem başvuruları kullanmak artık işe yaramıyor! Aşağıdakileri göz önünde bulundur:

private void print(char c) {
    System.out.println(c);
}

ve sonra

hello.chars()
        .forEach(this::print);

Bu, muhtemelen kayıplı bir dönüşüm olduğu için derleme hatası verecektir.

Sonuç:

API çünkü eklemek istemeyen bu şekilde dizayn edilmiştir CharStream, ben şahsen yöntem döndürmesi gerektiğini düşünüyorum Stream<Character>ve çözüm şu an kullanmaktır mapToObj(i -> (char)i)bir de IntStreamonlarla düzgün çalışması için muktedir.


7
Benim sonucum: API'nın bu kısmı tasarım tarafından kırıldı. Ama kapsamlı cevap için teşekkürler
Adam Dyga

27
+1, ancak benim teklifim codePoints()yerine kullanmaktır chars()ve zaten bir intkod noktasını ek olarak kabul eden bir çok kütüphane işlevi bulacaksınız char, örneğin tüm yöntemlerin java.lang.Characteryanı sıra StringBuilder.appendCodePoint, vb. Bu destek o zamandan beri var jdk1.5.
Holger

6
Kod noktaları hakkında iyi bir nokta. Bunları kullanmak, a Stringveya konumunda yedek çiftler olarak gösterilen ek karakterleri işleyecektir char[]. Bahse girerim ki çoğu charişlem kodu yanlış çiftleri idare eder.
Stuart Marks

2
@skiwi, tanım void print(int ch) { System.out.println((char)ch); }ve sonra yöntem referanslarını kullanabilirsiniz.
Stuart Marks

2
Neden Stream<Character>reddedildiğine ilişkin cevabımı görün .
Stuart Marks

90

Skiwi gelen cevap zaten önemli noktaların pek kaplı. Biraz daha arka plan dolduracağım.

Herhangi bir API'nin tasarımı bir dizi ödünleşmedir. Java'da zor konulardan biri, uzun zaman önce alınan tasarım kararlarıyla uğraşmaktır.

Temel öğeler 1.0'dan beri Java'da. İlkel nesneler nesne olmadığı için Java'yı "saf olmayan" nesne yönelimli bir dil yaparlar. İlkellerin eklenmesi, inanıyorum ki, nesne yönelimli saflık pahasına performansı artırmak için pragmatik bir karardı.

Bu, yaklaşık 20 yıl sonra bugün hala birlikte yaşadığımız bir ödünleşmedir. Java 5'te eklenen otomatik boks özelliği, kaynak kodunu boks ve kutudan çıkarma yöntemi çağrıları ile dağınık hale getirme ihtiyacını çoğunlukla ortadan kaldırdı, ancak ek yük hala orada. Birçok durumda farkedilmez. Ancak, bir iç döngü içinde boks veya kutudan çıkarma yapsaydınız, önemli miktarda CPU ve çöp toplama yükü uygulayabildiğini görürsünüz.

Streams API'sını tasarlarken, ilkelleri desteklememiz gerektiği açıktı. Boks / kutudan çıkarma yükü paralellikten herhangi bir performans faydasını öldürür. Bununla birlikte, tüm ilkeleri desteklemek istemedik , çünkü bu API'ya büyük miktarda karmaşa ekleyecekti. (Gerçekten bir a görebiliyor ShortStreammusunuz?) "Hepsi" veya "hiçbiri" bir tasarım için rahat yerler, ama ikisi de kabul edilebilir. Bu yüzden "bazı" makul bir değer bulmak zorunda kaldı. Biz ilkel uzmanlık ile sona erdi int, longve double. (Şahsen ben dışarıda intkalırdım ama bu sadece benim.)

Çünkü CharSequence.chars()geri dönmeyi düşündük Stream<Character>(erken bir prototip bunu uygulamış olabilir) ama boks yükü nedeniyle reddedildi. Bir String'in charilkel olarak değerleri olduğu düşünüldüğünde , arayan muhtemelen değer üzerinde biraz işlem yapıp doğrudan bir dizgeye geri döndürdüğünde, koşulsuz olarak boks uygulamak bir hata gibi görünebilir.

Ayrıca CharStreamilkel bir uzmanlık olarak düşündük , ancak kullanımı API'ya ekleyeceği toplu miktarla karşılaştırıldığında oldukça dar görünüyordu. Eklemek faydalı görünmüyordu.

Bunun arayanlara verdiği ceza, temsil edilen değerleri IntStreamiçerdiğini ve dökümün uygun yerde yapılması gerektiğini bilmeleri gerektiğidir. Aşırı yüklenmiş API çağrıları olduğu ve davranışlarında belirgin şekilde farklı olduğu için bu iki kat kafa karıştırıcıdır . Ek bir karışıklık noktası da ortaya çıkabilir çünkü çağrı da geri döner, ancak içerdiği değerler oldukça farklıdır.charintsPrintStream.print(char)PrintStream.print(int)codePoints()IntStream

Bu, birkaç alternatif arasından pragmatik olarak seçim yapmakla ilgilidir:

  1. Basit, zarif, tutarlı bir API ile sonuçlanan, ancak yüksek performans ve GC ek yükü getiren ilkel uzmanlıklar sağlayamadık;

  2. API'yi karmaşıklaştırma ve JDK geliştiricilerine bir bakım yükü getirme pahasına, eksiksiz bir ilkel uzmanlık seti sağlayabiliriz; veya

  3. oldukça dar bir kullanım alanında (char işleme) arayanlar üzerinde nispeten küçük bir yük oluşturan orta büyüklükte, yüksek performanslı bir API vererek ilkel uzmanlıkların bir alt kümesini sağlayabiliriz.

Sonuncuyu seçtik.


1
Güzel cevap! Ancak neden chars()biri Stream<Character>(küçük performans cezası ile) ve diğeri döndüren olmak için iki farklı yöntem olamaz diye cevap vermez IntStream? İnsanların Stream<Character>performans cezası üzerinde rahatlığa değdiğini düşünürlerse, yine de insanların bunu eşlemeleri muhtemeldir .
skiwi

3
Minimalizm buraya geliyor. chars()Char değerlerini bir döndüren yöntem zaten varsa IntStream, aynı değerleri ancak kutulu biçimde alan başka bir API çağrısına sahip olmak için fazla bir şey eklemez. Arayan, değerleri çok sorunsuz bir şekilde kutuya alabilir. Elbette, bu (muhtemelen nadiren) durumda bunu yapmak zorunda değilsiniz, ancak API'ye karmaşa ekleme pahasına daha uygun olacaktır.
Stuart Marks

5
Yinelenen soru sayesinde bunu fark ettim. Özellikle bu yöntemin nadiren kullanıldığı gerçeği göz önüne alındığında, chars()geri dönüşün IntStreambüyük bir sorun olmadığını kabul ediyorum . Ancak geri dönüştürmek için yerleşik bir yol var iyi olurdu IntStreamiçin String. İle yapılabilir .reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString(), ama gerçekten uzun.
Tagir Valeev

7
@TagirValeev Evet, biraz hantal. Kod noktaları akışı ile (bir IntStream) çok kötü değil: collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(). Sanırım gerçekten daha kısa değil, ancak kod noktalarını kullanmak (char)dökümleri önler ve yöntem referanslarının kullanılmasına izin verir. Ayrıca, vekilleri düzgün şekilde işler.
Stuart Marks

2
@IlyaBystrov Ne yazık ki ilkel akışlar gibi IntStreambir collect()yöntem yok Collector. collect()Önceki yorumlarda belirtildiği gibi sadece üç argümanlı bir yöntemleri vardır.
Stuart Marks
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.