天天看点

Java 8

目录

  • ​Lambda表达式​
  • ​​引入​​
  • ​​参数列表​​
  • ​​表达式​​
  • ​​变量​​
  • ​​函数式接口​​
  • ​​consumer、supplier、runnable、function、predicate​​
  • ​​方法引用​​
  • ​​接口默认方法​​
  • ​Stream流​
  • ​​Stream的本质​​
  • ​​Stream执行流程​​
  • ​Optional​
  • ​​Optional API​​

Lambda表达式

引入

Lambda表达式,Stream流

Java是一门面向对象的语言,从Java8出来后,加入了函数式编程。这个概念就被推翻了。函数式编程对并行开发,基于事件的开发有非常特别的优势。

$(function(){
  $('.click').click(function(){
  myClickLogic();
  })
}) 
jquery中表示页面加载完之后要做事情。一个方法作为参数传到另外一个方法之中。
      

在java中要实现类似的效果,要使用匿名内部类

package com.java8.lambda;

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Comparator;

public class LambdaTest1 {

class User{
public String name;
public int score;

public User(String name, int score) {
this.name = name;
this.score = score;
        }

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", score=" + score +
'}';
        }
    }

@Test
public void testOldUse(){
User[] us = new User[]{
new User("张三",90),
new User("李四",80),
new User("匿名",76)

        };
//对User数组按照score排序
Arrays.sort(us,new Comparator<User>(){
@Override
public int compare(User o1, User o2) {
//                return o1.score - o2.score;  //如果前面是一个很大的数,后面是一个很小的数。这么一减,就可能溢出
return Integer.compare(o1.score,o2.score);
            }
        });
System.out.println(Arrays.toString(us));

    }

@Test
/**
* 可能里面的子线程还没有执行,Test线程就执行完了
* 简单测试没有问题
*/
public void testOldUse2(){
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("hello,lambda");
            }
        }).start();
    }
}

      

传一个接口,在接口的实现类里面实现自己的逻辑。没有js那么简洁优雅

思考1:

  1. Arrays.sort的第二个参数一定是Comparator接口
  2. Comparator里面一定要重写compare方法
  3. compare方法一定要返回int

    这些一定要java程序员写出来吗?这些不能够由jvm虚拟机推导吗?

将上面所说的三个东西删除掉,在参数列表和方法体之间加上​

​->​

​就可以让代码编译通过。这一段东西就是Lambda表达式

@Test
public void testNewUse(){
User[] us = new User[]{
new User("张三",90),
new User("李四",80),
new User("匿名",76)
        };
//对User数组按照score排序
Arrays.sort(us,
           (User o1, User o2) -> {
return Integer.compare(o1.score,o2.score);
        });
System.out.println(Arrays.toString(us));
    }
      

为什么可以推导?因为有三个一定,将代码简化为lambda,jvm可以将lambda反推回匿名内部类的写法。Lambda只是将以前一定要java程序员要做的事情省略掉。省略掉一定可以推导出来的东西。业务逻辑无法推导

Lambda表达式还可以改进。

  1. 代码块里面只有一行代码,可以删除掉花括号。

    return

    可以不要了。

    Integer.compare()

    返回值一定是int

    Arrays.sort(us, (User o1, User o2) -> Integer.compare(o1.score,o2.score) );

  2. 参数的类型可以省略

    o1,o2是us数组里面的元素

    Arrays.sort(us, (o1,o2) -> Integer.compare(o1.score,o2.score) );

jvm可以帮助我们推导什么?

  1. 不管方法是否有参数,小括号不能够省略。小括号代表方法

    new Thread(() -> System.out.println("hello,lambda")).start();

编译器会把我们写的代码转成字节码,转换的过程中可以把lambda语句还原成匿名内部类

@Test
public void testOldUse3(){
Runnable run =  new Runnable(){
@Override
public void run() {
System.out.println("hello lambda!");
           }
       };
new Thread(run).start();
new Thread(run).start();
    }

@Test
public void testNewUse3(){
Runnable run = () -> System.out.println("hello lambda!");
new Thread(run).start();
new Thread(run).start();
    }
      

