Stream和方法引用
1.Stream流
1.for循环带来的弊端
- 在jdk8中,lambda专注于做什么,而不是怎么做
- for循环的语法就是怎么做
- for循环的循环体才是做什么
遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
集合存储案列:
import java.util.ArrayList;
import java.util.List;
public class Demo{
public static void main(String[] args){
// 创建一个list集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("赵强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
list<String> listA = new ArrayList<>();
for(String str:list){
if(str.startsWidth("张")){
listA.add(str);
}
}
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
List<String> ListB = new ArrayList<>();
for(String s:listA){
if(s.length()==3){
listB.add(s);
}
}
}
}
2.使用Stream的更优写法
import java.util.ArrayList;
import java.util.List;
public class Demo{
public static void main(String[] args){
// 创建一个list集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("赵强");
list.add("张三丰");
list.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
}
}
流思想
Stream(流)是一个来自数据源的元素队列
- 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,二十按需计算。
- 数据源流的来源,可以是 集合, 数组等。
和以前的Collection操作不同,Stream操作还有两个基础的特征:
Pipelining:中间操作都会返回流对象本省。着多个操作可以串联一个管道,如同流式风格,可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
内部迭代:以前对集合变量都是通过Iterator或者是增强for循环的方式,显示在集合外部进行迭代,这种就是外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。
使用流的步骤
获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。
3.获取流
获取一个流的方法非常简单,有以下几种常用的方式:
- 所有的Collection集合都可以通过Stream默认方法获取流;
- default Stream<E> stream()
- Stream接口的静态方法of可以获取数据对应的流。
- static <T> Stream<T> of(T.. values)
- 参数是一个可变参数,那么可以传递一个数组
public class Demo{
public static void main(String[] args){
// 把集合转为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,春初到Set集合中
Set<String> KeySet = map.KeySet();
Stream<String> stream3 = KeySet.stream();
//获取值,存储带一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1,2,3,4,5,6);
//可变参数传递数组
Integer[] arr = {1,2,3,4,5,6};
Stream<Integer> stream7 = Stream.of(arr);
Strng[] arr2 = {"a", "bn", "cd"};
Stream<String> stream8 = Stream.of(arr2);
}
}
4.常用方法
forEach方法
Stream流常用方法 forEach
- void forEach(Consumer<? super T> action):
- 该方法节后一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
- Consumer接口是一个消费型的函数式接口, 可以传递Lambda表达式,消费数据
简单记:
- forEach方法,用来遍历流中的数据
- 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
public class Demo{
public class void main(String[] args){
// 获取一个Stream流
Stream<String> stream = Stream.of("张三","李四","王五","赵六","田七");
// 使用stream流中的方法forEach对Stream流中的数据进行遍历
/* stream.forEach((String name)->{
System.out.println(name);
});*/
stream.forEach(name->system.out.println(name));
}
}
filter方法
用于对Stream流中的数据进行过滤
- Stream<T> filter(Predicate<? super T> predicate);
- filter方法中的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
- Predicate中的抽象方法:
- boolean test(T t)
public class Demo{
public static void main(String[] args){
// 创建一个Stream流
Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
// 对Stream中的元素进行过滤,只要姓张的人
Stream<String> stream2 = stream.filter((String name)->{return name.stratsWith("张");});
// 遍历Stream2
stream2.forEach(name->System.out.println(name));
/*
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用完毕方法,数据就会流转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
IllegalStateException: stream has already been operated upon or closed
*/
// 遍历stream流
stream.forEach(name-> System.out.println(name));
}
}
map方法
用于类型转换
- 如果需要将流中的元素映射到另一个流中,可以使用map方法
- <R> Stream<R> map(Function <? super T, ? extends R> mapper);
- 该接口需要一个Function函数式接口参数,可以将当前流中的T类型转换为另一种R数据的流
- Function的抽象方法
- R apply(T t);
public class Demo{
public static void main(String[] args){
// 获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4");
// 使用map方法,把字符串类型的整数,转换为Integer类型的整数
Stream<Integer> stream2 = stream.map((String s)-> {return Integer.parseInt(s);});
// 变量stream2
stream2.forEach(i -> System.out.println(i));
}
}
count 方法
用于统计Stream流中元素的个数
- long count();
- count 方法是一个终结方法,返回值是一个long类型的整数,只能使用一次,使用完之后,不可以使用流的其他方法
public class Demo{
public static void main(String[] args){
//获取一个Stream流
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Stream<Integer> Stream = list.stream();
long count = stream.count();
System.out.println(count); // 4
}
}
limit方法
用于截取流中的元素
- limit方法可以对流进行截取,支取前n个
- Stream<T> limit(long maxSize);
- 参数是一个int类型。如果当前的长度大于参数则进行截取, 否则不进行截取
- limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
public class Demo{
public static void main(String[] args){
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用limit放啊进行截取,只要前三个元素
Stream<String> stream2 = stream.limit(3);
// 遍历操作
stream2.forEach(name->System.out.println(name));
}
}
skip方法
用于跳过元素,截取后面的元素
- 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
- Stream<T> skip(long n);
- 如果流的当前长度大于n,则跳过钱n个, 否则得到一个长度为0的空流.
public class Demo{
public static void main(String[] args){
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用skip方法跳过前面三个元素
Stream<String> stream2 = stream.skip(3);
// 遍历stream2
stream2.forEach(name-> System.out.println(name));
}
}
concat方法
用于把流组合在一起
- 如果有两个流,希望合并成一个流,那么可以使用Stream接口的静态方法concat
- static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T>b)
public class Demo{
public static void main(String[] args){
//创建一个Stream流
Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
// 获取一个Stream流
String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
Stream<String> stream2 = Stream.of(arr);
// 把以上两个流组成为一个流
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(name->System.out.println(name));
}
}
2.方法引用
1.冗余的Lambda
定义一个打印的函数式接口
@FunctionalInterface
public interface Printable {
//定义字符串的抽象方法
void print(String s);
}
打印字符串
public class Demo{
// 定义一个方法,参数传递的是Printable的接口,对字符串进行打印
public static void printString(Printable p){
p.print("hello world");
}
public static void main(String[] args){
//调用printString方法,方法的参数Printable是一个函数式的接口,所以可以传递lambda函数
printString((s)->{
System.out.println(s);
});
/*
分析:
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出
注意:
1.System.out对象是已经存在的
2.println方法也是已经存在的
所以我们可以使用方法引用来优化Lambda表达式
可以使用System.out方法直接引用(调用)println方法
*/
printString(System.out::println);
}
}
2.分析
这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out 对象中的 println(String) 方法。既然Lambda希望做的事情就是调用 println(String) 方法,那何必自己手动调 用呢?
3.使用方法引用
public class Demo{
public static void printString(Printable p){
p.print("Hello world");
}
public static void main(String[] args){
// 调用方法使用方法的引用
printString(System.out::println);
}
}
4.方法引用符
双冒号:: 为引用运算符,称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,name我们可以使用双冒号来引用该方法作为lambda的替代者。
语法分析
例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于
printString 方法的函数式接口参数,对比下面两种写法,完全等效:
- Lambda表达式写法: s -> System.out.println(s);
- 方法引用写法: System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。
第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全一
样,而第二种方法引用的写法复用了已有方案,更加简洁。
注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常
5.通过对象名引用成员方法
定义类
public class MethodRefObject{
public void printUpperCase(String str){
System.out.println(str.toUpperCase());
}
}
函数式接口定义
@FunctionalInterface
public interface Printable{
void print(String str);
}
那么当需要使用这个printUpperCase成员方法来代替Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例。则可以通过对象名引用成员方法,代码:
/*
通过对象名引用成员方法
使用前提:
对象名是已经存在的,成员方法也存在
*/
public class Demo{
// 定义一个方法,方法的参数传递Printable接口
public static void printString(Ptrintable p){
p.print("hello");
}
public static void main(String[] args){
// 创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
printString(obj::printUpperCaseString);
}
}
6.通过类名称引用静态方法
由于在java.lang.Math类中已经存在存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。
函数式接口:
@FunctionalInterface
public interface Calcable{
int calc(int num);
}
public class Demo{
public static int method(int number, Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args){
// 调用method方法,传递计算绝对值
int number = method(-10, Math::abs);
System.out.println(number2);
}
}
7.通过super引用成员方法
定义见面的函数式接口
@FunctionalInterface
public interface Greetable{
//定义一个见面的方法
void greet();
}
定义父类
public class Human{
//定义一个sayHello的方法
public void sayHello(){
System.out.println("hello,我是Human");
}
}
定义子类
public class Man extends Human{
//子类重写父类的sayHello方法
@Override
public void sayHello(){
System.out.println("hello 我是Man");
}
//定义一个方法参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
public static void main(String[] args){
new Man().show();
}
}
// hello 我是Human
8.通过this引用成员方法
定义购买的函数式接口
@FunctionalInterface
public interface Richable{
// 定义一个想买什么就买什么的方法
void buy();
}
定义一个类
使用this引用本类的成员方法
public class Husband{
// 定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环买一套四合院");
}
// 定义一个结婚的方法参数传递Richable接口
public void marry(Richable r){
r.buy();
}
//定义一个非常高兴的方法
public void soHappy(){
marry(this::buyHouse);
}
public static void main(String[] args){
new Husband().soHappy();
}
}
9.类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示
定义Person类
public class Person{
private String name;
public Person(String name){
this.name=name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}
Person对象的函数式接口
@FunctionalInterface
public interface PersonBuilder{
Person builderPerson(String name);
}
Demo
public class Demo{
//定义一个方法,传递的是姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name, PersonBuilder pb){
Person person = pb.builderPersom(name);
System.out.println(person.getName());
}
public static void main(String[] args){
/*构造方法new Person(String name) 已知
创建对象已知 new
就可以使用Person引用new创建对象*/
//使用Person类的带参构造方法,通过传递的姓名创建对象
printName("古力娜扎", Person::new);
}
}
10.数组的构造器引用
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
定义一个创建数组的函数式接口
@FunctionalInterface
public class ArrayBuilder{
//定义一个创建int类型数组的方法,参数传递的是数组的长度,返回创建的int类型数组
int[] builderArray(int length);
}
Demo
public class Demo{
/*
定义一个方法
方法的参数传递创建数组的长度和ArrayBuilder接口
方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
*/
public static int[] createArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args){
/*
使用方法引用优化Lambda表达式
已知创建的就是int[]数组
数组的长度也是已知的
就可以使用方法引用
int[]引用new,根据参数传递的长度来创建数组
*/
int[] array = createArray(10, int[]::new);
System.out.println(Arrays.toString(array));
System.out.println(array.length);
}
}
Stream和方法引用的更多相关文章
- Stream流方法引用
一.对象存在,方法也存在,双冒号引用 1.方法引用的概念: 使用实例: 1.1先定义i一个函数式接口: 1.2定义一个入参参数列表有函数式接口的方法: 1.3调用这个入参有函数式接口的方法: lamb ...
- Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API
新语法 方法引用Method references Lambda语法 Lambda语法在AndroidStudio中报错 Stream API 我正参加2016CSDN博客之星的比赛 希望您能投下宝贵 ...
- JavaSE复习(七)Stream流和方法引用
Stream流 全新的Stream概念,用于解决已有集合类库既有的弊端. 传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们 ...
- Stream流、方法引用
Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 Stream流.方法引用 ... ...
- Java8特性之Lambda、方法引用以及Stream流
Java 8 中的 Streams API 详解:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/ Java笔记——Jav ...
- JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用
jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default Lambda表达式 L ...
- 2020你还不会Java8新特性?方法引用详解及Stream 流介绍和操作方式详解(三)
方法引用详解 方法引用: method reference 方法引用实际上是Lambda表达式的一种语法糖 我们可以将方法引用看作是一个「函数指针」,function pointer 方法引用共分为4 ...
- java基础第11期——Stream流、方法引用、junit单元测试
1.Stream流 Stream流与io流是不同的东西,用于解决集合类库已有的弊端, 1.1 获取Stream流: Collection集合的Stream方法,注意Map集合要经过转化 default ...
- Java8新特性代码示例(附注释)- 方法引用,Optional, Stream
/** * java8中的函数式接口,java中规定:函数式接口必须只有一个抽象方法,可以有多个非抽象方法,同时,如果继承实现了 * Object中的方法,那么也是合法的 * <p> * ...
随机推荐
- 《ElasticSearch6.x实战教程》正式推出(附图书抽奖)
经过接近1个月的时间,ElasticSearch6.x实战教程终于成册.这本实战教程小册有很多不足(甚至可能有错误),也是第一次完整推出一个系列的教程. 1年前,我开始真正接触ES,在此之前仅停留在知 ...
- redux、react-redux、redux-thunk、redux-saga使用及dva对比
一.redux使用 Redux的核心概念其实很简单:将需要修改的state都存入到store里,发起一个action用来描述发生了什么,用reducers描述action如何改变state tree ...
- [原创]MySQL数据库查询和LVM备份还原学习笔记记录
一.查询语句类型: 1)简单查询 2)多表查询 3)子查询 4)联合查询 1)简单查询: SELECT * FROM tb_name; SELECT field1,field2 FROM tb_nam ...
- Oracle 开发使用笔记一
1 前段时间换了新公司,工作一直很忙,没什么时间做总结! 关于几个知识点简单做下总结: 1绑定变量的使用: 1)使用几次,在后面的using中要声明几次,使用的顺序要对应声明的顺序 2 存储过程中执行 ...
- 个人永久性免费-Excel催化剂功能第32波-空行空列批量插入和删除
批量操作永远是效率提升的王道,也是Excel用户们最喜欢能够实现的操作虽说有些批量操作不一定合适Excel的最佳实践操作,但万千世界,无奇不有,特别是在国人眼中领导最大的等级森严的职场环境下.Exce ...
- 人民网基于FISCO BCOS区块链技术推出“人民版权”平台
FISCO BCOS是完全开源的联盟区块链底层技术平台,由金融区块链合作联盟(深圳)(简称金链盟)成立开源工作组通力打造.开源工作组成员包括博彦科技.华为.深证通.神州数码.四方精创.腾讯.微众银行. ...
- JWT(JSON WEB TOKEN)实例
JWT的工具类 加密解密工具 package top.wintp.crud.util; import com.auth0.jwt.JWTSigner; import com.auth0.jwt.JWT ...
- AppBoxFuture: 二级索引及索引扫描查询数据
数据库索引对于数据查询的重要性不可言喻,因此作者在存储层实现了二级索引,以及利用索引进行扫描的功能.目前仅实现了分区表与非分区表的本地索引(数据与索引共用一个Raft组管理),全局索引及反向索引待 ...
- python中的内存机制
首先要明白对象和引用的概念 (例子:a=1, a为引用,1为对象,对象1的引用计数器为1,b=1此时内存中只有一个对象1,a,b都为引用,对象的引用计数器此时为2,因为有两个引用) a=1,b=1 i ...
- Pinyin4j简单使用教程
Pinyin4j是一个流行的Java库,支持中文字符和拼音之间的转换,拼音输出格式可以定制,在项目中经常会遇到需求用户输入汉字后转换为拼音的场景,这时候Pinyin4j就可以派上用场 有自己私服的可以 ...