1.为什么使用lambda表达式
“lambda表达式”是一段可以传递的代码,因此它可以被执行一次或多次。
形如以下代码块:
class Worker implements Runnable{
@Override
public void run() {
System.out.println("This is Worker!!!");
}
}
class LengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
当我们想执行Worker类的代码时需要先构建Worker类的实例,然后启动线程或者提交到线程池中:
Worker worker = new Worker();
new Thread(worker).start();
当我们想要对字符串数组按照长度进行排序时,可以将LengthComparator对象传递给Arrays的sort方法:
String[] strings = {"333", "1", "22"};
Arrays.sort(strings, new LengthComparator());
System.out.println(Arrays.stream(strings).collect(Collectors.toList()));
sort方法会一直调用compare方法对数组元素进行排序,知道数组完全有序为止。
以上两个例子的共同点:一段代码被传递给调用者(一个线程或者一个排序方法),这段代码随后会被调用。
这就是lambda表达式出现的原因,正如开头说的“lambda表达式”是一段可以被传递的代码。
2.lambda表达式的语法
(参数...)—> { 表达式 }
lambda可以有如下多种变形:
//无参数
new Thread(()-> System.out.println("This is Worker!!!")).start();
//一个参数
Consumer<String> con2 = (String s1) -> {System.out.println(s1);};
con2.accept("我是lambda");
//省略参数类型
/**
* Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
* 类型推断:Lambda 表达式中的参数类型都是由编译器推断 得出的。 Lambda 表达式中无需指定类型,程序依然可 以编译,
* 这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。 Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。
* 这就是所谓的 “类型推断”
**/
Consumer<String> con1 = (s) -> { System.out.println(s); };
con1.accept("我是lambda");
//一个参数,省略参数类型和小括号
Consumer<String> con1 = s -> { System.out.println(s); };
con1.accept("我是lambda");
//需要两个参数(两个参数及以上必须用小括号),并且有返回值
Comparator<Integer> com1 = (l1,l2) -> {
System.out.println(l1);
System.out.println(l2);
return l1.compareTo(l2);
};
//当 Lambda 体只有一条语句时,return 与大括号可以省略
Comparator<Integer> com1 = (l1, l2) -> l1.compareTo(l2);
3.函数式接口
java中有很多已有的接口都需要封装代码块,例如Runnable、Comparator等,lambda表达式与这些接口是向后兼容的。
java8引入了函数式接口的概念,即只包含一个抽象方法的接口,被称为函数式接口,这种接口可以通过lambda表达式来创建该接口的对象。
一般我们会在函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
java8中函数式接口示例
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
@FunctionalInterface
public interface Comparator<T> {
//唯一的抽象方法
int compare(T o1, T o2);
//父类Object类的方法,不是抽象方法,因为它们已经实现了。
boolean equals(Object obj);
//默认方法
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
//默认方法
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
......
}
说明:Object是所有类的默认父类,也就是说任何对象都会包含Object里面的方法,即使是函数式接口的实现,也会有Object的默认方法,所以,重写Object中的方法,不会计入接口方法中,除了final不能重写的,Object中所能重写的方法,写到接口中,不会影响函数式接口的特性。
从中我们知道:
- 一个函数式接口有且只有一个抽象方法。
- 默认方法不是抽象方法,因为它们已经实现了。
- 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。
所以虽然Comparator接口中有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口。
可以说lambda表达式就是为函数式接口服务的。
@FunctionalInterface public interface Demo { public void test(String str); } public class DemoTest { @Test public void main() { Demo demo = new Demo() { @Override public void test(String str) { System.out.println(str); } }; demo.test("匿名内部类"); Demo demo1 = str -> System.out.println(str); demo1.test("lambda表达式"); } }
lambda表达式与匿名内部类的区别:
匿名内部类需要写抽象方法的方法名等内容
而lambda只要写清抽象方法的参数和方法体即可
4.方法的引用
有些时候,想要传递给其他代码的操作已经有了实现的方法,此时可以使用lambda表达式的方法引用简化编码。
创建了函数式接口的匿名内部类对象
重写了函数式接口的抽象方法并在重写的方法中调用被引用的方法
通俗的说,就是用lambda创建了函数式接口的实现类对象,正好lambda要写的抽象体是其他方法的方法体
格式如下:
对象::实例方法
类::静态方法
类::实例方法
举例:
List<User> list = DataExample.getList();
//改造前
list.forEach(user -> System.out.println(user));
//改造后
list.forEach(System.out::println);
String[] str = {"22", "11", "33", "77", "55"};
//改造前
Arrays.sort(str, (str1,str2) -> str1.compareToIgnoreCase(str2));
//改造后
Arrays.sort(str, String::compareToIgnoreCase);
静态方法的引用
//函数式接口
@FunctionalInterface
public interface Demo {
public void test(String str);
}
//函数式接口
@FunctionalInterface
public interface Demo2 {
public int test(String str);
}
/**
* 静态方法引用
* 格式 : 类名::方法名
* 注意事项:
* 被引用的方法参数列表和函数式接口中抽象方法的参数一致
* 接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
* 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值
* 下面两个例子中,分别调用System.out类的println静态方法,Integer类的parseInt静态方法
* 由于满足抽象参数列表与引用参数列表相同,所以可以写成静态方法引用的格式
**/
@Test
public void main1(){
//改造前
printString("静态方法引用,无返回值", (str) -> System.out.println(str));
//改造后
printString("静态方法引用,无返回值", System.out::println);
//静态方法引用,有返回值。改造前
System.out.println(printString2("111", (str) -> Integer.parseInt(str)));
//静态方法引用,有返回值。改造后
System.out.println(printString2("111", Integer::parseInt));
}
public static void printString(String str, Demo d) {
d.test(str);
}
public static int printString2(String str, Demo2 d) {
return d.test(str);
}
对象方法的引用
//函数式接口
@FunctionalInterface
public interface Demo {
public void test(String str);
}
//函数式接口
@FunctionalInterface
public interface Demo3 {
public String run(String str);
}
/**
* 对象方法引用
* 格式:对象名::非静态方法名
* 注意事项与静态方法引用完全一致
*
* Person类中有一个方法go(String name)方法体是Demo实现类对象需要的方法体且方法列表参数一致,返回值类型相同
* Person类中有一个方法run(String name)方法体是Demo3实现类对象需要的方法体且方法列表参数一致,返回值类型相同
* 则可以利用lambda创建Demo和Demo3的实现类对象,然后重写的抽象方法体就是调用Person对象的go方法和run方法
* 符合对象引用方法的所有要求,则可以写成如下样式
*
**/
@Test
public void main2(){
//无返回值
Demo demo = new Demo() {
@Override
public void test(String str) {
System.out.println(str);
}
};
Demo demo1 = (str) -> System.out.println(str);
Demo demo2 = new Person()::go;
demo2.test("demo");
//有返回值
Demo3 demo3 = (str) -> str.concat(" 引用方法");
Demo3 demo31 = new Person()::run;
System.out.println(demo31.run("demo"));
}
构造方法引用
@FunctionalInterface
public interface Demo4 {
public Person run(String str);
}
public class Person {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public void go(String name){
System.out.println(name + "走起来");
}
public String run(String name){
return name.concat(" 引用方法");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
/**
* 构造方法引用
* 格式:类名::new
* 注意事项:被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致
*
* 由于函数式接口Demo4中抽象方法,返回值是Person对象,且参数列表与Person类中的有参构造方法相同
* 则可以通过创建函数式接口的实现类对象,方法体通过调用类中的有参构造方法创建对象
* 使用了有参构造方法引用写成了代码中如下demo41的形式
*
**/
@Test
public void main3(){
//改造前
Demo4 demo4 = (str) -> new Person(str);
demo4.run("demo4");
//改造后
Demo4 demo41 = Person::new;
System.out.println(demo41.run("demo41"));
}
数组构造方法引用
@FunctionalInterface
public interface Demo5 {
public String[] test(int length);
}
/**
* 数组构造方法引用
* 格式:数据类型[ ]::new
**/
@Test
public void main4(){
Demo5 demo5 = (length) -> {return new String[length];};
Demo5 demo51 = String[]::new;
System.out.println(demo51.test(5).length);
}
特定类型的方法引用
/**
* 特定类型的方法引用
* 格式:类名::非静态方法
* 特定类型方法引用,在Comparator函数式接口的抽象方法中传入的参数有两个,可是compareToIgnoreCase()方法参数只有一个,第一个传入的参数作调用对象
* 这就满足了特定类型的方法引用,所以可以简化成类名::非静态方法的形式
**/
@Test
public void main5(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"1111","5555","3333","2222");
//改造前
Collections.sort(list,(string1,string2)->string1.compareToIgnoreCase(string2));
//改造后
Collections.sort(list,String::compareToIgnoreCase);
System.out.println(list);
}
类中方法调用父类或本类方法引用
@FunctionalInterface
public interface Demo {
public void test(String str);
}
public class Person {
String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public void go(String name){
System.out.println(name + "走起来");
}
public String run(String name){
return name.concat(" 引用方法");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
public class User extends Person {
public void go(String name){
System.out.println(name + "跑起来");
}
public void test(){
//调用自身的方法
Demo demo = str -> this.go(str);
Demo demo1 = this::go;
demo1.test("demo1 ");
//调用父类的方法
Demo demo2 = str -> super.go(str);
Demo demo3 = super::go;
demo3.test("demo3 ");
}
}
/**
* 类中方法调用父类或本类方法引用
* 格式:
* this::方法名
* super::方法名
*
* 在有继承关系的类中,若方法想调用本类或父类的成员方法
* 在函数式接口抽象方法与成员方法参数列表相同,且返回值类型相同的情况下也可以使用this和super的方法引用来简写原本的lambda代码
**/
@Test
public void main6(){
User user = new User();
user.test();
}
5.变量作用域
6.默认方法
函数式接口的默认方法由default关键字修饰,且默认方法有具体的实现。
默认方法终结了以前的一种经典,即提供一个接口,以及一个实现接口的大多数或全部方法的抽象类,例如Collection/AbstractCollection。现在只需要在接口中利用默认方法来实现那些方法。
例如:
Collection<E> extends Iterable<E>java.lang.Iterable#forEach
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
默认方法会带来一个问题:
如果一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法,该选择哪个呢?
Java中的规则是这样的:1.选择父类中的方法。如果一个父类提供了具体的实现方法,那么接口中具有相同名称和参数的默认方法会被忽略。
2.接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数类型的方法(不管该方法是否是默认方法),那么你必须通过覆盖该方法来解决冲突。
如下:
Demo8实现Demo6、Demo7接口,由于都有getName方法,出现冲突,所以需要子类重写getName方法解决冲突。
@FunctionalInterface
public interface Demo6 {
void test();
default String getName(String name){
return "Demo6 我是:" + name;
}
}
public interface Demo7 {
default String getName(String name){
return "Demo7 我是:" + name;
}
}
public class Demo8 implements Demo6,Demo7 {
@Override
public void test() {
System.out.println(111);
}
@Override
public String getName(String name) {
return Demo6.super.getName(name);
}
}
如下:
Demo9继承Demo61类、实现Demo6接口,虽然Demo61和Demo6都有getName方法, 但是Demo61是类,所以在Demo9未重写getName方法的前提下,只有父类Demo61下的getName方法起作用,接口中的getName默认方法会被忽略,这就是所谓的“类优先原则”。
@FunctionalInterface
public interface Demo6 {
void test();
default String getName(String name){
return "Demo6 我是:" + name;
}
}
public class Demo61 {
/*public void test(){
System.out.println("demo61");
}*/
public String getName(String name){
return name;
}
}
public class Demo9 extends Demo61 implements Demo6 {
@Override
public void test() {
System.out.println("Demo9");
}
}
小心:
你不能为Object中的方法重新定义一个默认方法。例如,你不能定义一个默认方法toString或者equals,即使这对于List这样的接口很有吸引力。“类优先”规则的结果是,这样的方法永远不可能优先于Object.toString或者Object.equals.
7.接口中的静态方法
接口中的静态方法很简单,用static关键字声明即可。
注意:不能同时使用default和static关键字修饰方法。
实现函数式接口的接口,如果又加了新的抽象方法,那么子类中父类的这个接口就不再是函数式接口