lambda表达式

发布于:2025-02-11 ⋅ 阅读:(40) ⋅ 点赞:(0)

xu介绍泛型:

 T  :代表类型String...:统配
 K :表示key()
 V :表示value()
 E :表示entry(实体)

为什么使用lambda表达式

1.lambda 出自于jdk8新特性,是当前最流行的之一。
2.lambda 是一段可以传递的代码,因此它可以被执行。

lambda 使用

1.格式: 参数、箭头 --> {表达式}

类型:
(String first,String second)-->Integer .compare(filst.length(),second.length() ) 
若无参数则为空()->(filst.length()<second.length() )

接受参数需要 为接口函数式接口

若:计算代码无法用表达式 表示,可以 使用编写方法的方式进行编写: 使用{} 并且需要return

(String first,String second) -> {
if(first.length()<second.length())   return "小于";
else if(first.length()=second.length()) return "等于";
else return "大于"
 }

在这里插入图片描述
表达式的参数类型可以被推导:

Comparetor <String> comp =(first,second)(可以推导) -> Integer.compare(first.length(),second.length());

可以在表达式中添加修饰词或者注解

(final String name) ->
(@Nonull String name) ->

2.方法的引用
System.out::println; 等同于 x-> System.out.println(x);
解释一下sout: Systerm 是类名, out是 静态 ,println是方法
“::”此操作符将方法和对象或者类的名字分隔开来有以下几种情况:

  1. 对象 ::实例方法 ==== eg:System.out :: println
  2. 类::静态方法 ==== eg:Math::pow
  3. 类::实例方法 ==== String :: compareToIgnoreCase
  4. 还可以使用this,super::实例方法。
    3.构造器/函数引用
List<String> list=new ArrayList();
list.add("1");
list.add("jqri");

//lambda
List <String> list=list.stream().map(String::new).collent(Collectors.toList());
//遍历的结果 :[1, jqri]

解释:在构造器引用中方法名为 new ,eg :string ::new 表示 String 类的构造器引用,若有多个构造器则会根据上下文进行推断。
4.变量作用域(一些约束)

1.    //变量作用域
        repeatMeaasge("hello",1000);
2.    public static void repeatMeaasge(String text, int count) {
        Runnable runnable =
                () -> {
            for (int i = 0; i < count; i++) {
                System.out.println(text);
                Thread.yield();
               // yield()方法会通知线程调度器放弃对处理器的占用,但调度器可以忽视这个通知。yield()方法主要是为了保障线程间调度的连续性,防止某个线程一直长时间占用cpu资源
            }
        };
        new Thread(runnable).start();
    }

***1.在lambda表达式中,需要遵循约束,“被引用的变量”的值不可以被更改
原因:更改lambda表达式中的变量不是线程安全。假设有一系列并发,每个线程都会更新共享一个计数器。
2.lambda表达式的局部变量(eg:num)可以不用声明为final

//错误写法
    public static void repeatMeaasge(String text, int count) {
        Runnable runnable =
                () -> {
                   while (count>0){
                       count--;  //此时编译会出错
                        System.out.println(text);
                    }
        };
        new Thread(runnable).start();
    }

***在lambda表达式的方法体与嵌套代码块有着相同的作用域。
它不允许声明一个与局部变量同名的参数或者局部变量。

Path first =Path.get("/user/bin");
Comparator<String> comp=(first,second) ->Integer.compare(first.length(),second.length());
//此时会发生错误:变量first已经被定义过了

在表达式中 this.toString()会调用Application 对象的toString()

扩展:
Application的特点:
单例模式创建:每个应用启动时,首先会创建Application对象,并且实例化该对象,Application对象只有一个,全局通用。
生命周期:Application的生命周期与整个App的生命周期相同。

5.默认方法

Lsit <String> list=new ArrayList();
list.add("10");
list.add("100");
//获取数据方式
// 普通
//1
for(int i=0;i<list.size();i++){
sout(list.get(i));
}
//2 
for(String a:list){sout(a); }

//jdk8 
//1 输出结果为数组格式
list.stream().map(String :: new).collent(Collents.toList());

//2 单独输出属性值
list.forEach(System.out::println)

1.“类优先规则”====在表达式中,若用两个方法发生冲突,可以通过一个继承和一个实现来进行解决
它会使用父类中的方法而忽略实现类的冲突方法.
2.若两个接口中都有一个相同的接口方法,则需要重写有自己手动解决.(在重写方法中指明用到的那么方法:接口.super.eat();)
注意:不能为Object中的方法重新定义一个默认方法。
作用:保证与java7的兼容性

eg: class Student extends Person implents Named{......}

6.函数式接口
定义:接口中只包含一个抽象方法,可以使用lambda来创建接口的对象。
可以使用注解@functionalInterface 作用:检测接口是否为函数式接口 使用在接口上
使用:一般可以作为方法的参数和返回值。
接口中可以包含:
1.普通方法(仅有一个)
2.默认方法(default ,可以多个)
3.静态方法(static ,可以多个)

7.引用.stream().map()的几种方式 返回的是list集合
Map(): 需要对一个流中的 值进行某种形式转换,可以考虑使用map. 将数组转换为集合List

