JDK8新特性

       Java的发展历史  Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Java8 商用收费从2019年1月份开始,Oracle JDK开始对JavaSE 8 之后的版本开始进行商用收费,确切的说是 8u201/202 之后的版本。

Stream API

        Java 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。Stream API引入的目的在于弥补Java函数式编程的缺陷。对于很多支持函数式编程的语言,map()、reduce()基本上都内置到语言的标准库中了,不过,Java 8的Stream API总体来讲仍然是非常完善和强大,足以用很少的代码完成许多复杂的功能。
        创建一个Stream有很多方法,最简单的方法是把一个Collection变成Stream。我们来看最基本的几个操作:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = numbers.stream();
    stream.filter((x) -> {
        return x % 2 == 0;
    }).map((x) -> {
        return x * x;
    }).forEach(System.out::println);
}

        集合类新增的stream()方法用于把一个集合变成Stream,然后,通过filter()、map()等实现Stream的变换。Stream还有一个forEach()来完成每个元素的迭代。为什么不在集合类实现这些操作,而是定义了全新的Stream API?Oracle官方给出了几个重要原因:
        一是集合类持有的所有元素都是存储在内存中的,非常巨大的集合类会占用大量的内存,而Stream的元素却是在访问的时候才被计算出来,这种“延迟计算”的特性有点类似Clojure的lazy-seq,占用内存很少。
        二是集合类的迭代逻辑是调用者负责,通常是for循环,而Stream的迭代是隐含在对Stream的各种操作中,例如map()。
        要理解“延迟计算”,不妨创建一个无穷大小的Stream。如果要表示自然数集合,显然用集合类是不可能实现的,因为自然数有无穷多个。但是Stream可以做到。自然数集合的规则非常简单,每个元素都是前一个元素的值+1,因此,自然数发生器用代码实现如下:

class NaturalSupplier implements Supplier<Long> {

    long value = 0;

    public Long get() {
        this.value = this.value + 1;
        return this.value;
    }
}

            反复调用get(),将得到一个无穷数列,利用这个Supplier,可以创建一个无穷的Stream:

public static void main(String[] args) {
    Stream<Long> natural = Stream.generate(new NaturalSupplier());
    natural.map((x) -> {
        return x * x;
    }).limit(10).forEach(System.out::println);
}

        对这个Stream做任何map()、filter()等操作都是完全可以的,这说明Stream API对Stream进行转换并生成一个新的Stream并非实时计算,而是做了延迟计算。当然,对这个无穷的Stream不能直接调用forEach(),这样会无限打印下去。但是我们可以利用limit()变换,把这个无穷Stream变换为有限的Stream。利用Stream API,可以设计更加简单的数据接口。例如,生成斐波那契数列,完全可以用一个无穷流表示(受限Java的long型大小,可以改为BigInteger):

class FibonacciSupplier implements Supplier<Long> {

    long a = 0;
    long b = 1;

    @Override
    public Long get() {
        long x = a + b;
        a = b;
        b = x;
        return a;
    }
}

public class FibonacciStream {

    public static void main(String[] args) {
        Stream<Long> fibonacci = Stream.generate(new FibonacciSupplier());
        fibonacci.limit(10).forEach(System.out::println);
    }
}

        如果想取得数列的前10项,用limit(10),如果想取得数列的第20~30项,用:

List<Long> list = fibonacci.skip(20).limit(10).collect(Collectors.toList());

        最后通过collect()方法把Stream变为List。该List存储的所有元素就已经是计算出的确定的元素了。用Stream表示Fibonacci数列,其接口比任何其他接口定义都要来得简单灵活并且高效。
计算π可以利用π的展开式:π/4 = 1 - 1/3 + 1/5 - 1/7 + 1/9 - ...
把π表示为一个无穷Stream如下:

class PiSupplier implements Supplier<Double> {

    double sum = 0.0;
    double current = 1.0;
    boolean sign = true;

    @Override
    public Double get() {
        sum += (sign ? 4 : -4) / this.current;
        this.current = this.current + 2.0;
        this.sign = ! this.sign;
        return sum;
    }
}

Stream<Double> piStream = Stream.generate(new PiSupplier());
piStream.skip(100).limit(10)
.forEach(System.out::println);

这个级数从100项开始可以把π的值精确到3.13~3.15之间:
3.1514934010709914
3.1317889675734545
3.1513011626954057
3.131977491197821
3.1511162471786824
3.1321589012071183
3.150938243930123
3.132333592767332
3.1507667724908344
3.1325019323081857
利用欧拉变换对级数进行加速,可以利用下面的公式:
用代码实现就是把一个流变成另一个流:

