Lamdba(表达式,方法应用,聚合操作)

一、Lambda表达式

1、普通方法

public class Hero implements Comparable<Hero>{
    public String name; 
    public float hp;
       
    public int damage;
       
    public Hero(){
          
    }
      
    public Hero(String name) {
        this.name =name;
  
    }
      
    //初始化name,hp,damage的构造方法
    public Hero(String name,float hp, int damage) {
        this.name =name;
        this.hp = hp;
        this.damage = damage;
    }
  
    @Override
    public int compareTo(Hero anotherHero) {
        if(damage<anotherHero.damage)
            return 1;  
        else
            return -1;
    }
  
    @Override
    public String toString() {
        return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";
    }    
}
// 测试类
public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 10; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("筛选出 hp>100 && damange<50的英雄");
        filter(heros);
    }
  	// 找出血量>100 和 伤害值小于50
    private static void filter(List<Hero> heros) {
        for (Hero hero : heros) {
            if(hero.hp>100 && hero.damage<50)
                System.out.print(hero);
        }
    }
  
}

2、匿名类方式

定义接口

import charactor.Hero;
 
public interface HeroChecker {
    public boolean test(Hero h);
}

测试类

public class TestLambda {
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("初始化后的集合:");
        System.out.println(heros);
        System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");
        // 匿名类方式
        HeroChecker checker = new HeroChecker() {
            @Override
            public boolean test(Hero h) {
                return (h.hp>100 && h.damage<50);
            }
        };
           
        filter(heros,checker);
    }
   
    private static void filter(List<Hero> heros,HeroChecker checker) {
        for (Hero hero : heros) {
            if(checker.test(hero))
                System.out.print(hero);
        }
    }
   
}

3、Lambda方式

使用Lambda方式筛选出数据

filter(heros,(h)->h.hp>100 && h.damage<50);

4、 从匿名类演变成Lambda表达式

Lambda表达式可以看成是匿名类一点点演变过来

1、匿名类的正常写法

HeroChecker c1 = new HeroChecker() {

​ public boolean test(Hero h) {

​ return (h.hp>100 && h.damage<50);

​ }

};

2、把外面的壳子去掉

只保留方法参数方法体
参数和方法体之间加上符号 ->

HeroChecker c2 = (Hero h) ->{

​ return h.hp>100 && h.damage<50;

};

3、把return和{}去掉

HeroChecker c3 = (Hero h) ->h.hp>100 && h.damage<50;

4、把 参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号)

HeroChecker c4 = h ->h.hp>100 && h.damage<50;

5、 把c4作为参数传递进去

filter(heros,c4);

6、直接把表达式传递进去

filter(heros, h -> h.hp > 100 && h.damage < 50);

5、匿名方法

匿名类 概念相比较,
Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。

虽然代码是这么写

filter(heros, h -> h.hp > 100 && h.damage < 50);

但是,Java会在背后,悄悄的,把这些都还原成。引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。

6、Lambda的弊端

Lambda表达式虽然带来了代码的简洁,但是也有其局限性。

  1. 可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解
  2. 不便于调试,很难在Lambda表达式中增加调试信息,比如日志
  3. 版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。

Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。

二、lambda --> 方法引用

1、引用静态方法

首先为TestLambda添加一个静态方法:

public static boolean testHero(Hero h) {

return h.hp>100 && h.damage<50;

}

Lambda表达式:

filter(heros, h->h.hp>100 && h.damage<50);

lambda表达式调用这个静态方法

filter(heros, h->TestLambda.testHero(h));

调用静态方法改写

filter(heros, TestLambda::testHero);

2、引用对象方法

与引用静态方法很类似,只是传递方法的时候,需要一个对象的存在

TestLambda testLambda= new TestLambda();

filter(heros, testLambda::testHero));

3、引用容器中的对象

首先为Hero添加一个方法

public boolean patch(){

return this.hp>100 && this.damage<50;

}

在lambda表达式中调用容器中的对象Hero的方法

filter(heors, h-> h.patch());

patch恰好就是容器中的对象Hero的方法,那就可以进一步改写为

filter(heros, Hero::patch);

3、引用构造器(难理解)

有的接口中的方法会返回一个对象,比如java.util.function.Supplier提供
了一个get方法,返回一个对象

public interface Supplier {

​ T get();

}

设计一个方法,参数是这个接口

public static List getList(Supplier s){

​ return s.get();

}

为了调用这个方法,有3种方式
第一种匿名类:

Supplier s = new Supplier(){

​ public List get(){
​ return new ArrayList();

​ }

};

第二种Lambda表达式

Supplier s = getList(()->new ArrayList());

第三种引用构造器

SUppliser s = getList(ArrayList:: new );

三、lambda - > java集合的聚合操作

1、传统遍历和聚合操作遍历

for循环遍历

for (Hero h : heros) {

if (h.hp > 100 && h.damage < 50)

​ System.out.println(h.name);

}

