JavaRush /Java-Blog /Random-DE /Java 8-Funktionen – Der ultimative Leitfaden (Teil 1)
0xFF
Level 9
Донецк

Java 8-Funktionen – Der ultimative Leitfaden (Teil 1)

Veröffentlicht in der Gruppe Random-DE
Der erste Teil der Übersetzung des Artikels Java 8 Features – The ULTIMATE Guide . Der zweite Teil ist hier (Link kann sich ändern). Java 8-Funktionen – Der ultimative Leitfaden (Teil 1) - 1 Anmerkung des Herausgebers : Dieser Artikel wurde veröffentlicht, als Java 8 für die Öffentlichkeit verfügbar war, und alles deutet darauf hin, dass es sich tatsächlich um eine Hauptversion handelt. Hier haben wir zahlreiche Leitfäden für Java Code Geeks bereitgestellt, z. B. Playing with Java 8 – Lambdas and Concurrency , Java 8 Date and Time API Guide: LocalDateTime und Abstract Class vs. Interface in the Java 8 Era . Wir verlinken auch auf 15 unbedingt gelesene Java 8-Tutorials aus anderen Quellen . Natürlich schauen wir uns einige der Nachteile an, wie zum Beispiel die dunkle Seite von Java 8 . Es ist also an der Zeit, alle Hauptfunktionen von Java 8 bequem an einem Ort zu sammeln. Genießen!

1. Einleitung

Ohne Zweifel ist die Veröffentlichung von Java 8 das größte Ereignis seit Java 5 (veröffentlicht vor ziemlich langer Zeit, im Jahr 2004). Es brachte Java viele neue Funktionen, sowohl in der Sprache, im Compiler, in den Bibliotheken, in den Tools als auch in der JVM (Java Virtual Machine). In diesem Tutorial werfen wir einen Blick auf diese Änderungen und demonstrieren verschiedene Anwendungsfälle anhand von Beispielen aus der Praxis. Der Leitfaden besteht aus mehreren Teilen, die sich jeweils mit einem bestimmten Aspekt der Plattform befassen:
  • Sprache
  • Compiler
  • Bibliotheken
  • Werkzeuge
  • Laufzeitumgebung (JVM)

2. Neue Funktionen in Java 8

Auf jeden Fall handelt es sich bei Java 8 um ein Major Release. Wir können sagen, dass es aufgrund der Implementierung der Funktionen, nach denen jeder Java-Entwickler gesucht hat, so lange gedauert hat. In diesem Abschnitt werden wir die meisten davon behandeln.

2.1. Lambdas und funktionale Schnittstellen