参数列表

  1. lambda的历史: 波长单位,带有参数变量的表达式称为lambda表达式

    避免匿名内部类使用过多,使代码更加简洁,去掉无意义的代码,只留下核心的逻辑

  2. lambda表达式的基本语法
  • 参数列表 -> 表达式 ;
  • 如果没有参数,直接用小括号表示,小括号不能够省略
  • 如果只有一个参数,

    如果加了参数类型,小括号不能省略;

    如果去掉参数类型,小括号能够省略

  • 两个或者多个参数, 不管是否写参数类型,小括号不能省略

    String[] strs = new String[]{"Alice","Mike","Byte"}; Arrays.sort(strs,(s1,s2)->Integer.compare(s1.length(),s2.length())); System.out.println(Arrays.toString(strs));

  • 如果参数要加修饰符或者标签,参数一定要加上完整的类型

表达式

  • 如果表达式只有一行代码,可以直接不写花括号{}

    接口里面只有一行代码

  • 如果表达式有多行,要加上花括号变成代码块
  • 如果表达式是代码块,并且方法需要返回值,那么在代码块中必需返回一个返回值
  • 如果只有单行的情况,并且方法需要返回值,绝对不能写​

    ​return​

    ​,编译器会自动帮助我们推导​

    ​return​

变量

public void repeatPrint(String content,int times){
Runnable run = () -> {
for(int i=0;i<times;i++)
System.out.println(content);
        };
new Thread(run).start();
    }

@Test
public void testVar(){
this.repeatPrint("lambda",5);
    }
      
  • 参数(参数列表的变量,接口的方法的参数)
  • lambda里面声明的局部变量
  • 自由变量(不是参数,也不是局部变量)

结论:Lambda表达式找那个的自由变量会被保存,无论是什么时候执行Lambda表达式,都可以直接使用

自由变量在Lambda在是不能修改的,一旦在Lambda中使用到变量,变量就会变成​

​final​

​修饰。

操作自由变量的代码块,称为闭包

参数和局部变量的使用方式和普通的变量使用方式相同

  • this关键字注意问题

Lambda表达式中的​

​this​

​指向创建Lambd表达式的方法中的​

​this​

匿名内部类的​

​this​

​指向匿名内部类的实例

package com.java8.lambda;

import org.junit.jupiter.api.Test;

public class LambdaTest3 {
public void repeatPrint(String content,int times){
Runnable run = () -> {
for(int i=0;i<times;i++)
System.out.println(content);
System.out.println(this);
        };
new Thread(run).start();
    }
public void repeatPrint2(String content,int times){

Runnable run = new Runnable() {
@Override
public void run() {
for(int i=0;i<times;i++)
System.out.println(content);
System.out.println(this);
            }
        };
new Thread(run).start();
    }

@Test
public void testVar(){
this.repeatPrint("lambda",3);
/**
* lambda
* lambda
* lambda
* com.java8.lambda.LambdaTest3@467738e0
*/
    }

@Test
public void testVar2(){
this.repeatPrint2("lambda",3);
/**
* lambda
* lambda
* lambda
* com.java8.lambda.LambdaTest3$1@bd94a08
*/
    }

}
      

函数式接口

  1. 我们能够写Lambda表达式的地方?一个接口,且接口只有一个抽象方法

    函数式接口里面可以写Object类的方法

Comparator接口里面除了compare,equals,还有被default修饰的方法?

  1. 在Java中,把只有一个抽象方法的接口称为函数式接口,如果一个接口是函数式接口,我们可以在接口上添加

    @FunctionalInterface

    注解标明这是一个函数式接口
@FunctionalInterface
public interface IMyWork {
void doWork();
}      
package com.java8.lambda;

import org.junit.jupiter.api.Test;

public class LambdaTest4 {

public void wrapWork(IMyWork work){
System.out.println("do some wrap work...");
work.doWork();
    }

@Test
public void test(){
//this.wrapWork(()-> System.out.println("do real work"));
IMyWork work = () -> System.out.println("do real work");
this.wrapWork(work);
//Runnable work = () -> System.out.println("do real work");
    }
}      
  1. 无论是否标识@FunctionInterface,只要满足函数式接口的接口,Java都会直接识别为函数式接口

    加上注解的好处,如果不满足函数式解口,可以自动提醒

  2. Java中提供Lambda表达式唯一的作用是简化函数式接口
  3. 有了上下文环境,Lambda表达式才能够反推相应的接口(才能够知道自己的类型)
  4. Lambda表达式中的异常处理
  • 在代码块中处理
  • 在接口的方法中声明

    ​void doWork() throws Exception;​

