Elf versteckte Perlen von Java 11

Java 11 hat keine innovativen Funktionen eingeführt, enthält jedoch einige Juwelen, von denen Sie vielleicht noch nichts gehört haben. Sie haben sich bereits die neuesten String , Optional , Collection und anderen Arbeitspferden angesehen? Wenn nicht, dann sind Sie bei der Adresse angelangt: Heute werden wir uns 11 versteckte Juwelen aus Java 11 ansehen!


Typinferenz für Lambda-Parameter


Beim Schreiben eines Lambda-Ausdrucks können Sie wählen, ob Sie Typen explizit angeben oder überspringen möchten:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

Java 10 führte var , konnte aber nicht in Lambdas verwendet werden:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

In Java 11 ist dies bereits möglich. Aber warum? Es scheint nicht so, als hätte var mehr als nur einen Typpass gegeben. Obwohl dies der Fall ist, hat die Verwendung von var zwei geringfügige Vorteile:


  • macht die Verwendung von var universeller, indem die Ausnahme von der Regel entfernt wird
  • Mit dieser Option können Sie dem Parametertyp Anmerkungen hinzufügen, ohne den vollständigen Namen verwenden zu müssen

Hier ist ein Beispiel für den zweiten Fall:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

Obwohl das Mischen abgeleiteter, expliziter und impliziter Typen in Lambda-Ausdrücken der Form (var type, String option, index) -> ... implementiert werden kann, wurde diese Arbeit ( im Rahmen von JEP-323 ) nicht ausgeführt. Daher ist es notwendig, einen der drei Ansätze zu wählen und ihn für alle Parameter des Lambda-Ausdrucks einzuhalten. Die Notwendigkeit, var für alle Parameter anzugeben, um Anmerkungen für einen von ihnen hinzuzufügen, kann etwas ärgerlich sein, ist aber im Allgemeinen tolerierbar.


Stream-Verarbeitung von Strings mit 'String::lines'


Hast du eine mehrzeilige Zeichenfolge? Möchten Sie mit jeder Zeile etwas anfangen? Dann ist String::lines die richtige Wahl:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

Beachten Sie, dass die ursprüngliche Zeile die \r\n Schraubenbegrenzer verwendet und obwohl ich unter Linux bin, hat lines() immer noch beschädigt. Dies liegt an der Tatsache, dass diese Methode trotz des aktuellen Betriebssystems \r , \n und \r\n als Zeilenumbrüche interpretiert - auch wenn sie in derselben Zeile gemischt sind.


Ein Zeilenstrom enthält niemals die Zeilentrennzeichen selbst. Zeilen können leer sein ( "\n\n \n\n" , der 5 Zeilen enthält), aber die letzte Zeile der ursprünglichen Zeile wird ignoriert, wenn sie leer ist ( "\n\n" ; 2 Zeilen). (Hinweis des Übersetzers: Es ist praktisch, wenn sie eine line , aber eine string , und wir haben beide.)


Im Gegensatz zu split("\R") sind lines() faul und bieten, wie ich zitiere , "eine bessere Leistung [...] durch schnellere Suche nach neuen Zeilenumbrüchen". (Wenn jemand einen Benchmark für JMH zur Überprüfung einreichen möchte, lassen Sie es mich wissen.) Es spiegelt auch den Verarbeitungsalgorithmus besser wider und verwendet eine bequemere Datenstruktur (Stream anstelle von Array).


Leerzeichen mit 'String::strip' usw. entfernen


Anfangs hatte String eine trim zum Entfernen von Leerzeichen, die als alles mit Codes bis U+0020 . Ja, BACKSPACE ( U+0008) ist ein Leerraum wie BELL ( U+0007 ), aber LINE SEPARATOR ( U+2028 ) wird nicht mehr als solcher betrachtet.


Java 11 führte die strip Methode ein, deren Ansatz mehr Nuancen aufweist. Es verwendet die Character::isWhitespace von Java 5, um zu bestimmen, was genau entfernt werden muss. Aus seiner Dokumentation geht hervor, dass dies:


  • SPACE SEPARATOR , LINE SEPARATOR , PARAGRAPH SEPARATOR , aber kein untrennbarer Raum
  • HORIZONTAL TABULATION ( U+0009 ), LINE FEED ( U+000A ), VERTICAL TABULATION ( U+000B ), FORM FEED VERTICAL TABULATION ( U+000B ), CARRIAGE RETURN ( U+000D )
  • FILE SEPARATOR ( U+001C ), GROUP SEPARATOR ( U+001D ), RECORD SEPARATOR ( U+001E ), UNIT SEPARATOR ( U+001F )

Mit der gleichen Logik gibt es zwei weitere Reinigungsmethoden, stripLeading und stripTailing , die genau das tun, was von ihnen erwartet wird.


