Java8的新特征有很多,在涉及接口的时,我们就接触了接口的静态方法和默认方法,在使用常用类时,我们应用了新版的日期时间API。其实Java8最具革命性的两个新特性:Lambda表达式和StreamAPI。其次就是Optioanl类成功解决最令人头疼的空指针异常的问题。
1.什么是函数式编程思想?
在数学中,我们接触到了函数,函数就是有输入量、输出量的一套计算方案,就是“拿什么东西做什么事情”。在编程中的函数,也具有类似的概念,例如A调用B的时候,给B实参为形参赋值,然后通过运行方法体即可,返回给A一个结果。对于调用者A来说,关注的是B具有什么样的功能。相比之一,面向对象过分强调了“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法–强调做什么,而不是以什么形式做。
简言之:
①面向对象的思想:就是做一件事情,找一个能解决这个事情的对象,调用对应的方法完成事情即可。
②函数式编程思想:只要能获取到结果,谁去做并不重要,看重的是结果,不重视过程。
2.Java中使用Lambda表达式的好处
Lambda表达式其实就是实现SAM接口的语法糖(给“函数式接口/SAM”的变量或形参赋值),这样使得Java也算是支持函数式编程的语言。
①极大的减少了代码的冗余
②相对冗长的匿名类(替换原来匿名内部类的写法),增强代码可读性
3.什么是“语法糖”?
“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。
4.比对Lambda表达式应用
需求;通过实现Runnable接口,来启动一个线程,线程要完成一件任务,成功打印出“coding0110lin ”。下面我会使用三种方式进行实现,差距就显而易见。
(1)不使用匿名内部类
package com.conding0110lin.csnd;
import org.junit.Test;
public class RunnableTest {
@Test
public void test01() {
MyRunnable my = new MyRunnable();
Thread thread = new Thread(my);//创建线程对象
thread.start();// 启动线程,调用run()
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("coding0110lin");//成功打印
}
}
(2)使用匿名内部类
相对不使用匿名内部类的实现,代码少些许。
public class RunnableTest {
@Test
public void test02() {
Thread thread = new Thread(new MyRunnable(){
@Override
public void run() {
System.out.println("coding0110lin");//成功打印
}
});//创建线程对象
thread.start();// 启动线程,调用run()
}
}
(3)使用Lambda表达式
相对匿名内部类的实现,执行效果完全一样的。
public class RunnableTest {
@Test
public void test03() {
thread = new Thread(() -> System.out.println("coding0110lin")).start();//成功打印
}
}
总结:我们可以看出,以上三种实现方式都成功实现需求的打印效果,而Lambda表达式从代码的语义上与两者相同:就是启动一个线程,但是线程的内容是以更简洁的形式被指定,摆脱了“不得不创建接口对象”的束缚,抛却了“抽象方法的覆盖重写”的负担,优势一目了然。
5.什么是函数式接口?
lambda表达式其实就是实现SAM(函数式)接口的语法糖(语法),所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法(静态方法、默认方法),此方法个数不限制。我们把满足这种特征的接口,就称之为函数式接口。
只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
函数式接口只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
//接口当中抽象方法的 public abstract 是可以省略的
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
思考:在JavaSE阶段中,我们接触过的接口,有哪些满足SAM接口的呢?
接口 | (有且只有一个)抽象方法 | 有无@FunctionalInterface标记 |
---|---|---|
java.lang.Runnable | void run() | 有 |
java.util.Comparator | int compare(T t1, T t2) | 有 |
java.lang.Comparable | int compareTo(T t) | 无 |
java.io.FileFilter | boolean accept(File pathname) | 有 |
java.lang.Iterable | Iterator iterator() | 无 |
说明:如果加了@FunctionalInterface注解,即明确是函数式接口,我们可以放心的使用Lambda表达式;如果没有标记@FunctionalInterface注解,表示可能后面这个接口会修正,称为一个非函数式接口,目前虽然也可以使用Lambda表达式,但是建议不要使用。
小结:
(1)建议只针对标记@FunctionalInterface这个注解的函数式接口,我们使用Lambda表达式。
(2)若我们自己要声明函数式接口,请加上@FunctionalInterface这个注解。
public class MyInterfaceTest {
}
// 注解声明该接口是函数式接口
@FunctionalInterface
interface MyInterface {
public abstract void method();// 抽象方法(有且只有一个)
default void method2() {// 默认方法
}
public static void method3() {// 静态方法
}
// 再添加一个抽象方法就会--报错
/*
* void method4() { }
*/
// Invalid '@FunctionalInterface' annotation; MyInterface is not a
// functional interface
}
6.Java8中新增的函数式接口有哪些?
在Java8版本中,增加了很多新的函数式接口,在java.util.function中,总的来可以分为四类:
①消费型接口:抽象方法#有参无返回值
②供给型接口:抽象方法#无参有返回值
③判断型接口:抽象方法#有参有返回值,但是返回值类型是boolean类型
④功能型接口:抽象方法#有参有返回值
⑴函数式接口一之消费型接口
这类接口的抽象方法特点:有参,但是无返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
经典代表:Consumer | void accept(T t) | 接收一个对象用于完成功能 |
①BiConsumer<T,U>【Binary(二元)】 | void accept(T t, U u) | 接收两个对象用于完成功能 |
②DoubleConsumer | void accept(double value) | 接收一个double值 |
③IntConsumer | void accept(int value) | 接收一个int值 |
④LongConsumer | void accept(long value) | 接收一个long值 |
⑤ObjDoubleConsumer | void accept(T t, double value) | 接收一个对象和一个double值 |
⑥ObjIntConsumer | void accept(T t, int value) | 接收一个对象和一个int值 |
⑦ObjLongConsumer | void accept(T t, long value) | 接收一个对象和一个long值 |
⑵函数式接口二之供给型接口
这类接口的抽象方法特点:无参,但是有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
经典代表:Supplier | T get() | 返回一个对象 |
①BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
②DoubleSupplier | double getAsDouble() | 返回一个double值 |
③IntSupplier | int getAsInt() | 返回一个int值 |
④LongSupplier | long getAsLong() | 返回一个long值 |
⑶函数式接口三之判断型接口
这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。
接口名 | 抽象方法 | 描述 |
---|---|---|
经典代表:Predicate | boolean test(T t) | 接收一个对象 |
BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
DoublePredicate | boolean test(double value) | 接收一个double值 |
IntPredicate | boolean test(int value) | 接收一个int值 |
LongPredicate | boolean test(long value) | 接收一个long值 |
⑷函数式接口四之功能型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 | 抽象方法 | 描述 |
---|---|---|
经典代表:Function<T,R> | R apply(T t) | 接收一个T类型对象,返回一个R类型对象结果 |
①UnaryOperator【Binary(二元)】 | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
②DoubleFunction | R apply(double value) | 接收一个double值,返回一个R类型对象 |
③IntFunction | R apply(int value) | 接收一个int值,返回一个R类型对象 |
④LongFunction | R apply(long value) | 接收一个long值,返回一个R类型对象 |
⑤ToDoubleFunction | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
⑥ToIntFunction | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
⑦ToLongFunction | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
⑧DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
⑨DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
⑩IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
⑪IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
⑫LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
⑬LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
⑭DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
⑮IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
⑯LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
⑰BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
⑱BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
⑲ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
⑳ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
⑳+1 ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
⑳+2 DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
⑳+3IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
⑳+4LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
7. Lambda表达式语法
Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。其实本质上,Lambda表达式是用于实现【SAM函数式接口】的“抽象方法”。
Lambda表达式基本语法格式: (形参列表) -> {Lambda体}Lambda表达式语法
8.Lambda表达式语法说明
①(形参列表):它是你要赋值的函数式接口的抽象方法的形参列表
②->:Lambda操作符,英文状态下输入,中间不要加空格,一个减号一个大于号
③{Lambda体}:它是你要赋值的函数式接口的抽象方法的实现,即实现该抽象方法的方法体
9.Lambda表达式如何优化代码
①当(形参列表)的形参类型是已知的或者是可以推断的,那么形参列表的数据类型可以省略
②当(形参列表)的形参个数只有一个,并且数据类型已经省略的情况下,那么()也可以省略
③当(形参列表)无参,那么()是不能省略的
④当{Lambda体}中语句只有一句,那么{}和;可以省略
⑤当{Lambda体}中语句只有一句,如果这句语句是一个return语句,那么要么连同return一起省略,要么不省略
⑤+如果{Lambda体}的{}没有省略,那么每个语句仍然要;,如果对应的抽象方法有返回值,仍然要return
public class TestLambda {
@Test
public void test00() {
Thread thread = new Thread(new MyRunnable(){
@Override
public void run() {
System.out.println("coding0110lin");//成功打印
}
});//创建线程对象
thread.start();// 启动线程,调用run()
}
@Test
public void test01(){
/*
* 需求启动这个线程,打印"coding0110lin"信息
*/
// Thread t = new Thread(() -> {System.out.println("coding0110lin");});//需要一个Runnable类型的实参
//因为{Lambda体}中只有一句语句,现在省略{}和里面语句的;
Thread t = new Thread(() -> System.out.println("coding0110lin"));
t.start();
}
@Test
public void test02(){
/*
* Java8中,Collection<T>系列的集合增加了一个方法
* default void forEach(Consumer<? super T> action)
* 此方法的形参是Consumer<T>类型,即消费型接口
* 说明调用这个方法时,可以用Lambda表达式为Consumer的形参赋值
*
* Consumer<T>接口的抽象方法 void accept(T t)
*
* 例如:要实现Consumer<T>的抽象方法,打印集合的元素
* (T t) -> {System.out.println(t);}
*/
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
//default void forEach(Consumer<? super T> action)
// list.forEach((String t) -> {System.out.println(t);});
//省略lambda表达式的形参列表的数据类型--因为形参类型String是已知的或者是可以推断
// list.forEach((t) -> {System.out.println(t);});
//因为形参只有一个,并且数据类型已经省略
// list.forEach(t -> {System.out.println(t);});
//因为{Lambda体}中只有一句语句,现在省略{}和里面语句的;
list.forEach(t -> System.out.println(t));
}
@Test
public void test03(){
/*
* Java8版本给Collection<T>增加了
* default boolean removeIf(Predicate<? super E> filter)
*
* 形参是:Predicate<T>,判断型的函数式接口,抽象方法 boolean test(T t)
* 用Lambda表达式为Predicate<T>形参赋值
*/
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("lin");
list.add("da");
list.add("xia");
/* list.removeIf((String t) -> {
if(t.length()>=5)
return true;
return false;
});*/
//省略lambda表达式的形参列表的数据类型
/* list.removeIf((t) -> {
if(t.length()>=5)
return true;
return false;
});*/
//省略()
/* list.removeIf(t -> {
if(t.length()>=5)
return true;
return false;
});*/
//改装成只有一个语句
// list.removeIf(t -> {return t.length()>=5;});
list.removeIf(t -> t.length()>=5);
System.out.println(list);
}
@Test
public void test04(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("lin");
list.add("da");
list.add("xia");
//删除里面的字符串,当这个字符串的长度>=5
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if(str.length()>=5){
iterator.remove();
}
}
System.out.println(list);
}
}
10.如何优化Lambda表达式(方法引用&构造器引用)
Lambda表达式作用是为了简化匿名内部类的冗余代码,而方法引用和构造器引用作用是为了简化Lambda表达式。
11.方法引用&构造器引用的应用场景
当Lambda表达式满足一些特殊情况时,可以使用。
(1)当Lambda表达式的{Lambda体}只有一个语句,并且这句语句是通过调用一个某个类或某个对象的现有的方法来完成的。
(2)并且Lambda表达式(形参列表)中的所有形参,都被用上了,用在了{Lambda体}中这个调用方法的实参列表中,或者,其中第一个形参作为调用这个方法的对象,其余形参作为调用这个方法的实参列表。
(3)整个Lambda表达式中没有其他多余的数据出现。
12.方法引用&构造器引用的语法
①方法引用的语法格式: 对象/类名 :: 方法名
⑴对象::实例方法名 例如:System.out::println
⑵类名::静态方法名
⑶类名::实例方法
②构造器引用:一种特殊的方法引用,当Lambda表达式满足一些特殊情况时,可以使用。
⑴当Lambda表达式的{Lambda体}只有一个语句,并且这句语句是通过创建一个对象完成的。
⑵并且Lambda表达式(形参列表)中的所有形参,都被用上了,用在了{Lambda体}中创建对象的构造器的实参列表
⑶整个Lambda表达式中没有其他多余的数据出现
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.Test;
public class TestMethodReference {
@Test
public void test01(){
//java.util.Arrays:
//public static <T> List<T> asList(T... a)
List<String> list = Arrays.asList("hello","java","world");
//Consumer<T>:void accept(T t)
// list.forEach((String t) -> {System.out.println(t);});
//省略形参类型,{Lambda体}的{}等
// list.forEach(t -> System.out.println(t));
//再次省略,用方法引用
/*
* (1){Lambda体}只有一个语句
* (2){Lambda体}是通过调用out对象的现有方法println方法完成
* (3)Lambda表达式(形参列表)全部用在了调用println方法的实参列表中
* 满足方法引用的省略条件
*/
list.forEach(System.out::println);
}
@Test
public void test02(){
//供给型接口:Supplier<T> T get()
//例子:Stream
/* Stream.generate(() -> {return Math.random();})
.forEach((Double d) -> {System.out.println(d);});*/
/* Stream.generate(() -> Math.random())
.forEach(d -> System.out.println(d));*/
Stream.generate(Math::random)
.forEach(System.out::println);
}
@Test
public void test03(){
String[] arr = {"helloo","Helloo","abstract","ABCD"};
//希望对字符串进行排序,不区分大小写
//Comparator<T> int compare(T t1, T t2)
// Arrays.sort(arr, (String t1, String t2) -> {return t1.compareToIgnoreCase(t2);});
// Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
Arrays.sort(arr, String::compareToIgnoreCase);
System.out.println(Arrays.toString(arr));
}
@Test
public void test04(){
//Runnable void run()
// Thread t = new Thread(() -> {System.out.println("hello");});
// Thread t = new Thread(() -> System.out.println("hello"));
/*Thread t = new Thread(System.out::println);//打印空行
t.start();*/
new Thread(() -> System.out.println("hello")).start();
}
@Test
public void test05(){
//这里要给Supplier<T> T get()的变量赋值
// Supplier<String> s = () -> {return new String();};
//简化
// Supplier<String> s = () -> new String();
//再简化
Supplier<String> s = String::new;
}
@Test
public void test06(){
Optional<String> opt = Optional.ofNullable(null);
//Supplier<T> T get()的形参赋值
String name = opt.orElseGet(() -> "无名字");
System.out.println(name);
Optional<Person> opt2 = Optional.empty();
Person per = opt2.orElseGet(() -> new Person(opt.orElse("无名字")));//反例
System.out.println(per);
}
}
class Person{
private String name;
public Person(String name) {
super();
this.name = name;
}
public Person() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}
推荐阅读往期博文:
•【JavaSE】Java8最具革命性新特性之StreamAPI
#轻松一刻
☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!
☞本人博客:https://coding0110lin.blog.csdn.net/ 欢迎转载,一起技术交流吧!