JavaRush /Blog Java /Random-PL /Funkcje Java 8 — kompletny przewodnik (część 1)
0xFF
Poziom 9
Донецк

Funkcje Java 8 — kompletny przewodnik (część 1)

Opublikowano w grupie Random-PL
Pierwsza część tłumaczenia artykułu Funkcje Java 8 – Przewodnik ULTIMATE . Druga część jest tutaj (link może ulec zmianie). Funkcje Java 8 — kompletny przewodnik (część 1) — 1 Nota wydawcy : Ten artykuł został opublikowany w czasie, gdy Java 8 była publicznie dostępna i wszystko wskazuje na to, że jest to rzeczywiście wersja główna. Tutaj udostępniliśmy mnóstwo przewodników Java Code Geeks, takich jak Playing with Java 8 - Lambdas and Concurrency , Java 8 Date and Time API Guide: LocalDateTime i Klasa abstrakcyjna a interfejs w erze Java 8 . Oferujemy także linki do 15 obowiązkowych samouczków dotyczących języka Java 8 z innych źródeł . Oczywiście zwracamy uwagę na niektóre wady, takie jak ciemna strona Java 8 . Nadszedł więc czas, aby dla Twojej wygody zebrać wszystkie główne funkcje Java 8 w jednym miejscu. Cieszyć się!

1. Wstęp

Bez wątpienia wydanie Java 8 jest największym wydarzeniem od czasu Java 5 (wydanego dość dawno temu, w 2004 roku). Wniosło wiele nowych funkcji do Java, zarówno w języku, kompilatorze, bibliotekach, narzędziach, jak i JVM (Java Virtual Machine). W tym samouczku przyjrzymy się tym zmianom i zademonstrujemy różne przypadki użycia na rzeczywistych przykładach. Przewodnik składa się z kilku części, z których każda dotyczy konkretnego aspektu platformy:
  • Język
  • Kompilator
  • Biblioteki
  • Narzędzia
  • Środowisko wykonawcze (JVM)

2. Nowe funkcje w Javie 8

W każdym razie Java 8 jest wersją główną. Można powiedzieć, że trwało to tak długo ze względu na wdrożenie funkcji, których szukał każdy programista Java. W tej sekcji omówimy większość z nich.

2.1. Lambdy i interfejsy funkcjonalne

Lambdy (znane również jako metody prywatne lub anonimowe) to największa i najbardziej oczekiwana zmiana języka w całej wersji Java 8. Pozwalają nam określić funkcjonalność jako argument metody (poprzez deklarację funkcji wokół niej) lub określić kod jako dane : koncepcje znane każdemu programiście funkcjonalności.programowanie _ Wiele języków na platformie JVM (Groovy, Scala , ...) miało lambdy od samego początku, ale programiści Java nie mieli innego wyjścia, jak tylko reprezentować lambdy poprzez anonimowe klasy. Debata na temat projektu lambdy zajęła społeczeństwu dużo czasu i wysiłku. Ale w końcu znaleziono kompromisy, które doprowadziły do ​​pojawienia się nowych, zwięzłych projektów. W najprostszej formie lambda może być reprezentowana jako lista parametrów oddzielonych przecinkami, symbol –> i treść. Na przykład:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Należy pamiętać, że typ argumentu e jest określany przez kompilator. Ponadto można jawnie określić typ parametru, zawijając parametr w nawiasy. Na przykład:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
Jeśli ciało lambda jest bardziej złożone, można je owinąć w nawiasy klamrowe, podobnie jak w przypadku zwykłej definicji funkcji w Javie. Na przykład:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
Lambda może odwoływać się do członków klasy i zmiennych lokalnych (w sposób dorozumiany czyniąc wywołanie skutecznym niezależnie od tego, czy finalpole jest dostępne, czy nie). Na przykład te 2 fragmenty są równoważne:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
I:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambda może zwrócić wartość. Typ zwracany zostanie określony przez kompilator. Deklaracja returnnie jest wymagana, jeśli treść lambdy składa się z jednej linii. Poniższe dwa fragmenty kodu są równoważne:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
I:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
Twórcy języka długo zastanawiali się, jak uczynić istniejące funkcje przyjaznymi dla lambdy. W rezultacie pojawiła się koncepcja funkcjonalnego interfejsu. Interfejs funkcjonalny to interfejs z tylko jedną metodą. W rezultacie można go niejawnie przekonwertować na wyrażenie lambda. java.lang.Runnablei java.util.concurrent.Callabledwa świetne przykłady funkcjonalnych interfejsów. W praktyce interfejsy funkcjonalne są bardzo kruche: jeśli ktoś doda do definicji interfejsu choćby jedną inną metodę, przestanie ona działać i proces kompilacji nie zostanie dokończony. Aby uniknąć tej kruchości i wyraźnie zdefiniować przeznaczenie interfejsu jako funkcjonalnego, w Javie 8 dodano specjalną adnotację @FunctionalInterface(wszystkie istniejące interfejsy w bibliotece Java otrzymały adnotację @FunctionalInterface). Spójrzmy na tę prostą definicję interfejsu funkcjonalnego:
@FunctionalInterface
public interface Functional {
    void method();
}
Należy pamiętać o jednym: metody domyślne i metody statyczne nie naruszają zasady interfejsu funkcjonalnego i można je zadeklarować:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambdy to najpopularniejsza funkcja Java 8. Mają cały potencjał, aby przyciągnąć więcej programistów do tej wspaniałej platformy i zapewnić inteligentną obsługę funkcji w czystej Javie. Bardziej szczegółowe informacje można znaleźć w oficjalnej dokumentacji .