Lambdas (auch bekannt als private oder anonyme Methoden) sind die größte und am meisten erwartete Sprachänderung in der gesamten Java 8-Version. Sie ermöglichen es uns, Funktionalität als Methodenargument anzugeben (indem wir eine Funktion darum herum deklarieren) oder Code als Daten anzugeben : Konzepte, mit denen jeder Funktionsentwickler vertraut ist. Programmierung _ Viele Sprachen auf der JVM-Plattform (Groovy, Scala , ...) hatten vom ersten Tag an Lambdas, aber Java-Entwickler hatten keine andere Wahl, als Lambdas durch anonyme Klassen darzustellen. Die Diskussion über das Design von Lambdas hat in der Öffentlichkeit viel Zeit und Mühe gekostet. Doch irgendwann wurden Kompromisse gefunden, die zur Entstehung neuer prägnanter Designs führten. In seiner einfachsten Form kann ein Lambda als eine durch Kommas getrennte Liste von Parametern, ein –> Symbol und einen Körper dargestellt werden. Zum Beispiel:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Beachten Sie, dass der Typ des Arguments e vom Compiler bestimmt wird. Darüber hinaus können Sie den Typ eines Parameters explizit angeben, indem Sie den Parameter in Klammern setzen. Zum Beispiel:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Falls der Lambda-Körper komplexer ist, kann er in geschweifte Klammern eingeschlossen werden, ähnlich wie bei einer regulären Funktionsdefinition in Java. Zum Beispiel:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Ein Lambda kann auf Klassenmitglieder und lokale Variablen verweisen (wodurch der Aufruf implizit wirksam wird, unabhängig davon, ob auf finaldas Feld zugegriffen wird oder nicht). Diese beiden Snippets sind beispielsweise gleichwertig:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
UND:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambdas können einen Wert zurückgeben. Der Rückgabetyp wird vom Compiler bestimmt. Eine Deklaration returnist nicht erforderlich, wenn der Körper des Lambda aus einer Zeile besteht. Die beiden folgenden Codeausschnitte sind äquivalent:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
UND:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Die Sprachentwickler haben lange darüber nachgedacht, wie sie bestehende Funktionen Lambda-freundlich machen können. Daraus entstand das Konzept einer funktionalen Schnittstelle. Eine funktionale Schnittstelle ist eine Schnittstelle mit nur einer Methode. Dadurch kann er implizit in einen Lambda-Ausdruck konvertiert werden. java.lang.Runnableund java.util.concurrent.Callablezwei großartige Beispiele für funktionale Schnittstellen. In der Praxis sind funktionale Schnittstellen sehr fragil: Wenn jemand der Schnittstellendefinition auch nur eine weitere Methode hinzufügt, ist diese nicht mehr funktionsfähig und der Kompilierungsprozess wird nicht abgeschlossen. Um diese Fragilität zu vermeiden und die Absicht einer Schnittstelle explizit als funktional zu definieren, wurde in Java 8 eine spezielle Annotation hinzugefügt @FunctionalInterface(alle vorhandenen Schnittstellen in der Java-Bibliothek erhielten die Annotation @FunctionalInterface). Schauen wir uns diese einfache Definition einer funktionalen Schnittstelle an:
@FunctionalInterface
public interface Functional {
    void method();
}
Dabei ist eines zu beachten: Standardmethoden und statische Methoden verstoßen nicht gegen das Prinzip einer funktionalen Schnittstelle und können wie folgt deklariert werden:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambdas sind die beliebteste Funktion von Java 8. Sie haben das Potenzial, mehr Entwickler für diese wunderbare Plattform zu gewinnen und intelligente Unterstützung für Funktionen in reinem Java bereitzustellen. Ausführlichere Informationen finden Sie in der offiziellen Dokumentation .

2.2. Standardschnittstellen und statische Methoden

Java 8 hat die Definition von Schnittstellen um zwei neue Konzepte erweitert: Standardmethode und statische Methode. Standardmethoden machen Schnittstellen etwas ähnlich zu Merkmalen, dienen aber einem etwas anderen Zweck. Sie ermöglichen es Ihnen, neue Methoden zu vorhandenen Schnittstellen hinzuzufügen, ohne die Abwärtskompatibilität für zuvor geschriebene Versionen dieser Schnittstellen zu beeinträchtigen. Der Unterschied zwischen Standardmethoden und abstrakten Methoden besteht darin, dass abstrakte Methoden implementiert werden müssen, während dies bei Standardmethoden nicht der Fall ist. Stattdessen muss jede Schnittstelle eine sogenannte Standardimplementierung bereitstellen, und alle Nachkommen erhalten diese standardmäßig (mit der Möglichkeit, diese Standardimplementierung bei Bedarf zu überschreiben). Schauen wir uns das Beispiel unten an.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // oder не реализовывать его
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
Die Schnittstelle Defaulabledeklariert eine Standardmethode notRequired()mithilfe eines Schlüsselworts defaultals Teil der Methodendefinition. Eine der Klassen, DefaultableImpl, implementiert diese Schnittstelle und lässt die Standardmethode unverändert. Eine andere Klasse, OverridableImpl, überschreibt die Standardimplementierung und stellt eine eigene bereit. Eine weitere interessante Funktion, die in Java 8 eingeführt wurde, besteht darin, dass Schnittstellen statische Methoden deklarieren (und Implementierungen davon anbieten) können. Hier ist ein Beispiel:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Ein kleiner Codeausschnitt kombiniert die Standardmethode und die statische Methode aus dem obigen Beispiel:
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}
Die Konsolenausgabe dieses Programms sieht folgendermaßen aus:
Default implementation
Overridden implementation
Die Standardmethodenimplementierung in der JVM ist sehr effizient und der Methodenaufruf wird durch Bytecode-Anweisungen unterstützt. Standardmethoden ermöglichten die Weiterentwicklung vorhandener Java-Schnittstellen, ohne den Kompilierungsprozess zu unterbrechen. Gute Beispiele sind die vielen Methoden, die der Schnittstelle hinzugefügt wurden java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Obwohl sie leistungsstark sind, sollten Standardmethoden mit Vorsicht verwendet werden: Bevor Sie eine Standardmethode deklarieren, sollten Sie zweimal darüber nachdenken, ob dies wirklich notwendig ist, da dies zu Problemen führen kann Unklarheiten und Fehler bei der Kompilierung komplexer Hierarchien. Nähere Informationen finden Sie in der Dokumentation .

