Java has not declined. People's understanding of it is just beginning
I'm glad to share the new features of Java 8 here. This article will take you step by step through all the new features of Java 8. I will show you the default method, lambda expression, method reference and repeated annotation in the interface through simple example code. After reading this article, you will understand the new changes in flow, function, interface, map extension and date. No nonsense, put the code here!
Default methods for interfaces
Java 8 allows us to add a non abstract method modified by the default keyword in the interface. This feature is called the extension method. Here is an example:
interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
In the interface Formula, in addition to the abstract method calculation, the non abstract method sqrt is also defined. The implementation class inherits the abstract method calculation. The default method can be called directly
Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0
Formula is an anonymous object inheriting the formula interface. The example code is very detailed: only 6 lines of code realize a simple sqrt(a * 100) calculation. In the next chapter, we will introduce a more perfect scheme to realize the use of single method object in Java 8
Lambda expressions
The following example shows you how to sort the strings in the List in the previous version:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
Through a static Collections.sort You often find yourself creating an anonymous Comparator class and sorting through it
In order to get rid of the way of creating anonymous objects to achieve sorting, Java 8 brings a more concise way to achieve it. lambda expression:
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
You will find that the code is more concise and readable. Even it can be more concise:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
You can directly omit the {} and return keywords for a single line method body. You can even make it shorter:
Collections.sort(names, (a, b) -> b.compareTo(a));
The java compiler can automatically recognize parameter types, so you can omit them. Let's have a deeper understanding of the broader application of lambda expressions in Java
Functional interfaces
How do lambda expressions match Java types? Each lambda expression is equivalent to specifying the type of an interface. A function interface that must define a method to draw a line. Each lambda expression type will match this abstract method. Because the default method is not an abstract method, you can freely add the default method to your function interface
We can use any interface as a lambda expression, which only needs to contain an abstract method. In order to ensure that your interface meets the requirements, you need to add a @ functional interface annotation to your interface. When you use this annotation, this interface can only define an abstract method, or the compiler will report an error
Example:
@FunctionalInterface interface Converter<F, T> { T convert(F from); }
Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
Note that @ functional interface is also valid code
Method and constructor references
The above code can be applied in a static way to make it more concise:
Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
Java 8 can pass a method or constructor reference through:: keyword. The above example shows you how to reference a static method. We can also reference a common object method:
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } }
Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
Let's see how:: refers to constructors. First, we define two constructors in a javabean:
class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
Next, we create a factory interface to create a Person object:
interface PersonFactory<P extends Person> { P create(String firstName, String lastName); }
To implement the factory interface manually, we associate everything with the constructor:
PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");
We use Person::new to create a reference to the Person constructor. The Java compiler automatically creates an object through PersonFactory.create Parameters match the appropriate constructor
Lambda scopes
Accessing local variables from lambda expressions is similar to anonymous objects. You can access final decorated local variables and instantiated local properties and static variables
Accessing local variables
lambda expression can read a final decorated local variable from the local:
final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
Unlike anonymous objects, the variable num does not need to be decorated with final in anonymous objects. It can also be called normally:
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
However, num must implicitly use final decoration to compile. The following code will not compile:
int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); num = 3;
Writing num into a lambda expression is also prohibited
Accessing fields and static variables
Compared with local variables, we can read and write a global attribute and static variable in lambda expression. This is the same as in anonymous object
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
Accessing default interface methods
Remember the first instance of formula? Interface formula defines a default method, sqrt, which can be accessed by every formula instance including anonymous objects. However, it is not suitable for lambda expressions
lambda expressions cannot access the default method of the interface. The following code cannot be compiled:
Formula formula = (a) -> sqrt( a * 100);
Built in functional interfaces
The DK 1.8 API contains many built-in functions, many of which are familiar in the old versions, such as Comparator and Runnable. These interfaces support Lambda expressions by adding @ functional interface annotation
But Java 8 API also adds many new interfaces to make programming easier. Many of these new functions are based on the well-known Google Guava function library. Even if you are familiar with these function libraries, you need to pay attention to how these methods are extended in the interface and how they are used
Predictions (judgments)
Judgment is to pass a parameter and return a Boolean value. This interface contains various default methods to form a complex logical judgment unit (and, or, negate)
Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate();
Functions
The function takes a parameter and returns a result. The default method can be combined, and then
Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Suppliers (producers)
Suppliers returns the result of a given generic type. Unlike functions, suppliers do not need to pass parameters
Supplier<Person> personSupplier = Person::new; personSupplier.get(); // new Person
Consumers
Consumers represents an operation performed on an input parameter
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Comparators (comparison)
Comparators are familiar in the old version. Java 8 adds several default methods for this interface
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
Options
Optionals is not a function interface, but its function is to avoid null pointer exception. It is a very important concept for us to understand it quickly
Optional is a simple container that contains empty or non empty values. Imagine if a method can return an empty or non empty value. In Java 8, you can replace empty by returning an optional value
Optional<String> optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Streams
java.util.Stream Represents a sequence of elements on which one or more operations can be performed. Stream operations can include operations on the middle part or the terminal part. Terminal operations can return a certain type, and intermediate operations can return the flow itself. You can call a line by multiple methods. Streams can be created by a source, For example. Like java.util.Collection Either lists or sets (not supported by maps) in can create streams. Streams can operate in serial or parallel
First, let's see how to perform sequence operations. First, we create a source through a List containing strings:
List<String> stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1");
In Java 8, Collections already inherit streams, so you can call it in Java 8 are extended so you can simply create Collection.stream() and Collection.parallelStream() to create a stream. I'll show you the operation of most streams
Filter
Filter receives a judgment to filter all elements in the flow. This operation is an intermediate operation, which enables us to perform another flow operation (ForEach) on the result. ForEach accepts a consumer operation for each filtered flow element. ForEach is a terminal operation. It returns the value void, so we cannot call another function operation
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Sorted
Sorted is an intermediate operation that can return a sorted stream. This element is sorted by nature unless you pass a Comparator
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2"
Note that sorted is just to create a sorted view operation, and there is no operation to sort the returned collection. The sorted stringCollection is not affected:
System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map (map)
Intermediate operation map converts each element to another element through the given function. The following example is to convert each string to uppercase. You can also use map to convert the type of each object. Generic flow depends on the generic function you pass to map
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match (match)
Various matching operations can be used to determine whether a flow is deterministic. All of these operations are terminal operations and return a Boolean value
boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
Count (Statistics)
Count is the number of elements in a terminal operation return stream
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3 Reduce(merge)
This terminal operation completes the element merging operation in a stream through the given function. The returned result saves the value through Optional
Optional<String> reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" Parallel Streams(Parallel flow)
As mentioned above, overflow can be serial or parallel. Serial sequential operation is single thread, while parallel flow operates on multiple threads
The following example shows you how to increase performance through parallel flow
First, create a large List with unique element Occurrences:
int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Now test the time it takes to sort
Sequential Sort(Serial sort) long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 ms Parallel Sort(Parallel sorting) long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 ms
You can see that the above code is basically the same, but the parallelism is about 50% faster. You can test to use the parallelStream() operation instead of stream()
Map (map)
As mentioned above, map does not support stream operations. However, the new map supports various new methods and common task operations
Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val));
The above code is simple and easy to understand: putIfAbsent method tests the traditional non empty; forEach method traverses every value in the map for consumer operation
This example shows how to use functions to operate on a map:
map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33
Next, we learn how to give a key and how to remove the object. Now we add a key value pair:
map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null
Another useful approach:
map.getOrDefault(42, "not found"); // not found
Merging objects in a map is easy:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat
If the key value pair does not exist, the merge operation can be used to change the existing value
Date API
Java 8 includes a new date and time operation API java.time In the package. The new Date API is comparable to joda time package, but they are not the same. Here is an example of the main new API in the new date
Clock
The clock provides a way to access the current date and time. The clock means that the time zone will probably be replaced System.currentTimeMillis() to get the current millisecond. Such a instantaneous point is represented by the class Instant on the time line. Instead of the one that can be used java.util.Date Traditional objects
Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones (time zone)
Time zone is replaced by ZoneId. You can easily access it through a static factory method. Time zone definition makes up for an important transition between instantaneous and local time and date
System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00] LocalTime(Local time)
The local time represents a time that does not contain a time zone, for example. 10pm or 17:30:15. The following two examples create two local time zones that contain the above defined time zones. Then we compare the differences between the two time and calendar in hours and minutes
LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239
LocalTime can easily create new instances from various factory methods, including formatted strings
LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37
Localdate (local date)
The local date represents a clear date, for example. 2014-03-11. It is an unalterable full simulation of LocalTime. This simple example shows how to calculate a new date by adding or subtracting days, months, or years. Note that each operation returns a new instance
LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY
Parsing LocalDate from a string is as simple as parsing LocalTime:
DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24
Localdatetime (local datetime)
LocalDateTime represents a date time. It is a combination of time and date in the above example. LocalDateTime is also immutable. Its working principle is the same as that of LocalTime and LocalDate. We can obtain an attribute from date time by using the method
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439
The traditional timezone can be converted into an instant. The instant can also be easily converted into a traditional java.util.Date .
Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
Formatting date times is just like formatting dates and times. We can also use predefined formats to create customized formats
DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13
Not like java.text.NumberFormat The new DateTimeFormatter is immutable and thread safe
Annotations
Annotations can be reused in Java 8. Let's learn more about them through examples
First, we define a wrapper annotation that contains an actual annotation array:
@interface Hints { Hint[] value(); } @Repeatable(Hints.class) @interface Hint { String value(); }
Java 8 enables us to use the same annotations defined by @ Repeatable annotation
Variant 1: Using container annotations(old school) @Hints({@Hint("hint1"), @Hint("hint2")}) class Person {} Variant 2: Use duplicate annotations(new school) @Hint("hint1") @Hint("hint2") class Person {}
Using the variant 2java compiler will implicitly set an @ Hints annotation. This is very important to read annotation information through reflection
Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2
Although we have never defined @ hints annotation on the Person class, it can always get the annotation through getannotation( Hints.class )However, there is a more convenient method getannotations bytype, which can directly access all @ Hint annotations
In addition, using Java8 annotations are two new goals of the extension:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}
My Java 8 new feature case ends here. If you want to learn all the new features and classes of JDK 8 API, you can pay attention to the official account: program zero world, which helps you understand all the new classes and some hidden new features in JDK 8. Arrays.parallelSort, StampedLock, completable future, etc
I hope this article can help you, and I hope you can gain some experience after reading. You can also feedback to me through official account: Zero world.