【黑马程序员西安校区】Java基础之Stream




1、java基础之Stream
        java8提供了Stream,通过StreamAPI可以简化一些代码的表达方式,但是要学习Stream,必须先来学习Lambda表达式。首先我们要学习的就是Lamdba表达式基础。
1.1、Lamdba表达式的表示方式
        在java中我们通常都是定义单行变量,就好比如下的操作
        
[Java]  纯文本查看  复制代码
?
1
2
3
nt n = 10 ;   
String str = "hello" ;   
boolean b = true ;

        而Lamdba是函数变量,什么是函数变量呢,就等于我们用一个名称来表示一个函数,如下所示
        
[Java]  纯文本查看  复制代码
?
1
2
3
elloFunction = public void sayHello() {   
   System.out.println( "hello world!" );   
}

        对于上面一个函数变量而言,public是访问控制修饰符,在对于函数的变量而言,没有意义,可以省略,之后就成如下的样子
        
[Java]  纯文本查看  复制代码
?
1
2
3
helloFunction = void sayHello(){   
   System.out.println( "hello world!" );   
}

        
对于函数变量而言,我们关心的是函数这个变量的结果,函数的名称就没有任何意义,void表示没有返回值,也没有意义,将这两个删除之后
        
[Java]  纯文本查看  复制代码
?
1
2
3
elloFunction = () {   
   System.out.println( "hello world!" );   
}

        如果这个函数中只有一行代码,我们可以将花括号省略,使用->来替换前花括号,最后就变成了如下的一个样子
        
[Java]  纯文本查看  复制代码
?
1
helloFunction = ()‐>System.out.println( "hello world!" );

        已上就是一个Lamdba表达式的写法,通过这个例子各位应该清楚什么是Lamdba了吧,它其实就是一个函数级别的变量,当然这个代码是无法被编译。在继续讲解之前,我们来看一下其他几个函数的转换问题
        
[Java]  纯文本查看  复制代码
?
1
2
3
blic int add( int a, int b) {   
   return a+b;   
}

        对于以上函数,首先定义一个变量来存储这个函数,然后去掉public和add名称,最后就是返回值的处理,只要直接声明函数的结果是a+b即可
        
[Java]  纯文本查看  复制代码
?
1
addFunction = ( int a, int b)‐>a+b;


1.2、通过java实现Lamdba
        下面我们会通过一个例子来实现一个Lamdba表达式,首先看helloFunction,对于helloFunction这个变量而言如果需要符合语法规则,第一是需要一个变量类型,在java8中通过接口来定义这个函数类型,只要创建一个接口,接口中有一个和这个函数的参数和返回值一样的抽象函数即可。

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
interface HelloFunction {   
     /**   
      * helloFunction中的函数没有返回值和没有参数,此时只要HelloFunction接口中   
      * 有一个不带参数和没有返回值的函数即可   
      */   
     public void foo();   
}

        此时的helloFunction就可以使用这个接口作为变量类型
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
blic class HelloLambda {   
     public static void main(String[] args) {   
         //HelloFunction中要存在一个同样类型和同样返回值的函数   
         HelloFunction helloFunction = () ‐> System.out.println( "hello world!" );   
         helloFunction.foo();   
     }   
}

        此时如果修改HelloFunction中的函数的参数类型和返回值都会报错,而且HelloFunction中也不能添加其他的方法,如果添加其他方法都会报错,所以函数接口中只能有一个方法。Java提供了一个Annotation来声明这种函数接口@FunctionalInterface接下来看看add方法的操作,原理也一样,定义接口,接口中有个方法有两个int的参数和int的返回值。
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
ctionalInterface   
interface AddFunction {   
     public int add( int a, int b);   
}   
    
public static void main(String[] args) {   
   //此时由于确定了函数值的类型,也就等于确定了参数的类型,所以,函数的参数的类型也可以省略   
   AddFunction addFunction = (a,b) ‐> a+b;   
   System.out.println(addFunction.add( 12 , 22 ));   
}

        需要注意的是由于在定义函数变量时,可以通过函数变量的类型就可以知道里面的函数参数类型,所以在Lamdba表达式中也不用声明函数类型。

1.3、Lamdba表达式和匿名内部类
        Lamdba表达式的这种方式非常类似Java的匿名内部类
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
匿名内部类,整个操作和Lamdba非常类似   
AddFunction af = new AddFunction() {   
   public int add( int a, int b) {   
     return a+b;   
   };   
};   
System.out.println(af.add( 12 , 22 ));

        两者的操作非常类似,当然到后面我们会发现他们之间的区别,目前我们所看到的主要区别有这样三点:1、匿名内部类的接口可以有多个方法,但是Lamdba的函数接口只能有一个方法。2、匿名内部类的实现方式比较麻烦一些。3、对于Lamdba表达式而言,并不一定是固定类型的,只要有另外一个接口满足这个函数的基本要求,就可以用来设置Lamdba表达式,这是Lamdba表达式的优点,利用这个优点可以快速的开发多线程的程序。举个例子,java的Runnable接口中只有一个run方法,这个接口和方法完全满足HelloFunction的需求,我们就可以按照下面的方式来定义这种写法。
        
