Lambda表达式
又称为函数式编程
面向对象思想: 想要做什么事情,就需要创建这个类的对象,通过对象来完成某些事情
函数式的思想:忽略了面向对象复杂的语法,强调做什么,而不是以什么形式做
面向对象的思想实现多线程
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程在运行");
}
}
public class DemoRunnable {
public static void main(String[] args) {
//创建Runnable实现类对象
Runnable run = new RunnableImpl();
//新建Thread类对象,传入Runnable实现类对象
Thread t = new Thread(run);
//调用start方法
t.start();
System.out.println(Thread.currentThread().getName() + "线程在运行");
//简化写法,使用内部类的写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程在运行");
}
}).start();
}
}
代码分析:
1,Thread类需要Runnable接口作为参数,其中抽象的run()方法是用来指定任务内容的核心
2,为了指定run()方法的内容,不得不去实现Runnable接口,写实现类
3,为了省去定义一个实现类的麻烦,使用了匿名内部类去实现
4,在匿名内部类中,必须要重写run()方法,方法名、返回值等内容都需要重写一遍
5,在整个代码中,核心的内容是方法体,以前的写法,不得不把一些其他的信息都写一遍
6,我们想要的其实是run()方法的核心代码的运行,不得不去做了其他的事情
使用lambda表达式,可以解决以上的问题
//lambda表达式的写法
new Thread(
()->{System.out.println(Thread.currentThread().getName() + "线程在运行");}
).start();
对比几种实现方式:
1,实现Runnable接口,在main中,new Thread传入接口实现类,调用start方法,实现多线程
语法比较繁琐,还需要创建实现类,比较麻烦
2,匿名内部类的写法,在new Thread的时候,传入Runnable的实现
可以省去一个实现类的定义,但是语法比较复杂,看不太懂
3,lambda表达式的写法
比较简洁,没学习过也看不太懂
Lambda表达式的格式:
由三个部分组成
1,一些参数
2,一个箭头
3,一段代码
格式: (参数)->{方法的具体实现内容}
lambda表达式一般只能使用在,实现一些函数式接口的时候使用,
() :表示对应了这个接口中的抽象方法,方法如果有参数,那么就需要传入参数,没有参数就空着,多个参数,逗号隔开
->:传递的意思,把参数传递给方法体
{}:重写接口中方法的方法体
函数式接口【重点】
经过前面的学习,相信大家对于Lambda表达式已经有了初步的了解。
总结一下:
Lambda表达式是接口的匿名内部类的简写形式
接口必须满足:有且仅有一个抽象方法
其实这样的接口,我们称为函数式接口,我们学过的Runnable、Comparator都是函数式接口的典型代表。
概念
函数式接口,即适用于函数式编程场景的接口。
而Java中的函数式编程体现就是Lambda,所以函数式接口就是可 以适用于Lambda使用的接口。
只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的public abstract 是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
Integer getValue(Integer num);
}
@FunctionalInterface接口
但是在实践中,函数接口是非常脆弱的,只要有人在接口里多添加一个方法,那么这个接口就不是函数接口了,就会导致编译失败。
与 @Override 注解的作用类似,Java 8提供了一个特殊的注解@FunctionalInterface来克服上面提到的脆弱性并且显示地表明函数接口。而且jdk8版本中,对很多已经存在的接口都添加了@FunctionalInterface注解,例如Runnable接口
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
自定义函数式接口
需求:对一个数进行运算,比如乘方、加减另一个数等
自定义一个函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
Integer getValue(Integer num);
}
/*
对一个数进行运算,比如乘方、加减另一个数等
*/
public class NumberTest {
//定义一个方法,将函数式的接口作为参数使用
public static int operation(int num, MyFunction function) {
return function.getValue(num);
}
public static void main(String[] args) {
//计算一个数的平方
int num = operation(100, i -> i * i);
System.out.println(num);
}
}
4大内置核心函数式接口
Supplier接口
接口说明
JDK源码
@FunctionalInterface
public interface Supplier<T> {
// 无需参数,返回一个T类型结果
T get();
}
Supplier,英文翻译就是“供应者”,顾名思义:只产出,不收取。所以不接受任何参数,返回T类型结果。
用来获取一个泛型参数指定类型的对象数据。
由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
示例
示例:定义一个方法产生指定个数的整数,并放入到集合中,在测试方法中遍历该集合
/*
示例:定义一个方法产生指定个数的整数,并放入到集合中,在测试方法中遍历该集合
*/
public class SupplierDemo {
public static void main(String[] args) {
List<Integer> list = getNumList(3, () -> (int) (Math.random() * 100));
//for (Integer integer : list) {
// System.out.println(integer);
//}
list.forEach(System.out::println);
}
//定义一个方法产生指定个数的整数,并放入到集合中
public static List<Integer> getNumList(int num, Supplier<Integer> sup) {
List<Integer> list = new ArrayList();
//这里的num表示指定个数
for (int i = 0; i < num; i++) {
list.add(sup.get());
}
return list;
}
}
练习:求数组最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。
/*
示例:使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。
*/
public class SupplierDemo01 {
public static void main(String[] args) {
int[] arr = {1,3,22,34,56,23};
int maxNum = getMax(() -> {
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
//定义一个方法产生指定个数的整数,并放入到集合中
public static int getMax(Supplier<Integer> sup) {
return sup.get();
}
}
Consumer接口
接口说明
java.util.function.Consumer<T>接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定。
JDK源码:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
抽象方法:accept
消费一个指定泛型的数据。基本使用如:
public class ConsumerDemo {
public static void main(String[] args) {
consumerMoney(100,money -> System.out.println("消费了"+money+"元"));
}
//定义一个花钱 方法
public static void consumerMoney(double money,Consumer<Double> consumer){
consumer.accept(money);
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。
要想实现组合,需要两个或多个Lambda表达式即可,而andThen的语义正是“一步接一步”操作。
例如两个步骤组 合的情况:
public class ConsumerDemo01 {
public static void main(String[] args) {
consumerStr(
s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.concat("world"))
);
}
public static void consumerStr(Consumer<String> con1,Consumer<String> con2){
con1.andThen(con2).accept("hello");
}
}
Predicate接口
接口说明
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>接口。
JDK源码
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
抽象方法:test
用于条件判断的场景,Predicate系列参数不固定,但是返回的一定是boolean类型。
需求:将满足条件的字符串放入到集合中【过滤字符串】
/*
需求:将满足条件的字符串放入到集合中【过滤字符串】
过滤出长度大于4的字符,放入集合中
*/
public class PredicateDemo {
public static void main(String[] args) {
//先创建一个字符串集合
List<String> list = Arrays.asList("hello", "world", "ok", "oracle", "java");
//找出集合中长度大于4的字符串
List<String> strList = filterStr(list, (str) -> str.length() > 4);
for (String s : strList) {
System.out.println(s);
}
}
//过滤字符串方法,将满足条件的字符串加入到集合中
public static List<String> filterStr(List<String> list, Predicate<String> pre) {
ArrayList<String> strList = new ArrayList<>(); //符合条件的字符串放到这个list中
for (String s : list) {
if (pre.test(s)) {
strList.add(s);
}
}
return strList;
}
}
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate 条件使用“与”逻辑连接起来实 现“并且”的效果时,可以使用default方法and 。
public class PredicateDemo {
public static void main(String[] args) {
//先创建一个字符串集合
List<String> list = Arrays.asList("hello", "world", "ok", "oracle", "java");
//找出集合中长度大于4的字符串
List<String> strList = filterStr(list,
str -> str.length() > 4,
str -> str.contains("h"));
for (String s : strList) {
System.out.println(s);
}
}
//过滤字符串方法,将满足条件的字符串加入到集合中
public static List<String> filterStr(List<String> list, Predicate<String> pre1,Predicate<String> pre2) {
ArrayList<String> strList = new ArrayList<>(); //符合条件的字符串放到这个list中
for (String s : list) {
if (pre1.and(pre2).test(s)) {
strList.add(s);
}
}
return strList;
}
}
默认方法:or
与and的“与”类似,默认方法or实现逻辑关系中的“或”
上述and的条件改为或:即字符串长度>4或包含h,只需要修改上面的filterStr方法即可
public class PredicateDemo {
public static void main(String[] args) {
//先创建一个字符串集合
List<String> list = Arrays.asList("hei", "world", "ok", "oracle", "java");
//找出集合中长度大于4的字符串
List<String> strList = filterStr(list,
str -> str.length() > 4,
str -> str.contains("h"));
for (String s : strList) {
System.out.println(s);
}
}
//过滤字符串方法,将满足条件的字符串加入到集合中
public static List<String> filterStr(List<String> list, Predicate<String> pre1,Predicate<String> pre2) {
ArrayList<String> strList = new ArrayList<>(); //符合条件的字符串放到这个list中
for (String s : list) {
if (pre1.or(pre2).test(s)) {
strList.add(s);
}
}
return strList;
}
}
默认方法:negate
它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样:
修改条件:取长度<=3的字符串
public class PredicateDemo {
public static void main(String[] args) {
//先创建一个字符串集合
List<String> list = Arrays.asList("hei", "world", "ok", "oracle", "java");
//找出集合中长度大于4的字符串
List<String> strList = filterStr(list,
str -> str.length() > 4
);
for (String s : strList) {
System.out.println(s);
}
}
//过滤字符串方法,将满足条件的字符串加入到集合中
public static List<String> filterStr(List<String> list, Predicate<String> pre1) {
ArrayList<String> strList = new ArrayList<>(); //符合条件的字符串放到这个list中
for (String s : list) {
if (pre1.negate().test(s)) {
strList.add(s);
}
}
return strList;
}
}
另一种写法
Predicate<String> predicate1 = (s)-> s.length() > 1;
Predicate<String> predicate2 = (s)-> s.contains("a");
System.out.println(predicate1.and(predicate2).test("hello"));
Function接口
接口说明
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件, 后者称为后置条件。
Function代表的是有参数,有返回值的函数。还有很多类似的Function接口:
接口名 描述
BiFunction<T,U,R> 接收两个T和U类型的参数,并且返回R类型结果的函数
DoubleFunction<R> 接收double类型参数,并且返回R类型结果的函数
IntFunction<R> 接收int类型参数,并且返回R类型结果的函数
LongFunction<R> 接收long类型参数,并且返回R类型结果的函数
ToDoubleFunction<T> 接收T类型参数,并且返回double类型结果
ToIntFunction<T> 接收T类型参数,并且返回int类型结果
ToLongFunction<T> 接收T类型参数,并且返回long类型结果
DoubleToIntFunction 接收double类型参数,返回int类型结果
DoubleToLongFunction 接收double类型参数,返回long类型结果
看出规律了吗?这些都是一类函数接口,在Function基础上衍生出的,要么明确了参数不确定返回结果,要么明确结果不知道参数类型,要么两者都知道。
抽象方法:apply
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。 使用的场景
例如:将String类型经过处理之后转换为String类型。
public class FunctionDemo {
public static void main(String[] args) {
String str = strOperation("helloworld", s -> s.substring(2, 5));
System.out.println(str);
}
//定义一个方法实现字符串的处理
public static String strOperation(String str, Function<String,String> function){
return function.apply(str);
}
}
默认方法:andThen
Function接口中有一个默认的 andThen方法,用来进行组合操作。JDK源代码如下:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和Consumer中的 andThen差不多:
apply方法中的例子:先去除字符串两端空格,再截取
/*
先去除字符串两端空格,再截取
*/
public class FunctionDemo {
public static void main(String[] args) {
String str = strOperation(" helloworld ",
s -> s.trim(),
s -> s.substring(2, 6));
System.out.println(str);
}
//定义一个方法实现字符串的处理
public static String strOperation(String str, Function<String, String> f1, Function<String, String> f2) {
return f1.andThen(f2).apply(str);
}
}
Stream API【重点】
了解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
传统集合的多步遍历
几乎所有的集合(如Collection接口或 Map接口等)都支持直接或间接的遍历操作。
当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。
示例
public class DemoForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("oracle");
list.add("mysql");
for (String s : list) {
System.out.println(s);
}
}
}
循环遍历的弊端
Java8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行 了对比说明。
现在,我们仔细体会上例代码,可以发现:
for循环的语法就是“怎么做”
for循环的循环体才是“做什么”
为什么使用循环?因为要进行遍历。
但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从 第一个到最后一个顺次处理的循环。
前者是目的,后者是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
将集合A根据条件一过滤为子集B;
然后再根据条件二过滤为子集C。
那怎么办?
示例:
public class DemoForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("oracle");
list.add("mysql");
//for (String s : list) {
// System.out.println(s);
//}
//过滤集合包含a的字符串
List<String> newList = new ArrayList();
for (String s : list) {
if (s.contains("a")){
newList.add(s);
}
}
//过滤字符串长度为4的字符串
List<String> newList1 = new ArrayList();
for (String s : newList) {
if (s.length() == 4){
newList1.add(s);
}
}
//遍历过滤完的数据
for (String s : newList1) {
System.out.println(s);
}
}
}
Stream的更优写法
public class DemoForEach01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("oracle");
list.add("mysql");
list.stream()
.filter(s -> s.contains("a"))
.filter(s -> s.length()==4)
.forEach(System.out::println);
}
}
Stream操作的3步骤
创建 Stream
一个数据源(如:集合、数组),获取一个流
中间操作
一个中间操作链,对数据源的数据进行处理
终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
创建Stream的4种方式
java.util.stream.Stream<T> 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
所有的Collection集合都可以通过stream默认方法获取流;
Stream接口的静态方法of可以获取数组对应的流。
根据Collection获取流
java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流
//返回一个顺序流
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
//返回一个并行流
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
public class CollectionStream {
public static void main(String[] args) {
List<String> list = new ArrayList();
Stream<String> stream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
Vector vector = new Vector();
Stream stream2 = vector.stream();
}
}
根据Map获取流
Map<String, String> map = new HashMap();
Stream<String> stream = map.keySet().stream();
Stream<String> stream1 = map.values().stream();
Stream<Map.Entry<String, String>> stream2 = map.entrySet().stream();
根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of
/*Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static <T> Stream<T> stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
*/
Stream<String> stream2 = Arrays.stream(names);
}
}
注意:of方法的参数其实是一个可变参数,所以支持数组
创建无限流
可以使用静态方法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)
Stream的常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。
终结操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。
流进行了终止操作后,不能再次使用
逐一处理:foreach
虽然方法名字叫forEach ,但是与for循环中的“for-each”昵称不同。
源码
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
复习Consumer接口
java.util.function.Consumer<T>接口是一个消费型接口。
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。
示例
public class DemoForEach01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("oracle");
list.add("mysql");
list.stream().forEach(System.out::println);
}
}
过滤:filter
可以通过filter方法将一个流转换成另一个子集流。
源码
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
复习Predicate接口
此前我们已经学习过java.util.stream.Predicate函数式接口,其中唯一的抽象方法为:
boolean test(T t);
该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法 将会留用元素;如果结果为false,那么 filter 方法将会舍弃元素。
示例:
public class DemoForEach01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("oracle");
list.add("mysql");
list.stream()
.filter(s -> s.contains("a"))
.filter(s -> s.length()==4)
.forEach(System.out::println);
}
}
映射:map
如果需要将流中的元素映射到另一个流中,可以使用map方法。
方法签名 :
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
复习Function接口
此前我们已经学习过java.util.stream.Function函数式接口,其中唯一的抽象方法为:
R apply(T t);
这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。
示例:
/*
把字符串类型转为int类型
*/
public class DemoStream01 {
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("10", "20", "30");
Stream<Integer> rs = stringStream.map(s -> Integer.parseInt(s));
rs.forEach(System.out::println);
}
}
统计个数:count
正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:
long count();
该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。
示例:
/*
将字符串过滤后,返回数量
*/
public class DemoStream01 {
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("张无忌", "张三丰", "赵敏");
long num = stringStream.filter(s -> s.contains("张")).count();
System.out.println(num);
}
}
取前n个:limit
limit方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
示例:
/*
将字符串过滤后,返回数量
*/
public class DemoStream01 {
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("张无忌", "张三丰", "赵敏");
//取前几个符合要求的数据
Stream<String> limit = stringStream.limit(2);
System.out.println(limit.count());
}
}
跳过前n个:skip
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
示例:
public class DemoStream01 {
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("张无忌", "张三丰", "赵敏");
//取前几个符合要求的数据
Stream<String> skip = stringStream.skip(2);
skip.forEach(System.out::println);
}
}
组合:concat
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
示例:
public class DemoStream01 {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("张无忌", "张三丰", "赵敏");
Stream<String> stream2 = Stream.of("周芷若", "灭绝师太");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out::println);
}
}
其他常用方法
distinct() 刷选,通过流所生成元素hashCode()和equals()去除重复元素
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值
练习
数字处理
需求:给定一个数字列表,如何返回一个由每个数的平方构成的列表呢? 比如,给定【1,2,3,4,5】,应该返回【1,4,9,16,25】
实践应用示例
使用 Stream 处理集合数据
筛选出长度大于等于5的字符串,并打印输出:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
list.stream()
.filter(s -> s.length() >= 5)
.forEach(System.out::println);
输出结果:
banana
orange
grapefruit
将集合中的每个字符串转换为大写,并收集到新的列表中:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
List<String> resultList = list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(resultList);
输出结果:
[APPLE, BANANA, ORANGE, GRAPEFRUIT, KIWI]
统计集合中以字母"a"开头的字符串的数量:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
long count = list.stream()
.filter(s -> s.startsWith("a"))
.count();
System.out.println(count);
输出结果:
1
使用并行流来提高处理速度,筛选出长度小于等于5的字符串,并打印输出:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
list.parallelStream()
.filter(s -> s.length() <= 5)
.forEach(System.out::println);
输出结果:
apple
kiwi
使用 Stream 对集合中的整数求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum);
输出结果:
15
以上示例展示了如何使用 Stream 对集合数据进行筛选、转换、统计等操作。通过链式调用 Stream 的中间操作和终端操作。
使用 Stream 进行文件操作
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class FileStreamExample {
public static void main(String[] args) {
String fileName = "file.txt";
// 读取文件内容并创建 Stream
try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
// 打印文件的每一行内容
stream.forEach(System.out::println);
// 统计文件的行数
long count = stream.count();
System.out.println("总行数:" + count);
// 筛选包含关键词的行并打印输出
stream.filter(line -> line.contains("keyword"))
.forEach(System.out::println);
// 将文件内容转换为大写并打印输出
stream.map(String::toUpperCase)
.forEach(System.out::println);
// 将文件内容收集到 List 中
List<String> lines = stream.collect(Collectors.toList());
System.out.println(lines);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的代码中,首先指定了要读取的文件名 file.txt。然后使用 Files.lines() 方法读取文件的每一行内容,并创建一个 Stream 对象。接下来,我们对 Stream 进行一些操作:
使用 forEach() 方法打印文件的每一行内容。
使用 count() 方法统计文件的行数。
使用 filter() 方法筛选出包含关键词的行,并打印输出。
使用 map() 方法将文件内容转换为大写,并打印输出。
使用 collect() 方法将文件内容收集到 List 中。
请根据实际需求修改代码中的文件名、操作内容和结果处理方式。需要注意的是,在使用完 Stream 后,应及时关闭文件资源,可以使用 try-with-resources 语句块来自动关闭文件。另外,请处理可能出现的 IOException 异常。
使用 Stream 实现数据转换和筛选
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Amy", "Bob", "Charlie", "David", "Eva");
// 转换为大写并筛选出长度大于3的名称
List<String> result = names.stream()
.map(String::toUpperCase)
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
// 打印结果
result.forEach(System.out::println);
}
}
在上述代码中,我们首先创建了一个包含一些名字的列表。然后使用 Stream 对列表进行操作:
使用 stream() 方法将列表转换为一个 Stream。
使用 map() 方法将每个名称转换为大写。
使用 filter() 方法筛选出长度大于3的名称。
使用 collect() 方法将筛选后的结果收集到一个新的列表中。
最后,我们使用 forEach() 方法打印结果列表中的每个名称。
方法引用
是什么
何时使用:若Lambda体中的内容有方法已经实现了,就可以使用方法引用。
方法引用就是Lambda表达式,就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
要求:实现抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
方法引用:使用操作符::将类(或对象) 与方法名分隔开来。
语法
总共有四类方法引用:
语法 描述
类名::静态方法名 类的静态方法的引用
类名::非静态方法名 类的非静态方法的引用
实例对象::非静态方法名 类的指定实例对象的非静态方法引用
类名::new 类的构造方法引用
示例
对象::实例方法名
@Test
public void test1() {
Consumer<String> con = str -> System.out.println(str); //原始写法
con.accept("hello");
PrintStream ps = System.out;
Consumer<String> con1 = ps::println; //对象::非静态方法
con1.accept("world");
Consumer<String> con2 = System.out::println; //上面con1的简化写法
con2.accept("abcd");
}
@Test
public void test2() {
Emp emp = new Emp();
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());
Supplier<Integer> sup2 = emp::getAge; //对象::非静态方法
System.out.println(sup2.get());
}
类::静态方法名
//类::静态方法名
@Test
public void test3() {
Comparator<Integer> com = (x, y) ->Integer.compare(x, y);
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(3, 5));
}
类::实例方法名
@Test
public void test4() {
BiPredicate<String, String> bp = (x,y) ->x.equals(y);
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "bcd"));
}
注意:若Lambda参数列表中第一个参数是实例方法的调用者,并且第二个参数是实例方法的参数(或无参数)时:ClassName::methodName
构造器引用 ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
//构造器引用
@Test
public void test5() {
Supplier<Emp> emp = () -> new Emp();
Supplier<Emp> emp2 = Emp::new; //无参构造
System.out.println(emp2.get());
Function<String, Emp> emp3 = (x) -> new Emp(x); //Emp中需要 public Emp(String name)
Function<String, Emp> emp4 = Emp::new; //简化写法
System.out.println(emp3.apply("孙悟空") + "---"+emp4.apply("猪八戒"));
}
4.3.5 数组引用 type[] :: new
@Test
public void test6() {
Function<Integer, String[]> fun = (x) -> new String[x];
Function<Integer, String[]> fun2 = String[]::new;
String[] strs = fun2.apply(10);
System.out.println(strs.length);
}