Java匿名类及Lambda表达式详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Apple_hzc/article/details/82761767

一、初识Lambda

有这样一个情景:

定义一个类Hero,包括age和life两个属性:

class Hero{
	public int life = 0;
	public int age = 0;
	public Hero(int life,int age){
		this.life = life;
		this.age = age;
	}
}

现在创建一个ArrayList来存放20个随机生成age和life的Hero,并添加到herolist中,并将age大于20且life小于800的Hero找出,代码如下:

public class Test{
    public static void main(String[] args){
        ArrayList<Hero> herolist = new ArrayList<Hero>();
        Random random = new Random();
        for(int i = 0;i < 20;i++){
            herolist.add(new Hero(random.nextInt(1000), random.nextInt(20)));
        }
        checkhero(herolist); //通过普通方法来过滤,筛选出满足条件的数据
    }

    private static void checkhero(ArrayList<Hero> list) {
		// TODO Auto-generated method stub
		for(Hero hero : list){
			if(hero.age >= 15 && hero.life <= 800){
				System.out.println("hero:" + hero.age + " " + hero.life);
			}
		}
	}
}


匿名类方式

匿名类是不能有名字的类,它们不能被引用,只能在创建时用New语句来声明它们。匿名类的声明是在编译时进行的,实例化在运行时进行。

匿名类的目的是在某个地方需要特殊的实现,因此在该处编写其实现,并获取它的实例,调用它的方法。不要在匿名内部类编写其他的方法,是不可见的。形式为:new <类或接口> <类的主体>

对于本文中的情景,首先准备一个接口filter,提供一个test(Hero)方法,然后通过匿名类的方式,实现这个接口:

public class Test{
    public static void main(String[] args){
        ArrayList<Hero> herolist = new ArrayList<Hero>();
        Random random = new Random();
        for(int i = 0;i < 20;i++){
            herolist.add(new Hero(random.nextInt(1000), random.nextInt(20)));
        }
        filter f = new filter(){
			@Override
			public boolean test(Hero hero) {
				// TODO Auto-generated method stub
				return hero.age >= 15 && hero.life <= 800;
			}
			
		};
        checkhero(herolist, f); //使用匿名类过滤
    }

    private static void checkhero(ArrayList<Hero> list) {
		// TODO Auto-generated method stub
		for(Hero hero : list){
			if(hero.age >= 15 && hero.life <= 800){
				System.out.println("hero:" + hero.age + " " + hero.life);
			}
		}
	}
}

interface filter{
	public boolean test(Hero hero);
}

接着调用filter,传递这个f进去进行判断,这种方式就很像通过Collections.sort在对一个集合排序,需要传一个Comparator的匿名类对象进去一样。

Lambda方式

使用Lambda方式筛选数据:

checkhero(herolist,h->h.age >= 15 && h.life <= 800); //Lambda表达式

