目录
一、Lambda表达式
1.1、为什么使用Lambda表达式?
1.2、Lambda的标准格式
Lambda的标准格式
无参无返回值的Lambda
有参有返回值的Lambda
1.3、Lambda的实现原理
1.4、Lambda省略模式
1.5、Lambda表达式的前提条件
1.6、Lambda与匿名内部类对比
1.7、JDK8接口新增的两个方法
JDK8接口增强介绍
接口默认方法的定义格式
接口默认方法的使用
接口静态方法的定义格式
接口静态方法的使用
接口静态方法与默认方法的区别
1.8、常用内置函数式接口
Supplier
Consumer
Function
Predicate
1.8、方法引用
Lambda的冗余场景
常见引用方式
对象名::引用成员方法
类名::引用静态方法
类名::引用实例方法
类名::new引用构造器
数组::new引用数组构造器
Lambda是一个匿名函数,我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
匿名内部类与Lambda表达式写法对比:显而易见Lambda表达式更加简便。
public class TestLambda {
public static void main(String[] args) {
//使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("普通匿名内部类执行了");
}
}).start();
//使用Lambda
new Thread(()->{
System.out.println("Lambda表达式执行了");
}).start();
}
}
(参数类型 参数名称) -> {
代码体;
}
public interface Swimmable {
public abstract void swimming();
}
public class TestLambda2 {
public static void main(String[] args) {
//无参无返回值的Lambda
goSwimming(()->{
System.out.println("无参无返回值的Lambda");
});
}
public static void goSwimming(Swimmable s){
s.swimming();
}
}
public interface Swimmable {
public abstract int swimming(String name);
}
public class TestLambda2 {
public static void main(String[] args) {
//有参有返回值的Lambda
goSwimming((String name)->{
System.out.println(name + "正在游泳");
return 10;
});
}
public static void goSwimming(Swimmable s){
s.swimming("尼采");
}
}
普通的匿名内部类编译后是会生成一个名字带$的类,但Lambda不会生成其它类文件,但是Lambda表达式会在这个类中新生成一个私有的静态方法,并且Lambda表达式的代码会放到这个新增的方法中。同时Lambda底层还是会以普通匿名内部类的形式去展现,只不过在内部类中调用了之前生成的私有的静态方法。
在Lambda标准格式的基础上,使用省略写法的规则为:
1、小括号内参数的类型可以省略。
2、如果小括号内有且仅有一个参数,则小括号可以省略。
3、如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号。
public static void main(String[] args) {
List books = new ArrayList<>();
books.add("百年孤独");
books.add("霍乱时期的爱情");
books.add("一桩事先张扬的凶杀案");
books.add("没有人给他写信的上校");
//省略前
books.forEach((t) -> {
System.out.println(t);
});
//省略后
books.forEach(t -> System.out.println(t));
}
1、方法的参数或局部变量类型必须为接口才能使用Lambda。
2、接口中有且仅有一个抽象方法(函数式接口)。
1、所需的类型不一样:
匿名内部类:需要的类型可以是类、抽象类、接口。
Lambda:需要的类型必须是接口。
2、抽象方法的数量不一样:
匿名内部类:所需的接口中抽象方法的数量随意。
Lambda:表达式所需的接口只能有一个抽象方法。
3、实现原理不同:
匿名内部类:在编译后会形成class。
Lambda:在程序运行的时候动态生成class。
JDK8以前的接口:
interface 接口名 {
静态常量;
抽象方法;
}
JDK8的接口:
interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法;
}
接口中的默认方法实现类不需要重写,可以直接使用,实现类也可以根据需要重写。这样就方便接口的扩展。
interface 接口名 {
public default void method(){
//代码
}
}
public interface Swimmable {
public default void eat(String name){
System.out.println(name + "在吃饭");
}
}
class SwimmableImpl implements Swimmable{
}
public class TestLambda2 {
public static void main(String[] args) {
Swimmable sw = new SwimmableImpl();
sw.eat("马尔克斯");
}
}
interface 接口名 {
修饰符 static 返回值类型 方法名(){
//代码
}
}
public interface Swimmable {
public static void eat(String name){
System.out.println(name + "在吃饭");
}
}
class SwimmableImpl implements Swimmable{
}
public class TestLambda2 {
public static void main(String[] args) {
Swimmable.eat("马尔克斯"); //接口的静态方法只能用接口名调用
}
}
1、默认方法通过实例调用,静态方法通过接口名调用。
2、默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
3、静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名调用。
他们主要在java.util.function包中。下面是最常用的几个接口:
供给型接口,对应的Lambda表达式需要“对外提供”一个符合泛型类型的数据对象。
@FuncationalInterface
public interface Supplier {
public abstract T get();
}
实例:返回数组元素最大值
public class TestLambda2 {
public static void main(String[] args) {
int[] arr = {5,8,1,20,2,17};
printMax(() -> {
Arrays.sort(arr);
return arr[arr.length - 1];
});
}
public static void printMax(Supplier supplier){
int max = supplier.get();
System.out.println(max);
}
}
与Supplier接口正好相反,它不是生产一个数据,而是消费一个数据。
@FuncationalInterface
public interface Consumer {
public abstract void accept(T t);
}
实例:将一个字符串转成大写和小写的字符串
public class TestLambda2 {
public static void main(String[] args) {
printHello((String str) -> {
System.out.println(str.toUpperCase());
});
}
public static void printHello(Consumer consumer){
consumer.accept("hello");
}
}
java.util.function.Function
@FuncationalInterface
public interface Function {
public abstract R apply(T t);
}
实例:将字符串转成数字
public class TestLambda2 {
public static void main(String[] args) {
getNumber((String str) -> {
Integer i = Integer.parseInt(str);
return i;
});
}
public static void getNumber(Function function){
Integer num = function.apply("10");
System.out.println(num);
}
}
有时候我们需要对某种类型进行判断,从而得到一个boolean值结果。
@FuncationalInterface
public interface Predicate {
public abstract boolean test(T t);
}
实例1:判断一个人名如果超过3个字就认为是很长的名字
public class TestLambda2 {
public static void main(String[] args) {
isLongName((String name) -> {
return name.length() > 3;
});
}
public static void isLongName(Predicate predicate){
boolean b = predicate.test("马尔克斯");
System.out.println(b);
}
}
实例2:判断一个字符串中包含W,也包含H
public class TestLambda2 {
public static void main(String[] args) {
exercise((String str) -> {
return str.contains("H");
},(String str) -> {
return str.contains("W");
});
}
public static void exercise(Predicate p1,Predicate p2){
boolean b1 = p1.test("Hello");
boolean b2 = p2.test("World");
if(b1 && b2){
System.out.println("既包含W,也包含H");
}
}
}
使用and可以将代码改写为:
public class TestLambda2 {
public static void main(String[] args) {
exercise((String str) -> {
return str.contains("H");
},(String str) -> {
return str.contains("W");
});
}
public static void exercise(Predicate p1,Predicate p2){
String str = "HelloWorld";
boolean b = p1.and(p2).test(str);
if(b)
System.out.println("既包含W,也包含H");
}
}
public class Y {
public static void main(String[] args) {
//使用方法引用
//这个指定的方法getMax去重写接口的抽象方法accept,到时候调用接口的抽象方法就是调用传递过去的方法
printMax(Y::getMax);
}
public static void getMax(int[] arr){
int sum = 0;
for (int n : arr){
sum += n;
}
System.out.println(sum);
}
public static void printMax(Consumer consumer){
int[] arr = {11,22,33,44,55};
consumer.accept(arr);
}
}
1、对象::方法名
2、类名::静态方法
3、类名::普通方法
4、类名::构造器
5、String[]::数组构造器
//正常使用Lambda
@Test
public void test01(){
Date now = new Date();
Supplier su1 = () -> {
return now.getTime();
};
Long aLong = su1.get();
System.out.println(aLong);
}
//对象引用Lambda
@Test
public void test02(){
Date now = new Date();
Supplier su2 = now::getTime;
Long aLong = su2.get();
System.out.println(aLong);
}
注意:你调用方法的参数和返回值要和接口中抽象方法的返回值和参数要一致,比如上述代码,getTime()方法参数是无参,返回值是Long类型,那我们接口中的get()方法也是一样的。
@Test
public void test01(){
Supplier sup = System::currentTimeMillis;
Long aLong = sup.get();
System.out.println(aLong);
}
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。
当调用的方法无参时:
@Test
public void test01(){
Function fun = String::length;
Integer length = fun.apply("Hello");
System.out.println(length);
}
当方法有一个参数时:
@Test
public void test02(){
BiFunction fun = String::substring;
String result = fun.apply("HelloWorld", 3);
System.out.println(result);
}
由于构造器的名称与类名完全一样,所以构造器引用使用类名称::new的格式表示。
@Test
public void test01(){
Supplier sup = Person::new;
Person person = sup.get();
System.out.println(person);
}
数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。
@Test
public void test01(){
Function fun = int[]::new;
int[] apply = fun.apply(5);
System.out.println(apply);
}