consumer、supplier、runnable、function、predicate

  • consumer

    Lambda没有返回值

  • supplier

    Lambda有返回值

  • runnable

    Lambda既无输出也无输入

  • function

    Lambda可以有输入,输出

  • predicate

    Lambda返回布尔类型

方法引用

Integer[] is = new Integer[]{2,3,4,6,4,3,5};
/**
* x,y原封不动的作为compare的参数
*/
Arrays.sort(is,(x,y)->Integer.compare(x,y));
/**
* 方法引用(类::静态方法)
*/
Arrays.sort(is, Integer::compare);
System.out.println(Arrays.toString(is));

      
Integer[] is = new Integer[]{2,3,4,6,4,3,5};
List<Integer> la = Arrays.asList(2, 3, 4, 6, 4, 3, 5);
la.forEach(x-> out.println(x+" ")); 
//println是System类下out对象的一个方法
la.forEach(System.out::println);
      
  1. 类::静态方法

  2. 对象::方法

    Integer[] is = new Integer[]{2,3,4,6,4,3,5}; LambdaTest5 lt = new LambdaTest5(); //自己写的类 //方法引用(对象::方法) Arrays.sort(is, lt::compare); System.out.println(Arrays.toString(is));

  3. 对象::静态方法

  4. 构造器引用, 需要有以一个无参构造

    类::new

    构造器引用对应的函数式接口里面的方法格式一定是,返回一个对象,方法没有参数

    @FunctionalInterface public interface IMyCreator<T extends List<?>> { T create(); }

    package com.java8.lambda; import org.junit.jupiter.api.Test; import java.util.LinkedList; import java.util.List; public class LambdaTest6 { public <T> List<T> asList(IMyCreator<List<T>> creator,T... a){ List<T> list = creator.create(); for(T t:a) list.add(t); return list; } @Test public void test(){ List<Integer> list = this.asList(LinkedList::new,2,3,4,5,1,10,8,6); //LinkedList::new 相当于 ()->{reurn new ArrayList();} list.forEach(System.out::println); System.out.println(list.getClass()); } }

注:java泛型方法带有返回值

Java 8

接口默认方法

允许在接口里面写默认的实现。写实在的方法

  1. 用接口的方式来实现以前无法达到的多重继承

    接口1:

    public interface ILandAnimal { void run(); default void breathInAir(){ System.out.println("breath in air..."); } default void breath(){ System.out.println("LandAnimal breath"); } }

    接口2:

    public interface IWaterAnimal { void swim(); default void breathInWater(){ System.out.println("breath in water..."); } default void breath(){ System.out.println("waterAnimal breath"); } }

    父类:

    public class BigMouth { public void openMouth(){ System.out.println("大嘴巴..."); } }

    Forg

    package com.java8.interfaceMethod; public class Forg extends BigMouth implements IWaterAnimal,ILandAnimal { @Override public void run() { System.out.println("对不起,我只会跳"); } @Override public void swim() { System.out.println("对不起,我没有尾巴,只会蛙泳"); } @Override public void breath() { ILandAnimal.super.breath(); } public static void main(String[] args) { Forg forg = new Forg(); forg.swim(); forg.breathInWater(); forg.run(); forg.breathInAir(); forg.openMouth(); forg.breath(); /** * 对不起,我没有尾巴,只会蛙泳 * breath in water... * 对不起,我只会跳 * breath in air... * 大嘴巴... * LandAnimal breath */ } }

  2. 如果两个接口出现了相同名字的默认方法,那么java要求实现类重写方法,即实现类必需实现冲突的方法,指定使用哪个接口中的默认实现

    @Override public void breath() { ILandAnimal.super.breath(); }

  3. 父类的方法和接口的默认方法冲突

    父类有一个方法和接口的一个默认方法有相同的方法签名,那么使用的是父类里面的方法。

    所有的类隐式继承了Object类,不要期望通过使用接口的默认方法来改变Object对象里面的方法

  4. 接口里面可以写接口的静态方法

    public interface IWaterAnimal { void swim(); static void count(){ System.out.println("现在的动物种类正在减少"); } }

    IWaterAnimal.count();

  5. 常见的一些模式被消灭了

    工具类:DefaultHandler.java

    适配器模式:WindowAdapter.java

    写接口的方法做成default方法就可以了,使用的时候覆盖即可

    Comparator即作为函数式接口,又作为工具类存在

Stream流

案例1:

@Getter
@Setter
@AllArgsConstructor
@ToString
public class User {
private String name;
private int score;

}

      
package com.java8.stream;

import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.stream.Collectors;

public class StreamTest1 {

private List<User> us = new ArrayList<>();

public void prepared(){
Random random = new Random();
for(int i=0;i<100;i++){
us.add(new User("user"+i,random.nextInt(50)+50));
        }
    }

//1. 列出班上超出85分的学生姓名,并且按照降序的方式输出用户的名字
@Test
public void test1(){
this.prepared();
System.out.println(us);
List<String> ret = new ArrayList<>();
List<User> temp = new ArrayList<>();
for(User u:us){
if(u.getScore()>85){
temp.add(u);
            }
        }
temp.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return Integer.compare(o2.getScore(),o1.getScore());
            }
        });

for (User u:temp){
ret.add(u.getName());
        }

System.out.println(ret);
/**
* 业务逻辑只有三个地方,其他地方都是在遍历
*/

/*
*  使用Stream
* 1. 得到集合的流对象
* 2. 使用filter方法进行过滤
* 3. 使用sorted方法完成了排序
* 4. 使用map方法把User的流变成了String的流
* 5. 使用collect把String的流变成了一个List<String>
*/
ret = us.stream()
                .filter(u->u.getScore()>85)
//.sorted((u1,u2)->Integer.compare(u2.getScore(),u1.getScore()))
                .sorted(Comparator.comparing(User::getScore).reversed())//比较完Score排序后再逆序
                .map(u->u.getName())
                .collect(Collectors.toList());
System.out.println(ret);
    }
}

      

