0
点赞
收藏
分享

微信扫一扫

JDK8 新特性

小美人鱼失去的腿 2022-02-18 阅读 159

个人博客

个人博客: https://www.crystalblog.xyz/

备用地址: https://wang-qz.gitee.io/crystal-blog/

1. 简介

JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性. JDK8帮助文档

  • Lambda表达式
  • 函数式接口
  • 方法引用和构造器调用
  • Stream API
  • 接口中的默认方法和静态方法
  • 新的日期时间API
  • Optional

2. Lambda表达式

2.1 介绍

lambda表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的“语法糖”,但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .

2.2 使用

Lambda表达式由逗号分隔的参数列表->符号语句块三部分组成 .

// jdk8之前需要循环集合进行遍历输出
Arrays.asList( "a", "b", "d" ).forEach( (String s) -> {
    System.out.print( s );
} );

如果语句块只有一句, 可以省略大括号; 参数列表的类型也可以省略, 如果只有一个参数, 参数的括号也可以省略.

Arrays.asList( "a", "b", "d" ).forEach( s -> System.out.print( s ) );

如果有语句块中有返回值

// jdk8之前,sort(Comparator c)需要写一个匿名内部类实现
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

可以继续简化, 只有一个返回值语句, 大括号和return都可以省略.

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

2.3 案例

需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.

传统方式编写, 每次筛选都要对集合进行循环遍历判断, 这些都是重复的操作.

/**
 * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
 */
public class LambdaTest {

    @Test
    public void testFilterProducts() {
        List<Product> products = getProducts();

        products = this.filterProductByColor(products);
        products = this.filterProductByPrice(products);
        products.forEach(System.out::println);
    }

    // 筛选颜色
    public List<Product> filterProductByColor(List<Product> list) {
        List<Product> prods = new ArrayList<>();
        for (Product product : list) {
            if ("红色".equals(product.getColor())) {
                prods.add(product);
            }
        }
        return prods;
    }

    // 筛选价格
    public List<Product> filterProductByPrice(List<Product> list) {
        List<Product> prods = new ArrayList<>();
        for (Product product : list) {
            if (product.getPrice() < 8000) {
                prods.add(product);
            }
        }
        return prods;
    }
    
    // 添加商品数据
    private List<Product> getProducts() {
        List<Product> products = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            Product pro = Product.builder()
                    .color(i % 2 == 0 ? "红色" : "绿色")
                    .price(i * 1000)
                    .build();
            products.add(pro);
        }
        return products;
    }
}

使用lambda表达式+函数接口进行过滤

package cn.itcast.test.newf;

import cn.itcast.pojo.Product;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
 */
public class LambdaTest {
    // lambda表达式过滤
    @Test
    public void testFilterProducts2() {
        List<Product> products = getProducts();

        products = filterProductByPredicate(products, (product -> Objects.equals("红色", product.getColor())));
        products = filterProductByPredicate(products, (product -> Double.compare(8000, product.getPrice()) > 0));
        products.forEach(System.out::println);
    }

    // 使用lambda表达式, Predicate为断言的函数接口, 接口方法返回boolean类型
    public List<Product> filterProductByPredicate(List<Product> list, Predicate<Product> p) {
        List<Product> prods = new ArrayList<>();
        for (Product product : list) {
            if (p.test(product)) {
                prods.add(product);
            }
        }
        return prods;
    }
}

不使用函数接口, 单纯使用lambda表达式实现

@Test
public void testFilterProducts3() {
	List<Product> products = getProducts();
	products.stream()
			.filter((product -> Objects.equals("红色", product.getColor())))
			.filter(product -> Double.compare(8000, product.getPrice()) > 0)
			.forEach(System.out::println);
}

测试结果

Product(color=红色, price=2000.0)
Product(color=红色, price=4000.0)
Product(color=红色, price=6000.0)

2.4 总结

Lmabda表达式的语法总结: () -> ();

场景语法
无参数无返回值() -> System.out.println(“Hello WOrld”)
有一个参数无返回值(x) -> System.out.println(x)
有且只有一个参数无返回值x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句(x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句(x,y) -> xxxx

3. 函数式接口

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。

3.1 简介

函数接口指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。 当然接口中可以包含其他的方法(默认,静态,私有) .

java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解**@FunctionalInterface** , 所以可以通过@FunctionalInterface注解检测接口是否为一个函数式接口 .

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
    // 默认方法
    default void defaultMethod() {}    
    // 静态方法
    static void staticMethod(){}
}