2.2. Domyślne interfejsy i metody statyczne

Java 8 rozszerzyła definicję interfejsów o dwa nowe pojęcia: metodę domyślną i metodę statyczną. Metody domyślne sprawiają, że interfejsy są nieco podobne do cech, ale służą nieco innemu celowi. Umożliwiają dodawanie nowych metod do istniejących interfejsów bez przerywania kompatybilności wstecznej dla wcześniej napisanych wersji tych interfejsów. Różnica między metodami domyślnymi a metodami abstrakcyjnymi polega na tym, że metody abstrakcyjne muszą zostać zaimplementowane, a metody domyślne nie. Zamiast tego każdy interfejs musi zapewniać tzw. implementację domyślną, a wszyscy potomkowie otrzymają ją domyślnie (z możliwością obejścia tej domyślnej implementacji, jeśli to konieczne). Spójrzmy na poniższy przykład.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // Lub не реализовывать его
    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";
    }
}
Interfejs Defaulabledeklaruje metodę domyślną notRequired(), używając słowa kluczowego defaultjako części definicji metody. Jedna z klas DefaultableImplimplementuje ten interfejs, pozostawiając metodę domyślną bez zmian. Inna klasa OverridableImplzastępuje domyślną implementację i udostępnia własną. Kolejną interesującą funkcją wprowadzoną w Javie 8 jest to, że interfejsy mogą deklarować (i oferować implementacje) metod statycznych. Oto przykład:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
Mały fragment kodu łączy metodę domyślną i metodę statyczną z powyższego przykładu:
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() );
}
Dane wyjściowe tego programu w konsoli wyglądają następująco:
Default implementation
Overridden implementation
Domyślna implementacja metody w JVM jest bardzo wydajna, a wywoływanie metod jest obsługiwane przez instrukcje w kodzie bajtowym. Domyślne metody umożliwiały ewolucję istniejących interfejsów Java bez przerywania procesu kompilacji. Dobrymi przykładami są liczne metody dodane do interfejsu java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), ... Mimo że metody domyślne są potężne, należy ich używać ostrożnie: przed zadeklarowaniem metody domyślnej powinieneś dwa razy pomyśleć, czy jest to naprawdę konieczne, ponieważ może to prowadzić do niejasności i błędów kompilacji w złożonych hierarchiach. Bardziej szczegółowe informacje można znaleźć w dokumentacji .

2.3. Metody referencyjne

Metody referencyjne implementują użyteczną składnię do odwoływania się do istniejących metod lub konstruktorów klas lub obiektów Java (instancji). Wraz z wyrażeniami lambda metody referencyjne sprawiają, że konstrukcje językowe są zwarte i zwięzłe, dzięki czemu są oparte na szablonach. Poniżej znajduje się klasa Carjako przykład różnych definicji metod, wyróżnijmy cztery obsługiwane typy metod referencyjnych:
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() );
    }
}
Pierwszą metodą referencyjną jest odwołanie do konstruktora ze składnią Class::newlub alternatywą dla typów generycznych Class< T >::new. Należy pamiętać, że konstruktor nie ma argumentów.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
Druga opcja to odwołanie do metody statycznej o składni Class::static_method. Należy pamiętać, że metoda przyjmuje dokładnie jeden parametr typu Car.
cars.forEach( Car::collide );
Trzeci typ to odwołanie do metody instancji dowolnego obiektu określonego typu o składni Class::method. Należy pamiętać, że metoda nie akceptuje żadnych argumentów.
cars.forEach( Car::repair );
I ostatni, czwarty typ to odwołanie do metody instancji określonej klasy o składni instance::method. Należy pamiętać, że metoda akceptuje tylko jeden parametr typu Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
Uruchomienie wszystkich tych przykładów jako programów Java daje następujące wyniki konsoli (odniesienia do klas Carmogą się różnić):
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
Bardziej szczegółowe informacje i szczegóły dotyczące metod referencyjnych można znaleźć w oficjalnej dokumentacji .

2.4. Zduplikowane adnotacje

