JDK1.8新特性

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);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值