文章目录
lambda表达式与流处理
lambda表达式简介
lambda表达式可以用非常少的代码实现抽象方法。lambda表达式不能独立运行,因此必须实现函数式接口,并且返回一个函数式接口对象。
// 语法格式如下
()->结果表达式 // 无参方法
参数 -> 结果表达式 // 只有一个参数方法
(参数1,参数2,……,参数n) -> 结果表达式 // 实现多个参数方法
lambda表达式也可以实现复杂方法,右侧表达式换成代码块就可以了
() -> { 代码块 }
参数 -> { 代码块 }
(参数1,参数2,……,参数n) ->{ 代码块 }
lambda表达式的语法非常抽象,但有着很强大的自动化功能,如自动识别泛型、自动数据类型转换等。
总结:
() -> { 代码块 }
这个方法 按照 这样的代码来实现
lambda表达式实现函数接口
函数式接口
函数式接口指的是仅包含一个抽象方法的接口,接口中的方法简单明了地说明了接口的用途。
// 例子
interface MyInterface{
void method();
}
lambda表达式实现无参抽象方法
很多函数式接口的抽放方法是无参的,如run()
// 使用lambda表达式实现打招呼接口
interface SayHiInterface{
String say();
}
public class NoParamterDemo {
public static void main(String[] args) {
// lambda 表达式实现打招呼接口,返回抽象方法结果
SayHiInterface pi = ()->"你好啊,这是lambda表达式";
System.out.println(pi.say());
}
}
lambda表达式实现有参抽象方法
// 使用lambda表达式实现加法计算
interface AdditionInterface{
int add(int a,int b);
}
public class Paramter {
public static void main(String[] args) {
// lambda表达式实现加法接口,返回相加的值
AdditionInterface np = (x,y) -> x + y;
int result = np.add(15,26);
System.out.println("相加的结果为" + result);
}
}
lambda表达式使用代码块
// 使用lambda表达式为考试成绩分类
interface CheckGrade{
String check(int grade);
}
public class GradeDemo {
public static void main(String[] args) {
CheckGrade g = (n) -> {
if(n >= 90 &&n <= 100)
return "成绩为优";
else if(n >= 80 && n <= 90)
return "成绩为良";
else if(n >= 60 && n <= 80)
return "成绩为中";
else
return "成绩为差";
};
System.out.println(g.check(89));
}
}
lambda表达式调用外部变量
lambda表达式除了可以调用定义好的参数,还可以调用表达式以外的变量。
注意这些变量的值有些可以被修改,有些却不能被修改。例如:lambda表达式不能修改局部变量的值,可以修改外部成员变量的值。
lambda表达式子无法更改局部变量
// 使用lambda表达式修改局部变量(不行)
interface VariableInterface1 {
void method();
}
public class VariableDemo1 {
public static void main(String[] args) {
int value = 100;
VariableInterface1 v = () -> {
// 程序报错
// int num = value - 90;
// value = 12;
};
}
}
lambda表达式可以修改成员变量
// 使用lambda表达式修改类成员变量
interface VariableInterface2{
void method();
}
public class VariableDemo2 {
int value = 100;
public void action(){
VariableInterface2 v = () -> {
value = -12;
};
System.out.println("运行接口方法之前value=" + value);
v.method();
System.out.println("运行接口方法之后value=" + value);
}
public static void main(String[] args) {
VariableDemo2 demo = new VariableDemo2();
demo.action();
}
}
lambda表达式与异常处理
很多接口的抽象方法为了保证程序的安全性,会在定义时就抛出异常。但是在lamdba表达式中没有抛出异常的语法,因为lambda表达式会默认抛出抽象方法原有的异常。
// 使用lambda表达式实现防沉迷接口
import java.util.Scanner;
interface AntiaddictInterface{ // 防沉迷接口
boolean check(int age) throws UnderAgeException; // 抽象检查方法,抛出用户未成年人异常
}
class UnderAgeException extends Exception{ // 自定义未成年人异常
public UnderAgeException(String message){ // 有参构造方法
super(message); // 调用原有父类构造方法
}
}
public class ThrowExceptionDemo { // 测试类
public static void main(String[] args) {
// lambda表达式创建AntiaddictInterface对象,默认抛出原有异常
AntiaddictInterface ai = (a) -> {
if(a < 18){
throw new UnderAgeException("未满18岁,开启防沉迷模式!");
}else{
return true;
}
};
Scanner sc = new Scanner(System.in); // 创建控制塔扫描器
System.out.println("请输入年龄:"); // 控制台提示
int age = sc.nextInt(); // 获取用户输入年龄
try { // 因为接口方法抛出异常,所以此处必须捕捉异常
if(ai.check(age)){ // 验证年龄
System.out.println("欢迎进入xx世界");
}
} catch (UnderAgeException e) {
System.err.println(e); // 控制台打印异常报告
}
sc.close(); // 关闭扫描器
}
}
方法的引用
引用静态方法
语法如下
类名::静态方法名
例子
// 使用lambda表达式引用静态方法
interface StaticMethodInterface{
int method(int a,int b);
}
public class StaticMethodDemo {
static int add(int x,int y){
return x + y;
}
public static void main(String[] args) {
StaticMethodInterface sm = StaticMethodDemo::add;
int result = sm.method(15,16);
System.out.println("接口方法结果:" + result);
}
}
引用成员方法
语法如下
类名::静态方法名
例子
// 使用lambda表达式引用成员方法
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;
// 有错?
//im = new demo::format;
Date date = new Date();
System.out.println("默认格式:" + date);
//System.out.println("接口输出的格式:" + im.method(date));
}
}
引用带泛型的方法
泛型是java开发经常使用到的功能,“::”操作符支持引用带泛型的方法。除方法外,“::”操作符也支持引用带泛型的类
例子
// 使用lambda表达式引用带泛型的方法
import java.util.HashSet;
interface ParadigmInterface<T>{
int method(T[] t);
}
public class ParadigmDemo {
// 静态方法,使用泛型参数,在方法名之前定义泛型。此方法用于查找数组中的重复元素个数
static public <T> int repeatCoount(T[] t){
int arrayLength = t.length;
java.util.HashSet<T>set = new HashSet<>();
for(T tmp:t){
set.add(tmp);
}
return arrayLength - set.size();
}
public static void main(String[] args) {
Integer a[] = {1,1,2,3,1,5,6,1,8,8};
String s[] = {"王","李","赵","陈","李","孙","张"};
// 有错
// 创建接口对象,Integer作为泛型,引入ParadigmDemo类的静态方法,方法名药定义泛型
//ParadigmInterface<Integer> p1 = new ParadigmDemo::<Integer> repeatCoount;
// 创建接口对象,String作为泛型,引入ParadigmDemo类的静态方法
// 方法名若不定义泛型,则默认使用接口已定义好的泛型
ParadigmInterface<String> p2 = ParadigmDemo::repeatCoount;
System.out.println("字符串数组重复元素个数:" + p2.method(s));
}
}
引用构造方法
引用无参数构造方法
语法如下
类名::new
例子
// 使用lamdba表达式引用无参构造方法
interface ConstructorsInterface1{
ConstructorsInterface1 action();
}
public class ConstructorsDemo1 {
public ConstructorsDemo1(){
System.out.println("调用无参数的构造方法");
}
public ConstructorsDemo1(int i){
System.out.println("调用有参数的构造方法");
}
public static void main(String[] args) {
// 报错
// ConstructorsInterface1 a = ConstructorsDemo1::new;
// ConstructorsDemo1 b = a.action();
}
}
引用有参数的构造方法
例子
// 使用lambda表达式引用有参数的构造方法
interface ConstructorInterface2{
ConstructorInterface2 action(int i);
}
public class ConstructorsDemo2 {
public ConstructorsDemo2(){
System.out.println("调用无参构造方法");
}
public ConstructorsDemo2(int i){
System.out.println("调用有参数的构造方法,参数为:" + i);
}
public static void main(String[] args) {
// 报错
// ConstructorInterface2 a = ConstructorsDemo2::new;
// ConstructorsDemo2 b = a.action(123);
}
}
引用数组构造方法
语法如下
类名[]::new
例子
// 使用lambda表达式引用数组的构造方法
interface ArraysConsInterface<T>{
// 抽象方法返回对象数组,方法参数决定数组个数
T action(int n);
}
public class ArraysConsDemo {
public static void main(String[] args) {
// 引用数组构造方法
ArraysConsInterface<ArraysConsDemo[]> a = ArraysConsDemo[]::new;
ArraysConsDemo array[] = a.action(3);
array[0] = new ArraysConsDemo();
array[1] = new ArraysConsDemo();
array[2] = new ArraysConsDemo();
// 如果调用或给array[3]赋值,代码则会抛出下标越界异常
}
}
Fuction接口
java.utilfunction包已经提供了很多预定义函数式接口,就是没有实现任何功能,仅用来封装lambda表达式对象。该包中最常用的接口是Funtion<T,R>接口。
方法 | 功能说明 | 方法返回值 |
---|---|---|
apply(T t) | 抽象方法。按照被子类实现的逻辑,执行函数。参数为被操作泛型对象 | R |
addThen(Function<? super R,?extends V>after) | 先执行apply(t)方法,将执行结果作为本方法的参数,再按照after函数逻辑继续执行 | (T t) ->after.apply(apply(t)) |
compose(Function<? super V,?extends T>before) | 先按照before函数逻辑操作接口的被操作对象t,再将执行结果作为apply()方法的参数 | (V v) -> apply(before.apply(v)) |
static identity() | 此方法是静态方法。返回一个Function对象,此时对象apply方法只会返回参数值 | t -> t |
例子
// 使用lambda表达式拼接IP地址
import java.util.function.Function;
public class FunctionDemo {
// 创建Function接口对象,参数类型是integer[],返回值类型是String
Function<Integer[],String> function = (n) -> {
StringBuilder str = new StringBuilder();
for(Integer num : n){ // 遍历输出ip地址
str.append(num);
str.append('.');
}
str.deleteCharAt(str.length() - 1);
return str.toString();
};
public static void main(String[] args) {
Integer[] ip = {192,168,1,1};
FunctionDemo demo = new FunctionDemo();
System.out.println(demo.function.apply(ip));
}
}
流处理
流处理语句有点类似数据库的SQL语句,可以执行非常复杂的过滤、映射、查找和收集功能,并且代码很少。唯一缺点是代码可读性不高。
例子
// 创建员工类,并按照表创建初始化数据,并将其全部放入ArrayList列表中
import java.util.ArrayList;
import java.util.List;
public class Employee {
private String name;
private int age;
private double salary;
private String sex;
private String dept;
// 构造方法
public Employee(String name, int age, double salary, String sex, String dept) {
this.name = name;
this.age = age;
this.salary = salary;
this.sex = sex;
this.dept = dept;
}
// 重写toString方法,方便打印员工信息
@Override
public String toString() {
return "name=" + name + ",age=" + age + ",salary=" + salary + ",sex=" + sex + ",dept=" + dept;
}
// 员工get()方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
public String getSex() {
return sex;
}
public String getDept() {
return dept;
}
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接口介绍
流处理接口都在java.util.stream
包下。BaseStream接口是最基础的接口,但是最常用的是BaseStream
接口的一个子接口——Stream
接口是泛型接口,所以流中操作的元素可以使任何类的对象。
Stream类接口的常用方法
方法 | 返回值 | 功能描述 | 类型 |
---|---|---|---|
count() | long | 返回流中元素个数 | 终端操作 |
distinct() | Stream | 去除流中重复元素 | 中间操作 |
filter(Predicte<?super T> predicate) | Stream | 返回一个满足指定条件的流 | 中间操作 |
forEach(Consumer<? super T> action) | void | 遍历流中的每一个元素,执行action动作 | 终端操作 |
limit(long maxSize) | Stream | 获取流中的每一个元素,执行action动作 | 中间操作 |
map(Function<? super T,? extends R>mapper) | Stream | 对流中的元素调用mapper方法,产生包含这些元素的一个新的LongStream流 | 终端操作 |
mapToDouble(ToDoubleFunction<? super T>mapper) | DoubleStream | 对流中的元素调用mapper方法,产生包含这些元素的一个新的DoubleStream流 | 中间操作 |
mapToInt(ToIntFunction<? super T>mapper) | IntStream | 对流中的元素调用mapper方法,产生包含这些元素的一个新的IntStream流 | 中间操作 |
mapToLong(ToLongFunction<? super T>mapper) | LongStream | 对流中的元素调用mapper方法,产生包含这些元素的一个新的LongStream流 | 中间操作 |
max(Comparator<? super T>mapper) | Optional | 根据指定比较器规则,获取流中最大元素 | 终端操作 |
min(Comparator<? super T>mapper) | Optional | 根据指定比较器规则,获取流中最小元素 | 终端操作 |
skip(long n) | Stream | 去除元素中n个元素 | 中间操作 |
sorted() | Stream | 将流中元素排序 | 终端操作 |
sorted(Comparator<? super T> comparator) | Stream | 将流中的元素按照指定比较器规则进行排序 | 终端操作 |
Collection接口
新增两个可以获取流对象的方法
语法如下
Stream<E> stream();
Stream<E> parallelstream();
所有集合类都是Collection接口的子类
List<Integer> list = new ArrayList<Integer>(); // 创建集合
Stream<Integer> s = list.stream(); // 获取集合流对象
Optional类
Optional类像是一个容器,可以保存任何对象。Optional类使用final修饰,所以不能有任何子类。Optional类是带有泛型的类,所以该类可以保存任何对象的值。
Optional类提供的常用方法
方法 | 返回类型 | 功能描述 |
---|---|---|
empty() | Optional | 静态方法。返回一个表示空值的Optional实例 |
filter() | Optional | 如果Optional实例的value是有值的,并且该值与给定的条件匹配,则返回包含这个值的Optional实例,否则返回一个表示空值的Optional实例,否则返回一个表示空值的Optional实例 |
get() | T | 如果Optional实例的value有值,则返回值为任意,否则排除NoSuchElementException |
of(T value) | Optional | 静态方法。返回一个value值等于参数值的Optional实例 |
ofNullable(T other) | Optional | 返回一个value值等于参数值的非null的Optional实例 |
orElse(T other) | T | 如果Optional实例的value是有值的,则返回value值,否则返回参数值 |
例子
// 使用Optional类创建“空”对象
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
Optional<String> strValue = Optional.of("hello"); // 创建有值的对象
boolean haveValueFlag = strValue.isPresent(); // 判断对象中的值是否为空
System.out.println("strValue对象是否有值:" + haveValueFlag);
if(haveValueFlag){
String str = strValue.get();
System.out.println("strValue对象的值是:" + str);
}
Optional<String> noValue = Optional.empty(); // 创建空值对象
boolean noValueFlag = noValue.isPresent(); // 判断对象的值是否为空
System.out.println("noValue对象是否有值:" + noValueFlag);
if(noValueFlag){
String str = noValue.get();
System.out.println("noValue对象的值是:" + str);
}else{
String str = noValue.orElse("使用默认值");
System.out.println("noValue对象的值是:" + str);
}
}
}
Collectors类
Collectors类为搜集器类,该类实现了java.util.collector
接口,可以将Stream流对象进行各种各样的封装、归集、分组等操作。同时,Collectors类还提供了很多实用的数据加工方法,如数据统计计算等。
方法 | 功能描述 |
---|---|
averageDouble(ToDoubleFunction<? super T>mapper) | 计算流元素的平均值 |
averageInt(ToIntFunction<? super T>mapper) | 计算流元素的平均值 |
averageLong(ToLongFunction<? super T>mapper) | 计算流元素的平均值 |
counting() | 统计元素个数 |
maxBy(Comparator<? super T>comparator) | 返回符合条件的最大值元素 |
minBy(Comparator<? super T>comparator) | 返回符合条件的最小值元素 |
summarizingInt(ToIntFunction<? super T>mapper) | 返回流元素的和 |
joining() | 按照顺序将元素连接成一个String类型数据 |
joining(CharSequence delimiter) | 按照顺序将元素连接成一个String类型数据,并指定元素之间的间隔符 |
toList() | 将流中元素封装成一个List集合 |
toMap(Function<? super T,?extends K>keyMapper,Function<?super T,?extends U>valueMapper) | 将流中元素封装成Map集合 |
toSet() | 将流中元素封装成Set集合 |
groupingBy(Function<? super T, ?extends K>classifier) | 根据分类函数对元素进行分组,并将结果封装成一个Map集合 |
groupingBy(Function<? super T, ?extends K>classifier,Collector<?super T,A,D>downstream) | 根据分类函数对元素进行分组,并将结果封装成一个Map集合。一个参数为一级分组条件,第二个参数为耳机分组条件 |
数据过滤
数据过滤见名知意就是在杂乱的数据中筛选出需要的数据,类似SQL语句中的WHERE关键字,就出一定的条件。将符合条件的数据过滤并展示出来。
filter()方法
该方法可以将lambda表达式作为参数,然后按照lambda表达式的逻辑过滤流中的元素。过滤出想要的流元素后,还需使用Stream提供的collect()方法按照指定方法重新封装。
例子
// 输出1~10中所有的奇数
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FilterOddDemo {
static void printeach(String message, List list){
System.out.println(message);
list.stream().forEach(n->{
System.out.print(n + " ");
});
System.out.println();
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i = 1;i < 10;i ++){
list.add(i);
}
printeach("集合原有元素:",list);
Stream<Integer> stream = list.stream();
// 将集合中的所有奇数过滤出来,把过滤结果冲更新赋值给流对象
stream = stream.filter(n -> n % 2 == 1);
List<Integer> result = stream.collect(Collectors.toList());
printeach("过滤之后的集合元素",result);
}
}
distinct()方法
distinct()方法是Stream接口提供的过滤方法。可以去除流中的重复元素,效果类似于SQL语句中的DISTINCT关键字一样。
例子
// 去除List集合中的重复数字
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DistinctDemo {
static void printeach(String message, List list){
System.out.println(message);
list.stream().forEach(n -> {
System.out.print(n + " ");
});
System.out.println();
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(2);
list.add(3);
list.add(3);
printeach("去重前:",list);
Stream<Integer> stream = list.stream();
stream = stream.distinct();
List<Integer> result = stream.collect(Collectors.toList());
printeach("去重后:" , result);
}
}
limit()方法
limit()方法是Stream接口提供的方法,该方法可以获取流中的前n个元素
语法如下
stream = stream.limit(int n);
skip()方法
skip()方法是Stream接口提供的方法,该方法可以忽略流中的前n个元素
语法如下
stream = stream.skip(int n);
数据映射
数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。
用原有集合的一些信息覆盖原集合
数据查找
这里的数据查找并不是在流中获取数据,而是判断是否有符合条件的数据,查找的结果是一个boolean值或者Object对象。
allMatch()方法
allMatch()方法是Stream接口提供的方法,该方法会判断流中的元素是否全部符合某一条件,返回结果是boolean值。如果全部符合则返回true,否则返回false。
语法如下
boolean bool = stream.allMatch(x -> y > z)
anymatch()方法
同上allMatch()方法,不同在于一旦有符合条件的返回true,全部没有返回false、
noneMatch()方法
逻辑和allMatch()方法正好相反
findFirst()方法
该方法会返回符合条件的第一个元素
数据收集
理解为高级的“数据映射”和“数据查找”
一般有两种应用场景:数据统计和数据分组