Stream流的写法更加贴近自然语言

案例2:

//统计出平均分数
@Test
public void test2(){
this.prepared();
double totalScore = 0;
for (User u:us){
totalScore+=u.getScore();
        }
if(us.size()>0){
double avg = totalScore/us.size();
System.out.println(avg);
        }

//使用Stream
//Optional可选,可能为空
us.stream().mapToInt(User::getScore)
                .average().ifPresent(System.out::println);
    }
      

Java8中的Stream是对集合对象功能的增强,它专注于对集合对象进行各种便利,高效的聚合操作或者大批量的数据操作。

Stream处理和集合相关的东西,Stream提供了很多直接使用Lambda的方法,Stream的重点在于数据的操作(里面隐含了遍历,只要传入逻辑即可)

Stream的本质

外部迭代带来的问题

Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8};
List<Integer> ret = new ArrayList<>();
for(Integer i:ints)
if(i>5)
ret.add(i);
System.out.println(ret);
      

业务的代码和遍历的代码揉在一块儿,需要准备另外一个列表来保存结果。循环的代码和业务处理的代码混淆

Java 8

Stream内部循环,代码里面不需要写循环的操作

Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8};
Stream.of(ints)
    .filter(x->x>5)
    .collect(Collectors.toList())
    .forEach(System.out::println);
      
Java 8
  1. Stream是不会存储数据的
  2. Stream是不会修改源数据的
  3. Stream是单向,不可重复使用的
  4. Stream的部分操作是延迟的
  • 只要Stream的方法返回的是Stream,那么这些就是延迟执行的方法

调用了一个方法,马上执行,我们叫做迫切执行方法;调用了一个方法,并不会立刻执行,叫做延迟执行方法。