Und wenn Sie nur herausfinden müssen, ob die Zeile nach dem Entfernen von Leerzeichen leer ist, müssen Sie sie nicht wirklich löschen - verwenden Sie einfach isBlank :


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

Wiederholen von Zeichenfolgen mit 'String::repeat'


Fangen Sie die Idee:


Schritt 1: JDK im Auge behalten

Behalten Sie die JDK-Entwicklung im Auge


Schritt 2: Finden von Fragen zu StackOverflow

Suchen Sie nach verwandten Fragen zu Stackoverflow


Schritt 3: Ankunft mit einer neuen Antwort basierend auf zukünftigen Änderungen

Kommen Sie mit einer neuen Antwort, die auf bevorstehenden Änderungen basiert


Schritt 4: ????

Schritt 4: Gewinn

¯ \ _ (ツ) _ / ¯


Wie Sie sich vorstellen können, hat String eine neue repeat(int) . Es funktioniert genau im Einklang mit den Erwartungen und es gibt wenig zu diskutieren.


Erstellen von Pfaden mit 'Path::of'


Ich mag die Path API sehr, aber das Konvertieren von Pfaden zwischen verschiedenen Ansichten (wie Path , File , URL , URI und String ) ist immer noch ärgerlich. Dieser Punkt ist in Java 11 weniger verwirrend geworden, indem zwei Paths::get Methoden in Path::of Methoden kopiert wurden:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

Sie können als kanonisch betrachtet werden, da beide alten Paths::get Methoden neue Optionen verwenden.


Lesen und Schreiben von Dateien mit 'Files::readString' und 'Files::writeString'


Wenn ich aus einer großen Datei lesen muss, verwende ich normalerweise Files::lines , um einen faulen Stream ihrer Zeilen zu erhalten. Um eine große Datenmenge zu schreiben, die möglicherweise nicht vollständig im Speicher gespeichert ist, verwende ich Files::write um sie als Iterable<String> .


Aber was ist mit dem einfachen Fall, wenn ich den Inhalt einer Datei als einzelne Zeile verarbeiten möchte? Dies ist nicht sehr praktisch, da Files::readAllBytes und die entsprechenden Varianten von Files::write mit Byte-Arrays arbeiten.


Und dann erscheint Java 11 und writeString den Files readString und writeString :


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

Klar und einfach zu bedienen. Bei Bedarf können Sie den Charset an readString und in writeString auch an ein OpenOptions Array übergeben.


Leeren Sie die E / A mit 'Reader::nullReader' usw.


OutputStream Sie einen OutputStream , der nirgendwo schreibt? Oder ein leerer InputStream ? Was ist mit Reader und Writer , die nichts tun? Java 11 bietet alles:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(Anmerkung des Übersetzers: In commons-io diese Klassen seit ungefähr 2014.)


Ich bin jedoch überrascht - ist null wirklich das beste Präfix? Mir gefällt nicht, wie es "absichtliche Abwesenheit" bedeutet ... Vielleicht wäre es besser, noOp zu verwenden? (Anmerkung des Übersetzers: Höchstwahrscheinlich wurde dieses Präfix aufgrund der allgemeinen Verwendung von /dev/null .)


{ } ~> [ ] mit 'Collection::toArray'


Wie konvertieren Sie Sammlungen in Arrays?


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

Die erste Option, objects , verliert alle Informationen zu Typen, sodass sie im Flug ist. Was ist mit dem Rest? Beide sind sperrig, aber der erste ist kürzer. Letzteres erstellt ein Array mit der erforderlichen Größe, damit es produktiver aussieht (dh es scheint "produktiver zu sein", siehe Glaubwürdigkeit ). Aber ist es wirklich produktiver? Nein, im Gegenteil, es ist (im Moment) langsamer .


Aber warum sollte mich das interessieren? Gibt es nicht einen besseren Weg, dies zu tun? In Java 11 gibt es:


 String[] strings_fun = list.toArray(String[]::new); 

Eine neue Variante von Collection::toArray , die IntFunction<T[]> akzeptiert, d. H. Eine Funktion, die die Größe eines Arrays abruft und ein Array mit der erforderlichen Größe zurückgibt Es kann kurz als Referenz auf einen Konstruktor der Form T[]::new (für ein bekanntes T ) ausgedrückt werden.


