你发任你发,我用Java8
一、Lambda表达式
lambda 表达式是一个匿名函数。 lambda 表达式允许把一个函数作为参数进行传递。
可能刚看到这两句话时,不知道是什么意思。那么,对比一下 js 中的 setInterval 函数的用法,你就能找到一些感觉了
//每一秒执行一次匿名函数。(模拟时钟) setInterval(function() { console.log("当前时间为:" + new Date()); }, 1000);
如上,function(){}这段,就是一个匿名函数,并且可以把它作为参数传递给 setInterval 函数。
这是因为,在 js 中,函数是一等公民。
然而,在 Java 中,对象才是一等公民。但是,到了 JDK8 我们也可以通过 lambda 表达式表示同样的效果。
lambda 表达式语法如下:
(参数1,参数2) -> { 方法体 }
左边指定了 lambda 表达式所需要的所有参数,右边用来描述方法体。-> 即为 lambda 运算符。
想一下,在之前我们通过匿名内部类的方式来启动一个线程,是怎么做的?
public class LambdaTest { @Test public void test(){ new Thread(new Runnable() { @Override public void run() { System.out.println("线程运行..."); } }).start(); } }
现在,若把它改为用 lambda 表达式,则为,
public class LambdaTest { @Test public void test(){ // 一行搞定 new Thread(()->System.out.println("线程运行...")).start(); } }
可以发现,明显用 lambda 表达式,写法更简洁了。
其实,Lambda 表达式就是函数式编程的体现。(什么,你还不知道什么是函数式编程?那还不赶快百度去。)
注意事项
1.参数列表的数据类型会自动推断。也就是说,如果匿名函数有参数列表的话,只需要写2.参数名即可,不需要写参数的类型。
3.如果参数列表为空,则左边只需要写小括号即可。
4.如果参数只有一个,则可以省略小括号,只写参数的名称即可。
5.如果方法体中只有一条执行语句,则可以省略右边的大括号。若有返回值,则可以把 6.return 和大括号同时省略。
二、接口默认方法和静态方法
2.1 接口默认方法
我们知道,在 Java 的接口中,只能定义方法名,不能实现方法体的,具体的实现需要子类去做。
但是,到了 JDK8 就不一样了。在接口中,也可以通过 default关键字来实现方法体。
那么,就有小伙伴疑惑了。好端端的,为什么要加入这个奇怪的功能呢,它有什么用?
当然是为了提高代码的重用性了。此外,接口的默认方法可以在不影响原来的继承体系的情况下,进行功能的拓展,实现接口的向下兼容。
我滴天,好抽象。那,就用实例来说明一下吧。
假设各种动物的继承体系如下,
public interface Animal { //所有动物都需要吃东西,具体吃什么,让子类去实现 void eat(); } public class Bird implements Animal { @Override public void eat() { System.out.println("早起的鸟儿有虫吃!"); } } public class Cat implements Animal { @Override public void eat() { System.out.println("小猫爱吃鱼!"); } }
现在,需要对 Animal接口拓展功能了。动物不能只会吃东西吧,它也许会奔跑,也许会飞行。那么,我在接口中添加两个方法, run 和 fly 就可以了吧。
这样定义方法虽然是可以的,但是,问题就来了。接口中定义了方法,实现类就要实现它的所有方法。小猫会奔跑,但是不会飞啊。而小鸟会飞,你让它在地上跑不是委屈人家嘛。
所以,这个设计不是太合理。
此时,就可以在接口中定义默认方法。子类不需要实现所有方法,可以按需实现,或者直接使用接口的默认方法。
因此,修改 Animal 接口如下,把 run 和 fly 定义为默认方法,
public interface Animal { //所有动物都需要吃东西,具体吃什么,让子类去实现 void eat(); default void run(){ System.out.println("我跑"); } default void fly(){ System.out.println("我飞"); } } public class Main { public static void main(String[] args) { Bird bird = new Bird(); bird.fly(); Cat cat = new Cat(); cat.run(); } }
在 JDK8 的集合中,就对 Collection 接口进行了拓展,如增加默认方法 stream() 等。既增强了集合的一些功能,而且也能向下兼容,不会对集合现有的继承体系产生影响。
2.2 接口静态方法
另外,在接口中也可以定义静态方法。这样,就可以直接通过接口名调用静态方法。(这也很正常,接口本来就不能实例化)
需要注意的是,不能通过实现类的对象去调用接口的静态方法。
public interface MyStaticInterface { static void method(){ System.out.println("这是接口的静态方法"); } } public class MyStaticInterfaceImpl implements MyStaticInterface { public static void main(String[] args) { //直接通过接口名调用静态方法,不能通过实现类的对象调用 MyStaticInterface.method(); } }
三、函数式接口
如果一个接口中只有一个抽象方法,则称其为函数式接口。可以使用 @FunctionalInterface 注解来检测一个接口是否为函数式接口。
JDK提供了常见的最简单的四种函数式接口:(必须掌握哦)
1.Consumer,消费型接口。接收一个参数,没有返回值。其方法有:void accept(T t);
2.Supplier,供给型接口。没有参数,带返回值。其方法:T get();
3.Function<T, R>,函数型接口。接收一个参数,返回一个结果。其方法:R apply(T t);
4.Predicate,断言型接口。接收一个参数,返回boolean值。其方法:boolean test(T t);
我这里举例了它们的使用方法
public class LambdaTest { @Test public void test2(){ //打印传入的 msg printMsg((s)-> System.out.println(s),"听朋友说「烟雨星空」公众号不仅文章好看,还免费送程序员福利,我心动了"); } public void printMsg(Consumer<String> consumer,String msg){ //消费型,只有传入参数,没有返回值 consumer.accept(msg); } @Test public void test3(){ //返回一个 0~99 的随机数 Integer content = getContent(() -> new Random().nextInt(100)); System.out.println(content); } public Integer getContent(Supplier<Integer> supplier){ //供给型,传入参数为空,带返回值 return supplier.get(); } @Test public void test4(){ //传入一个字符串,然后把它都转换成大写字母。 System.out.println(transfer((str) -> str.toUpperCase(), "My wechat : mistyskys")); } public String transfer(Function<String,String> func,String str){ // 函数型,传入一个参数,对其进行处理之后,返回一个结果 return func.apply(str); } @Test public void test5(){ //定义一个list,用来做筛选 ArrayList<String> list = new ArrayList<>(); list.add("zhangsan"); list.add("lisi"); list.add("jerry"); list.add("tom"); //筛选出集合中,字符串长度大于 3 的,并加入到结果集。 List<String> filterResult = filter((str) -> str.length() > 3, list); System.out.println(filterResult.toString()); } public List<String> filter(Predicate<String> predicate, List<String> list){ List<String> result = new ArrayList<>(); for (String str : list) { //断言型,传入一个参数,并返回true或者false。 //这里的逻辑是,若断言为真,则把当前的字符串加入到结果集中 if(predicate.test(str)){ result.add(str); } } return result; } }
还有一些其他函数式接口,都在java.util.function包下,可以自行查看。使用方法都是一样的,不再赘述。
除此之外,JDK 中还有很多函数式接口,例如 Comparator.java。只要类上边看到了 @FunctionalInterface 这个注解,你都可以使用 lambda 表达式来简化写法。
四、方法引用
概念:方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。
这里强调一下已经存在的含义。因为,lambda表达式本质上就是一个匿名函数。我们知道,函数就是做逻辑处理的:拿一些数据,去做一些操作。
如果,我们发现有其他地方(类或者对象)已经存在了相同的逻辑处理方案,那么就可以引用它的方案,而不必重复写逻辑。这就是方法引用。
其实方法引用就是一个lambda表达式的另外一种更简洁的表达方式。也可以说是语法糖。
只不过,这里要求 lambda 表达式需要符合一定的要求。首先,方法体只有一行代码。其次,方法的实现已经存在。此时,就可以用方法引用替换 lambda 表达式。
方法引用的操作符为双冒号::。
下边就以最简单的一个我们非常常见的打印语句为例。
//遍历数组里边的元素,并打印,用lambda表达式 String[] arr = new String[]{"zhangsan","lisi"}; Arrays.asList(arr).forEach((s)-> System.out.println(s));
可以发现,lambda 表达式只有一行代码,且方法体逻辑为打印字符串。而打印字符串的方案,在 System.out 对象中已经存在方法 println() 了。
所以,此处 lambda 表达式可以用方法引用替换。
// 注意:方法引用中的方法名不可带括号。 Arrays.asList(arr).forEach(System.out::println);
方法引用有以下四种形式
1.对象 :: 实例方法
2.类 :: 静态方法
3.类 :: 实例方法
4.类 :: new
下边举例说明:
public class ReferTest { public static void main(String[] args) { //函数式接口的抽象方法的参数列表和返回值类型,必须与方法引用对应的方法参数列表和返回值类型保持一致(情况3除外,比较特殊)。 //======= 1.对象::实例方法 ========= // lambda 表达式 Consumer consumer1 = (s) -> System.out.println(s); consumer1.accept("hello world"); //方法引用。Consumer的accept方法,和System.out的println方法结构一样, //都是传入一个参数,无返回值。故可以用方法引用。 Consumer consumer2 = System.out::println; consumer2.accept("hello java"); //======= 2.类::静态方法 ========= Integer[] arr = new Integer[]{12,20,15}; List<Integer> list = Arrays.asList(arr); // lambda 表达式 Comparator<Integer> com1 = (o1, o2) -> Integer.compare(o1, o2); Collections.sort(list,com1); //方法引用。Comparator的compare方法,和Integer的compare静态方法结构一样, //都是传入两个参数,返回一个int值,故可以用方法引用。 Comparator<Integer> com2 = Integer::compare; Collections.sort(list,com2); //======= 3.类::实例方法 ========= // lambda表达式 Comparator<Integer> com3 = (o1, o2) -> o1.compareTo(o2); //方法引用。这种形式比较特殊,(o1, o2) -> o1.compareTo(o2) , //当第一个参数o1为调用对象,且第二个参数o2为需要引用方法的参数时,才可用这种方式。 Comparator<Integer> com4 = Integer::compareTo; //======= 4.类::new ========= // lambda表达式 Supplier<String> supplier1 = () -> new String(); //方法引用。这个就比较简单了,就是类的构造器引用,一般用于创建对象。 Supplier<String> supplier2 = String::new; } }
题外话:方法引用,有时候不太好理解,让人感觉莫名其妙。所以,如果不熟悉的话,用 lambda 表达式完全没有问题。就是习惯的问题,多写就有感觉了。
五、Optional
Optional 类是一个容器类。在之前我们通常用 null 来表达一个值不存在,现在可以用 Optional 更好的表达值存在或者不存在。
这样的目的,主要就是为了防止出现空指针异常 NullPointerException 。
我们知道,像层级关系比较深的对象,中间的调用过程很容易出现空指针,如下代码。
User user = new User(); //中间过程,user对象或者address对象都有可能为空,从而产生空指针异常 String details = user.getAddress().getDetails();
其中,对象的关系如下,
// 地址信息类 public class Address { private String province; //省 private String city; //市 private String county; //县 private String details; //详细地址 public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCounty() { return county; } public void setCounty(String county) { this.county = county; } public String getDetails() { return details; } public void setDetails(String details) { this.details = details; } } //用户类 public class User { private String name; private Address address; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
在 Optional 类出现之前,为了防止空指针异常,可以这样做。(每一层都添加判空处理)
private static String getUserAddr(User user){ if(user != null){ Address address = user.getAddress(); if(address != null){ return address.getDetails(); }else { return "地址信息未填写"; } }else { return "地址信息未填写"; } }
可以发现,代码冗长,还不利于维护,随着层级关系更深,将会变成灾难(是否依稀记得js的回调地狱)。
那么,有了 Optional 类,我们就可以写出更优雅的代码,并且防止空指针异常。(后边就填坑)
下面,就一起领略一下 Optional 的魅力吧!
5.1 创建 Optional 对象
实际上,Optional 是对原值(对象)的一层包装,我们看下 Optional 的源码就知道了。
它把真正需要操作的对象 T 封装成 value 属性。构造器私有化,并提供三种静态的创建 Optional 对象的方法。
public final class Optional<T> { //EMPTY 代表一个值为空的 Optional 对象 private static final Optional<?> EMPTY = new Optional<>(); //用 value 来代表包装的实际值 private final T value; //值为null的构造函数 private Optional() { this.value = null; } //要求值不为null的构造函数,否则抛出空指针异常,见requireNonNull方法 private Optional(T value) { this.value = Objects.requireNonNull(value); } /** 此为Objects类的requireNonNull方法 public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } */ // 1. 创建一个值为空的 Optional 对象 public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } // 2. 创建一个值不为空的 Optional 对象 public static <T> Optional<T> of(T value) { return new Optional<>(value); } // 3. 创建一个值可为空的 Optional 对象 // 如果值 value 为空,则同1,若不为空,则同2 public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); } }
因此,当我们十分确定传入的user对象不为空时,可以用 Optional.of(user)方法。若不确定,则用 Optional.ofNullable(user),这样在后续的操作中可以避免空指针异常(后续map说明)。
5.2 常用方法
1、get方法
public T get() { //如果值为null,则抛出异常,否则返回非空值value if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
2、isPresent方法
//判断值是否存在,若值不为空,则认为存在 public boolean isPresent() { return value != null; }
看到这,不知道有没有小伙伴和我当初有一样的疑惑。既然有判空方法 isPresent,还有获取对象的 get 方法。那开头的那个坑,是不是就可以改写为如下
//注意此时user类型为Optional<User> private static String getUserAddr(Optional<User> user){ //如果user存在,则取address对象 if(user.isPresent()){ Address address = user.get().getAddress(); //把address包装成Optional对象 Optional<Address> addressOptional = Optional.ofNullable(address); //如果address存在,则取details地址信息 if(addressOptional.isPresent()){ return addressOptional.get().getDetails(); }else { return "地址信息未填写"; } }else{ return "地址信息未填写"; } }
这样看起来,好像功能也实现了。但是,我们先不说代码并没有简洁(反而更复杂了),其实是陷入了一个怪圈了。
因为,if(user.isPresent()){}和手动判空处理 if(user!=null){}实质上是没有区别的。这就是受之前一直以来的代码思维限制了。
所以,我们不要手动调用 isPresent 方法 。
不要奇怪,isPresent 方法,其实是为了 Optional 中的其他方法服务的(如map方法),本意并不是为了让我们手动调用。你会在后续多个方法中,见到 isPresent 的身影。
3、ifPresent
//传入一个消费型接口,当值存在时,才消费。 public void ifPresent(Consumer<? super T> consumer) { if (value != null) consumer.accept(value); }
与 isPresent 方法不同, ifPresent 方法是我们推荐使用的。
如可以这样判空
Optional<User> user = Optional.ofNullable(new User()); user.ifPresent(System.out::println); //不要用下边这种 if (user.isPresent()) { System.out.println(user.get()); }
4、orElse 和 orElseGet
public T orElse(T other) { return value != null ? value : other; } public T orElseGet(Supplier<? extends T> other) { return value != null ? value : other.get(); }
这两个方法都是当值不存在时,用于返回一个默认值。如user对象为null时,返回默认值
@Test public void test1(){ User user = null; System.out.println("orElse调用"); User user1 = Optional.ofNullable(user).orElse(createUser()); System.out.println("orElseGet调用"); User user2 = Optional.ofNullable(user).orElseGet(() -> createUser()); } private User createUser() { //此处打印,是为了查看orElse和orElseGet的区别 System.out.println("createUser..."); return new User(); } //打印结果 orElse调用 createUser... orElseGet调用 createUser...
以上是user为null时,两个方法是没有区别的。因为都需要创建user对象作为默认值返回。
5、orElseThrow
6、map
7、flatMap
8、filter