2.3. Referenzmethoden

Referenzmethoden implementieren eine nützliche Syntax, um auf vorhandene Methoden oder Konstruktoren von Java-Klassen oder -Objekten (Instanzen) zu verweisen. Zusammen mit Lambda-Ausdrücken machen Referenzmethoden Sprachkonstrukte kompakt und prägnant und somit vorlagenbasiert. Nachfolgend finden Sie eine Klasse Carals Beispiel für verschiedene Methodendefinitionen. Lassen Sie uns die vier unterstützten Arten von Referenzmethoden hervorheben:
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}
Die erste Referenzmethode ist eine Referenz auf einen Konstruktor mit Syntax Class::newoder eine Alternative für Generics Class< T >::new. Beachten Sie, dass der Konstruktor keine Argumente hat.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
Die zweite Option ist ein Verweis auf eine statische Methode mit der Syntax Class::static_method. Beachten Sie, dass die Methode genau einen Parameter vom Typ akzeptiert Car.
cars.forEach( Car::collide );
Der dritte Typ ist eine Referenz auf eine Methode einer Instanz eines beliebigen Objekts eines bestimmten Typs mit der Syntax Class::method. Beachten Sie, dass die Methode keine Argumente akzeptiert.
cars.forEach( Car::repair );
Und der letzte, vierte Typ ist ein Verweis auf eine Methode einer Instanz einer bestimmten Klasse mit der Syntax instance::method. Bitte beachten Sie, dass die Methode nur einen Parameter vom Typ akzeptiert Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
Das Ausführen aller dieser Beispiele als Java-Programme erzeugt die folgende Konsolenausgabe (die Klassenreferenz Carkann variieren):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Ausführlichere Informationen und Einzelheiten zu Referenzmethoden finden Sie in der offiziellen Dokumentation .

2.4. Doppelte Anmerkungen

Seit Java 5 die Unterstützung für Anmerkungen eingeführt hat , ist diese Funktion sehr beliebt geworden und wird sehr häufig verwendet. Eine der Einschränkungen bei der Verwendung von Annotationen bestand jedoch darin, dass dieselbe Annotation nicht mehr als einmal an derselben Stelle deklariert werden kann. Java 8 bricht diese Regel und führt doppelte Anmerkungen ein. Dadurch können dieselben Anmerkungen an der Stelle, an der sie deklariert wurden, mehrmals wiederholt werden. Doppelte Anmerkungen sollten sich selbst mithilfe von annotation annotieren @Repeatable. Tatsächlich handelt es sich dabei weniger um eine Änderung der Sprache als vielmehr um einen Compiler-Trick, während die Technik dieselbe bleibt. Schauen wir uns ein einfaches Beispiel an:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
Wie wir sehen können, Filterist die Klasse mit @Repeatable( Filters. class) annotiert. Filtersist einfach der Besitzer von annotations Filter, aber der Java-Compiler versucht, ihre Anwesenheit vor Entwicklern zu verbergen. Daher Filterableenthält die Schnittstelle Annotationen Filter, die zweimal deklariert werden (ohne zu erwähnen Filters). Die Reflection-API bietet außerdem eine neue Methode getAnnotationsByType()zum Zurückgeben doppelter Anmerkungen irgendeines Typs (denken Sie daran, dass Filterable. class.getAnnotation( Filters. class) eine vom Compiler injizierte Instanz zurückgibt Filters). Die Ausgabe des Programms sieht folgendermaßen aus:
filter1
filter2
Ausführlichere Informationen finden Sie in der offiziellen Dokumentation .

2.5. Verbesserte Typinferenz