[Java]  纯文本查看  复制代码
?
1
Runnable rfunction = () ‐> System.out.println( "hello world!" );

        此时的cfunction其实就是一个实现了Runnable的函数对象,同样可以作为Thread的参数传进去,就可以实现多线程的开发。
        Lamdba作为函数的参数值
        Lamdba表达式还可以作为函数的参数值来使用,看如下一个例子
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class TypeLamdba {   
     public static void main(String[] args) {   
//      StrLengthFunction sf = (s)‐>s.length();   
//      printStrLen(sf, "this is a Lamdba!");   
         //上面的两行代码可以使用下面这种表示方式   
         printStrLen((s)‐>s.length(), "this is a Lamdba!" );   
     }   
         
     /**   
      * 该方法的参数使用了函数接口作为第一个参数   
      */   
     private static void printStrLen(StrLengthFunction sl,String value) {   
         System.out.println(sl.length(value));   
     }   
         
     @FunctionalInterface   
     interface StrLengthFunction {   
         public int length(String str);   
     }   
    
}

        通过上面一个例子,我们获取了字符串的长度,并且通过Lamdba表达式将其打印出来,这里大家看起来似乎比较的麻烦,但是这个实例提供一种操作。下面我们将看一个基于Lamdba处理列表的实例。
1.4、Lamdba表达式的具体实例
        首先创建一个实体类Student
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Student {   
     private String name;   
     private String no;   
     private int age;   
         
     public Student() {}   
         
     public Student(String name, String no, int age) {   
         super ();   
         this .name = name;   
         this .no = no;   
         this .age = age;   
     }   
   /.省略getter和setter./   
}


        接着创建一个列表
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
public class TestStudent {   
     public static void main(String[] args) {   
         List<Student> stuList = Arrays.asList(   
                 new Student( "张三" , "001" , 19 ),   
                 new Student( "李四" , "005" , 22 ),   
                 new Student( "王五" , "010" , 14 ),   
                 new Student( "赵六" , "004" , 18 ),   
                 new Student( "何琦" , "006" , 12 )   
                 );   
     }   
}

        我们先按照传统的方式来完成如下三个操作,根据用户no排序,列表所有的用户信息,显示年龄大于15岁的所有学生。
        这三个操作如果不使用Lamdba表达式非常简单,比较大小只要通过Collections.compare()方法即可完成,第二个参数是一个Comparator的接口,我们通过内部类来实现,下面是三个函数的代码
        
[AppleScript]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/ / 根据学号排序   
     public static void sortByNo ( List < Student > list ) {   
         Collections.sort ( list , new Comparator < Student > ( ) {   
             public int compare ( Student o 1 , Student o 2 ) {   
                 return o 1. getNo ( ) .compareTo ( o 2. getNo ( ) ) ;   
             } ;   
         } ) ;   
     }   
     / / 列表所有学生信息   
     public static void listAll ( List < Student > stuList ) {   
         for ( Student stu : stuList ) {
  System.out.println ( stu ) ;   
         }   
             
     }   
     / / 过滤小于 15 岁的学生   
     public static void filterAge ( List < Student > list ) {   
         for ( Student stu : list ) {   
             if ( stu.getAge ( ) > 15 ) {   
                 System.out.println ( stu ) ;   
             }   
         }   
     }

        已上三个代码都是以硬编码的方式实现,这种所带来的问题是不灵活,特别是filterAge,在此处其实除了过滤年龄,我们还可能会有其他的条件过滤需求,所以可以通过传入一个条件接口来实现,看看下面改造后的代码
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
blic static void filterCondition(List<Student> stuList,Condition<Student> c) {   
   for (Student stu:stuList) {   
     if (c.test(stu)) {   
       System.out.println(stu);   
     }   
   }   
}   
    
interface Condition<T> {   
   public boolean test(T t);   
}

        增加了一个Condition的接口,来传入条件,当满足条件之后就执行打印,这个条件可以通过匿名内部类的方式来实现
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
lterCondition(stuList, new Condition<Student>() {   
   @Override   
   public boolean test(Student t) {   
     if (t.getAge()> 15 ) return true ;   
     return false ;   
   }   
});

        已上实现了这个操作,这种方式看起来复杂,但是它提供了一个条件检测,只要用不同的方式Condition接口,就可以根据条件来处理不同的信息,这和java中File类的FileFilter非常类似。
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class TestNewStudentByJdk7 {   
     public static void main(String[] args) {   
         List<Student> stuList = Arrays.asList(   
                 new Student( "张三" , "001" , 19 ),   
                 new Student( "李四" , "005" , 22 ),   
                 new Student( "王五" , "010" , 14 ),   
                 new Student( "赵六" , "004" , 18 ),   
                 new Student( "何琦" , "006" , 12 )
         );   
         sortByNo(stuList);   
         listAll(stuList);   
         System.out.println( "‐‐‐‐‐‐‐‐‐‐‐‐‐" );   
         filterCondition(stuList, new Condition<Student>() {   
             @Override   
             public boolean test(Student t) {   
                 if (t.getAge()> 15 ) return true ;   
                 return false ;   
             }   
         });   
     }   
         
     //根据学号排序   
     public static void sortByNo(List<Student> list) {   
         Collections.sort(list, new Comparator<Student>() {   
             public int compare(Student o1, Student o2) {   
                 return o1.getNo().compareTo(o2.getNo());   
             };   
         });   
     }   
     //列表所有学生信息   
     public static void listAll(List<Student> stuList) {   
         for (Student stu:stuList) {   
             System.out.println(stu);   
         }   
             
     }   
         
     public static void filterCondition(List<Student> stuList,Condition<Student> c) {   
         for (Student stu:stuList) {   
             if (c.test(stu)) {   
                 System.out.println(stu);   
             }   
         }   
     }   
         
     interface Condition<T> {   
         public boolean test(T t);   
     }   
}

        下面我们将看一下Lamdba的实现方式,首先看sortByName方法,Comparator接口中只有一个compare的函数,所以我们可以使用Lamdba来简化这一部分的操作。
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
//学号排序   
public static void sortByNo(List<Student> list) {   
   Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));   
}

        接下来通过Lamdba的方式来操作filter方法,这个使用了Lamdba之后会简化非常多的操作,看如下三个例子
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
//查询所有年龄大于15的人
filterCondition(stuList,(t)‐>t.getAge()> 15 );   
//查询姓张的所有学生   
filterCondition(stuList,(t)‐>t.getName().startsWith( "张" ));   
//查询所有学生对象,条件为true   
filterCondition(stuList, (t)‐> true );

        可见使用了Lamdba之后整个代码得到了极大的精简,看一下完整的代码
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TestStudentByLamdba {   
     public static void main(String[] args) {   
         List<Student> stuList = Arrays.asList(   
                 new Student( "张三" , "001" , 19 ),   
                 new Student( "李四" , "005" , 22 ),   
                 new Student( "王五" , "010" , 14 ),   
                 new Student( "赵六" , "004" , 18 ),   
                 new Student( "何琦" , "006" , 12 )   
                 );   
         sortByNo(stuList);   
         System.out.println( "age > 15" );   
         //查询所有年龄大于15的人   
         filterCondition(stuList,(t)‐>t.getAge()> 15 );   
         System.out.println( "name like 张" );   
         //查询姓张的所有学生   
         filterCondition(stuList,(t)‐>t.getName().startsWith( "张" ));   
         System.out.println( "all student" );   
         //查询所有学生对象,条件为true   
         filterCondition(stuList, (t)‐> true );   
     }   
         
     //根据学号排序   
     public static void sortByNo(List<Student> list) {   
         Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));   
     }   
         
     public static void filterCondition(List<Student> stuList,Condition<Student> c) {   
         for (Student stu:stuList) {   
             if (c.test(stu)) {   
                 System.out.println(stu);   
             }   
         }   
     }   
         
     interface Condition<T> {   
         public boolean test(T t);   
     }   
}

        关于Lamdba的讲解第一部分就到这里,相信通过这些实例,会让大家对Lamdba有一个基本的认识,下一部分将会讲解一些相对深入的知识。
1.5、Java基础之Lamdba表达式 深入表达式
        上一讲给大家介绍了Lamdba表达式入门,让我们先回顾上一讲的实例代码
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class TestStudentByLamdba {   
     public static void main(String[] args) {   
         List<Student> stuList = Arrays.asList(   
                 new Student( "张三" , "001" , 19 ),   
                 new Student( "李四" , "005" , 22 ),   
                 new Student( "王五" , "010" , 14 ),   
                 new Student( "赵六" , "004" , 18 ),   
                 new Student( "何琦" , "006" , 12 )   
                 );   
         sortByNo(stuList);   
         System.out.println( "age > 15" );   
         //查询所有年龄大于15的人   
         filterCondition(stuList,(t)‐>t.getAge()> 15 );   
         System.out.println( "name like 张" );   
         //查询姓张的所有学生   
         filterCondition(stuList,(t)‐>t.getName().startsWith( "张" ));   
         System.out.println( "all student" );   
         //查询所有学生对象,条件为true   
         filterCondition(stuList, (t)‐> true );   
     }   
         
     //根据学号排序   
     public static void sortByNo(List<Student> list) {   
         Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));   
     }   
         
     public static void filterCondition(List<Student> stuList,Condition<Student> c) {   
         for (Student stu:stuList) {   
             if (c.test(stu)) {   
                 System.out.println(stu);   
             }   
         }   
     }   
         
     interface Condition<T> {   
         public boolean test(T t);   
     }   
}

        在该实例中我们通过Condition接口来处理条件的测试请求,如果要使用Lamdba表达式,我们将会使用到大量的接口,所以Java的开发团队也考虑到了这个问题,他们为我们提供了大量的Function的接口,供开发人员使用,所有的接口都在java.util.function包中,大家可以通过jdk的docs进行查看。其中Predicate 接口中就提供了test(T t)的 方法来完成对一个对象的比较操作。我们就可以进一步的简化以上的代码,可以不再需要Condition
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
//使用Predicate来替代Condition   
public static void filterCondition(List<Student> stuList,Predicate<Student> c) {   
   for (Student stu:stuList) {   
     if (c.test(stu)) {   
       System.out.println(stu);   
     }   
   }   
}

1.6、更多的Java Function接口
        出了Predicate外,Jdk还提供了几个非常好用的接口,这里简单进行了一些归纳
          
        JDK其实已经基本把最基础的函数接口都提供,大家在使用的时候一定思考清楚函数接口是什么,需不需要自己再添加,接下来我会展示另外一个接口的应用。依然是上面的例子,我们在filterCondition中有一个非常不好的地方
        
[Java]  纯文本查看  复制代码
?
1
2
3
f(c.test(stu)) {   
   System.out.println(stu);   
}

        System.out.println(stu) 是典型的硬编码,我们不一定要打印整个对象,我们可能只会涉及到打印对象的某个值,或者我们的输出不一定是控制台,而是文件,由于上面的硬编码使得上述需求无法实现,接下来利用Consumer来解决该问题,Consumer接口提供的方法可以接受一个对象来进行处理,刚好满足这个要求,先看看如何改造filterCondition方法。
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
9
public static void filterCondition(List<Student> stuList,Predicate<Student>
pre,Consumer<Student> con) {
    
         for (Student stu:stuList) {   
             if (pre.test(stu)) {   
                 con.accept(stu);   
             }   
         }   
}

        增加第三个参数Consumer来接受一个对象,此时即可在调用的地方通过Lamdba来完成操作
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
//查询所有年龄大于15的人,打印所有学生信息   
filterCondition(stuList,t‐>t.getAge()> 15 ,t‐>System.out.println(t));   
System.out.println( "name like 张" );   
//查询姓张的所有学生,仅仅打印姓名   
filterCondition(stuList,t‐>t.getName().startsWith( "张" ),t‐>System.out.println(t.getName()));   
System.out.println( "all student" );   
//查询所有学生对象,条件为true,仅仅打印学号   
filterCondition(stuList, t‐> true ,t‐>System.out.println(t.getNo()));

        通过第三个参数确定输出的结果,值得一提的是由于参数只有一个,我们可以将参数的括号去掉。完整代码如下
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class TestStudentByLamdba3 {   
     public static void main(String[] args) {   
         List<Student> stuList = Arrays.asList(   
                 new Student( "张三" , "001" , 19 ),   
                 new Student( "李四" , "005" , 22 ),   
                 new Student( "王五" , "010" , 14 ),   
                 new Student( "赵六" , "004" , 18 ),   
                 new Student( "何琦" , "006" , 12 )   
                 );   
         sortByNo(stuList);   
         System.out.println( "age > 15" );   
         //查询所有年龄大于15的人,打印所有学生信息   
         filterCondition(stuList,(t)‐>t.getAge()> 15 ,t‐>System.out.println(t));   
         System.out.println( "name like 张" );   
         //查询姓张的所有学生,仅仅打印姓名   
         filterCondition(stuList,(t)‐>t.getName().startsWith( "张" ),t‐
>System.out.println(t.getName()));
    
         System.out.println( "all student" );   
         //查询所有学生对象,条件为true,仅仅打印学号   
         filterCondition(stuList, (t)‐> true ,t‐>System.out.println(t.getNo()));   
     }   
         
     //根据学号排序   
     public static void sortByNo(List<Student> list) {   
         Collections.sort(list,(s1,s2)‐>s1.getNo().compareTo(s2.getNo()));   
     }   
         
     public static void filterCondition(List<Student> stuList,Predicate<Student>
pre,Consumer<Student> con) {
    
         for (Student stu:stuList) {   
             if (pre.test(stu)) {   
                 con.accept(stu);   
             }   
         }   
     }   
}

        通过这个例子,相信大家对Lamdba表达式有了更进一步的了解,而且应该也看到Lamdba表达式给我们带来的好处
1.7、Lamdba表达式处理异常和表达式封装模式
        异常处理是Java中一个非常重要的错误处理手段,我们接下来通过实例来看一下Lamdba的异常处理,我们将会通过异常处理提出一中比较理想的封装模式,其实非常像设计模式中的代理模式,首先看如下实例
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
public class TestLamdba {
       public static void main(String[] args) {
           int [] nums = { 1 , 2 , 3 , 4 };
           int key = 2 ;
           cal(nums,key,(v,k)‐>System.out.println(v+k));
       }
       
       private static void cal( int []nums, int key,BiConsumer<Integer, Integer> bc) {
           for ( int n:nums) {
               bc.accept(n, key);
           }
       }
   }

        以上程序主要完成对数组的处理方法,可以对数组里面的数据进行统一的运算,代码中统一加了key值,只要稍加改动就可以对数组进行另一种处理
        
[Java]  纯文本查看  复制代码
?
1
cal(nums,key,(v,k)‐>System.out.println(v/k));

        此时就会完成数组的除法运算,当使用除法时,如果key的值为0,那就会出现ArithmeticException。那我们该如何来处理这个异常呢?第一种方案在cal方法中添加异常处理模块
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
9
private static void cal( int []nums, int key,BiConsumer<Integer, Integer> bc) {
     for ( int n:nums) {
       try {
         bc.accept(n, key);
       } catch (ArithmeticException e) {
         e.printStackTrace();
       }
     }
   }

        这种方案明显是不妥的,因为Biconsumer不一定会发生ArithmeticException,因为调用是在运行时刻才知道的,所以在这里捕获异常不合理,第二种方案是在cal的调用处处理异常
      
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
cal(nums,key,(v,k)‐>{
     try {
       System.out.println(v/k);
     } catch (ArithmeticException e) {
       e.printStackTrace();
     }
   });

        这种方式虽然可以在调用时捕获异常,但这种方式和Lamdba的简洁方式有所违背,这更像是匿名内部类的处理方式,所以依然不建议使用这种方式,下面我们将会介绍第三种解决方案,基于封装模式的解决方案。
        我们首先再添加一个方法wrapperLamdba 该方法用来封装Consumer这个表达式,并且传入参数BiConsumer

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
/**
   * 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
    */
   private static BiConsumer<Integer, Integer>
       wrapperLamdba(BiConsumer<Integer, Integer> bc) {
       return bc;
   }

        该方法没有做任何处理,但参数是一个BiConsumer类型,此时在调用时就可以用这个函数进行封装
        cal(nums,key,wrapperLamdba((v,k)->System.out.println(v/k))); 由于wrapperLamda什么都没有做,所以此处不会进行任何处理,如果修改wrapperLamdba的代码如下
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
private static BiConsumer<Integer, Integer>
       wrapperLamdba(BiConsumer<Integer, Integer> bc) {
       return (v,k)‐> System.out.println(v+k);
   }

        如果这样处理,我们会发现cal中调用的Lamdba不起作用了,因为不管cal中的lamdba是什么类型,最后都会返回+的操作,这说明我们可以通过封装的方法来替换原有的表达式,这样我们就可以在这个封装方法中进行异常处理,这带来的好处就是可以在一个位置集中处理所有异常信息,在设计和使用上更加合理一些
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
private static BiConsumer<Integer, Integer>
               wrapperLamdba(BiConsumer<Integer, Integer> bc) {
       return (v,k)‐> {
           try {
               bc.accept(v, k);
           } catch (ArithmeticException e) {
               System.out.println( "发生异常:" +e.getMessage());
           }
       };
   }


        这种模式带来的好处是不仅仅可以处理异常,还可以加入一些限定条件,当不满足条件时,可以用新的表达式完成替换。完整代码如下:

        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class TestLamdba {
       public static void main(String[] args) {
           int [] nums = { 1 , 2 , 3 , 4 };
           int key = 0 ;
           cal(nums,key,wrapperLamdba((v,k)‐>System.out.println(v/k)));
       }
       
       private static void cal( int []nums, int key,BiConsumer<Integer, Integer> bc) {
           for ( int n:nums) {
               bc.accept(n, key);
           }
       }
       /**
        * 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
        */
       private static BiConsumer<Integer, Integer>
               wrapperLamdba(BiConsumer<Integer, Integer> bc) {
           return (v,k)‐> {
               try {
                   bc.accept(v, k);
               } catch (ArithmeticException e) {
                   System.out.println( "发生异常:" +e.getMessage());
               }
           };
       }
   }

1.8、方法引用

        Lamdba表达式对方法的引用有一种特殊的写法,先看看下面的实例

        


        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
public class SimpleObject {
       public static void main(String[] args) {
           Thread t = new Thread(()‐>printSth());
           t.start();
       }
       
       private static void printSth() {
           System.out.println( "hello world!" );
       }
   }

        例子很简单,创建了一个Runnable的Lambda表达式,并且启动了该线程,()->printSth() 有另外一种表示方法,此时printSth是静态方法可以通过SimpleObject::printSth 来替换

        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void main(String[] args) {
     //Thread t = new Thread(()‐>printSth());
     Thread t = new Thread(SimpleObject::printSth);
     t.start();
   }
   对于方法引用还有另外一种表示方法
   
   public class FooObject {
   
       public static void main(String[] args) {
   //      printSth(new User(1,"foo"), u‐>System.out.println(u));
           //此时println会自动输出Consumer方法的参数,也就是u这个对象
           printSth( new User( 1 , "foo" ),System.out::println);
       }
       
       private static void printSth(User u,Consumer<User> c) {
           c.accept(u);
       }
   }
   
   class User {
       private int id;
       private String name;
       public User( int id, String name) {
           super ();
           this .id = id;
           this .name = name;
       }
       public User() {
           
       }
       /*省略了getter和setter*/
       @Override
       public String toString() {
           return "User [id=" + id + ", name=" + name + "]" ;
       }
   }

1.9、java8中的集合遍历

        在java8中增加了一些新的遍历方法,在jdk5中提供了一种增强的for循环来遍历数组和集合类


        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static void main(String[] args) {
     List<Student> stuList = Arrays.asList(
       new Student( "张三" , "001" , 19 ),
       new Student( "李四" , "005" , 22 ),
       new Student( "王五" , "010" , 14 ),
       new Student( "赵六" , "004" , 18 ),
       new Student( "何琦" , "006" , 12 )
     );
     /**
            * 增强的for循环
            */
     for (Student stu:stuList) {
       System.out.println(stu);
     }
   }

        Java中提供了基于Lamdba表达式的遍历方式,非常类似javascript函数的闭包,通过forEach来进行遍历,先看看forEach的源码

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
default void forEach(Consumer<? super T> action) {
     Objects.requireNonNull(action);
     for (T t : this ) {
       action.accept(t);
     }
   }

        参数是Consumer,所以只要以Lamdba的方法来编写就快速完成遍历

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
/**
   * 等于执行了
   for (T t : this) {
     action.accept(t);
   }
   t就等于参数p
   */
   stuList.forEach(p‐>System.out.println(p));


        可以利用上一部分讲的函数引用方式来执行

        
[Java]  纯文本查看  复制代码
?
1
2
//使用方法引用的方式来编写也可以
   stuList.forEach(System.out::println);

        完整代码:

        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TestCollection {
       public static void main(String[] args) {
           List<Student> stuList = Arrays.asList(
                   new Student( "张三" , "001" , 19 ),
                   new Student( "李四" , "005" , 22 ),
                   new Student( "王五" , "010" , 14 ),
                   new Student( "赵六" , "004" , 18 ),
                   new Student( "何琦" , "006" , 12 )
                   );
           /**
            * 增强的for循环
            */
           for (Student stu:stuList) {
               System.out.println(stu);
           }
           
           System.out.println( "java8中新增的方法" );
           /**
            * 等于执行了
               for (T t : this) {
                 action.accept(t);
               }
               t就等于参数p
            */
           stuList.forEach(p‐>System.out.println(p));
           //使用方法引用的方式来编写也可以
           stuList.forEach(System.out::println);
       }
       
   }

        这就是Lamdba表达式的基本用法,只要掌握熟练之后,会提供很多简化的方式来提高开发效率,特别是在Spring5中经常使用基于Function的表达式,下一部分将会介绍Stream,Stream在Hibernate5中已经提供支持了。

2、Java基础之Stream
         Java8由于引入了Lamdba表达式这种非常方便的表示方式,使用Lambda可以简化整个集合类的遍历操作,所以也就为Stream提供了基础,Stream非常类似MongoDB中的集合处理,通过管道的形式来完成数据的遍历和聚合,读这一篇文章的前提条件是了解Java8的Lamdba表达式。

2.1、Stream预览        
          先看一个简单的例子。
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class BaseStream {
       public static void main(String[] args) {
           List<Student> stuList = Arrays.asList(
                   new Student( "张三" , "001" , 19 ),
                   new Student( "李四" , "005" , 22 ),
                   new Student( "王五" , "010" , 14 ),
                   new Student( "赵六" , "004" , 18 ),
                   new Student( "何琦" , "006" , 12 )
                   );
           
           stuList.stream()
               .filter(s‐>s.getAge()> 16 )
               .forEach(s‐>System.out.println(s.getName()));
       }
   }
        首先创建了一个list的列表,通过stream()方法可以将列表转换为一个Stream对象,Stream就具备了管道的功能,代码中首先执行了filter,首先看filter的源码
        Stream filter(Predicate<? super T> predicate); 该方法传入了Predicate这个Function接口,这个函数接口可以接受一个对象,返回一个boolean的值,所以我们返回了age大于16的人,此时就等于管道中只有年龄大于16岁的所有student,之后将这些数据向后提交,之后执行了forEach管道,forEach上一讲已经介绍过了,可以传入一个对象,没有返回值,在代码中我们通过输出的方式输出了这个对象。通过这个简单的例子我们可以快速了解java8的Stream,相信Stream会在未来成为java的一种重要的集合处理手段,下面我们将主要介绍Stream中常用的几个方法。