在Stream里面,返回的不是一个Stream的基本都是迫切执行的方法

  • 延迟执行的方法一定要等到一个迫切执行的方法执行的时候,才会执行

    public boolean compare(int x){ System.out.println("调用了compare方法"); return true; } @Test public void test3() { Integer[] ints = {1, 2, 3, 4, 5, 6, 7, 8}; Stream<Integer> stream = Stream.of(ints).filter(this::compare); System.out.println("================"); stream.collect(Collectors.toList()).forEach(System.out::println);//collect是迫切执行的方法 /** * ================ * 调用了compare方法 * 调用了compare方法 * 调用了compare方法 * 2 * 4 * 1 */ }

    可以简单理解为Stream是一种高级的Iterator,这种Iterator迭代器提供了更多关于其包含的数据的操作功能。

    创建水流,搭建管道,得到干净的水

  1. 使用Stream
  1. 创建一个Stream
  2. 对Stream进行操作和变换(延迟操作)
  3. 通过Stream得到一个j结果(迫切操作/执行延迟操作)

Stream执行流程

Java 8

​stream.filter(x->x>5).filter(x->x<8).collect(toList());​

collect就像是一个开关。

不是第一个filter过滤完了,第二个filter才打开,6可以直接通过管道。容易执行并行操作

Java 8

Optional

Optional出现是为了解决空指针问题。

Optional可以优化,使得我们的代码更加简单

并不是所用了Optional就不会产生空指针了,它强制我们去思考空指针

NullPointer是最糟糕的隐藏错误。函数调用栈变深,溯源变得困难

Optional是一个泛型类,泛型就是我们使用的类。Optional为我们提供了一些增强的方法

Optional API

​of​

获取Optional包裹的类型

创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException

Optional<String> name = Optional.of("Alice");
String s = name.get();
System.out.println(s); //Alice

Optional<String> someNull = Optional.of(null); //java.lang.NullPointerException

      

​ofNullable​

可以接受参数为null的情况。可能会导致空指针传递,蔓延

Optional<String> name = Optional.ofNullable(null);//精髓所在。防止空指针。这一步还不会报错
String s = name.get();//直接取就会报错
      

​empty​

返回一个空的Optional对象

Optional<String> name = Optional.of("Alice");
Optional<Object> empty = name.empty();
System.out.println(empty); //Optional.empty
      

​get​

如果Optional有值则将其返回,否则抛出NoSuchElementException。

​isPresent​

如果Optional实例有值则为其调用consumer,否则不做处理

//name.ifPresent(t-> System.out.println(name.get()));
// 省去lambda 传递参数的过程
Optional<String> name = Optional.of("Alice");
name.ifPresent(System.out::println);
      

​orElse​

如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。

Optional<String> t1 = Optional.of("Alice");
Optional<String> t2 = Optional.ofNullable(null);
String s1 = t1.orElse("默认值");
String s2 = t2.orElse("默认值");
System.out.println(s1);
System.out.println(s2);
/**
* Alice
* 默认值
*/
      

​orElseThrow​

判空,如果存在,则返回相应的值; 如果不存在,则抛出一个异常

public String testOrElseThrow() {
Optional<String> name = Optional.of("Alice");
return   name.orElseThrow(()->new RuntimeException("exception"));

    }

public String testOrElseThrow2(){
Optional<Object> name2 = Optional.ofNullable(null);
return (String) name2.orElseThrow(()->new RuntimeException("exception"));
    }

@Test
public void test2(){
String s = this.testOrElseThrow();
String s2 = this.testOrElseThrow2(); //java.lang.RuntimeException: exception
    }

      

​filter​

接受参数为Predicate对象,用于对Optional对象进行过滤,如果符合Predicate的条件,返回Optional对象本身,否则返回一个空的Optional对象。

Optional<String> t1 = Optional.ofNullable("a");
String s1 =t1.filter(t->t.equals("a")).orElse("wrong");
System.out.println(s1); //a
      

​map​

不是Stream里的map

map()方法的参数为Function(函数式接口)对象,map()方法将Optional中的包装对象用Function函数进行运算,并包装成新的Optional对象(包装对象的类型可能改变)

Optional<String> t1 = Optional.ofNullable("a");
String s1 = t1.map(t -> t + "b").orElse("c");
System.out.println(s1);   //ab
      

Optional类结合Lambda表达式的使用能够让开发的代码更简洁和优雅