Der Java 8-Compiler hat viele Verbesserungen bei der Typinferenz erhalten. In vielen Fällen können vom Compiler explizite Typparameter definiert werden, wodurch der Code sauberer wird. Schauen wir uns ein Beispiel an:
package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
Und hier ist die Verwendung mit type Value<String>:
package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}
Der Typparameter Value.defaultValue()wird automatisch ermittelt und muss nicht explizit angegeben werden. In Java 7 lässt sich das gleiche Beispiel nicht kompilieren und muss als <NOBR>Value.<String>defaultValue()</NOBR> umgeschrieben werden.

2.6. Erweiterte Unterstützung für Anmerkungen

Java 8 erweitert den Kontext, in dem Anmerkungen verwendet werden können. Heutzutage kann fast alles eine Annotation haben: lokale Variablen, generische Typen, Superklassen und implementierte Schnittstellen, sogar Methodenausnahmen. Im Folgenden werden einige Beispiele vorgestellt:
package com.javacodegeeks.java8.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }
}
ElementType.TYPE_USEund ElementType.TYPE_PARAMETERzwei neue Elementtypen zur Beschreibung des relevanten Annotationskontexts. Annotation Processing APIwurde außerdem geringfügigen Änderungen unterzogen, um neue Annotationstypen in Java zu erkennen.

3. Neue Funktionen im Java-Compiler

3.1. Parameternamen

Im Laufe der Zeit haben Java-Entwickler verschiedene Möglichkeiten erfunden, Methodenparameternamen im Java-Bytecode zu speichern , um sie zur Laufzeit verfügbar zu machen (z. B. die Paranamer-Bibliothek ). Schließlich erstellt Java 8 diese schwierige Funktion in der Sprache (unter Verwendung der Reflection-API und -Methode Parameter.getName()) und im Bytecode (unter Verwendung des neuen Compiler-Arguments javac:) –parameters.
package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}
Wenn Sie diese Klasse ohne Verwendung eines Arguments kompilieren –parametersund dann das Programm ausführen, sehen Sie etwa Folgendes:
Parameter: arg0
Wenn der Parameter –parametersan den Compiler übergeben wird, ist die Programmausgabe anders (der tatsächliche Name des Parameters wird angezeigt):
Parameter: args
Für fortgeschrittene Maven-Benutzer kann das Argument –parameters mithilfe des Abschnitts zur Kompilierung hinzugefügt werden maven-compiler-plugin:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <compilerArgument>-parameters</compilerArgument>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
</plugin>
Um die Verfügbarkeit von Parameternamen zu überprüfen, stellt isNamePresent()die Klasse eine praktische Methode bereit Parameter.

4. Neue Java-Tools

Java 8 verfügt über eine Reihe neuer Befehlszeilentools. In diesem Abschnitt werden wir uns die interessantesten davon ansehen.

4.1. Nashorn-Motor: jjs

jjs ist eine eigenständige Nashorn-Engine, die befehlszeilenbasiert ist. Es nimmt eine Liste von JavaScript-Quellcodedateien und führt sie aus. Erstellen wir beispielsweise eine func.js- Datei mit folgendem Inhalt:
function f() {
     return 1;
};

print( f() + 1 );
Um diese Datei auszuführen, übergeben wir sie als Argument an jjs :
jjs func.js
Die Konsolenausgabe sieht folgendermaßen aus:
2
Weitere Einzelheiten finden Sie in der Dokumentation .

4.2. Klassenabhängigkeitsanalysator: jdeps

jdeps ist ein wirklich tolles Befehlszeilentool. Es zeigt Abhängigkeiten auf Paket- oder Klassenebene für Java-Klassen. Es akzeptiert eine .class- Datei, einen Ordner oder eine JAR-Datei als Eingabe. Standardmäßig gibt jdeps Abhängigkeiten an die Standardausgabe (Konsole) aus. Schauen wir uns als Beispiel den Abhängigkeitsbericht der beliebten Spring Framework- Bibliothek an . Um das Beispiel kurz zu halten, schauen wir uns nur die Abhängigkeiten für die JAR-Datei an org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
Dieser Befehl gibt ziemlich viel aus, daher analysieren wir nur einen Teil der Ausgabe. Abhängigkeiten werden nach Paketen gruppiert. Wenn keine Abhängigkeiten vorhanden sind, wird „nicht gefunden“ angezeigt .
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
Ausführlichere Informationen finden Sie in der offiziellen Dokumentation .
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION