Java学习之Stream流、方法引用
- 1. Stream流
- 1.1概述
- 1.2 集合获取Stream的方式
- 1.2.1 Collection(单列集合)都可以通过stream取得默认的流
- 1.2.2 数组获取Stream的流
- 1.3 Stream的一些常用方法
- 2.方法引用
- 2.1 概述
- 2.2 通过对象引用成员方法
- 2.3 通过类名引用成员方法
- 2.4 通过super引用成员方法
- 2.5 通过this引用成员方法
- 2.6 类的构造器引用
- 2.7 数组的构造器的引用
1. Stream流
1.1概述
说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
Stream是Java8最新加入的接口,Stream(流)是一个来自数据源的元素队列元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源 流的来源。 可以是集合,数组 等。
- stream特点:
- Stream流属于管道流,只能使用一次,第一个stream流使用完毕,数据就会转移到下一个stream流上
- 而第一个stream流使用完毕就会关闭,所以第一个流就不能再调用方法了
- Stream的基础特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道,如同流式风格(fluent style)。 这样做可以对操作进行优化,比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭 代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。
使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换), 这就允许对其操作可以 像链条一样排列,变成一个管道。
1.2 集合获取Stream的方式
1.2.1 Collection(单列集合)都可以通过stream取得默认的流
//单列集合获取流(Collection),所有单列集合获取Stream流的方式都一样(如 List,Set等)
Collection<String> collection = new ArrayList<String>();
//直接Collection中的stream方法来获取流
Stream<String> stream = collection.stream();
//双列集合怎么获取流
// 首先我们需要先把双列集合转换成为单列集合
Map<String, String> map = new HashMap<>();
//1.第一种是获取双列集合的key集合,然后在获取Stream流
Set<String> keySet = map.keySet();
Stream<String> stream1 = keySet.stream();
//2.通过获取双列集合的值集合,然后在获取stream
Collection<String> values = map.values();
Stream<String> stream2 = values.stream();
//获取双列集合的键值对结合
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String,String>> stream3 = entries.stream();
1.2.2 数组获取Stream的流
是直接通过Stream接口的of()静态方法 获取Stream流, 因为Stream.of(…)的参数是一个可变参数, 底层就是数组,所以我们直接传递数组可以,或则直接传递参数
//数组是如何获取Stream流的
String[] strArray = {"sda","sa","ssss"};
//1. 通过Stream接口的of()静态方法 获取Stream流
Stream<String> stream4 = Stream.of(strArray);
Stream<String> stream5 = Stream.of("123","234","345");
1.3 Stream的一些常用方法
- 流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方 法均为延迟方法。)
- 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调 用。本小节中,终结方法包括 count 和 forEach 方法。
实例代码:
public class StreamDemo2 {
public static void main(String[] args) {
ArrayList<String> nameArrayList = new ArrayList<>();
nameArrayList.add("张三");
nameArrayList.add("李四");
nameArrayList.add("王五");
nameArrayList.add("赵六");
//streamTestForEach(nameArrayList);
//streamTestFilter(nameArrayList,"王五");
//把字符串集合 映射成Integer类型的集合
ArrayList<String> arrayList2 = new ArrayList<>();
arrayList2.add("123");
arrayList2.add("456");
arrayList2.add("789");
//streamTestMap(arrayList2);
//使用stream流来获取集合的长度
//System.out.println("集合的个数======" + streamTestCount(arrayList2));
//获取集合的前几个元素
//streamTestLimit(nameArrayList);
//跳过集合的前面几个元素
//streamTestSkip(nameArrayList);
//使用stream把两个集合组合起来(相同类型的)
streamTestConcat(nameArrayList,arrayList2);
}
/*
测试Stream中的forEach方法
forEach: 该方法属于一个终结方法,就调用该方法之后,得到了结果在无法调用其他方法了
虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同。
该方法接收一个 Consumer 接口函数(消费型接口),会将每一个流元素交给该函数进行处理。
使用Stream来遍历集合
*/
public static void streamTestForEach(ArrayList<String> arrayList){
arrayList.stream().forEach(s -> System.out.println("name====" + s));
}
/*
使用Stream流来 去掉集合中的某个元素 并遍历集合
filter()筛选方法
可以通过 filter 方法将一个流转换成另一个子集流,该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
*/
public static void streamTestFilter(ArrayList<String> arrayList, String name){
arrayList.stream().filter(s -> !s.equals(name)).forEach(s -> System.out.println("name====" + s));
}
/*
map:如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
映射
例子: 把string类型的集合转换成 Integer类型的集合
*/
public static void streamTestMap(ArrayList<String> arrayList) {
arrayList.stream().map(str -> Integer.parseInt(str)).forEach(s -> System.out.println(s));
}
/*
使用stream来获取集合的长度,正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:
long count();
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)
该放放也是一个终结方法
*/
public static long streamTestCount(ArrayList<String> arrayList) {
return arrayList.stream().count();
}
/*
Stream<T> limit(long maxSize); : 方法可以对流进行截取,只取用前n个。
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
*/
public static void streamTestLimit(ArrayList<String> arrayList) {
arrayList.stream().limit(arrayList.size()-1).forEach(str -> System.out.println(str));
}
/*
Stream<T> skip(long n):如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流.
*/
public static void streamTestSkip(ArrayList<String> arrayList) {
arrayList.stream().skip(arrayList.size()-1).forEach(str -> System.out.println(str));
}
/*
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
*/
public static void streamTestConcat(ArrayList<String> arrayList, ArrayList<String> arrayList2) {
Stream.concat(arrayList.stream(), arrayList2.stream()).forEach(str -> System.out.println(str));
}
}
2.方法引用
2.1 概述
- 方法引用:使用方法引用优化Lambda表示式的前提
- 1.对象是已经存在的
- 2.成员方法也是已经存在的
只有满足这个两个条件,我们才可以使用方法引用优化Lambda表达式
::
: 是方法引用的引用符 而它所在的表达式被称为方法引用- 示例:
/*
* 定义一个函数式接口
*/
public interface Printable {
//打印字符串的抽象方法
public abstract void print(String s);
}
public class MethodDemo {
public static void main(String[] args) {
//调用方法,由于是一个函数式接口,所以可以使用Lambda表达式
testPrintable(str -> System.out.println(str));
/*
分析:
Lambda表达式的目的,打印参数传递的字符串
把参数str传递给 System.out对象,调用out对象中的println()方法进行了输出
注意:
1.System.out对象已经存在的
2.println()方法是已近存在的
所以我们可以使用方法引用来优化Lambda表达式
可以使用System.out直接应用println()方法
格式:
对象名::方法名
:: : 是方法引用的引用符 而它所在的表达式被称为方法引用
如果Lambda要表达的函数方案已经存在于某个方 法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
Lambda表达式:testPrintable(str -> System.out.println(str));
方法引用写法:testPrintable(System.out::println);
以上两种写法完全等效
注意: Lambda表达式中传递的参数一定是方法引用中的那个方法可以接受的类型,否则会抛出异常
在这里就是 str的参数类型 一定是println方法中可以接受的数据类型
*/
//使用方法引用优化Lambda表达式
testPrintable(System.out::println);
}
public static void testPrintable(Printable printable) {
printable.print("HelloWorld");
}
}
2.2 通过对象引用成员方法
/*
* 定义一个函数式接口
*/
public interface Printable {
//打印字符串的抽象方法
public abstract void print(String s);
}
public class PrintDemo {
//打印字符串方法
public void print(String str) {
System.out.println(str.toUpperCase());
}
}
public class MethodDemo {
public static void main(String[] args) {
//调用printString,方法参数是一个函数式接口
printString(s -> {
//创建对象,然后调用对象的方法打印字符串
PrintDemo obj = new PrintDemo();
obj.print(s);
});
//使用方法引用来优化Lambda表达式 对象名::方法名
//对象存在 PrintDemo obj = new PrintDemo();
//成员方法也存在 ,所以符合方法引用的条件
PrintDemo obj = new PrintDemo();
printString(obj::print);
}
public static void printString(Printable printable){
printable.print("Hello");
}
}
2.3 通过类名引用成员方法
@FunctionalInterface
public interface Calcuble {
/*
* 计算一个值的绝对值
*/
public abstract int calculateABS(int a);
}
public class MethodDemo {
public static void main(String[] args) {
//使用Lambda表达式来写
int index = method(-10, count -> Math.abs(count));
System.out.println("index======" + index);
/*
使用静态方法优化Lambda表达式
Math类是存在的
abs计算绝对值的方法是存在
所以我们可以使用类名引用静态方法来优化
格式:
类名::静态方法名
*/
int index2 = method(-20,Math::abs);
System.out.println("方法引用优化: index2====" + index2);
}
public static int method(int a, Calcuble calcuble){
//调用接口中的方法 计算绝对值
return calcuble.calculateABS(a);
}
}
2.4 通过super引用成员方法
@FunctionalInterface
public interface Greetable {
public abstract void greet();
}
/*
定义父类
*/
public class Human {
public void sayHello(){
System.out.println("Hello 我是Human");
}
}
/*
定义子类
*/
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("Hello,我是man");
}
public void show() {
//调用方法
method(() -> {
Human human = new Human();
human.sayHello();
});
method(() -> {
//因为是父子关系,所以我们可以通过关键字super代表父类,直接调用父类的方法
super.sayHello();
});
/*
如何使用super引用类成员的方法来优化
super是已经存在的
父类的成员方法是已经存在的
所以我们可以使用super引用类的成员方法优化
格式:
super::类的成员方法名称
*/
method(super::sayHello);
}
public void method(Greetable greetable){
greetable.greet();
}
public static void main(String[] args) {
new Man().show();
}
}
2.5 通过this引用成员方法
public interface MyInterface {
public abstract void sayHello();
}
public class MethodDemo {
public static void main(String[] args) {
new MethodDemo().show();
}
public static void test(MyInterface myInterface){
myInterface.sayHello();
}
public void show(){
//一般调用方法
test(() -> {
MethodDemo methodDemo = new MethodDemo();
methodDemo.buy();
});
//我们可以通过this调用当前的方法
test(() -> {
this.buy();
});
/*
我们可以通过this关键字使用this引用类的成员方法的方式来优化Lambda
格式:
this::类的成员方法名
*/
test(this::buy);
}
public void buy() {
System.out.println("想买什么买什么");
}
}
2.6 类的构造器引用
public interface PersonBuilder {
//根据传入的名称,返回一个person对象
public abstract Person builder(String name);
}
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class MethodDemo {
public static void main(String[] args) {
//调用方法-> 使用Lambda表达式调用
printPerson("张三",name -> new Person(name));
//使用方法引用——类的构造器方法引用
//格式: 类名::new
printPerson("李四",Person::new);
}
public static void printPerson(String name,PersonBuilder personBuilder) {
Person person = personBuilder.builder(name);
System.out.println(person.getName());
}
}
2.7 数组的构造器的引用
public interface ArrayBuilder {
//根据数组的长度创建一个数据
public abstract int[] builder(int length);
}
public class MethodDemo {
public static void main(String[] args) {
//Lambda表达式
int[] temp = creatArray(10,s -> new int[s]);
System.out.println(temp.length);
/*
已知创建的是int[] 数组
然后数组的长度已知
可以使用方法引用来优化Lambda表达式
格式:
int[](数组类型)::new
*/
int[] temp1 = creatArray(20,int[]::new);
System.out.println(temp1.length);
}
public static int[] creatArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builder(length);
}
}