让我们一步一步来理解怎样从匿名类演变成Lambda表达式:

  1. 匿名类的正常写法:
    filter f = new filter(){
        @Override
    	public boolean test(Hero hero) {
    	// TODO Auto-generated method stub
    		return hero.age >= 15 && hero.life <= 800;
    	}		
    };
  2. 将外壳去掉,只保留方法参数和方法体,参数和方法体之间加上->符号:

    filter t = (Hero h) ->{
    	return h.age >= 15 && h.life <= 800;
    };
  3. 把return和{}都去掉:

    filter t = (Hero h)->h.age >= 15 && h.life <= 800;
  4. 把参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号):

    filter t = h->h.age >= 15 && h.life <= 800;
  5. 把t作为参数传递进去,或者直接将Lambda表达式传递进去:

    checkhero(herolist,t; //将t作为参数传递进去
    checkhero(herolist,h->h.age >= 15 && h.life <= 800); //直接传递Lambda表达式

二、不同方法的引用

引用静态方法:

首先在Test类中添加一个静态方法:

public static boolean testHero(Hero h){
	return h.age >= 15 && h.life <= 800;
}

然后在Lambda表达式中调用这个静态方法,或者直接引用静态方法:

checkhero(herolist, h->JavaSenior.testHero(h)); //在Lambda表达式中使用静态方法
checkhero(herolist, JavaSenior::testHero); //直接引用静态方法,如果是Test类中的对象方法,则应形如 “ 对象::对象方法 ”

引用容器中的对象方法:

首先为Hero添加一个方法:

class Hero{
	public int life = 0;
	public int age = 0;
	public Hero(int life,int age){
		this.life = life;
		this.age = age;
	}
	public boolean match(){
		return this.age >= 15 && this.life <= 800;
	}
}

然后在Lambda表达式中调用容器中对象Hero的方法match:

checkhero(herolist, h->h.match()); //Lambda表达式中调用容器中的对象的match方法
checkhero(herolist, Hero::match); //原理同上

三、聚合操作

传统方式与聚合操作方式遍历数据

private static void checkhero(ArrayList<Hero> list) { //传统方式遍历与筛选
	// TODO Auto-generated method stub
	for(Hero hero : list){
		if(hero.age >= 20 && hero.life <= 800){
			System.out.println("hero:" + hero.age + " " + hero.life);
		}
	}
}
herolist //聚合方式过滤与筛选
		.stream()
		.filter(h->h.age >= 15 && h.life <= 800)
		.forEach(h->System.out.println(h.age + " " + h.life));

Stream和管道的概念

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

管道又分3个部分:

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

注: 这里的Stream和I/O章节中的InputStream,OutputStream是不一样的概念。

管道源

集合可直接调用stream()转换成管道源,数组可通过Arrays.stream(数组名)或者Stream.of(数组名)转换成管道源:

public class Test{
 
    public static void main(String[] args) {
        Random r = new Random();
        ArrayList<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
        .stream()
        .forEach(h->System.out.println(h.age + " " + h.life));
         
        //管道源是数组
        Hero[] hs = herolist.toArray(new Hero[herolist.size()]);
		Arrays.stream(hs)
        .forEach(h->System.out.println(h.age + " " + h.life));
         
    }
}

中间操作

每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
中间操作比较多,主要分两类:

  1. 对元素进行筛选:filter-匹配
                                distinct-去除重复(根据equals判断)
                                sorted-自然排序
                                sorted(Comparator<T>)-指定排序
                                limit-保留
                                skip-忽略
  2. 转换为其他形式的流:mapToDouble-转换为double的流
                                       map-转换为任意类型的流
public class Test{

    public static void main(String[] args) {

        Random r = new Random();
        ArrayList<Hero> heros = new ArrayList<Hero>();
        for (int i = 0; i < 5; i++) {
            heros.add(new Hero(r.nextInt(1000), r.nextInt(100)));
        }
        //制造一个重复数据
        heros.add(heros.get(0));
        System.out.println("初始化集合后的数据 (最后一个数据重复):");
        System.out.println(heros);

        System.out.println("满足条件age >= 15 && life <= 800的数据");
        heros
            .stream()
            .filter(h->h.age >= 15 && h.life <= 800)
            .forEach(h->System.out.print(h));
          
        System.out.println("去除重复的数据,去除标准是看equals");
        heros
            .stream()
            .distinct()
            .forEach(h->System.out.print(h));

        System.out.println("按照age排序");
        heros
            .stream()
            .sorted((h1,h2)->h1.age >= h2.age?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::getLife)
            .forEach(h->System.out.println(h));
          
        System.out.println("转换任意类型的Stream");
        heros
            .stream()
            .map((h)->h.age + " - " + h.life)
            .forEach(h->System.out.println(h));
    }    
}

class Hero{
	public int life = 0;
	public int age = 0;
	public Hero(int life,int age){
		this.life = life;
		this.age = age;
	}
	public double getLife(){
		return this.life;
	}
	public String toString(){
		return "Hero [age =" + this.age + ", life =" + this.life + "]\r\n";
	}
}

结束操作

当进行结束操作后,流就被使用“光”了,所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回。结束操作才真正进行遍历行为,前面的中间操作也在这个时候,才真正的执行。


常见结束操作如下:forEach()-遍历每个元素
                               toArray()-转换为数组
                               min(Comparator<T>)-取最小的元素
                               max(Comparator<T>)-取最大的元素
                               count()-总数
                               findFirst()-第一个元素

猜你喜欢

转载自blog.csdn.net/Apple_hzc/article/details/82761767