有这样一个情景:
定义一个类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
herolist = new ArrayList (); 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 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 herolist = new ArrayList();
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 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表达式:
filter f = new filter(){
@Override
public boolean test(Hero hero) {
// TODO Auto-generated method stub
return hero.age >= 15 && hero.life <= 800;
}
};
将外壳去掉,只保留方法参数和方法体,参数和方法体之间加上->符号:
filter t = (Hero h) ->{
return h.age >= 15 && h.life <= 800;
};
把return和{}都去掉:
filter t = (Hero h)->h.age >= 15 && h.life <= 800;
把参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号):
filter t = h->h.age >= 15 && h.life <= 800;
把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 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个部分:
注: 这里的Stream和I/O章节中的InputStream,OutputStream是不一样的概念。
管道源
集合可直接调用stream()转换成管道源,数组可通过Arrays.stream(数组名)或者Stream.of(数组名)转换成管道源:
public class Test{
public static void main(String[] args) {
Random r = new Random();
ArrayList heros = new ArrayList();
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, 中间操作是“懒”操作,并不会真正进行遍历。
中间操作比较多,主要分两类:
public class Test{
public static void main(String[] args) {
Random r = new Random();
ArrayList heros = new ArrayList();
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
max(Comparator
count()-总数
findFirst()-第一个元素