//eg:类名  类名 对象引用 =new 类名();
//.stream()之前就是要遍历的数组  

1.  List<对象> list= 对象引用.getName().stream().map(id ->{
//添加表达式
类名 对象引用2 =new 类名();
对象引用2.setName(对象引用.getName());
对象引用2.setId(id);
return 对象引用2;
}).collect(Collectors.toList());

2.  List<String> listByArray = 对象引用.stream().map(类名::getName).collect(Collectors.toList());  //将数组转换为list
 原始写法:  
 list<String> listByArray =new ArrayList();
 for(类名 对象引用: listByArray ){
    //重新修改对象元素
    listByArray.add(对象引用.getName());
  }
 //若是数组  return new ArrayList();

3.  list去重
  对象引用.stream().map(类名::getName).distinct().collect(Collectors.toList());

**8.引用.stream().forEach()的方式 **

//循环遍历
//eg:类名  类名 对象引用 =new 类名();
//.stream()之前就是要遍历的数组
对象引用.getName().stream().forEach(o -> { sout(); 
//还可以为一些处理方法
});
等于:
//forEach增强
for(String o:对象引用.getName() ){
sout();
//处理操作
}

常用的函数式接口

**属于:java.util.function.*
1.Supplier接口(生产数据)
特点: Supplier 中的泛型 添加什么类型 则调用的get()就会返回什么类型。

eg:求最大值
public class StadyApplication {
//函数式接口
public static  int getMax(Supplier <Integer> supplier){
return supplier.get();
} 
//main()
public static void main (String [] args){
//定义一个数组
int [] arr={1,3,4,2,4};
//定义max变量
getMax(()-{
int max =arr[0];
//循环遍历获取最大值
for(int i :arr){
if (i>max){
max=i;
}
}
return max;
});
}
}

2. Consumer 接口(消费数据)
特点:与supplier相反,泛型指定什么类型,就会返回什么类型

public class StadyApplication {
//函数式接口
public static void method(String name,Consumer<String> consumer){ 
consumer.accept(name); //抽象方法:传入一个指定泛型的参数,无返回值
}
//main
public static void main(String [] args){
method("123abc456def",(String name)->{
sout(name);
StringBuilder b=new StringBuilder(name).reverse();
sout(name);  //二者区别name顺序反转
});
}

}

常见函数式接口
在这里插入图片描述

StreamAPI

特点: Stream表面和一个集合类似,允许你改变和获取数据
1.Stream 自己不会存储元素,元素可能被存储在底层的集合中,或者根据需要产生出来。
2.Stream 操作符不会改变源对象。相反,它们会返回一个持有结构的新的Stream。
3.Stream 操作符可能是延迟执行,需要等到获取结果的时候才执行。

long count =words.Stream().filter(w->w.length()>12).count();//普通
long count =words.parallelStream().filter(w->w.length()>12).count();//可以并行执行过滤和统计

Stream 的使用
特点:Stream 是在jdk8 中 Collention 新添加的Stream 方法,可以将任何集合转化为一个Stream 。 eg:数组的转化
创建流程:1.创建stream ----2.stream中间操作 ----3.终止操作

Stream<String> words=Stream.of(contents.split("可以填正则表达式 "));

创建stream
Stream.of(String); —(接受可变长度的参数)
generate():接受一个无参数的函数。

//创建:一个含有随机数字的Stream
Stream<Double> random =Stream.generate(Math::random);

filter.map.flatMap方法
属于中间操作。
流转换:从一个流读取数据,并将转换是数据写入到另一个流中。
filter:过滤器转换。
map何时使用: 需要对一个流中的 值进行某种形式转换,可以考虑使用map.

//将单词转换为小写形式
Stream <String> lowercaseWords= words.map(String ::toLowerCase);

flatMap: 一个函数包含多个值的流 将结果展开为一个包含字符串的流。
提取子流和组合流

Stream.limit(n) ;   //返回一个包含n个元素的新流(若原始流的长度小于n,则会返回原始的流)
//此方法适用于裁剪指定长度的流。 
与
Stream.skip(n); //相反,抛弃前面n个元素


//产生n个随机数字
Stream <double> randoms=Stream.generate(Math::random).limit(100);
//丢弃/截掉第一个字符
//split("") 按照某某进行分割
Stream<String> words =Strema.of(collents.split("\\P[L]")).skip(1);

peek() :产生另一个与原始流具有相同元素的流,每次获取一个元素,都会调用一个函数。

Object[] powers=Stream.iterate(1.0,p -> p*2)
                 .peek(e->sout("fetching"+ e))
                 .limit(20).toArray();   //获取前20条数据,返回数组

有状态的转换

//扩展: 
//distinct() 会根据原始流中的元素返回一个具有相同顺序,抑制重复元素的新流(去重)。
//sorted():返回一个新的已排序的流
//Collentions.sort(); 会对原有的集合进行排序。

