1.Stream流
1.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(流)是一个来自数据源的元素队列
和以前的Collection操作不同,Stream操作还有两个基础的特征:
Pipelining:中间操作都会返回流对象本省。着多个操作可以串联一个管道,如同流式风格,可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
内部迭代:以前对集合变量都是通过Iterator或者是增强for循环的方式,显示在集合外部进行迭代,这种就是外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。
使用流的步骤
获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。
3.获取流
获取一个流的方法非常简单,有以下几种常用的方式:
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
简单记:
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流中的数据进行过滤
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方法
用于类型转换
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流中元素的个数
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方法
用于截取流中的元素
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方法
用于跳过元素,截取后面的元素
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方法
用于把流组合在一起
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之手,继而传递给 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);
}
}
原文:https://www.cnblogs.com/liudemeng/p/11363770.html