class EulerTransform implements Function<Double, Double> {

    double n1 = 0.0;
    double n2 = 0.0;
    double n3 = 0.0;

    @Override
    public Double apply(Double t) {
        n1 = n2;
        n2 = n3;
        n3 = t;
        if (n1 == 0.0) {
            return 0.0;
        }
        return calc();
    }

    double calc() {
        double d = n3 - n2;
        return n3 - d * d / (n1 - 2 * n2 + n3);
    }
}
Stream<Double> piStream2 = Stream.generate(new PiSupplier());
piStream2.map(new EulerTransform())
.limit(10)
.forEach(System.out::println);

可以在10项之内把π的值计算到3.141~3.142之间:
0.0
0.0
3.166666666666667
3.1333333333333337
3.1452380952380956
3.13968253968254
3.1427128427128435
3.1408813408813416
3.142071817071818
3.1412548236077655
还可以多次应用这个加速器:

Stream<Double> piStream3 = Stream.generate(new PiSupplier());
piStream3.map(new EulerTransform())
.map(new EulerTransform())
.map(new EulerTransform())
.map(new EulerTransform())
.map(new EulerTransform())
.limit(20)
.forEach(System.out::println);

20项之内可以计算出极其精确的值:
...
3.14159265359053
3.1415926535894667
3.141592653589949
3.141592653589719
可见用Stream API可以写出多么简洁的代码,用其他的模型也可以写出来,但是代码会非常复杂。
 

Lambda表达式

官方文档icon-default.png?t=N7T8https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#syntax十个实例:

1. 用() -> {}代码块替代了整个匿名类

// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8, too much code for too little to do");
    }
}).start();

//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

2 .使用Java 8 lambda表达式进行事件处理

// Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("Event handling without lambda expression is boring");
    }
});

// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});

3.使用lambda表达式对列表进行迭代

// Java 8之前:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
    System.out.println(feature);
}

// Java 8之后:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));
 
// 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示,
// 看起来像C++的作用域解析运算符
features.forEach(System.out::println);

4.使用lambda表达式和函数式接口Predicate 

        除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。下面是Java 8 Predicate 的例子,展示了过滤集合数据的多种常用方法。Predicate接口非常适用于做过滤。 

public static void main(args[]){
    List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
 
    System.out.println("Languages which starts with J :");
    filter(languages, (str)->str.startsWith("J"));
 
    System.out.println("Languages which ends with a ");
    filter(languages, (str)->str.endsWith("a"));
 
    System.out.println("Print all languages :");
    filter(languages, (str)->true);
 
    System.out.println("Print no language : ");
    filter(languages, (str)->false);
 
    System.out.println("Print language whose length greater than 4:");
    filter(languages, (str)->str.length() > 4);
}
 
public static void filter(List names, Predicate condition) {
    for(String name: names)  {
        if(condition.test(name)) {
            System.out.println(name + " ");
        }
    }
}

// 更好的办法
public static void filter(List names, Predicate condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}

可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的 filter() 方法替换成写在里面的内联代码,这就是lambda表达式的魔力。另外,Predicate接口也允许进行多重条件的测试,下个例子将要讲到。
例5、如何在lambda表达式中加入Predicate

上个例子说到,java.util.function.Predicate 允许将两个或更多的 Predicate 合成一个。它提供类似于逻辑操作符AND和OR的方法,名字叫做and()、or()和xor(),用于将传入 filter() 方法的条件合并起来。例如,要得到所有以J开始,长度为四个字母的语言,可以定义两个独立的 Predicate 示例分别表示每一个条件,然后用 Predicate.and() 方法将它们合并起来,如下所示:

// 甚至可以用and()、or()和xor()逻辑函数来合并Predicate,
// 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
    .filter(startsWithJ.and(fourLetterLong))
    .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));

类似地,也可以使用 or() 和 xor() 方法。本例着重介绍了如下要点:可按需要将 Predicate 作为单独条件然后将其合并起来使用。简而言之,你可以以传统Java命令方式使用 Predicate 接口,也可以充分利用lambda表达式达到事半功倍的效果。
例6、Java 8中使用lambda表达式的Map和Reduce示例