Interessanterweise Collection#toArray(IntFunction<T[]>) die Standardimplementierung von Collection#toArray(IntFunction<T[]>) immer 0 an den Array-Generator. Zuerst habe ich beschlossen, dass diese Lösung auf der besten Leistung für Arrays mit einer Länge von Null basiert. Jetzt denke ich, dass der Grund dafür sein kann, dass die Berechnung der Größe für einige Sammlungen eine sehr teure Operation sein kann und Sie diesen Ansatz nicht in der Standardimplementierung von Collection . Bestimmte Sammlungsimplementierungen wie ArrayList können diesen Ansatz jedoch ändern, in Java 11 jedoch nicht. Das ist es nicht wert, denke ich.


Abwesenheitsprüfung mit 'Optional::isEmpty'


Bei der häufigen Verwendung von Optional , insbesondere in großen Projekten, bei denen Sie häufig auf einen nicht Optional Ansatz stoßen, müssen Sie häufig prüfen, ob dieser einen Wert hat. Optional::isPresent gibt es eine Optional::isPresent Methode. Aber genauso oft muss man das Gegenteil wissen - dass Optional leer ist. Kein Problem, benutze einfach !opt.isPresent() , oder?


Natürlich ist es so möglich, aber es ist fast immer einfacher, die if Logik zu verstehen, if ihre Bedingung nicht invertiert ist. Und manchmal erscheint Optional am Ende einer langen Kette von Anrufen, und wenn Sie es auf nichts überprüfen müssen, müssen Sie wetten ! ganz am Anfang:


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

In diesem Fall überspringen Sie es ! sehr einfach. Ab Java 11 gibt es eine bessere Option:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

Prädikate mit 'Predicate::not' invertieren


Apropos Invertieren ... Die Predicate verfügt über eine negate : Sie gibt ein neues Prädikat zurück, das dieselbe Prüfung durchführt, aber das Ergebnis invertiert. Leider schaffe ich es selten, es zu benutzen ...


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

Das Problem ist, dass ich selten Zugriff auf die Predicate Instanz habe. Häufiger möchte ich eine solche Instanz über einen Link zu einer Methode abrufen (und invertieren), aber damit dies funktioniert, muss der Compiler wissen, wohin er den Verweis auf die Methode bringen soll - ohne sie kann er nichts tun. Und genau das passiert, wenn Sie das (String::isBlank).negate() : Der Compiler weiß nicht mehr, was String::isBlank sein soll, und gibt auf. Eine korrekt angegebene Kaste behebt dies, aber zu welchem ​​Preis?


Obwohl es eine einfache Lösung gibt. Verwenden Sie nicht die negate instance, sondern die neue statische Methode Predicate.not(Predicate<T>) aus Java 11:


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

Schon besser!


Reguläre Ausdrücke als Prädikat mit 'Pattern::asMatchPredicate'


Gibt es einen regulären Ausdruck? Müssen Sie Daten darauf filtern? Wie wäre es damit:


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

Ich habe mich sehr gefreut, diese Methode zu finden! Es ist erwähnenswert, dass dies eine Methode aus Java 8 ist. Hoppla, ich habe sie dann verpasst. Java 11 hat eine weitere ähnliche Methode hinzugefügt: Pattern::asMatchPredicate . Was ist der Unterschied?


  • asPredicate prüft, ob die Zeichenfolge oder ein Teil der Zeichenfolge mit dem Muster übereinstimmt (funktioniert wie s -> this.matcher(s).find() )
  • asMatchPredicate prüft, ob die gesamte Zeichenfolge mit dem Muster übereinstimmt (funktioniert wie s -> this.matcher(s).matches() )

Zum Beispiel haben wir einen regulären Ausdruck, der Telefonnummern überprüft, aber keine ^ und $ , um den Anfang und das Ende einer Zeile zu verfolgen. Dann funktioniert der folgende Code nicht wie erwartet:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

Hast du einen Fehler bemerkt? Eine Zeile wie " -152 ? +1-202-456-1414" wird gefiltert, da sie eine gültige Telefonnummer enthält. Auf der anderen Seite lässt Pattern::asMatchPredicate nicht zu, da die gesamte Zeichenfolge nicht mehr mit dem Muster übereinstimmt.


Selbsttest


Hier ist eine Übersicht aller elf Perlen - erinnern Sie sich noch daran, was jede Methode bewirkt? Wenn ja, haben Sie den Test bestanden.


  • in String :
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • im Path :
    • static Path of(String, String...)
    • static Path of(URI)
  • in Files :
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • in InputStream : static InputStream nullInputStream()
  • in OutputStream : static OutputStream nullOutputStream()
  • im Reader : static Reader nullReader()
  • in Writer : static Writer nullWriter()
  • in Collection : T[] toArray(IntFunction<T[]>)
  • in Optional : boolean isEmpty()
  • im Predicate : static Predicate<T> not(Predicate<T>)
  • im Pattern : Predicate<String> asMatchPredicate()

Viel Spaß mit Java 11!

Source: https://habr.com/ru/post/de441154/


All Articles