下面学习JDK8提供的常用函数式接口API, 主要有Consumer, Supplier , Predicate , Function.

3.2 Consumer

3.2.1 介绍

消费型接口,有参无返回值 , 参数类型由泛型决定 .

@FunctionalInterface
public interface Consumer<T> {
    //....
}

3.2.2 accept方法

表示消费一个指定泛型类型的数据 .

void accept(T t);

accept使用

/**
 * Consumer<T>:消费型接口,有参无返回值
 */
public class ConsumerTest {
    //定义一个方法
    //方法的参数传递一个字符串的姓名
    //方法的参数传递Consumer接口,泛型使用String
    //可以使用Consumer接口消费字符串的姓名
    private void accept(String name, Consumer<String> consumer) {
        consumer.accept(name);
    }

    @Test
    public void test() {
        //对传递的字符串进行消费
        //消费方式,把字符串进行反转输出
        accept("admin", (name) -> {
            String reName = new StringBuilder(name).reverse().toString();
            System.out.println(reName);
        });
    }
}

3.2.3 andThen方法

Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).

default Consumer<T> andThen(Consumer<? super T> after) {
	Objects.requireNonNull(after);
	return (T t) -> { accept(t); after.accept(t); };
}

andThen方法使用

/**
 * Consumer<T>:消费型接口,有参无返回值
 */
public class ConsumerTest {
    //定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
	private void andThen(String s, Consumer<String> c1, 
                         Consumer<String> c2) {
		//使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2
		c1.andThen(c2).accept(s);
	}
    
    @Test
    public void test2() {
        andThen("admin",
                (name) -> {
                    System.out.println(name);
                },
                (name) -> {
                    //消费方式,把字符串转换为大写输出
                    System.out.println(name.toUpperCase(Locale.ROOT));
                });
    }
}

3.2.4 案例

需求: 格式化打印信息 , 字符串数组中存有多条信息,请按照格式,“姓名:xx,性别:xx“的格式信息打印出来 .

package cn.itcast.test.jdk8;

import org.junit.Test;

import java.util.Locale;
import java.util.function.Consumer;

/**
 * Consumer<T>:消费型接口,有参无返回值
 */
public class ConsumerTest {
    //定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
    // 用两个Consumer接口消费字符串
    private void printInfo(String[] arr, Consumer<String> consumer, Consumer<String> consumer2) {
        //遍历字符串数组
        for (String s : arr) {
            consumer.andThen(consumer2).accept(s);
        }
    }

    @Test
    public void test3() {
        //定义一个字符串类型的数据
        String[] arr = {"刘辣子,女", "杨哈哈,男", "赵屌屌,男"};
        //调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
        printInfo(arr,
                (s) -> {
                    String name = s.split(",")[0];
                    System.out.print("姓名:" + name);
                },
                (s) -> {
                    String gender = s.split(",")[1];
                    System.out.println("、性别:" + gender + ".");
                });
    }
}

输出结果

姓名:刘辣子、性别:女.
姓名:杨哈哈、性别:男.
姓名:赵屌屌、性别:男.

3.3 Supplier

3.3.1 介绍

供给型接口,无参有返回值 .

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

3.3.2 get方法

Supplier接口仅包含一个无参的方法:T get() .

用来获取一个泛型参数指定类型的对象数据。意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。

/**
 * Supplier<T>:供给型接口,无参有返回值
 */
public class SupplierTest {

    public String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    @Test
    public void test() {
        //定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
        String s = getString(() -> "人才");
        System.out.println(s);
    }

    private Integer getMax(Supplier<Integer> supplier) {
        //定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer
        return supplier.get();
    }

    @Test
    public void test2() {
        //定义一个int类型的数组,并赋值
        int[] arr = {100, 78, -887, 66, 90};
        //调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int a : arr) {
                if (a > max) {
                    max = a;
                }
            }
            return max;
        });
        System.out.println(maxValue);
    }
}

3.4 Predicate

3.4.1 介绍

断言型接口,有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .

@FunctionalInterface
public interface Predicate<T> {
    //...
}

3.4.2 test方法

boolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false

boolean test(T t);