本例介绍最广为人知的函数式编程概念map。它允许你将对象进行转换。例如在本例中,我们将 costBeforeTax 列表的每个元素转换成为税后的值。我们将 x -> x*x lambda表达式传到 map() 方法,后者将其应用到流中的每一个元素。然后用 forEach() 将列表元素打印出来。使用流API的收集器类,可以得到所有含税的开销。有 toList() 这样的方法将 map 或任何其他操作的结果合并起来。由于收集器在流上做终端操作,因此之后便不能重用流了。你甚至可以用流API的 reduce() 方法将所有数字合成一个,下一个例子将会讲到。

// 不使用lambda表达式为每个订单加上12%的税
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
for (Integer cost : costBeforeTax) {
    double price = cost + .12*cost;
    System.out.println(price);
}
 
// 使用lambda表达式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);

例6.2、Java 8中使用lambda表达式的Map和Reduce示例
在上个例子中,可以看到map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个。Map和Reduce操作是函数式编程的核心操作,因为其功能,reduce 又被称为折叠操作。另外,reduce 并不是一个新的操作,你有可能已经在使用它。SQL中类似 sum()、avg() 或者 count() 的聚集函数,实际上就是 reduce 操作,因为它们接收多个值并返回一个值。流API定义的 reduceh() 函数可以接受lambda表达式,并对所有值进行合并。IntStream这样的类有类似 average()、count()、sum() 的内建方法来做 reduce 操作,也有mapToLong()、mapToDouble() 方法来做转换。这并不会限制你,你可以用内建方法,也可以自己定义。在这个Java 8的Map Reduce示例里,我们首先对所有价格应用 12% 的VAT,然后用 reduce() 方法计算总和。

// 为每个订单加上12%的税
// 老方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
    double price = cost + .12*cost;
    total = total + price;
}
System.out.println("Total : " + total);
 
// 新方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);

例7、通过过滤创建一个String列表

过滤是Java开发者在大规模集合上的一个常用操作,而现在使用lambda表达式和流API过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,即可以传入一个lambda表达式作为过滤逻辑。下面的例子是用lambda表达式过滤Java集合,将帮助理解。

// 创建一个字符串列表,每个字符串长度大于2
List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());
System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);

 另外,关于 filter() 方法有个常见误解。在现实生活中,做过滤的时候,通常会丢弃部分,但使用filter()方法则是获得一个新的列表,且其每个元素符合过滤原则。
例8、对列表的每个元素应用函数

我们通常需要对列表的每个元素使用某个函数,例如逐一乘以某个数、除以某个数或者做其它操作。这些操作都很适合用 map() 方法,可以将转换逻辑以lambda表达式的形式放在 map() 方法里,就可以对集合的各个元素进行转换了,如下所示。

// 将字符串换成大写并用逗号链接起来
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);

例9、复制不同的值,创建一个子列表

本例展示了如何利用流的 distinct() 方法来对集合进行去重。

// 用所有不同的数字创建一个正方形列表
List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
System.out.printf("Original List : %s,  Square Without duplicates : %s %n", numbers, distinct);

例10、计算集合元素的最大值、最小值、总和以及平均值

IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics() 。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各种摘要数据。在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。

//获取数字的个数、最小值、最大值、总和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());

Lambda表达式 vs 匿名类

既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析。一个关键的不同点就是关键字 this。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向包围lambda表达式的类。另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法。
lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });

//Compile time error : "local variables referenced from a lambda expression must be final or effectively final"

另外,只是访问它而不作修改是可以的,如下所示:

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });

函数式接口

Optional

https://blog.csdn.net/qq_34378496/article/details/120673861icon-default.png?t=N7T8https://blog.csdn.net/qq_34378496/article/details/120673861

​​​​​​​使用Base64

方法使用说明icon-default.png?t=N7T8https://www.cnblogs.com/dalianpai/p/12662969.html

 
接口的默认方法和静态方法

说明icon-default.png?t=N7T8https://blog.csdn.net/weixin_53041251/article/details/124835024


新增方法引用格式

:: 方法引用 也是JDK8中的新的语法

方法引用的格式

符号表示: ::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为 方法引用
应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。
常见的引用方式:
方法引用在JDK8中使用是相当灵活的,有以下几种形式:

instanceName::methodName 对象::方法名

Person person = new Person();
String s1 = person.name()
String s2 = persion::name;

ClassName::staticMethodName 类名::静态方法

long t1 = System.currentTimeMillis();
long t2 = System::currentTimeMillis;

ClassName::methodName 类名::普通方法

//Java面向对象中,类名只能调用静态方法,类名引用实例方法是用前提的,实际上是拿第一个参数作为方法的调用者
Function<String,Integer> f01 = (s)->s.length();
Function<String,Integer> function1 = String::length;