无状态:当从一个已过滤或者已映射的流中获取某个元素时,其结果并不依赖之前的元素。
有状态:需要记住已过滤或者已映射的元素。

    //有状态转换测试
    public static void unable(){
        List<String> list =Arrays.asList("x","x","x","a","a","c");
        long a= list.stream().distinct().count();
        System.out.println("获取数量"+a);
        String out =list.stream().distinct().collect(Collectors.joining(","));  //joining()  输出结果 可以按照方法格式进行分割
        System.out.println("有状态的转换"+out);
    }
    //结果:‘x’,'a','c'

聚合方法
定义:将流聚合为一个值,方便在程序中运行。一般都是终止操作。 eg : count() 、max()、min()。

//扩展
// Optional(可选择的)类型 :更好的表示缺少返回值的方式。
//findFirst() :返回非空集合的第一个值,通常与filter()结合起来使用。
 Optional <String> startWithQ =words.filter(s -> s.startsWith("Q")).findFirst();
 //查询开头字母为Q的单词。
//findAny():只要再任何片段中发现第一个匹配元素,都会结束整个整个计算(匹配所有元素)。
Optional <String> startWithQ =words.filter(s -> s.startsWith("Q")).findAny();
//anyMatch() boolean型 :判断是否含有匹配元素。返回true
//noneMatch() boolean型 :判断是否无元素。   返回true
 可以接受一个predicate参数,因此不需要使用filter方法。会检查整个流,但可以通过冰熊执行来提高速度。
Boolean startWithQ =words.parallel().anyMathch(s -> s.startsWith("Q"));

Optional(可选择的)类型 及使用
1.一个泛型对象或者对一个T类型的对象封装,也可以不是任何对象。
2.比一般指向T类型的引用安全。—不会返回null.

isPresent():反映对象是否有值。
ifPresent():一种形式可以接受一个函数,若存在可选值那么会将值传递给函数,否则不会进行任何操作。
optionalValue.ifPresent(v -> Process v);   //使用if语句时
optionalValue.ifPresent(v -> results.add (v)); //做添加操作
//还可以
optionalValue.ifPresent(results::add);

将结果收集到Map
Collectors.toMap(); //有两个函数参数,反别用来生成map的键和值。
toMap()的每种形式,都有一个而对应的toConcurrentMap(),用来产生一个并发的map,在并发map中,一个共享map要比合并map效率更高,但无法得到有序的结果

Map<Integer,String> idToName= people.collect(Collectors.toMap(Person::getId,Person::getName));

分组和分片
对一个map生成一个单独的集合进行分组。
可以使用groupingBy() ,会产生一个值为列表的map对象,可以改变map值的类型,set或者list
使用Collectors.toSet()/Collectors.tolist();

Map<String List<Locale>> countryLocals = (引用)locals.collect(Collectors.groupingBy(Locale::getCountry)); //若使用groupingByConcurrent()会获得一个并发map

//可以通过k值来验证
List<Locale> awissLocales= (上面引用)countryLocals.get(“key”);

java8提供的收集器:

1.counting():返回收集元素的总个数。
Map<String ,Long> countryToLocalCounts = loclaes.collent(groupingBy(Locale::getCountry,counting()));//计算每个国家有多少种语言。

2.summingInt(可以其他类型)() : 接受一个函数作为参数,生成求和;
Map<String,Integer> state = cities.collent(groupingBy(City::getState,sunningInt(City::getPopulation)));//根据城市计算总人口

3.MaxBy()minBy()会接受一个比较器,并且生成downStream元素中的最大值和最小值
//计算城市中最多的人口
Map<String ,City> state = cities.collenct(groupingBy(City::getState,maxBy(Comparator.comparing(City::getPopulation))));

4.mapping()会将一个函数应用到downstream结果上,需要另一个接收器来处理结果。
Map<String,Optional<String>> state = cities.collect(groupingBy(City::getState,mapping(City::getName,maxBy(Comparator.comparing(String::length)))));

原始类型流
用来存储原始类型值,不使用包装。Stream Api提供了IntStream,LongStream等流。

创建IntStram(),可以使用IntStream.of()和Arrays.stream()
IntStream stream1 = IntStream.of(1,12,3,4,5);
int[] values={1,12,3,4,5};
//创建数组的几种方式
1.数组类型 [] 数组名 = new 数组类型[];  //动态创建
2.数组类型 [] 数组名 = {数组0,数组1,...};
3.数组类型 [] 数组名 = new 数组类型{数组0,数组1,...};  //静态创建
IntStream stream2 = Arrays.stream(values,from,to);  //values是一个int[]

对象转换为原始流
使用方法:mapToInt、mapToLong、mapToDouble方法转换为一个原始类型流。

Stream<String> words =...;
IntStream lengths = words.mapToInt(String ::length);
//将原始流转换为一个对象流,使用boxed()
//intStream.range(包含初始值,不包含上限),类似循环,返回 Stream
Stream<Integer> int =IntStream.range(0,100).boxed();

并行流
目的:流使得并行计算变得容易。默认情况下,流操作创建一个串行流。
Collection.parallelStream()除外。parallel():可以将任意的串行流转换为一个并行流。

eg:
Stream<String> words = Stream.of(存放数组).parallel();  //串行转换为并行

可以通过limit()来提高速度,但会放弃有序。