test方法的使用

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法
    //参数传递一个String类型的字符串
    //传递一个Predicate接口,泛型使用String
    //使用predicate中的方法test对字符串进行判断,并把判断的结果返回
    public boolean checkString(String s, Predicate<String> predicate) {
        // 对某种数据类型的数据进行判断,结果返回一个boolean值
        return predicate.test(s);
    }

    @Test
    public void test() {
        //定义一个字符串
        String s = "abcdef";
        //调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
        //对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
        boolean b = checkString(s, str -> str.length() > 5);
        System.out.println(b);
    }
}  

3.4.3 and方法

逻辑表达式:可以连接多个判断的条件 .

&&:与运算符. 连接的多个判断条件都为true才返回true, 否则false.

default Predicate<T> and(Predicate<? super T> other) {
	Objects.requireNonNull(other);
	return (t) -> test(t) && other.test(t);
}

需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件必须同时满足,使用&&运算符连接两个条件 .

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法,方法的参数,传递一个字符串
    //传递俩个Predicate接口
    //1、判断字符串的长度是否大于5  2、判断字符串中是否包含a  两个条件必须同时满足,使用&&运算符连接两个条件
    private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
		//等价于return pre1.test(s)&&pre2.test(s);
        return predicate.and(predicate2).test(s); 
    }

    @Test
    public void test2() {
        //定义一个字符串
        String s = "asdfgi";
        //调用checkString方法,参数传递字符串和两个Lambda表达式
        boolean b = checkString(s,
                str -> !StringUtils.isEmpty(str) && str.length() > 5,
                str -> !StringUtils.isEmpty(str) && str.contains("a"));
        System.out.println(b);
    }
}

3.4.4 or方法

||:或运算符,连接的多个判断条件有一个为true就返回true, 都为false才返回false.

default Predicate<T> or(Predicate<? super T> other) {
	Objects.requireNonNull(other);
	return (t) -> test(t) || other.test(t);
}

需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件满足一个即可,使用||运算符连接两个条件 .

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    @Test
    public void test2() {
        //定义一个字符串
        String s = "asdfgi";
        //调用checkStringOr方法,参数传递字符串和两个Lambda表达式
        boolean b = checkStringOr(s,
                str -> !StringUtils.isEmpty(str) && str.length() > 5,
                str -> !StringUtils.isEmpty(str) && str.contains("a")
        );
        System.out.println(b);
    }

    //定义一个方法,方法的参数,传递一个字符串
    //传递俩个Predicate接口
    //1、判断字符串的长度是否大于5  2、判断字符串中是否包含a  满足一个条件即可,使用||运算符连接两个条件
    private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) {
        return predicate.or(predicate2).test(s);
    }
}

3.4.5 negate方法

!:非(取反):非真则假,非假则真 .

default Predicate<T> negate() {
	return (t) -> !test(t);
}

需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,返回false
如果字符串的长度小于5,返回true
使用取反符号!对判断的结果进行取反 .

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    //定义一个方法,方法的参数,传递一个字符串
    public boolean checkStringNegate(String s, Predicate<String> predicate) {
        //使用Predicate接口判断字符串的长度是否大于5
        //return !pre.test(s);
        return predicate.negate().test(s);
    }

    @Test
    public void testNegate() {
        //定义一个字符串
        String s = "asdfg";
        //调用checkString方法,参数传递字符串和Lambda表达式
        boolean b = checkStringNegate(s, str -> str.length() > 5);
        System.out.println(b);
    }
}

3.4.6 案例

需求: 集合信息筛选

数组当中有多条“姓名+性别”的信息如下:

String[] array = {"刘小甜甜,女","杨哈哈,男","赵屌屌,男"}

通过Predicate接口的拼装将符合要求的字符串筛选到ArrayList集合中
需求同时满足两个条件:必须为女生,姓名为4个字
1、有两个判断条件,需要使用两个Predicate接口,对条件进行判断
2、必须同时满足两个条件,可以使用and方法连接两个判断条件

/**
 * Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
 */
public class PredicateTest {
    // 定义一个方法, 两个Predicate接口参数
    public ArrayList<String> filter(String[] arr, Predicate<String> predicate, Predicate<String> predicate2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            if (predicate.and(predicate2).test(s)) {
                list.add(s);
            }
        }
        return list;
    }

    @Test
    public void test4() {
        //定义一个存储字符串的数组
        String[] array = {"刘小甜甜,女", "杨哈哈,男", "赵屌屌,男"};
        //调用filter方法,传递字符串数组和两个Lambda表达式
        ArrayList<String> list = filter(array,
                str -> !StringUtils.isEmpty(str) && str.split(",")[0].length() == 4,
                str -> !StringUtils.isEmpty(str) && str.split(",")[1].equals("女")
        );

        list.forEach(System.out::println);
    }
}