2.2、Stream的创建方式        
        只要是集合类都可以创建Stream,其中数组也可以支持,下面的代码显示了基于List的集合类和基于map的集合类操作,所有的Collection都有stream()方法直接将集合类转换为Stream
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class FirstStream {
       public static void main(String[] args) {
           List<String> strs = Arrays.asList( "a1" , "a2" , "a3" , "a4" , "a5" );
           strs.stream().forEach(System.out::println);
           Map<String,String> maps = new HashMap<String,String>();
           maps.put( "001" , "str1" );
           maps.put( "002" , "str2" );
           maps.put( "003" , "str3" );
           maps.put( "004" , "str4" );
           maps.entrySet().stream().forEach(System.out::println);
           maps.keySet().stream().forEach(System.out::println);
           maps.values().stream().forEach(System.out::println);
       }
   }
        处理List和Map之外,还可以直接将字符串,字符数组等类型直接转换为Stream,主要使用Stream.of方法
        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class SecondStream {
       public static void main(String[] args) {
           //转换为字符的stream,转换后的值是int类型,stream的类型是IntStream
           "abcdefg0" .chars().forEach(System.out::println);
           //基于字符的方式输出
           "abcdefg0" .chars().forEach(c‐> System.out.println(( char )c));
           //转换为数组
           int [] nums = { 1 , 2 , 3 , 4 , 5 , 6 };
           Stream.of(nums).forEach(System.out::println);
           //将数组转换为stream,使用Stream.of
           Stream.of( "hello,world,good,how,are,you" .split( "," ))
                   .forEach(System.out::println);
           //直接使用of的参数创建Stream
           Stream.of( "a" , "b" , "c" ).forEach(System.out::println);
           
       }
   }
        还可以直接使用Lamdba来创建Stream,使用Stream的generate方法,该方法会生成一个无限的Stream流,参数是一个Supplier的函数表达式,Supplier函数接口中的方法是获取一个对象,此时只要配合limit即可创建一些满足要求的随机数流
        
[Java]  纯文本查看  复制代码
?
1
Stream.generate(()‐>( int )(Math.random()* 100 )).limit( 20 ).forEach(System.out::println);
        通过iterate可以生成某种序列的流,iterate的第一个参数是种子,种子等于第一个数,之后按照第二个参数类推出去,例子如下,生成2的倍数的流
        
[Java]  纯文本查看  复制代码
?
1
Stream.iterate( 1 ,i‐>i* 2 ).limit( 10 ).forEach(System.out::println);
        Stream可以通过limit、distinct、sorted、filter等方法对流进行处理,这些处理完成之后都返回了流对象
        
[Java]  纯文本查看  复制代码
?
1
2
3
List<String> s = Arrays.asList( "1" , "4" , "2" , "1" , "5" , "4" , "2" , "8" , "5" );
           s.stream().distinct().sorted().limit( 4 )
                   .forEach(System.out::println);
        distinct表示取唯一值,sorted表示排序,limit表示取几个值。
2.3、Map、filter和toArray方法        
        首先看filter的源代码
        
[Java]  纯文本查看  复制代码
?
1
Stream<T> filter(Predicate<? super T> predicate);

        里面的参数是Predicate,这个函数接口中的函数是test,传入一个参数返回一个boolean类型的值,filter此处就是用来进行条件过滤,然后看一下map函数的代码
        
[Java]  纯文本查看  复制代码
?
1
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

        
[Java]  纯文本查看  复制代码
?
1
参数是Function,Function中的apply方法传入一个参数,返回另一个参数,这个函数用来重组对象

        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class FilterAndMap {
       public static void main(String[] args) {
           List<Student> list = Arrays.asList(
                   new Student( "001" , "zhangsan" , 20 ),
                   new Student( "002" , "lisi" , 30 ),
                   new Student( "003" , "wangwu" , 40 ),
                   new Student( "004" , "jake" , 23 ),
                   new Student( "005" , "Leon" , 21 )
           );
           list.stream().filter((t)‐
>t.getName().startsWith( "z" )).forEach(System.out::println);
           list.stream().filter(t‐>t.getAge()> 25 )
                   .map((t)‐>t.getName()+ "‐" +t.getAge()).forEach(System.out::println);
       }
   }

        filter设置了条件过滤,而map把student转换为了字符串(name+age)的方式。看一下运行的结果
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
Student{no= '001' , name= 'zhangsan' , age= 20 }
   //map的结果
   lisi‐ 30
   wangwu‐ 40

        Stream中提供了toArray方法来将Stream转换为数组,转换的数组类型是Object[],接下来看看两个操作
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
Object[] objs = list.stream().toArray();
   //对单个对象进行转换
   Stream.of(objs).forEach((obj)‐> System.out.println(((Student)obj).getName()));
   //直接转换为学生数组
   Student[] stus = list.stream().toArray(Student[]:: new );
   Stream.of(stus).forEach(System.out::println);