ClassName::new 类名::new 调用的构造器

//由于构造器的名称和类名完全一致,所以构造器引用使用 ::new 的格式使用,
Supplier<Person> sup 01= ()->new Person();
Supplier<Person> sup02 = Person::new;

TypeName[]::new String[]::new 调用数组的构造器

    Function<Integer,String[]> fun1 = (len)->new String[len]
    String[] a1 = fun1.apply(3);
    Function<Integer,String[]> fun2 = String[]::new;
    String[] a2 = fun2.apply(5);

方法引用的注意事项:

        被引用的方法,参数要和接口中的抽象方法的参数一样
        当接口抽象方法有返回值时,被引用的方法也必须有返回值

注解相关的改变

在JDK 8中,最重要的注解相关的变化之一是引入了java.lang.invoke.MethodHandles中的lookup()方法,它允许在运行时动态查找和调用方法,从而打破了反射API的限制,使其能够访问私有方法和构造函数。

此外,JDK 8中还引入了重复注解和类型注解,这些特性允许注解可以在同一个声明上多次使用,并允许在注解中使用类型声明。

下面是一个使用JDK 8引入的MethodHandles.lookup()来动态调用方法的简单示

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
 
public class DynamicMethodInvocation {
    public static void main(String[] args) throws Throwable {
        // 获取当前类的MethodHandles.lookup()
        MethodHandles.Lookup lookup = MethodHandles.lookup();
 
        // 查找并获取指定方法的MethodHandle
        MethodHandle methodHandle = lookup.findVirtual(DynamicMethodInvocation.class, "greet", MethodType.methodType(void.class, String.class));
 
        // 使用MethodHandle调用方法
        methodHandle.invokeExact("World");
        //JDK17
        methodHandle.invokeExact(new DynamicMethodInvocation(),"World");
    }
 
    // 被动态调用的方法
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

 在这个例子中,我们使用MethodHandles.lookup()获取了一个查找器对象,然后使用它来查找并获取一个MethodHandle,该MethodHandle可以用来在运行时动态地调用greet方法。invokeExact方法用于执行方法,传入了一个字符串作为参数。

可重复注解icon-default.png?t=N7T8https://cloud.tencent.com/developer/article/1079933
支持并行(parallel)数组

// 使用并行排序
Arrays.parallelSort(array);

使用和解释icon-default.png?t=N7T8https://www.cnblogs.com/Jacck/p/8095404.html

对并发类(Concurrency)的扩展

        在Java Development Kit (JDK) 8中,并发类库得到了显著的扩展,包括新的fork/join框架,以及CompletableFuture类,这些都是在java.util.concurrent包中。

1.Fork/Join框架:

Fork/Join框架是JDK 7中引入的,它提供了一种并行处理任务的方式。Fork意味着将一个大的任务分割成若干个小的任务,Join则意味着再将分割出来的小任务的结果合并起来。

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
 
class Fibonacci extends RecursiveTask<Integer> {
    final int n;
    public Fibonacci(int n) { 
        this.n = n;
    }
 
    @Override
    protected Integer compute() {
        if (n <= 1) 
            return n;
        Fibonacci f1 = new Fibonacci(n - 1);
        f1.fork();
        Fibonacci f2 = new Fibonacci(n - 2);
        return f2.compute() + f1.join();
    }
}
 
public class FibonacciDemo {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Fibonacci fibonacci = new Fibonacci(10);
        pool.invoke(fibonacci);
        System.out.println("Fibonacci of 10 is: " + fibonacci.join());
    }
}

2. CompletableFuture类

CompletableFuture类提供了一种改进的编程模式来处理异步编程,它可以在完成时获取结果,可以设置回调,也可以组合多个CompletableFuture。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
 
public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 20;
        });
 
        future.thenAccept(System.out::println).join();
    }
}

在这两个例子中,我们都展示了如何使用JDK 8的并发类库来处理并行任务。Fork/Join框架适合处理可以被划分为更小子任务的计算密集型任务,而CompletableFuture则适合处理异步计算。 

新的Date以及Time API

用法实例讲解1icon-default.png?t=N7T8https://baijiahao.baidu.com/s?id=1765657984236018735&wfr=spider&for=pc 用法实例讲解2icon-default.png?t=N7T8https://ost.51cto.com/posts/27790​​​​​​​新旧API优劣对比,只看前两段就可以,后面不详细icon-default.png?t=N7T8https://blog.csdn.net/ymxk2876721452/article/details/138754066

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_evenif

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值