14.1: lambada表达式
lambda表达式简介
lambda表达式可以用非常少的代码实现抽象方法。lambda表达式不能独立执行,因此,必须实现函数式接口,并且会返回一个函数式接口的对象。lambda表达式的语法非常特殊,格式如下。
()->结果表达式数
->结果表达式
(参数1.参数2....,参数n)->结果表达式
14.1.1:lambada表达式简介
package fourteen;
interface SayhHi{
String say();//抽象方法接口
}
public class NoParamDemo {
public static void main(String[] args) {
//无参数
//利用匿名内部类补全方法体
SayhHi sh1 = new SayhHi() {
public String say() {
return "这是匿名内部类";
}
};
System.out.print(sh1.say());
}
}
例题
package fourteen;
interface CheckGrade {
String check(int grade); // 查询成绩结果
}
public class GradeDemo {
public static void main(String[] args) {
CheckGrade g = (n) -> { // lambda实现代码块
if (n >= 90 && n <= 100) { // 如果成绩在90-100
return "成绩为优"; // 输出成绩为优
} else if (n >= 80 && n < 90) { // 如果成绩在80-89
return "成绩为良"; // 输出成绩为良
} else if (n >= 60 && n < 80) { // 如果成绩在60-79
return "成绩为中"; // 输出成绩为中
} else if (n >= 0 && n < 60) { // 如果成绩小于60
return "成绩为差"; // 输出成绩为差
} else { // 其他数字不是有效成绩
return "成绩无效"; // 输出成绩无效
}
}; // 不要丢掉lambda语句后的分号
System.out.println(g.check(89)); // 输出查询结果
}
}
//14.3
2lambda表达式实现函数接口
1、函数式接口
函数式接口指的是仅包含一个抽象方法的接口,接口中的方法简单明了地说明了接口的用途,如线程接口Runnable、动作事件监听接口ActionListener等。开发者可以创建自定义的函数式接口,例如:
interface MyInterface {
void method():
lambda 表达式实现有参抽象方法
抽象方法中有一个或多个参数的函数式接口也是很常见的,lambda表达式中可以用“(al,a2,a3)”的方法表示有参抽象方法,圆括号里标识符对应抽象方法的参数。如果抽象方法中只有一个参数, lambda 表达式则可以省略圆括号。
.lambda 表达式使用代码块
当函数式接口的抽象方法需要实现复杂逻辑而不是返回一个简单的表达式的话,就需要在lambda表达式中使用代码块。lambda表达式会自动判断返回值类型是否符合抽象方法的定义。
14.1.3 lambda 表达式调用外部变量
lambda 表达式除了可以调用定义好的参数,还可以调用表达式以外的变量。但是,这些外部的变量有些可以被更改,有些则不能。例如,lambda表达式无法更改局部变量的值,但是却可以更改外部类的成员变量(也可以叫作类属性)的值。
1. lambda 表达式无法更改局部变量
局部变量在lambda 表达式中默认被定义为final(静态)的,也就是说,lambda表达式只能调用局部变量,却不能改变其值。
14.2方法的引用
lambda 表达式还添加了一类新语法,用来引用方法,也就是说方法也可以作为一个对象被调用。根据不同的方法类型,方法的引用包括引用静态方法、引用成员方法和引用构造方法等。
14.2.1 引用静态方法
引用静态方法的语法如下:类名::静态方法名
这个语法中出现了一个新的操作符“::”,这是由两个英文冒号组成的操作符,冒号之间没有空格。这个操作符左边表示方法所属的类名,右边是方法名。需要注意的是,这个语法中方法名是没有圆括号的。
14.2.2引用成员方法
语法如下:
对象名::成员方法名
操作符左侧必须是一个对象名,不是类名。
14.2.4引用构造方法
lambda 表达式有3种引用构造方法的语法,分别是引用无参构造方法、引用有参构造方法和引用数组构造方法,下面分别进行讲解。
1.引用无参构造方法
引用构造方法的语法如下:
类名::new
因为构造方法与类名相同,如果操作符左右都写类名,会让操作符误以为是在引用与类名相同的静态方法,这样会导致程序出现Bug,所以引用构造方法的语法使用了new关键字。操作符右侧的写 new 关键字,表示引用构造方法。这个语法有一点要注意;ew关键字之后没有圆括号,也没有参数的定义。如果类中既有无参构造方法,又有有参构造方法,使用引用构造方法语法后,究竟哪一个方法被引用了呢?引用哪个构造方法是由函数式接口决定的,“::”操作符会返回与抽象方法的参数结构相同的构造方法。如果找不到参数接口相同的构造方法,则会发生编译错误。
若接口方法无参数,调用的就是无参的构造方法。
package fourteen;
import java.text.SimpleDateFormat;
import java.util.Date;
interface InstanceMethodInterface { // 创建测试接口
String method(Date date); // 带参数的抽象方法
}
public class InstanceMethodDemo {
public String format(Date date) { // 格式化方法
// 创建日期格式化对象,并指定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date); // 返回格式化结果
}
public static void main(String[] args) {
// 创建类对象
InstanceMethodDemo demo = new InstanceMethodDemo();
// 引用类对象的方法
InstanceMethodInterface im = demo::format;
Date date = new Date(); // 创建日期对象
System.out.println("默认格式:" + date); // 输出日期对象默认格式
// 输出经过接口方法处理过的格式
System.out.println("接口输出的格式:" + im.method(date));
}
}//14.8
//调用构造器
package fourteen;
interface ConIn{
ConDemo action();//创建接口,正好是这个类的构造器类型,抽象方法
}
public class ConDemo {
//使用无参的构造方法补全方法体
public ConDemo() {//构造方法要写小括号
System.out.println("无参构造方法");//构造方法
}
public ConDemo(int a) {//构造方法要写小括号
System.out.println("有参构造方法");//构造方法
}
public static void main(String args[]) {
ConIn ci1 = ConDemo:: new;//new一个对象
ci1.action();//调用action方法
//使用有参数的方法来补全方法体
//ConDemo action(int a);
//ConIn ci1 = ConDemo:: new;//new一个对象
//ci1.action(5);//调用action方法
}
}
流:
static List<Employee> getEmpList() { // 提供数据初始化方法
List<Employee> list = new ArrayList<Employee>();
// 添加员工数据
list.add(new Employee("老张", 40, 9000, "男", "运营部"));
list.add(new Employee("小刘", 24, 5000, "女", "开发部"));
list.add(new Employee("大刚", 32, 7500, "男", "销售部"));
list.add(new Employee("翠花", 28, 5500, "女", "销售部"));
list.add(new Employee("小马", 21, 3000, "男", "开发部"));
list.add(new Employee("老王", 35, 6000, "女", "人事部"));
list.add(new Employee("小王", 21, 3000, "女", "人事部"));
return list;
stream接口
package fourteen;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamDemo {
public static void main(String[] args) {
List<Employee> list=Employee.getEmpList();//从employee表拿到list表中的员工表
Stream<Employee> stream = list.stream();//调用list方法将表转换成流
//筛选年龄>30岁的员工
stream=stream.filter(sx-> sx.getAge()>30);//写条件,sx属于临时变量
//限制条数
stream = stream.limit(2);
List<Employee> result = stream.collect(Collectors.toList());//转成List链表
for (Employee sx : result) {//for循环输出结果
System.out.println(sx);
}
}
}
数据分组
package fourteen;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GroupDemo {
public static void main(String[] args) {
// 分组
List<Employee> list=Employee.getEmpList();//从employee表拿到list表中的员工表
Stream<Employee> stream = list.stream();//调用list方法将表转换成员工流
Map<String,List<Employee>> map = //创建map集合存入,String代指dept,list链表指代map集合的value
stream.collect(Collectors.groupingBy(Employee::getDept));//利用流调用collect方法,分组方法,传入方法
Set<String> depts = map.keySet();//获取map集合中的部门表
for(String dept:depts){//for循环一个部门,depts代指部门
System.out.println(dept+"员工信息如下:");//输出信息
List<Employee> temp = map.get(dept);//分组列表输出
for(Employee g : temp){//temp是临时变量
System.out.println(g);//
}
System.out.println();
}
}
}
数据过滤
数据过滤就是在杂乱的数据中筛选出需要的数据,类似SQL语句中的WHERE关键字,给出一定
的条件,将符合条件的数据过滤并 重新 展示出来。
1.filter()方法
filter()方法是Stream接口提供的过滤方法。该方法可以将lambda表达式作为参数,然后按照lambda表达式的逻辑过滤流中的元素。过滤出想要的流元素后,还需使用Stream提供的collect()方法按照指定方法重新封装。
2、distinct()方法
distinet()方法是 Stream接口提供的过滤方法。该方法可以去除流中的重复元素,效果与SQL语句中的 DISTINCT关键字一样。因为 distinct()方法属于中间操作,所以可以配合filter()方法一起使用。
3. limit()方法
limit()方法是Stream接口提供的方法,该方法可以获取流中前N个元素。
4. skip()方法
skip()方法是 Stream接口提供的方法,该方法可以忽略流中的前N个元素。
14.3.5 数据映射
数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。 Stream 接口提供了map0方法用来实现数据映射,map()方法会按照参数中的函数逻辑获取新的流对象,新的流对象中元素类型可能与旧流对象元素类型不相同。
14.3.7 数据收集
数据收集可以理解为高级的“数据过滤+数据映射”,是对数据的深加工。本节将讲解两种实用场景:数据统计和数据分组。
1.数据统计
数据统计不仅可以筛选出特殊元素,还可以对元素的属性进行统计计算。这种复杂的统计操作不是由Stream 实现的,而是由Collectors收集器类实现的,收集器提供了非常丰富的API,有着强大的数据挖掘能力。
2.数据分组
数据分组就是将流中元素按照指定的条件分开保存,类似SQL语言中的“GROUP BY”关键字。分组之后的数据会按照不同的标签分别保存成一个集合,然后按照“键-值”关系封装在Map对象中。
数据分组有一级分组和多级分组两种场景,首先先来介绍一级分组。
一级分组,就是将所有数据按照一个条件进行归类。例如,学校有100个学生,这些学生分布在3个年级中。学生按照年级分成了3组,然后就不再细分了,这就属于一级分组。
Collectors 类提供的 groupingBy()方法就是用来进行分组的方法,方法参数是一个Function接口对象,收集器会按照指定的函数规则对数据进行分组。