聚合操作遍历

heros

​ .stream()

​ .filter(h.hp > 100 && h.damage < 50)

​ .forEach(h -> System.out.println(h.name));

2、 Stream和管道的概念

heros

​ .stream()

​ .filter(h.hp > 100 && h.damage < 50)

​ .forEach(h -> System.out.println(h.name));

要了解聚合操作,首先要建立Stream管道的概念
Stream 和Collection结构化的数据不一样,Stream是一系列的元素,就像是生产线上的罐头一样,一串串的出来。
管道指的是一系列的聚合操作。

管道又分3个部分

  • 管道源:在这个例子里,源是一个List
  • 中间操作**: 每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。**
  • 结束操作**:当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回, 结束操作才进行真正的遍历行为,在遍历的时候,才会去进行中间操作的相关判断

3、管道源

把Collection切换成管道源很简单,调用stream()就行了。

heros.stream();

但是数组却没有stream()方法,需要使用

Arrays.stream(heros);

或者

Stream.of(heros);

4、中间操作

每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
中间操作比较多,主要分两类
对元素进行筛选 和 转换为其他形式的流
对元素进行筛选:
filter 匹配
distinct 去除重复(根据equals判断)
sorted 自然排序
sorted(Comparator) 指定排序
limit 保留
skip 忽略
转换为其他形式的流
mapToDouble 转换为double的流
map 转换为任意类型的流

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import charactor.Hero;
 
public class TestAggregate {
 
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        //制造一个重复数据
        heros.add(heros.get(0));
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);
        System.out.println("满足条件hp>100&&damage<50的数据");
         
        heros
            .stream()
            .filter(h->h.hp>100&&h.damage<50)
            .forEach(h->System.out.print(h));
         
        System.out.println("去除重复的数据,去除标准是看equals");
        heros
            .stream()
            .distinct()
            .forEach(h->System.out.print(h));
        System.out.println("按照血量排序");
        heros
            .stream()
            .sorted((h1,h2)->h1.hp>=h2.hp?1:-1)
            .forEach(h->System.out.print(h));
         
        System.out.println("保留3个");
        heros
            .stream()
            .limit(3)
            .forEach(h->System.out.print(h));
         
        System.out.println("忽略前3个");
        heros
            .stream()
            .skip(3)
            .forEach(h->System.out.print(h));
         
        System.out.println("转换为double的Stream");
        heros
            .stream()
            .mapToDouble(Hero::getHp)
            .forEach(h->System.out.println(h));
         
        System.out.println("转换任意类型的Stream");
        heros
            .stream()
            .map((h)-> h.name + " - " + h.hp + " - " + h.damage)
            .forEach(h->System.out.println(h));
         
    }
}

5、结束操作

当进行结束操作后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回,。
结束操作才真正进行遍历行为,前面的中间操作也在这个时候,才真正的执行。
常见结束操作如下:
forEach() 遍历每个元素
toArray() 转换为数组
min(Comparator) 取最小的元素
max(Comparator) 取最大的元素
count() 总数
findFirst() 第一个元素

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;

import charactor.Hero;
 
public class TestAggregate {
 
    public static void main(String[] args) {
        Random r = new Random();
        List<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
        }
        System.out.println("遍历集合中的每个数据");
        heros
            .stream()
            .forEach(h->System.out.print(h));
        System.out.println("返回一个数组");
        Object[] hs= heros
	        .stream()
	        .toArray();
        System.out.println(Arrays.toString(hs));
        System.out.println("返回伤害最低的那个英雄");
        Hero minDamageHero =
        heros
	        .stream()
	        .min((h1,h2)->h1.damage-h2.damage)
	        .get();
        System.out.print(minDamageHero);
        System.out.println("返回伤害最高的那个英雄");

        Hero mxnDamageHero =
                heros
                .stream()
                .max((h1,h2)->h1.damage-h2.damage)
                .get();
        System.out.print(mxnDamageHero);      
        
        System.out.println("流中数据的总数");
        long count = heros
        		.stream()
        		.count();
        System.out.println(count);

        System.out.println("第一个英雄");
        Hero firstHero =
                heros
                .stream()
                .findFirst()
                .get();
        
        System.out.println(firstHero);
        
    }
}

注意: 我尝试了代码一旦结束了聚合,也就是流用光了,数据有回复原来的样子 聚合方式操作集合不是永久性的 ,那么用聚合整理出来的集合就无法用来进行下面的操作,这怎么处理 --> 整理出来的集合, 应该用一个新的集合来接.

List list=heros
        ``.stream()
        ``.sorted((h1,h2) -> h1.damage>h2.damage?``1``:-``1``)
        ``.collect(Collectors.toList());

大部分内容代码来源:来源how2j.cn网站

猜你喜欢

转载自blog.csdn.net/qq_40749830/article/details/106773350