多线程第三讲
三、内部类和Lambda表达式
3.1 内部类
将一个类定义在另一个类里面或者方法里面,这样的类就被称为内部类。
内部类可以分为四种:成员内部类、局部内部类、匿名内部类、静态内部类
内部类编写:
/**
* 1、成员内部类
* 成员内部类前可加上四种访问修饰符。
* private:仅外部类可访问。
* protected:同包下或继承类可访问。
* default:同包下可访问。
* public:所有类可访问。
* 成员内部类里面是不能含静态属性或方法
* 2、局部内部类
* 局部内部类存在于方法中。 他和成员内部类的区别在于局部内部类的访问权限仅限于方法或作用域内。
* 局部内部类就像局部变量一样,前面不能访问修饰符以及static修饰符
* 3、匿名内部类
* 匿名内部类没有构造方法。也是唯一没有构造方法的内部类。
* 匿名内部类和局部内部类只能访问外部类的final变量。
* 是局部内部类的一种特殊写法。new的接口或者抽象类。
* 一般是作为参数出现的;
* 匿名内部类是可以在main方法中直接使用的
* 4、静态内部类
* 静态内部类和成员内部类相比多了一个static修饰符。
* 它与类的静态成员变量一般,是不依赖于外部类的。
* 因为外部类加载时只会加载静态域,所以静态内部类不能使用外部类的非静态变量与方法。
*/
public class TestClass {
private Integer age=1;
private static String name="外部类";
/**
* 成员内部类
*/
public class TestInnerClass{
private Integer age=2;
private String name="成员内部类";
public Integer getAge() {
returnage;
}
public void setAge(Integerage) {
this.age=age;
}
public String getName() {
returnname;
}
public void setName(Stringname) {
this.name=name;
}
}
public Integer getAge() {
returnage;
}
public void setAge(Integerage) {
this.age=age;
}
public String getName() {
returnname;
}
public void setName(Stringname) {
this.name=name;
}
/**
* 局部内部类:partClass
*/
public void methodDemo(){
System.out.println("这里是普通方法");
classPartClass{
Integerage=3;
Stringname="局部内部类";
public void partClass(){
//这里的name属性值是当前局部内部类的name属性值
System.out.println("这里是局部内部类里的方法,我的name属性值是"+name);
//这里的name属性值是外部类的name属性值
System.out.println("这里是局部内部类里的方法,我的name属性值是"+TestClass.this.name);
}
}
//局部内部类中的方法只能够在当前方法中进行调用
PartClasspartClass=newPartClass();
partClass.partClass();
}
/**
* 匿名内部类
* 一般是 new接口/抽象类;
* 一般是作为参数出现的;
* 是局部内部类的一种特殊形式
*/
Runnablerunnable=newRunnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
};
/**
* 静态内部类
*/
public static class StaticClass{
Integerage=3;
Stringname="静态内部类";
public void static Class(){
//这里的name属性值是当前局部内部类的name属性值
System.out.println("这里是静态内部类里的方法,我的name属性值是"+name);
//这里的name属性值是外部类的name属性值
System.out.println("这里是静态内部类里的方法,我的name属性值是"+TestClass.name);
}
}
}
测试类编写:
/**
* 1、成员内部类
* 如果要访问成员内部类的属性,要么保证这个成员内部类的属性是public修饰的,要么就对这个成员内部类的属性设置get和set方法
* 如果想new内部类,必须先new外部类 ;
* 内部类是可以操作外部类的属性和方法的
* 2、局部内部类
* 因为局部内部类中的方法只能在类中进行调用,所以这里可以直接调用局部内部类的外部方法
* 3、静态内部类
* 静态内部类不能操作外部类的非静态属性或方法
*/
public class TestFamily {
public static void main(String[] args) {
//实例化外部类
TestClasstestClass=newTestClass();
//获取外部类的age属性值
Integerage=testClass.getAge();
System.out.println("外部类的age数值值 = "+age);
//创建类的成员内部类对象的两种方法,
//TestClass.TestInnerClass testInnerClass = new TestClass().new TestInnerClass();
TestClass.TestInnerClasstestInnerClass=testClass.newTestInnerClass();
Integerage1=testInnerClass.getAge();
System.out.println("成员内部类的age属性值 = "+age1);
//测试局部内部类
testClass.methodDemo();
//测试静态内部类
TestClass.StaticClassstaticClass=newTestClass.StaticClass();
staticClass.staticClass();
}
}
3.2 lambda表达式(λ)
3.2.1 lambda的概念
Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升。
3.2.2 lambda的语法
Lambda表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 ->,该操作符被称为Lambda 操作符或箭头操作符。它将Lambda 分为两个部分:
左侧:指定了Lambda 表达式需要的所有参数
右侧:指定了Lambda 体,即Lambda 表达式 方法要执行的功能。
Lmabda表达式的语法总结: () -> {};
λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或语句块。
前置 | 语法 |
无参数无返回值 | () -> System.out.println(“Hello World”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
用到的函数式接口:
public interface NiMing1 {
//无参数无返回值
voidtestNoNo();
}
public interface NiMing2 {
//有参数无返回值
voidtestYesNo(Stringcontent);
}
public interface NiMing3 {
//有多个参数有返回值
inttestYesYes(intx,inty);
}
测试类:
/**
* 1、lambda表达式
* 若要写成lambda表达式,要求匿名内部类中有且仅有一个抽象方法
* 2、 快捷键转换lambda表达式:
* 前提:该项目支持lambda表达式
* 查看方式:file -> project structure -> project -> Language level 至少选中8以上的版本
* 快捷键:ALT+ENTER 选择 Replace with lambda
* 3、lambda表达式的重要特征:
* 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。(类型推断)
* 可选的参数圆括号:一个参数无需定义圆括号,没有或者多个参数需要定义圆括号。
* 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
* 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
* 4、类型推断
* 类型推断:上述Lambda 表达式中的参数类型是由编译器推断得出的。
* Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac 根据程序的上下文,在后台推断出了参数的类型。
* Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”
*/
public class TestLambda {
/**
* 无参无返的lambda写法
* 方法体的大括号可以删除
*/
//普通写法
// @Test
// public void testNoNo(){
// new NiMing1(){
// @Override
// public void testNoNo() {
// System.out.println("无参无返的lambda写法");
// }
// };
// }
//lambda写法
@Test
public void testNoNo(){
NiMing1niMing1= () -> {System.out.println("无参无返的lambda写法");};
niMing1.testNoNo();
}
/**
* 有一个参无返的lambda写法
* 参数的个数只有一个时,参数的()可以省略不写
* 方法体的大括号可以删除
*/
@Test
public void testYesNo(){
NiMing2niMing2= (str) -> {
System.out.println("str = "+str);
};
niMing2.testYesNo("有参无返的lambda写法");
}
/**
* 有多个参有返的lambda写法
* 参数的个数有多个时,参数的()不可以省略
* 如果有多条lambda体语句时,方法体的大括号不可以删除
* 如果只有一条返回值的lambda体语句时,方法体的括号可以删除,同时return关键字也删除
*/
@Test
public void testYesYes(){
NiMing3niMing3= (x,y) -> {
returnx+y;
};
inti=niMing3.testYesYes(2, 3);
System.out.println("i = "+i);
}
/**
* 向treeSet中放入Integer类型的数据
* 相同的值不能重复放入TreeSet中
* 要放对象就必须实现Comparator接口,重写其中的compare方法
* o1-o2 出来的结果就是升序排列(o1-o2>0就把o1放到后面)
* o2-o1 出来的结果就是降序排列(o2-o1>0就把o1放到后面)
* 如果只有一条返回值的lambda体语句时,方法体的括号可以删除,同时return关键字也删除
*/
@Test
public void testTreeSet(){
TreeSet<Integer> treeSet=newTreeSet<>((o1,o2) ->o1-o2);
treeSet.add(1);
treeSet.add(1);
treeSet.add(13);
treeSet.add(15);
System.out.println("treeSet = "+treeSet);
}
/**
* 在TreeSet中添加字符串,使用比较器的原理,如果长度相等就不添加,长度不同就添加进去.
*/
@Test
public void testTreeSetLength(){
TreeSet<String> treeSet=newTreeSet<String>((o1,o2) ->o1.length()-o2.length());
treeSet.add("kk");
treeSet.add("kk");
treeSet.add("lll");
treeSet.add("llll");
System.out.println("treeSet = "+treeSet);
}
}
3.3 函数式接口
3.3.1 概念
只包含一个抽象方法的接口,称为函数式接口。
抽象方法就是在面向对象编程语言中抽象方法指一些只有方法声明,而没有具体方法体的方法。抽象方法一般存在于抽象类或接口中。(参考java基础笔记)
可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
例:
/**
* 1、我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,
* 同时javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
* 2、@FunctionalInterface注解不是必须的
* 如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。
* 如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
* 3、只有一个抽象方法的接口才能称为函数式接口。
* 函数式接口中能存在非抽象方法(即默认、静态、私有方法),非抽象方法根据自己的需要进行编写。
* 4、函数式接口中实例类中一般作为方法的参数或者返回值使用,或者创建一个接口实现类然后在实例类中调用实现类使用。
* 5、经常使用的Runnable就是一个函数式接口。
*/
@FunctionalInterface
public interface NiMing1 {
//无参数无返回值
void testNoNo();
}
3.3.2 java内置的四大函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer<T> 消费型接口 | T | void | 对类型为T 的对象应用操作,包含方法:void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T 的对象,包含方法:T get(); |
Function<T,R>函数型接口 | T | R | 对类型为T 的对象应用操作,并返回结果。结果是R 类型的对象。包含方法:R apply(T t); |
Predicate<T> 断定型接口 | T | boolean | 确定类型为T 的对象是否满足某约束,并返回boolean 值。包含方法boolean test(T t); |
代码实现:
publicclassTestNeiZhi {
/**
* 消费型接口:消费一个数据,没有返回值,
* 仅仅简单的调用
*/
//减肥的方法
public void LoseWeight(Integerweight, Consumer<Integer>consumer){
consumer.accept(weight);
}
//买衣服的方法
public void BuyClothes(Integerweight, Consumer<Integer>consumer1,Consumer<Integer>consumer2){
consumer1.andThen(consumer2).accept(weight);
}
/**
* 测试消费型接口的accept方法
*/
@Test
public void TestAccept(){
LoseWeight(70, weight->System.out.println("我的目标体重是"+weight+"kg"));
}
/**
* 测试消费性接口的andThen方法
* 方法的入参和返回值都是consumer类型的时候,可以使用andThen,在消费数据的时候,多次对数据进行增强处理。
*/
@Test
public void TestAndThen(){
BuyClothes(70,weight->System.out.println("我减肥成功的体重是"+weight+"kg"),integer->System.out.println("我现在的体重是"+integer+"kg,我要去买新衣服了") );
}
/**
* 供给型函数:supplier接口 没有参数,有返回值
*/
public Date getCurrent(Supplier<Date>supplier){
returnsupplier.get();
}
@Test
public void testSupplier(){
Datecurrent=getCurrent(() ->newDate());
System.out.println("当前的时间是 = "+current);
}
/**
* 断定型函数:根据入参做判断,并返回boolean值的时候使用
* 一个集合里面有很多的城市,将含有州的城市取出来
*/
private List<String> getCitys(List<String>allCity, Predicate<String>predicate) {
List<String>citys=newArrayList<>();
for (Strings : allCity) {
if (predicate.test(s)){
citys.add(s);
}
}
returncitys;
}
@Test
public void testFilter(){
List<String>allCity=Arrays.asList("北京","上海","周口","济宁","西安","杭州","江西");
List<String>citys=getCitys(allCity, s->s.contains("州"));
System.out.println("citys = "+citys);
}
//and是满足两个推断 当含有州并且字符串的长度为2的时候将数据添加到一个新的集合里面
private List<String> getCitys2(List<String>allCity, Predicate<String>predicate,Predicate<String>predicate2) {
List<String> citys = newArrayList<>();
for (Strings : allCity) {
if (predicate.and(predicate2).test(s)){
citys.add(s);
}
}
returncitys;
}
@Test
public void testFilterAnd(){
List<String>allCity=Arrays.asList("北京","上海","周口","济宁","西安","杭州","江西");
List<String>citys=getCitys2(allCity, s->s.contains("州"),s->s.length()==2);
System.out.println("citys = "+citys);
}
/**
* 函数型接口
* 此接口接收一个参数,返回一个结果,参数和结果的数据类型根据实际情况稳定。
* 例如:输入一个字符串,返回一个整形
*/
public Integer testFunction(Stringstring, Function<String,Integer>function){
returnfunction.apply(string);
}
@Test
public void testApply(){
Integerinteger=testFunction("123", s->Integer.parseInt(s));
System.out.println(integer.getClass());
System.out.println("integer = "+integer);
}
//测试compose方法:将字符串转化为整型,也可以再将整型转换为字符串,也可以用于算法的计算
public String testFunction2(Stringstring, Function<String,Integer>function1,Function<Integer,String>function2){
returnfunction2.compose(function1).apply(string);
}
@Test
public void testCompose(){
Strings1=testFunction2("123", s->Integer.parseInt(s), s->s.toString());
System.out.println(s1.getClass());
System.out.println("String = "+s1);
}
}
3.4 方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法。
类型 | lambda表达式 | 方法引用 |
静态方法引用 | (args)->类名.method(args) | 类名::method |
实例方法引用 | (args)->inst.method(args) | 实例::method |
实例方法引用2 | (inst,args)->inst.method(args) | 类名::method |
构造方法引用 | (args)->new 类名(args) | 类名::new |
实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时: 类名::method
代码实现:
测试使用的类:
@Data
@NoArgsConstructor
@AllArgsConstructor
publicclassPerson {
privateStringname;
privateIntegerage;
/**
* 创建对象的方法,提供person对象
*/
public static Personcreate(Supplier<Person>supplier){
returnsupplier.get();
}
/**
* 静态方法
*/
public static voidtestStatic(Personperson){
System.out.println("静态方法被调用❥(^_-)"+person);
}
/**
* 实例方法(普通方法)
* @param person
*/
public void testInstance(Personperson){
System.out.println("实例方法被调用❥(^_-)"+person);
}
public void testInstanceX(){
System.out.println("实例方法被调用❥(^_-)--");
}
}
测试类:
/**
* 测试方法引用:
* 方法的引用就是lambda表达式的另一种写法
*/
publicclassTestFunctionReference {
/**
* 测试静态方法的方法引用
* 静态方法引用 (args)->类名.method(args) 类名::method
*/
@Test
publicvoidtestStatic(){
Persontest1=newPerson("test1", 66);
Persontest2=newPerson("test2", 88);
List<Person>personList=Arrays.asList(test1,test2);
//放到集合里,利用forEach循环,然后调用函数式接口,转换为lambda表达式方法,最后就是为了使用方法引用
//原始形态
// personList.forEach(new Consumer<Person>() {
// @Override
// public void accept(Person person) {
// Person.testStatic(person);
// }
// });
//lambda表达式形态: (args)->类名.method(args)
//personList.forEach(person -> Person.testStatic(person));
//方法引用形态: 类名::method
personList.forEach(Person::testStatic);
}
/**
* 测试实例方法的方法引用
* 实例方法引用 (args)->inst.method(args) 实例::method
*/
@Test
publicvoidtestInstance(){
Persontest1=newPerson("test1", 66);
Persontest2=newPerson("test2", 88);
List<Person>personList=Arrays.asList(test1,test2);
//放到集合里,利用forEach循环,然后调用函数式接口,转换为lambda表达式方法,最后就是为了使用方法引用
//原始形态
// personList.forEach(new Consumer<Person>() {
// @Override
// public void accept(Person person) {
// Person.create(new Supplier<Person>() {
// @Override
// public Person get() {
// return new Person();
// }
// }).testInstance(person);
// }
// });
//lambda表达式形态: (args)->inst.method(args)
//personList.forEach(person -> Person.create(() -> new Person()).testInstance(person));
// args: person
// inst: Person.create(() -> new Person())
// method: testInstance
//方法引用形态: 实例::method
personList.forEach(Person.create(() ->newPerson())::testInstance);
System.out.println("-----------构造方法引用------------");
//方法引用:构造方法引用 (args)->new 类名(args) 类名::new
personList.forEach(Person.create(Person::new)::testInstance);
}
/**
* 测试实例方法的方法引用
* 实例方法引用2 (inst,args)->inst.method(args) 类名::method
*/
@Test
publicvoidtestInstanceX(){
Persontest1=newPerson("test1", 66);
Persontest2=newPerson("test2", 88);
List<Person>personList=Arrays.asList(test1,test2);
//放到集合里,利用forEach循环,然后调用函数式接口,转换为lambda表达式方法,最后就是为了使用方法引用
//原始形态
// personList.forEach(new Consumer<Person>() {
// @Override
// public void accept(Person person) {
// Person.create(new Supplier<Person>() {
// @Override
// public Person get() {
// return new Person();
// }
// }).testInstanceX();
// }
// });
//lambda表达式形态: (inst,args)->inst.method(args)
//personList.forEach(person -> Person.create(() -> new Person()).testInstanceX());
// (inst,args): person,""
// 类名: Person.create(() -> new Person())
// method: testInstance
//方法引用形态: 类名::method
personList.forEach(Person::testInstanceX);
}
}
3.5 Stream API
Java8中有两大最为重要的改变。第一个是Lambda 表达式;另外一个则是Stream API。
Stream API ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
为什么要使用Stream API?
实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Redis等,而这些NoSQL的数据就需要Java层面去处理。
Stream 和Collection 集合的区别:Collection 是一种静态的内存数据结构,而Stream 是有关计算的.前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU 实现计算。
什么是 Stream
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据, Stream讲的是计算!”
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。
流可以是顺序的也可以是并行的。顺序流的操作是在单线程上执行的,而并行流的操作是在多线程上并发执行的。
3.5.1 Stream操作步骤
1、创建 Stream
一个数据源(如:集合、数组),获取一个流。
2、中间操作
一个中间操作链,对数据源的数据进行处理,例如筛选、映射、排序...
3、终止操作(终端操作)
一旦执行终止操作, 就执行中间操作链,并产生结果,之后不会再被使用
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理
3.5.1.1 创建Stream
/**
* 通过集合获取流
* 通过集合Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
* default Stream<E> stream(): 返回一个顺序流
* default Stream<E> parallelStream(): 返回一个并行流
*/
@Test
public void testCollection(){
//数据源
List<String>strings=Arrays.asList("aa","bb","cc","aa");
//获取串行流(适合单线程)
Stream<String>stream=strings.stream();
//获取并行流(适合多线程)
Stream<String>stringStream=strings.parallelStream();
//将并行流转变为串行流(顺序流)
Stream<String>parallel=stringStream.parallel();
//将串行流转变为并行流
Stream<String>sequential=stream.sequential();
}
/**
* 通过数组获取流
* 1、通过数组Java8 中的 Arrays 的静态方法 stream() 可以获取数组流
* static <T> Stream<T> stream(T[] array): 返回一个流
* 2、重载形式,能够处理对应基本类型的数组:
* public static IntStream stream(int[] array)
* public static LongStream stream(long[] array)
* public static DoubleStream stream(double[] array)
*/
@Test
public void testArray(){
//数据源
String[] strings=newString[]{"aa","bb","cc","aa"};
//获取流
Stream<String>stream=Arrays.stream(strings);
}
/**
* 通过Stream.of()获取流
* 通过Stream的of()可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
* public static<T> Stream<T> of(T... values): 返回一个流
*/
@Test
public void testOf(){
//数据源[数组]
String[] stringArray=newString[]{"aa","bb","cc","aa"};
//获取流
Stream<String>strings1=Stream.of(stringArray);
//数据源[集合]
List<String>stringList=Arrays.asList("aa","bb","cc","aa");
Stream<List<String>>stringList1=Stream.of(stringList);
}
/**
* 无限流∞
* 可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。
* 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
* 生成 public static<T> Stream<T> generate(Supplier<T> s)
*/
@Test
public void testUnlimited(){
//使用迭代获取从2开始的前十个偶数
Stream.iterate(2, integer->integer+2).limit(10).forEach(System.out::println);
//获取十个随机数
Stream.generate(() ->Math.random()).forEach(System.out::println);
}
3.5.1.2 中间操作
/**
* 中间操作
* 多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
* /
/**
* 筛选
* filter(Predicate p) 接收LambdaLambda,从流中排除某些元素。
* distinct() 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素
* limit(long maxSize) 截断流,使其元素不超过给定数量。
* skip(long n) 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补
*/
@Test
public void testFilter(){
List<String>list=Arrays.asList("北京","汴京","开封","郑州","燕京","南京","北京");
//获取一下含有京的城市的个数
longcount=list.stream().filter(s->s.contains("京")).count();
System.out.println("count = "+count);
//循环输出(两种写法一样的效果)
//list.stream().filter(s -> s.contains("京")).forEach(System.out::println);
//list.stream().filter(s -> s.contains("京")).forEach(s -> System.out.println(s));
//去重
//list.stream().filter(s -> s.contains("京")).distinct().forEach(System.out::println);
/**
* 北京
* 汴京
* 燕京
* 南京
*/
//跳过
//list.stream().filter(s -> s.contains("京")).distinct().skip(1).forEach(System.out::println);
/**
* 汴京
* 燕京
* 南京
*/
//截断
list.stream().filter(s->s.contains("京")).distinct().skip(1).limit(2).forEach(System.out::println);
/**
* 汴京
* 燕京
*/
}
/**
* 映射
* map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
* mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
* mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
* mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
* flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void testMapping(){
//求每个数字的平方
List<Integer>integers=Arrays.asList(1,2,3,4);
integers.stream().map(integer->integer*integer).forEach(System.out::println);
//flatMap方法测试:将每个对象转换为字符流
List<Person>personList=Arrays.asList(newPerson("test",23),newPerson("zxq",22));
//以对象进行输出
personList.stream().forEach(System.out::println);
//以字符进行输出
personList.stream().flatMap(person->fromObjToStream(person))
.forEach(System.out::println);
}
//把一个对象转为一个字符
public Stream<Character> fromObjToStream(Objectobj){
List<Character>characters=newArrayList<>();
//将对象转换为字符数组
char[] chars=obj.toString().toCharArray();
for (charaChar : chars) {
characters.add(aChar);
}
returncharacters.stream();
}
/**
* 排序
* sorted() 产生一个新流,其中按自然顺序排序
* sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序
*/
@Test
public void testSort(){
//按照年龄的倒序,对集合进行排序
List<Person>personList=Arrays.asList(newPerson("test",23),newPerson("zxq",22));
//以对象进行输出:以对象的形式是不能够直接进行比较的:解决方法如下
// 1、Person类没有实现Comparable接口,定义要比较的数据
// 2、使用sorted自带的比较器Comparator,进行比较:也有倒序和正序之分
personList.stream().sorted().forEach(System.out::println);
personList.stream().sorted((o1, o2) ->o2.getAge()-o1.getAge()).forEach(System.out::println);
}
3.5.1.3 终止操作
/**
* 终止操作
* 终端终止会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List 、rInteger,甚至是void 。
* 流进行了终止操作后,不能再次使用
*/
/**
* 查找与收集
* allMatch(Predicate p) 检查是否匹配所有元素
* anyMatch(Predicate p) 检查是否至少匹配一个元素
* noneMatch(Predicate p) 检查是否没有匹配所有元素(一个都不满足时结果为true)
* findFirst() 返回第一个元素
* findAny() 返回当前流中的任意元素
* count() 返回流中元素总数
* max(Comparator c) 返回流中最大值
* min(Comparator c) 返回流中最小值
* forEach(Consumer c) 内部迭代 使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代 它帮你把迭代做了
*/
@Test
publicvoidtestEnding(){
List<Integer>integers=Arrays.asList(1,2,3,4);
booleanb=integers.stream().allMatch(integer->integer<5);
System.out.println("b = "+b);
booleanb1=integers.stream().anyMatch(integer->integer==2);
System.out.println("b1 = "+b1);
booleanb2=integers.stream().noneMatch(integer->integer<1);
System.out.println("b2 = "+b2);
Optional<Integer>any=integers.stream().findFirst();
System.out.println("any = "+any);
Optional<Integer>any1=integers.stream().findAny();
System.out.println("any1 = "+any1);
}
/**
* 终止操作:归约
* 归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作
* reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
* reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional<T>
*/
@Test
publicvoidtestGy(){
//数据源
List<Integer>list=Arrays.asList(1, 3, 2, 8, 11, 4);
// //总和
// Optional<Integer> reduce = list.stream().reduce((integer, integer2) -> integer + integer2);
// //从optional中取值:get()
// Integer integer = reduce.get();
// System.out.println("integer = " + integer);
// //乘积
// Optional<Integer> reduce2 = list.stream().reduce((integer1, integer2) -> integer1 *integer2);
// Integer integer1 = reduce2.get();
// System.out.println("integer1 = " + integer1);
// //最大值
// Optional<Integer> reduce3 = list.stream().reduce(Integer::max);
// Integer integer2 = reduce3.get();
// System.out.println("integer2 = " + integer2);
// //最小值
// Optional<Integer> reduce4 = list.stream().reduce(Integer::min);
// Integer integer3 = reduce4.get();
// System.out.println("integer3 = " + integer3);
Integerreduce=list.stream().reduce(5, (integer, integer2) ->integer+integer2);
System.out.println("reduce = "+reduce);
}
/**
* 收集
* collect(Collector c) 将流转换为其他形式。接收一个Collector 接口的实现,用于给Stream 中元素做汇总的方法
* Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、 Set、Map)。 另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例
*/
@Test
publicvoidtestCollect(){
//数据源
List<String>strings= Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//收集到集合中
List<String>collect1=strings.stream().sorted((o1, o2) ->o2.length() -o1.length())
.collect(Collectors.toList());
System.out.println("collect1 = "+collect1);
//收集到Set集合中(无重复元素)
Set<String>collect2=strings.stream().collect(Collectors.toSet());
System.out.println("collect2 = "+collect2);
//把流中元素收集到创建的集合中
ArrayList<String>collect3=strings.stream().collect(Collectors.toCollection(ArrayList::new));
System.out.println("collect3 = "+collect3);
// 计算流中元素的个数
Longcollect=strings.stream().collect(Collectors.counting());
System.out.println("流中元素的个数 = "+collect);
//收集流中元素的长度统计值
IntSummaryStatisticscollect4=strings.stream().collect(Collectors.summarizingInt(value->value.length()));
System.out.println("流中元素的长度统计值 = "+collect4);
//collect4 = IntSummaryStatistics{count=7, sum=15, min=0, average=2.142857, max=4}
//计算流中元素长度的平均值
Doublecollect5=strings.stream().collect(Collectors.averagingInt(value->value.length()));
System.out.println("流中元素长度的平均值 = "+collect5);
//收集流中元素长度的总和
Integercollect6=strings.stream().collect(Collectors.summingInt(value->value.length()));
System.out.println("流中元素长度总和 = "+collect6);
//将流中每个字符串元素连接
Stringcollect7=strings.stream().collect(Collectors.joining());
System.out.println("链接之后的字符串为 = "+collect7);
//根据选择器比较最大值:自然顺序比较得到的就是最大值,如果采用倒序得到的就是最小值,当然也可以直接使用minBy()方法的自然顺序得到最小值
Optional<String>collect8=strings.stream().collect(Collectors.maxBy((o1, o2) ->o1.length()-o2.length()));
System.out.println("长度最大的值为 = "+collect8);
//从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
//BinaryOperator就是第三个参数
Integercollect9=strings.stream().collect(Collectors.reducing(0, value->value.length(), (integer, integer2) ->integer+integer2));
System.out.println("流中元素长度总和 = "+collect9);
//转换函数返回的类型:包裹另一个收集器,对其结果转换函数
Integercollect10=strings.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
System.out.println("元素收集到集合中,集合的长度为 = "+collect10);
//按照长度分组
//返回值类型Map<K,V> 按照k的值进行分组,这里按照长度进行分组,那么k就是长度的值
Map<Object, List<String>>collect11=strings.stream().sorted((o1, o2) ->o2.length() -o1.length())
.collect(Collectors.groupingBy(s->s.length()));
System.out.println("collect11 = "+collect11);
//collect1 = {0=[, ], 2=[bc], 3=[abc, efg, jkl], 4=[abcd]}
//根据字符串是否为空进行分区
Map<Boolean, List<String>>collect12=strings.stream().collect(Collectors.partitioningBy(s->s==""));
System.out.println("根据字符串是否为空进行分区 = "+collect12);
//根据字符串是否为空进行分区 = {false=[abc, bc, efg, abcd, jkl], true=[, ]}
}
收集操作具体方法与实例如下表:
方法 | 返回类型 | 作用 |
toList | List<T> | 把流中元素收集到List |
toSet | Set<T> | 把流中元素收集到Set |
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
counting | Long | 计算流中元素的个数 |
summingInt | Integer | 对流中元素的整数属性求和 |
averagingInt | Double | 计算流中元素Integer属性的平均值 |
summarizingInt | IntSummaryStatics | 收集流中Integer属性的统计值。如:平均值 |
joining | String | 连接流中每个字符串 |
maxBy | Optional<T> | 根据比较器选择最大值 |
minBy | Optional<T> | 根据比较器选择最小值 |
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
collectingAndThen | 转换函数返回的类型 | 包裹另一个接收器,对其结果转换函数 |
groupingBy | Map<K,List<T>> | 根据某属性值对流分组,属性为k,结果为v |
partitioningBy | Map<Boolean,List<T>> | 根据true或false进行分区 |
3.6 Optional类
到目前为止,空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
代码示范:
publicclassTestOptional {
/**
* 创建Optional类对象的方法:
* Optional.of(T t): 创建一个Optional实例,创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException 。
* Optional.empty() : 创建一个空的Optional实例
* Optional.ofNullable(T t):创建Optional实例,如果指定的值为null,则返回一个空的Optional。
*/
@Test
public void testNullable(){
Girlgirl=newGirl();
Optional<Girl>optionalGirlOf=Optional.of(girl);
System.out.println("optionalGirlOf = "+optionalGirlOf.get().getName());
Girlgirl1=null;
Optional<Girl>optionalGirl2=Optional.empty();
System.out.println("optionalGirl2 = "+optionalGirl2);
Optional<Girl>optionalGirl=Optional.ofNullable(girl1);
System.out.println("optionalGirl = "+optionalGirl);
}
/**
* 判断Optional容器中是否包含对象:
* boolean isPresent(): 检查Optional实例是否包含值。如果值存在返回true,否则返回false
* void ifPresent(Consumer<? super T> consumer) :检查Optional实例是否有值,如果实例值非null,就执行lambda表达式,否则不处理
*/
@Test
public void testPresent() {
GirlgirlNull=null;
Girlgirl=newGirl();
Optional<Girl>girl1=Optional.ofNullable(girl);
Optional<Girl>girlNull1=Optional.ofNullable(girlNull);
booleanpresent=girl1.isPresent();
System.out.println("该Optional(present)类中是否包含对象 = "+present);
booleanpresentNull=girlNull1.isPresent();
System.out.println("该Optional(presentNull)类中是否包含对象 = "+presentNull);
girl1.ifPresent(girl2->System.out.println("girl = "+girl2));//girl = Girl(name=null)
girlNull1.ifPresent(girl2->System.out.println("girl = "+girl2));//未执行输出语句
}
/**
* 获取Optional容器的对象:
* T get(): 如果调用对象包含值,返回该值,否则抛异常
* T orElse(T other):如果有值则将其返回,否则返回指定的other对象。
* T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
* T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
*/
@Test
public void testGet(){
Girlgirl1=null;
Optional<Girl>optionalGirl=Optional.ofNullable(girl1);
//Girl girl = optionalGirl.get();
Girlgirl=optionalGirl.orElse(newGirl("test"));
System.out.println("girl = "+girl);
}
/**
* 不使用Optional:找到一个男孩他的女朋友的名字
*/
@Test
public void testNoOptional(){
//空指针异常
// Boy boy = null;
// String name = boy.getGirl().getName();
// System.out.println("name = " + name);
Stringname="test";
Boyboy=null;
if(boy!=null){
Girlgirl=boy.getGirl();
if (girl!=null){
name=girl.getName();
System.out.println("name = "+name);
}
}
}
/**
* 使用Optional:找到一个男孩他的女朋友的名字
*/
@Test
public void testUseOptional() {
//Boy boy = null;//"Boy is null"
//Boy boy = new Boy();//"Girl is null"
Boyboy=newBoy(newGirl("Yes"));//Yes
Optional<Boy>optionalBoy=Optional.ofNullable(boy);
//如果boy为空,新建一个boy,并给这个新建的boy一个女朋友
Boyboy1=optionalBoy.orElse(newBoy(newGirl("Boy is null")));
//如果boy不为空,就找他原本女朋友的名字
Girlgirl=boy1.getGirl();
//如果他没有女朋友,就给他新建一个女朋友
Optional<Girl>girl1=Optional.ofNullable(girl);
Girlgirl2=girl1.orElse(newGirl("Girl is null"));
System.out.println("girl2 = "+girl2.getName());
}
}