Java养成计划—Java8
内容导航
打卡第43【36】天—在项目和计网中夹杂的
学习内容 : Lambda表达式
Java8的新特性
Java8的新特性有函数式接口,Lambda表达式,Stream API,接口增强,Optional类,新的日期时间API,还有其他的一些新特性。
什么是java8,也就是jdk 1.8,是java语言的一个主要版本。是java5之后最具革命性的版本,虽然现在已经迭代了很多版本,但是还是很有必要学习java8的特性,非常具有使用价值。
java8新特性简介
- 速度更快
- 代码更少(增加了新的语法:Lambda表达式)
- 强大的Stream API
- 便于运行
- 最大化减少空指针异常: Optional
- Nashorn引擎,允许在JVM上运行JS应用
Lambda表达式
Lambda引入
Lambda是一个匿名函数, 可以将其理解为是一段可以传递的代码(将代码像数据一样传递),使用它可以写出更简洁,更灵活的代码,作为一种更紧凑的代码风格,是代码语言更加灵活。
对于这个概念,就先不着急看深层的含义了,这里就先看个例子
package java8;
public class JavaTest {
public static void main(String[] args){
//之前讲解多线程的时候,就已经讲过第二种方式就是实现Runnable接口,Runnable接口下面就只有一个方法run
Runnable test1 = new Runnable() {
@Override
public void run() {
System.out.println("java8使用方便");
}
};
//要放在方法体中,而不是直接放在类体中
test1.run();//这里没有调start,不是多线程,只是简单的方法调用
/**
* Runnable里既然只有一个方法,那么我们创建Runnable对象时就不需要全部给写出来了,因为只有run方法,没有其他的
* 可能
*/
Runnable test2 = () ->System.out.println("java8好的很呐");
test2.run();
}
}
//这里两端代码都是可以正常执行的
所以我的理解就是中间的代码那些不会有第二种可能的地方就可以省掉,这里留下一个括号代表是一个对象,后面的就是run中的方法体,名词就不需要了;能省略好多就是好多,只要留下关键的,不会改变的都不用留下
public static void main(String[] args){
Comparator<Integer> compareResult = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2?1000:-1;
}
};
int result = compareResult.compare(13, 53);
System.out.println(result);
/**
* 这里的Comparator接口也只有一个方法,而上面很多字节都是不需要的,因为除了这种表达之外不会有其他的表达,所以
* 这里继续使用Lambda简化该段代码
* 这里的Integer是可变的,所以要带上
* 前面指明了,后面就不需要了
*/
Comparator<Integer> compareResult1 = (o1, o2) -> o1 > o2?1000:-1;
int result1 = compareResult1.compare(63, 53);
System.out.println(result1);
}
这段代码也是直接省略了很多相同的部分,什么方法名,大括号,类名前面给了,也就不需要了
Lambda就是通过 -> 符号来实现的,符号的前面不能什么都没有
,并且这里的表达式需要注意的是后面的省略要足够精简
Lambda具体分析
1
. 例子 : () -> System.out.println(“java8好的很呐”);
2
. 格式: Lambda的操作符为 -> ,名称为Lambda操作符
3
. 解释 : 操作符的左边为 lambda形参列表,其实就是接口中抽象方法的形参列表 ,右边为 lambda体,其实就是重写的抽象方法的方法体
4
. 类型【使用】
只有一条执行语句,若有返回值,那么return和大括号均可以省略
-
【接口中方法】无参无返回值
Runnable runable = () -> System.out.println("java8好的很呐"); runnable.run();
-
无返回值,但是需要一个参数
【参数列表不需要数据类型,可以类型推断】
这里重述一下类型推断,就是编译器可以根据之前的代码推断出数据类型,比如之前的泛型的例子,还有数组创建之类都是
ArraryList<String> list = new ArrayList<>(); //这里后面就可以不用写String了 int[] arr = {1,2,3}; //编译器推断是创建对象,并且是整型,所以就直接给出就可以了
那看一下使用类型推断后的表示方法,真的挺简洁的
Comparable<Integer> comparable = (o) -> o > 1?1000:-1; System.out.println(comparable.compareTo(34));
只有一个参数,去掉括号不会有歧义,可以去掉括号
Comparable<Integer> comparable = o -> o > 1?1000:-1;
System.out.println(comparable.compareTo(34));
-
有两个及以上参数,多条执行语句,可以有返回值 【最普通的情况】写大括号
Comparator<Integer> compareResult1 = (o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1 > o2?1000:-1; }; System.out.println(compareResult1.compare(63, 53));
这里方法体语句不只是有一条,所以要写多行,执行的时候,按照该执行的顺序执行
所以说操作符左边的形参列表的数据类型可以省略,只有一个参数可以省去括号;对于右边lambda体,如果只有一行语句,可以省略return和大括号
5
. 本质 : lambda表达式的本质就是接口的一个实例,这就是说该表达式就是一个对象,所以使用的场合当然就是创建对象的时候才使用,代表的就是一个实例;可以看到,这里的接口都是只有一个方法,这种接口有一个具体的名称叫做函数式接口
其实就是简化了之后,利用接口中的方法来实现各种操作,使用Lambda表达式可以让调用单方法接口是变得十分简洁
Lambda只能用于函数式接口
Lambda只能用于函数式接口,不能用于含有多个方法的,但是只有一个抽象方法的抽象类。比如下面的WindowAdapter就不能用Lambda简化
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if(type == 1) {
DisConnect();
}
System.exit(0);
}
}
);
可以看一下使用之后的程序报错
Multiple markers at this line
- The target type of this expression must be a functional interface
- The method addWindowListener(WindowListener) in the type Window is not applicable for the arguments ((WindowEvent
e) -> {})
所以Lambda的功能就是可以简洁地创建函数式接口地实例
函数式接口 FunctionalInterface
如果一个接口中只声明了一个抽象方法,那么这个接口就是函数式接口
通过阅读源码就可以发现有注解【想要进入源码,直接Ctrl + Shift +T】就可以进入相应的类查看源码
@FunctionalInterface
public interface Runnable { //这就是源码上的注解
自定义函数式接口时,可以加上该注解,系统就可以检验该接口是否是函数式接口。
加上之后javadoc中也会包含一条声明,说明这个接口是函数式接口
对于函数式接口的理解
java一直都是一门面向对象的编程语言,一切都是对象,比如这里的Lambda表达式就是一个对象,在其他的函数式编程语言中,函数十分重视,Lambda表达式也是一个函数,但是在java8中,Lambda表达式却是一个对象,不是函数,但是它们必须依附函数式接口才能实现【C中的函数就是只有一个方法,没有在函数体中再次定义函数】
只要一个表达式是函数式接口的实例,那么就可以使用Lambda表达式进行简化,以前使用的匿名实现类,现在都可以使用Lambda表达式来书写
java内置的四大核心函数式接口
- Consumer< T>消费型接口 , 参类型为T,返回值类型为void ,对类型T的对象进行操作,包含的方法是void accept(T t)
- Supplier< T> 供给型接口 无参 返回值类型为T 包含的方法为 : T get();
- Function< T,R> 函数式接口 参数类型为T, 返回值类型为R , 对类型为T的对象进行操作,返回结果 R apply(T t);
- Predicate< T> 断定型接口 参数类型为T, 返回类型为boolean 确定类型为T的对象是否满足某约束,返回boolean值, 包含 boolean test(T t);
所以如果我们使用的时候,发现需要进行相关操作,就使用这些方法就可以了
比如消费型接口
package java8;
import java.util.function.Consumer;
public class JavaTest {
//java内置functionalinterface的使用
//消费式接口:传入一个类型的对象,对该对象进行操纵
public static void consumer(int value, Consumer<Integer> con) {
con.accept(value);//对该对象进行操作【消费】
}
public static void main(String[] args) {
consumer(20, t -> System.out.println(t + 1));//这里上面已经知道是整型了,不需要类型
}
}
这里我们创建方法时就给一个对象,以及一个消费型接口,直接调用接口的方法就可以实现对接口的操作
再举一个例子
就是对于断定型接口的使用,这样子我们在创建方法时,最开始就可以不用明确判断方法而且不需要再去创建一个方法,在调用方法时就可以方便地加入操作方法
public static List<String> fitterList(List<String> list, Predicate<String> con) {
ArrayList<String> newList = new ArrayList<>();
for(String s : list) {
if(con.test(s)) {
newList.add(s);
}
}
return newList;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("Cfeng","java","study","every","day");
List<String> newList = fitterList(list, s -> s.contains("a"));//是否包含ava
for(String s : newList) {
System.out.println(s);
}
}
这里就在要过滤的时候才创建的判断方法,并且因为只有一行,所以可以省略到最简形式
java中还有其他类型的可以使用,可以去阅读java的API文档,去查找各种函数式接口的使用
方法引用
1
: 使用场景 :当传递给Lambda体的操作已经有实现的方法了,可以使用方法引用
2
: 本质 : 就是Lambda表达式,也是函数式接口的实例
3
: 理解 : 自我理解就是将原来Lambda体中需要传入的方法体使用另外一个已有的方法代替
4
: 分类 :主要有三种情况
-
对象 :: 非静态方法
-
类 :: 静态方法
-
类 :: 非静态方法
还有更简洁的使用方法就是方法引用,先看一下效果
Comparator<Integer> compare2 = Integer :: compare;
System.out.println(compare2.compare(10, 20));
也就是上面那很长的一段代码可以用这种方式简洁到一定的程度,这种方式的省略叫做方法引用
- 需要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用
- 方法引用其实就是一个lambda表达式,也就是一个函数式接口的实例,它时Lambda表达式的特例:就是函数式接口已经有实现的方法
方法引用通过 :: 符号实现
下面举几个例子来看一下
public static void main(String[] args) {
Comparator<Integer> com = Integer :: compare; //使用Integer类中的compare方法来代替原来的方法compare
System.out.println(com.compare(21, 22));//使用的类名引用的静态方法
PrintStream ps = System.out;
Consumer<String> consume = ps :: println;// 使用实例方法println代替原来的方法accept
consume.accept("I'm Cfeng");
}
这里举了两个例子,其实就可以看出使用的关键了; 首先我们要明白我们使用函数式接口的目的是要调用接口中那唯一的一个方法来进行相关操纵,而接口要创建实例必须实现抽象方法,所以就有了Lambda表达式,只用书写接口中的方法的参数列表和方法体就可以指代一个接口实例;而方法引用呢,则就是通过其他的已经存在的方法来替代这个方法体,只用使用 名称 :: 方法就可以调取相关的方法来替代这个抽象方法的撰写,只是需要注意的就是参数列表要相同,不然不能正常使用。
接下来还有构造方法引用以及其他的知识,明天再继续分享🌳