Odkąd w Javie 5 wprowadzono obsługę adnotacji , funkcja ta stała się bardzo popularna i szeroko stosowana. Jednakże jednym z ograniczeń stosowania adnotacji był fakt, że tej samej adnotacji nie można zadeklarować więcej niż raz w tym samym miejscu. Java 8 łamie tę zasadę i wprowadza zduplikowane adnotacje. Pozwala to na wielokrotne powtarzanie tych samych adnotacji w miejscu, w którym są zadeklarowane. Zduplikowane adnotacje powinny być opatrzone adnotacją @Repeatable. W rzeczywistości jest to nie tyle zmiana języka, co sztuczka kompilatora, podczas gdy technika pozostaje taka sama. Spójrzmy na prosty przykład:
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() );
        }
    }
}
Jak widzimy, klasa Filterjest opatrzona adnotacją @Repeatable( Filters. class). Filtersjest po prostu właścicielem adnotacji Filter, ale kompilator Java próbuje ukryć ich obecność przed programistami. Zatem interfejs Filterablezawiera adnotacje Filterzadeklarowane dwukrotnie (bez wzmianki o Filters). Interfejs API Reflection udostępnia także nową metodę getAnnotationsByType()zwracania zduplikowanych adnotacji pewnego typu (pamiętaj, że Filterable. class.getAnnotation( Filters. class) zwróci instancję wstrzykniętą przez kompilator Filters). Dane wyjściowe programu będą wyglądać następująco:
filter1
filter2
Bardziej szczegółowe informacje można znaleźć w oficjalnej dokumentacji .

2.5. Ulepszone wnioskowanie o typie

Kompilator Java 8 otrzymał wiele ulepszeń w zakresie wnioskowania o typach. W wielu przypadkach kompilator może zdefiniować jawne parametry typu, dzięki czemu kod będzie czystszy. Spójrzmy na jeden przykład:
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;
    }
}
A oto użycie z typem 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() );
    }
}
Parametr typu Value.defaultValue()jest określany automatycznie i nie trzeba go podawać jawnie. W Javie 7 ten sam przykład nie zostanie skompilowany i musi zostać przepisany jako <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Rozszerzona obsługa adnotacji

Java 8 rozszerza kontekst, w którym można używać adnotacji. Obecnie prawie wszystko może mieć adnotację: zmienne lokalne, typy ogólne, nadklasy i zaimplementowane interfejsy, a nawet wyjątki od metod. Poniżej przedstawiono kilka przykładów:
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_USEoraz ElementType.TYPE_PARAMETERdwa nowe typy elementów opisujące odpowiedni kontekst adnotacji. Annotation Processing APIprzeszedł również drobne zmiany w celu rozpoznawania nowych typów adnotacji w Javie.

3. Nowe funkcje w kompilatorze Java

3.1. Nazwy parametrów

Przez cały czas programiści Java wymyślali różne sposoby przechowywania nazw parametrów metod w kodzie bajtowym Java, aby udostępnić je w czasie wykonywania (na przykład biblioteka Paranamer ). Wreszcie Java 8 tworzy tę trudną funkcję w języku (używając API i metody Reflection Parameter.getName()) i kodzie bajtowym (używając nowego argumentu kompilatora 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() );
        }
    }
}
Jeśli skompilujesz tę klasę bez użycia argumentu, –parametersa następnie uruchomisz program, zobaczysz coś takiego:
Parameter: arg0
Po przekazaniu parametru –parametersdo kompilatora wynik programu będzie inny (pokazana zostanie rzeczywista nazwa parametru):
Parameter: args
W przypadku zaawansowanych użytkowników Mavena argument –parameters można dodać do kompilacji, korzystając z sekcji 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>
Aby sprawdzić dostępność nazw parametrów, istnieje wygodna isNamePresent()metoda udostępniona przez klasę Parameter.

4. Nowe narzędzia Java

Java 8 zawiera nowy zestaw narzędzi wiersza poleceń. W tej sekcji przyjrzymy się najciekawszym z nich.

4.1. Silnik Nashorna: jjs

jjs to samodzielny silnik Nashorn oparty na wierszu poleceń. Pobiera listę plików kodu źródłowego JavaScript i uruchamia je. Na przykład utwórzmy plik func.js z następującą zawartością:
function f() {
     return 1;
};

print( f() + 1 );
Aby uruchomić ten plik, przekażmy go jako argument do jjs :
jjs func.js
Dane wyjściowe konsoli będą wyglądać następująco:
2
Więcej szczegółów znajdziesz w dokumentacji .

4.2. Analizator zależności klas: jdeps

jdeps to naprawdę świetne narzędzie wiersza poleceń. Pokazuje zależności na poziomie pakietu lub klasy dla klas Java. Jako dane wejściowe akceptuje plik .class , folder lub plik JAR . Domyślnie jdeps wyprowadza zależności na standardowe wyjście (konsolę). Jako przykład przyjrzyjmy się raportowi zależności popularnej biblioteki Spring Framework . Aby skrócić przykład, przyjrzyjmy się zależnościom tylko dla pliku JAR org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
To polecenie generuje całkiem sporo danych wyjściowych, dlatego przeanalizujemy tylko część wyników. Zależności są pogrupowane według pakietów. Jeśli nie ma żadnych zależności, zostanie wyświetlony komunikat Nie znaleziono .
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
Bardziej szczegółowe informacje można znaleźć w oficjalnej dokumentacji .
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION