Java 8 Funktionen mit Beispielen

Java 8 wurde am 18. März 2014 veröffentlicht. Das ist schon lange her, aber immer noch laufen viele Projekte auf Java 8. Das liegt daran, dass es eine wichtige Veröffentlichung mit vielen neuen Funktionen war. Schauen wir uns alle aufregenden und wichtigen Funktionen von Java 8 mit Beispielcode an.

Kurzüberblick über die Funktionen von Java 8

Einige der wichtigen Funktionen von Java 8 sind;

  1. forEach() Methode in der Iterable-Schnittstelle
  2. Standard- und statische Methoden in Schnittstellen
  3. Funktionale Schnittstellen und Lambda-Ausdrücke
  4. Java Stream API für Massendatenoperationen auf Sammlungen
  5. Java-Zeit-API
  6. Verbesserungen der Collection-API
  7. Verbesserungen der Concurrency-API
  8. Verbesserungen der Java-IO

Lassen Sie uns einen kurzen Blick auf diese Java-8-Funktionen werfen. Ich werde einige Code-Schnipsel zur besseren Verständnis der Funktionen auf einfache Weise bereitstellen.

1. forEach() Methode in der Iterable-Schnittstelle

Immer wenn wir durch eine Sammlung traversieren müssen, müssen wir einen Iterator erstellen, dessen einziger Zweck es ist, über zu iterieren, und dann haben wir Geschäftslogik in einer Schleife für jedes der Elemente in der Sammlung. Wir könnten ConcurrentModificationException bekommen, wenn der Iterator nicht ordnungsgemäß verwendet wird.

Java 8 hat die forEach-Methode in das java.lang.Iterable-Interface eingeführt, sodass wir uns beim Schreiben des Codes auf die Geschäftslogik konzentrieren können. Die forEach-Methode nimmt ein Objekt vom Typ java.util.function.Consumer als Argument, so dass sie dabei hilft, unsere Geschäftslogik an einem separaten Ort zu haben, den wir wiederverwenden können. Lassen Sie uns die Nutzung von forEach anhand eines einfachen Beispiels sehen.

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

	public static void main(String[] args) {
		
		//Erstellen einer Beispielsammlung
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//Durchlaufen mit Iterator
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//Durchlaufen durch forEach-Methode von Iterable mit anonymer Klasse
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		//Durchlaufen mit Implementierung des Consumer-Interfaces
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//Implementierung des Consumers, die wiederverwendet werden kann
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

Die Anzahl der Zeilen mag zunehmen, aber die forEach-Methode hilft dabei, die Logik für die Iteration und die Geschäftslogik an einem separaten Ort zu haben, was zu einer höheren Trennung der Anliegen und saubererem Code führt.

2. Standard- und statische Methoden in Schnittstellen

Wenn Sie die Details der forEach-Methode genau lesen, werden Sie feststellen, dass sie in der Iterable-Schnittstelle definiert ist, obwohl wir wissen, dass Schnittstellen keinen Methodenrumpf haben können. Ab Java 8 wurden Schnittstellen erweitert, um eine Methode mit Implementierung zu haben. Wir können das Schlüsselwort default und static verwenden, um Schnittstellen mit Methodenimplementierung zu erstellen. Die Implementierung der forEach-Methode in der Iterable-Schnittstelle lautet:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Wir wissen, dass Java keine mehrfache Vererbung in Klassen bietet, da dies zum Diamantproblem führt. Wie wird das jetzt mit Schnittstellen umgegangen, da Schnittstellen jetzt ähnlich wie abstrakte Klassen sind?

Die Lösung besteht darin, dass der Compiler in diesem Szenario eine Ausnahme wirft und wir die Implementationslogik in der Klasse bereitstellen müssen, die die Schnittstellen implementiert.

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
	
	static void print(String str){
		System.out.println("Printing "+str);
	}
	
	// Versuch, die Object-Methode zu überschreiben, führt zu einem Kompilierungsfehler
	//"Eine Standardmethode kann keine Methode aus java.lang.Object überschreiben"
	
//	default String toString(){
//		return "i1";
//	}
	
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

Beachten Sie, dass beide Schnittstellen eine gemeinsame Methode log() mit Implementierungslogik haben.

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	// MyClass wird nicht kompiliert, ohne eine eigene log()-Implementierung zu haben
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

Wie Sie sehen können, hat die Interface1 eine statische Methodenimplementierung, die in der MyClass.log()-Methodenimplementierung verwendet wird. Java 8 verwendet default und static Methoden intensiv in der Collection API und Standardmethoden werden hinzugefügt, damit unser Code rückwärtskompatibel bleibt.

Wenn eine Klasse in der Hierarchie eine Methode mit der gleichen Signatur hat, werden Standardmethoden irrelevant. Das Objekt ist die Basisklasse, daher werden Standardmethoden für equals() und hashCode() in der Schnittstelle irrelevant. Deshalb sind Schnittstellen aus Gründen der besseren Klarheit nicht berechtigt, Standardmethoden von Object zu haben.

Für vollständige Details zu den Schnittstellenänderungen in Java 8 lesen Sie bitte Java 8 Schnittstellenänderungen.

3. Funktionale Schnittstellen und Lambda-Ausdrücke

Wenn Sie den obigen Schnittstellencode bemerken, werden Sie die @FunctionalInterface-Annotation bemerken. Funktionale Schnittstellen sind ein neues Konzept, das in Java 8 eingeführt wurde. Eine Schnittstelle mit genau einer abstrakten Methode wird zu einer funktionalen Schnittstelle. Wir müssen keine @FunctionalInterface-Annotation verwenden, um eine Schnittstelle als funktionale Schnittstelle zu kennzeichnen.

@FunctionalInterface-Annotation ist eine Einrichtung, um die versehentliche Hinzufügung abstrakter Methoden in den funktionalen Schnittstellen zu vermeiden. Sie können es sich wie die @Override-Annotation vorstellen, und es ist bewährte Praxis, sie zu verwenden. java.lang.Runnable mit einer einzigen abstrakten Methode run() ist ein großartiges Beispiel für eine funktionale Schnittstelle.

Einer der Hauptvorteile der funktionalen Schnittstelle besteht in der Möglichkeit, Lambda-Ausdrücke zu verwenden, um sie zu instanziieren. Wir können eine Schnittstelle mit einer anonymen Klasse instanziieren, aber der Code sieht sperrig aus.

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

Da funktionale Schnittstellen nur eine Methode haben, können Lambda-Ausdrücke problemlos die Methodenimplementierung bereitstellen. Wir müssen nur Methodenargumente und Geschäftslogik bereitstellen. Zum Beispiel können wir die obige Implementierung mithilfe eines Lambda-Ausdrucks schreiben:Wenn Sie nur eine Anweisung in der Methodenimplementierung haben, benötigen wir auch keine geschweiften Klammern. Zum Beispiel kann die obige Interface1-Anonymklasse mithilfe von Lambda wie folgt instanziiert werden:

Runnable r1 = () -> {
			System.out.println("My Runnable");
		};

Wenn Sie eine einzelne Anweisung in der Methodenimplementierung haben, benötigen wir keine geschweiften Klammern. Zum Beispiel kann die obige anonyme Klasse von Interface1 mit Lambda wie folgt instanziiert werden:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

Lambda-Ausdrücke sind also ein Mittel, um anonyme Klassen von funktionalen Schnittstellen einfach zu erstellen. Es gibt keine Laufzeitvorteile bei der Verwendung von Lambda-Ausdrücken, daher werde ich sie vorsichtig einsetzen, da ich es nicht stört, einige zusätzliche Codezeilen zu schreiben.

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

Sie können das vollständige Tutorial unter Java 8 Lambda Expressions Tutorial lesen.

4. Java Stream API für Massendatenoperationen auf Sammlungen

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

Die Collection-Schnittstelle wurde um die Standardmethoden stream() und parallelStream() erweitert, um den Stream für sequentielle und parallele Ausführung zu erhalten. Betrachten wir ein einfaches Beispiel für ihre Verwendung.

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

	public static void main(String[] args) {
		
		List<Integer> myList = new ArrayList<>();
		for(int i=0; i<100; i++) myList.add(i);
		
		//sequentielle Stream
		Stream<Integer> sequentialStream = myList.stream();
		
		//paralleler Stream
		Stream<Integer> parallelStream = myList.parallelStream();
		
		//Verwendung von Lambda mit Stream API, Filterbeispiel
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		//Verwendung von Lambda in forEach
		highNums.forEach(p -> System.out.println("High Nums parallel="+p));
		
		Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
		highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

	}

}

Wenn Sie den obigen Beispielcode ausführen, erhalten Sie ein Ausgabe wie diese:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

Beachten Sie, dass parallele Verarbeitungsdaten nicht in einer bestimmten Reihenfolge vorliegen, sodass die parallele Verarbeitung bei der Arbeit mit großen Sammlungen sehr hilfreich sein kann.

Es ist in diesem Beitrag nicht möglich, alles über die Stream-API abzudecken, Sie können alles über die Stream-API im Artikel Java 8 Stream API Beispiel-Tutorial lesen.

5. Java Time API

Es war immer schwierig, mit Datum, Uhrzeit und Zeitzonen in Java umzugehen. In Java gab es keine standardisierte Vorgehensweise oder API für Datum und Uhrzeit. Eine schöne Ergänzung in Java 8 ist die java.time-Paket, das den Prozess der Arbeit mit der Zeit in Java vereinfachen wird.

Nur anhand der Java Time API-Pakete kann ich schon spüren, dass sie sehr einfach zu bedienen sein werden. Es gibt einige Unterpakete wie java.time.format, das Klassen zum Drucken und Parsen von Datumsangaben und Uhrzeiten bereitstellt, und java.time.zone, das Unterstützung für Zeitzonen und deren Regeln bietet.

Die neue Time API bevorzugt Enums gegenüber ganzzahligen Konstanten für Monate und Wochentage. Eine nützliche Klasse ist DateTimeFormatter für die Umwandlung von DateTime-Objekten in Zeichenfolgen. Für ein vollständiges Tutorial schauen Sie sich Java Date Time API Beispiel-Tutorial an.

6. Verbesserungen der Sammlungs-API

Wir haben bereits die forEach()-Methode und die Stream-API für Sammlungen gesehen. Einige neue Methoden, die in der Collection-API hinzugefügt wurden, sind:

  • Iterator Standardmethode forEachRemaining(Consumer action), um die angegebene Aktion für jedes verbleibende Element auszuführen, bis alle Elemente verarbeitet wurden oder die Aktion eine Ausnahme auslöst.
  • Collection Standardmethode removeIf(Predicate filter), um alle Elemente dieser Sammlung zu entfernen, die das angegebene Prädikat erfüllen.
  • Collection spliterator()-Methode, die ein Spliterator-Instanz zurückgibt, das verwendet werden kann, um Elemente sequenziell oder parallel zu durchlaufen.
  • Map replaceAll()compute()merge()-Methoden.
  • Leistungsverbesserung für die HashMap-Klasse bei Schlüsselkollisionen

7. Verbesserungen der Concurrency-API

Einige wichtige Verbesserungen der konkurrenten API sind:

  • ConcurrentHashMap compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() und search() Methoden.
  • CompletableFuture, das explizit abgeschlossen werden kann (Festlegen seines Werts und seines Status).
  • Executors die newWorkStealingPool()-Methode, um einen Work-Stealing-Thread-Pool mit allen verfügbaren Prozessoren als Ziel-Parallelitätslevel zu erstellen.

8. Verbesserungen bei Java IO

Einige bekannte IO-Verbesserungen sind:

  • Files.list(Path dir), das einen Stream zurückgibt, der erst nach und nach mit den Einträgen im Verzeichnis gefüllt wird.
  • Files.lines(Path path), das alle Zeilen aus einer Datei als Stream liest.
  • Files.find(), das einen Stream zurückgibt, der erst nach und nach mit Path gefüllt wird, indem nach Dateien in einer Dateibaum, der bei einer gegebenen Startdatei verwurzelt ist, gesucht wird.
  • BufferedReader.lines(), das einen Stream zurückgibt, dessen Elemente Zeilen sind, die von diesem BufferedReader gelesen werden.

Verschiedene Verbesserungen der Java 8 Core API

Einige verschiedene API-Verbesserungen, die möglicherweise nützlich sind:

  1. ThreadLocal statische Methode withInitial(Supplier supplier), um Instanzen leicht zu erstellen.
  2. Das Comparator-Interface wurde um viele Standard- und statische Methoden für die natürliche Sortierung, umgekehrte Reihenfolge usw. erweitert.
  3. min(), max() und sum() Methoden in den Integer, Long und Double Wrapper-Klassen.
  4. logicalAnd(), logicalOr() und logicalXor() Methoden in der Boolean-Klasse.
  5. ZipFile.stream() Methode, um einen geordneten Stream über die Einträge des ZIP-Datei zu erhalten. Die Einträge erscheinen im Stream in der Reihenfolge, in der sie im zentralen Verzeichnis der ZIP-Datei erscheinen.
  6. Mehrere Hilfsmethoden in der Math-Klasse.
  7. jjs Befehl wird hinzugefügt, um den Nashorn-Motor aufzurufen.
  8. jdeps Befehl wird hinzugefügt, um Klassendateien zu analysieren
  9. Die JDBC-ODBC-Brücke wurde entfernt.
  10. Das PermGen-Speicherbereich wurde entfernt

Das war’s für die Java 8-Features mit Beispielprogrammen. Wenn ich wichtige Java 8-Features vermisst habe, lass es mich bitte in den Kommentaren wissen.

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples