1. lambda表达式
1.1 lambda表达式简述概要
JDK1.8开始之后的新技术,是一种代码的新语法
作用:“核心目的是为了简化匿名内部类的代码写法”。
Lambda表达式的格式:
(匿名内部类被重写方法的形参列表) -> {
被重写方法的方法体代码。
}
-> 就是一个新语法,没有实际含义,但是不能省略!
注意:
- Lambda表达式只能简化接口中只有一个抽象方法的匿名内部类写法。
- 接口中只有一个抽象方法的接口称为函数式接口。
- Lambda只能简化函数式接口的匿名内部类写法。
函数式接口,如
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@FunctionalInterface函数式接口注解:
一旦某个接口加上了这个注解,这个接口只能有且仅有一个抽象方法。
这个接口就可以被Lambda表达式简化。
λ表达式本质上是一个匿名方法
public class Demo04ThreadNameless {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
匿名内部类的好处与弊端:
一方面,匿名内部类可以帮我们省去实现类的定义;
另一方面,匿名内部类的语法——确实太复杂了!
仔细分析该代码中的语义, Runnable 接口只有一个 run 方法的定义:
- public abstract void run();
即制定了一种做事情的方案(其实就是一个函数):
- 无参数:不需要任何条件即可执行该方案。
- 无返回值:该方案不产生任何结果。
- 代码块(方法体):该方案的具体执行步骤。
1.2、Lambda标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda 表达式的 标准格式 为:
(参数类型 参数名称 ) ‐> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
- -> 是新引入的语法格式,代表指向动作。
- 大括号内的语法与传统方法体要求基本一致。
1.3 例子
例子
public static void test02(){
new Thread(
// () -> { System.out.println("run...."); }
() -> System.out.println("run....") //加{}需加;
).start(); ;
}
lambda重写后
new Thread(
() -> System.out.println("run....")
).start(); ;
1.4 练习
需求 :
- 使用数组存储多个Person对象
- 对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序
注意: 使用Lambda表达式时, 接口必须是函数式接口(接口内只能有一个抽象方法)
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// get、set、toString
}
public class ArraysDemo {
public static void main(String[] args) {
Person[] arr = {
new Person("杨幂", 20),
new Person("潘盼盼", 24),
new Person("迪丽热巴", 13)
};
// 使用匿名内部类的方式
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
for (Person person : arr) {
System.out.println(person.toString());
}
System.out.println("______________________");
// 使用Lambda来实现.
Arrays.sort(arr, (Person o1, Person o2) -> {
return o1.getAge() - o2.getAge();
});
// 简化方式. 当抽象方法方法体中只有一条return语句时. 可以省略return
// 若方法体中只有一条语句,那可以省略包含主体的大括号
Arrays.sort(arr, (Person o1, Person o2) -> o1.getAge() - o2.getAge());
for (Person person : arr) {
System.out.println(person.toString());
}
}
}
1.5 Lambda省略格式
1.5.1 省略规则
Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)
(1)如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
(2)如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
(3)参数类型可以省略不写。
(4)如果只有一个参数,参数类型可以省略,同时()也可以省略。
1.6 Lambda的使用前提
Lambda 的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。 无论是JDK内置的 Runnable 、 Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一 时,才可以使用Lambda。
- 使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
public class LambdaDemo01 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("胡伟光");
names.add("甘挺");
names.add("洪磊");
names.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
names.forEach((String s) -> {
System.out.println(s);
});
names.forEach((s) -> {
System.out.println(s);
});
names.forEach(s -> {
System.out.println(s);
});
names.forEach(s -> System.out.println(s) );
// 方法引用!
names.forEach(System.out::println);
}
}
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
1.6.1 方法引用:
方法引用是为了进一步简化Lambda表达式的写法。
方法引用的格式:类型或者对象::引用的方法。
关键语法是:“::”
前面参数与后面一致
public class MethodDemo01 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
lists.forEach( s -> System.out.println(s));
// 方法引用!
lists.forEach(System.out::println);
}
}
1.7 方法引用有四种形式:(了解)
1.静态方法的引用。
2.实例方法的引用。
3.特定类型方法的引用。
4.构造器引用。
1.7.1.静态方法的引用。
引用格式:
类名::静态方法。
小结:
静态方法引用的格式: 类名::静态方法。
重要:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致,才可以引用简化!
public class MethodDemo01 {
public static void main(String[] args) {
List<Student> lists = new ArrayList<>();
Student s1 = new Student("李铭",18,'男');
Student s2 = new Student("冯龙",23,'男');
Student s3 = new Student("王乐乐",21,'男');
Collections.addAll(lists , s1 , s2 , s3);
System.out.println(lists);
Collections.sort(lists, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
// Lambda表达式简化参数二:匿名内部类的Comparator写法!
Collections.sort(lists, (Student o1, Student o2) -> {
return o1.getAge() - o2.getAge();
});
Collections.sort(lists, (Student o1, Student o2) -> o1.getAge() - o2.getAge());
Collections.sort(lists, ( o1, o2) -> o1.getAge() - o2.getAge());
// 使用静态方法进行简化!Student中定义了静态方法compareByAge
Collections.sort(lists, ( o1, o2) -> Student.compareByAge(o1 , o2));
// 如果前后参数是一样的,而且方法是静态方法,既可以使用静态方法引用
Collections.sort(lists, Student::compareByAge);
System.out.println(lists);
}
}
1.7.2. 实例方法的引用
格式:
对象::实例方法
简化步骤:
a.定义一个实例方法,把需要的代码放到实例方法中去。
实例方法引用的注意事项
” 重要:被引用的方法的参数列表要和函数式接口中的抽象方法的参数列表一致。“
public class MethodDemo01 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
// 对象是 System.out = new PrintStream();
// 实例方法:println()
// 前后参数正好都是一个
lists.forEach(s -> System.out.println(s));
lists.forEach(System.out::println);
}
}
1.7.3. 特定类型方法的引用。
特定类型:String ,任何类型。
格式:
特定类型::方法
注意:
如果第一个参数列表中的形参中的第一个参数作为了后面的方法的调用者,并且其余参数作为后面方法的形参,那么就可以用特定类型方法引用了。
public class MethodDemo01 {
public static void main(String[] args) {
String[] strs = new String[]{"James", "AA", "John",
"Patricia","Dlei" , "Robert","Boom", "Cao" ,"black" ,
"Michael", "Linda","cao","after","sBBB"};
// public static <T> void sort(T[] a, Comparator<? super T> c)
// 需求:按照元素的首字符(忽略大小写)升序排序!!!
Arrays.sort(strs, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);// 按照元素的首字符(忽略大小写)比较。
}
});
Arrays.sort(strs, (String s1, String s2) -> {
return s1.compareToIgnoreCase(s2);// 按照元素的首字符(忽略大小写)比较。
});
Arrays.sort(strs, ( s1, s2 ) -> s1.compareToIgnoreCase(s2));
// 特定类型的方法引用:
Arrays.sort(strs, String::compareToIgnoreCase);
System.out.println(Arrays.toString(strs));
}
}
1.7.4 构造器引用。
格式是:
类名::new
注意点:前后参数一致的情况下,又在创建对象就可以使用构造器引用
s -> new Student(s) => Student::new
public class ConstructorDemo01 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
// 集合默认只能转成Object类型的数组。
Object[] objs = lists.toArray();
System.out.println("Object类型的数组:"+ Arrays.toString(objs));
// 我们想指定转换成字符串类型的数组!!
// 最新的写法可以结合构造器引用实现 。
// default <T> T[] toArray(IntFunction<T[]> generator)
String[] strs = lists.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});
String[] strs1 = lists.toArray(s -> new String[s] );
String[] strs2 = lists.toArray(String[]::new);
System.out.println("String类型的数组:"+ Arrays.toString(strs2));
}
}
------------------------------------------------------
1.8 其他
2. λ表达式的类型(它是Object吗?)
λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数式接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数式接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:
@FunctionalInterface
public interface Runnable { void run(); }
public interface Callable<V> { V call() throws Exception; }
public interface ActionListener { void actionPerformed(ActionEvent e); }
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
你可以用一个λ表达式为一个函数式接口赋值:
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
然后再赋值给一个Object:
Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!
必须显式的转型成一个函数式接口才可以:
Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
一个λ表达式只有在转型成一个函数式接口后才能被当做Object使用。所以下面这句也不能编译:
System.out.println( () -> {} ); //错误! 目标类型不明
必须先转型:
System.out.println( (Runnable)() -> {} ); // 正确