2.4、收集和分组操作
        Stream提供了分组操作,类似于数据库的groupby等操作,在介绍这些知识之前,我们首先需要给大家介绍一下Collectors,这是收集器,可以将stream中的东西收集到一个List、Set或者Map中。
        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
Map<String,Integer> maps = list.stream()
                       .collect(Collectors.toMap(Student::getName,Student::getAge));
   System.out.println(maps);
   List<Student> list1 = list.stream().filter(t‐>t.getAge()> 22 )
           .sorted((t1,t2)‐>(t1.getAge()‐t2.getAge()))
           .collect(Collectors.toList());
   System.out.println(list1);


        大家看一下结果

        
[Java]  纯文本查看  复制代码
?
1
2
3
{jake= 23 , lisi= 30 , zhangsan= 20 , wangwu= 40 , Leon= 21 }
   [Student{no= '004' , name= 'jake' , age= 23 }, Student{no= '002' , name= 'lisi' , age= 30 },
Student{no= '003' , name= 'wangwu' , age= 40 }]

        Collectors可以完成数据的收集工作,这是配合groupby的基础,接下来看一个groupby的实例,我们建一个Person的对象,看一下分组的实例

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
System.out.println(persons.stream().collect(Collectors.groupingBy(p‐>p.getCountry())));
   //另外一种写法
   System.out.println(persons.stream().collect(
       Collectors.groupingBy(Person::getCountry,
                           Collectors.counting())));

        

        Collectors中还有一个partitioningBy,这个方法的参数是一个Precidate的函数接口,它用来统计是否满足要求的数据,返回的map中有一个是boolean,另一个是List或者Array,也可以是Collectors的集合数据(counting,max)等

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
System.out.println(persons.stream()
         .collect(Collectors.partitioningBy(t‐>t.getCountry().equals( "USA" ))));
    
   System.out.println(persons.stream()
         .collect(Collectors.partitioningBy(t‐
>t.getCountry().equals( "USA" ),Collectors.counting())));

        看看这四个的结果

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
7
8
{USA=[org.konghao.stream.Person @15aeb7ab , org.konghao.stream.Person @7b23ec81 ], China=
[org.konghao.stream.Person @6acbcfc0 , org.konghao.stream.Person @5f184fc6 ], Germany=
[org.konghao.stream.Person @3feba861 ]}
   {USA= 2 , China= 2 , Germany= 1 }
   { false =[org.konghao.stream.Person @6acbcfc0 , org.konghao.stream.Person @5f184fc6 ,
org.konghao.stream.Person @3feba861 ], true =[org.konghao.stream.Person @15aeb7ab ,
org.konghao.stream.Person @7b23ec81 ]}
   { false = 3 , true = 2 }




        已上就是Stream的统计查询,这个功能非常的实用,虽然使用原来的方式可以实现,但工作量要大得多。

2.5、match操作

        Stream提供了几种匹配操作,AllMatch表示要全部满足要求才返回true,noneMatch要所有不满足才返回真,anyMatch表示要有一个满足要求就返回true,看如下代码:

        
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class TestAllMatch {
       static int i = 0 ;
       public static void main(String[] args) {
           List<Student> list = Arrays.asList(
                   new Student( "001" , "zhangsan" , 20 ),
                   new Student( "002" , "lisi" , 30 ),
                   new Student( "003" , "wangwu" , 40 ),
                   new Student( "004" , "jake" , 23 ),
                   new Student( "005" , "Leon" , 21 )
           );
   
           //只要有一个不满足要求就马上退出函数,并且返回false,AllMatch需要所有满足才为true
           boolean b1 = list.stream().allMatch(p‐>{
               boolean flag = p.getAge()> 20 ;
               System.out.println( "#" +(i++));
               return flag;
           });
   
           System.out.println(b1);
           //所有人的年龄都大于等于20,所以返回true
           boolean b2 = list.stream().allMatch(p‐>p.getAge()>= 20 );
           System.out.println(b2);
   
           //有一个满足就返回true
           System.out.println(list.stream().anyMatch(p‐>p.getName().startsWith( "z" )));
           //所有不满足才是true
           System.out.println(list.stream().noneMatch(p‐>p.getName().startsWith( "k" )));
   
       }
   }

        看看结果

        
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
# 0
   false
   true
   true
   true

        第一个才执行了一次就退出函数了,就表示只要满足条件就不会再做任何的判断了。

        Lamdba的系列已经讲解完了,里面应该已经把入门的所有知识都讲解了,特别是Stream,目前可能很多人还不习惯这种操作方式,但是相信将来一定是一种主流的操作方式,因为它的确提供了非常便利的操作。希望这三部分的内容能够对大家有所帮助。

猜你喜欢

转载自blog.csdn.net/qq_39581763/article/details/80608135
今日推荐