3.5 Function

3.5.1 介绍

函数式接口,有参有返回值 . java.util.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据.
前者称为前置条件,后者称为后置条件 .

@FunctionalInterface
public interface Function<T, R> {
    // ...
}

3.5.2 apply方法

3.5.3 andThen方法

3.6 自定义函数式接口

定义函数式接口

@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法, public abstract可以省略
    public abstract void method();
}

接口实现类方式实现接口方法

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
    @Override
    public void method() {
        System.out.println(">>>MyFunctionalInterfaceImpl");
    }
}

函数式接口的使用

/**
 * 函数式接口的使用测试
 */
public class MyFunctionalInterfaceTest {
    @Test
    public void test() {
        // 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());
        // 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println(">>>MyFunctionalInterface的匿名内部类实现");
            }
        });
        // 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式
        show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现"));
    }

    // 定义一个方法,参数使用函数式接口MyFunctionalInterInterface
    public void show(MyFunctionalInterface myFunctionalInterface) {
        myFunctionalInterface.method();
    }
}

测试结果

>>>MyFunctionalInterfaceImpl
>>>MyFunctionalInterface的匿名内部类实现
>>>MyFunctionalInterface的lambda表达式实现

3.7 Lambda的延迟加载

使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.

场景: 程序运行根据日志级别输出日志信息.

实现方式1

/**
 * 根据不同日志级别输出日志信息
 */
public class LogPrintTest {
    @Test
    public void test() {
        //定义三个日志信息
        String msg1 = "Hello ";
        String msg2 = "World ";
        String msg3 = "Java";
        //调用showLog方法,传递日志级别和日志信息
        printLog(2, msg1 + msg2 + msg3);
    }

    //定义一个根据日志的级别,显示日志信息的方法
    public void printLog(int level, String message) {
        //对日志的等级进行判断,如果式1级别,那么输出日志信息
        if (level == 1) {
            System.out.println(message);
        }
    }
}

实现方式2: Lamdba表达式优化

/**
 * 根据不同日志级别输出日志信息
 */
public class LogPrintTest {
    @Test
    public void test2() {
        //定义三个日志信息
        String msg1 = "Hello ";
        String msg2 = "World ";
        String msg3 = "Java";
        //调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式
        showLog(1, () -> msg1 + msg2 + msg3);
    }

    //定义一个显示日志的方法,方法的参数传递日志的等级和Message接口
    public void showLog(int level, MessageBulider mb) {
        //对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法
        if (level == 1) {
            System.out.println(mb.builderMessage());
        }
    }

    // 定义一个函数式接口
    @FunctionalInterface
    public interface MessageBulider {
        //定义一个拼接消息的抽象方法,返回拼接的消息
        String builderMessage();
    }
}

3.8 函数式接口作为方法的参数

Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如, java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

lambda表达式作为方法的参数使用

/**
 * lambda表达式作为方法的参数
 */
public class LambdaTest2 {
    @Test
    public void test() {
        startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!"));
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(">>>匿名内部类方式实现线程任务执行!");
            }
        });
    }

    //定义一个方法startThread,方法的参数使用函数式接口Runnable
    private void startThread(Runnable task) {
        //开启多线程
        new Thread(task).start();
    }
}

3.9 函数式接口作为方法的返回值

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。

例如, 当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时, 就可以调该方法获取。

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    // .....
}

lambda表达式作为方法的返回值使用

/**
 * lambda表达式作为方法的返回值
 */
public class LambdaTest3 {

    @Test
    public void test() {
        String[] array = {"aaa", "bbb", "ccc"};
        System.out.println(Arrays.toString(array));
        //  lambda表达式实现 Comparator#compare方法返回值
        Arrays.sort(array, (o1, o2) -> o2.length() - o1.length());
        System.out.println(Arrays.toString(array));
        // 匿名内部类方式实现 Comparator#compare方法返回值
        Arrays.sort(array, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.length() - o1.length();
            }
        });
    }
}

4. 方法引用和构造器调用

5. Stream API

6. 接口中的默认方法和静态方法

7. 新的日期时间API

8. Optional容器

9. 并行流和串行流

举报

相关推荐

0 条评论