JDK1.8新特性
接口方法
接口中的属性默认有:static final 修饰符修饰;
定义的方法默认是抽象方法;
现在可以写默认方法;
由于JDK1.8的API,在已有的接口上,新增了很多的新方法,这种新增变化带来的问题,正如上述的情况 一样,也会给使用这些接口的老用户带来很多的不便。
为了解决这个问题,JDK1.8中引入了一种新的机制:接口可以支持在声明方法的同时,提供实现。 主要通过两种方式可以完成这种操作:
- 默认方法
- 静态方法
1.1默认方法
定义语法:
interface InterfaceName{
default returnType methodName(arg-list){ }
}
默认的访问修饰符是public
例如:
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
两大优势:
-
可以让接口更优雅的的升级,减少使用人员操作的负担;
不必随着接口方法的实现,从而修改实现代码,因为默认方法在子类用可以不用实现;
-
可以让实现类中省略很多不必要的方法的空实现;
接口继承与默认方法的冲突:
例如:一个类C,实现两个接口A、B,其中接口B继承接口A
interface A{
default void test(){
System.out.println("A");
}
}
interface B extends A{
default void test(){
System.out.println("B..");
}
}
class C implements A,B{
public static void main(String[] args) {
C c=new C();
c.test();
}
}
//运行结果:
B..
方法调用的判断规则:
-
类中声明的方法优先级最高
类或父类中声明的方法要高于任何默认方法的优先级;
-
如果无法根据第一条进行判断,那么子接口的优先级更高;
例如:B接口继承了A接口,那么B接口就比A接口更具体,优先级更高;
上述例子可以证明;
-
最后,- 还是无法判断,那么继承了多个接口的类,必须通过重写方法来确定方法的调用;
例如:一个类C,继承父类A,实现接口B
class A{
public void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B");
}
}
class C extends A implements B{
public static void main(String[] args){
C c = new C();
c.test();
}
}
//运行结果:default method test in A
因为A是类,B是接口,A里面的test方法优先级更高
例如:一个类C,实现两个接口A、B,但是A和B之间没有子父接口关系
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B")
}
}
//如下代码编译会报错。
/*class C implements A,B{
public static void main(String[] args){
C c = new C();
c.test();
}
}*/
//如果C需要同时实现A和B接口,那么必须显示覆盖
class C implements A,B{
public void test(){
//如果在C中需要显示访问A/B的默认方法,可以使用接口名.super.方法名();
A.super.test();
B.super.test();
//或者自己编写test方法的实现
}
}
1.2静态方法
和类中定义的静态方法类似,接口中的静态方法可以直接通过 接口名.静态方法名 的形式进行访问。
interface InterfaceName{
static returnType methodName(arg-list){
//代码实现
}
}
访问:
InterfaceName.methodName();
;
例如:
public interface StaticMethod{
public static void test(){
System.out.println("我是StaticMethod接口中的静态方法!");
}
}
class A implements StaticMethod{}
class Test{
public static void main(String args[]){
//运行成功
StaticMethod.test();
//编译报错:
//Static method may be invoked on containing interface class only
//A.test();
}
}
注意,接口中的静态方法,只能使用当前接口的名字来调用
Lambda
Lambda表达式是JDK1.8新增的一种语法,以确保在java代码中可以支持函数式编程,让代码的表示含义 更简单。 理解lambda表达式之前,需要先理解行为参数化和函数式编程的概念。
行为参数化
在java代码中,我们如果需要运算,那么大部分我们的过程是确定的,但是实际参与运算的数却不确 定。
例如,根据用户输入的两个数,求两个数之间所有数据的和
package com.Lambda13.Case;
public class Test{
public static int sum(int a,int b,Act act){
int res=a;
for (int i = a+1; i <=b ; i++) {
res=act.action(res,i);
}
return res;
}
public static void main(String[] args) {
//计算3-5之间的累加结果
System.out.println(sum(3, 5, new Add()));
//计算3-5之间的累乘结果
System.out.println(sum(3, 5, new Multiply()));
}
}
interface Act{
int action(int res,int i);
}
//定义累加的核心操作行为
class Add implements Act{
@Override
public int action(int res, int i) {
return res+=i;
}
}
//定义累乘的核心操作行为
class Multiply implements Act{
@Override
public int action(int res, int i) {
return res*=i;
}
}
上述代码中,将我们要执行的核心计算操作,定义成了一个参数,传给了 sum方法
我们可以通过这个参数,给方法传递不同的行为,来实现不同操作,这就是行为参数化
java中,不允许孤立的代码存在,我们要想将行为(核心操作代码)传递给 sum
方法,就必须要 将这些核心操作代码,包装在一个实现了Action的类中。
为了减少声明和定义类,Java提供了匿名内部类的实现,来简化我们刚才的调用过程:
package com.Lambda13.Case;
public class Test{
public static int sum(int a,int b,Act act){
int res=a;
for (int i = a+1; i <=b ; i++) {
res=act.action(res,i);
}
return res;
}
public static void main(String[] args) {
int result=0;
result=sum(1, 6, (int res, int i)-> {
return res+i;//累加
});
//内部类的简化
/*result = sum(1,6,new Act(){
public int action(int res,int i){
return res*i;
}
});*/
result=sum(1,6,(res,i)->res*i);
//累乘
}
}
interface Act{
int action(int res,int i);
}
使用了匿名内部类的方式,虽然简化了之前的代码,但是每次调用还是编写了很多相同代 码,例如
new Action(){}
;public int action(int result,int next){}
;
这个new对象的操作,还有action方法声明的操作,每次都是重复的,其实我们真正的关心的只有三点:
- 方法的参数列表
- 方法中的核心操作代码
- 方法的返回类型
也就是,传入指定参数,通过核心计算,给出最后结果
函数式编程,就是将之前通过传递Action匿名对象的过程,变成一个计算求值的过程,那么这个求值的 表达式就是所谓的函数式编程。
class Test{
public static void main(String[] args){
int result = 0;
/*
//忽略掉之前匿名内部类形式中,不重要的部分
Action add = (int result,int i) -> {
return result + i;
};
*/
//简化写法
Act add = (result,i) -> result + i;
result = calculate(3,5,add);
//去掉中间变量add
//相当于,第三个参数,我们传了一个核心的计算行为
//这个核心的计算行为,就是对Action接口中action方法的实现
//result = calculate(3,5,(result,next) -> result + next);
//[3,5]之间,累加的结果
System.out.println(result);
}
}
函数式编程
函数式编程,和面向对象编程、以及面向过程编程一样,它们都是编程的一种方式。
函数式编程是面向数学的抽象,将计算过程描述为一种表达式求值。简单说,函数式程序就是一个表达 式。
严格意义上的表达式,就是由数据和操作符按照一定的规则,组合在一起形成的序列,并且所有的表达 式都是有返回结果的,这是这里所说的表达式和代码语句的最大的区别。
但是在java中,是允许函数式编程中没有任何返回值的,因为java中有关键字 void ,但是在其他一些专 门的函数式编程语言中,是没有 void 的。
/*
Action add = (int result,int next) -> {
return result + next;
};
*/
//简化写法
Action add = (result,next) -> result + next;
上面代码中, (result,next) -> result + next;
就是java中函数式编程的一些体现,其本质含义就 是根据函数的入参,通过表示式的计算,最后返回一个结果。
(result,next) -> result + next
; 在JDK1.8中,这部分就称之为Lambda表达式。
通过lambda表达式来支持函数式编程;
Lambda概述
Lambda表达式,可以用来表示一个函数,它只关注函数的参数列表,函数主体、返回类型,并且 可以将此函数作为一个参数,进行传递。
Lambda表达式还有另一个存在的意义,那就是作为一个接口的实现类对象;
可以看出,Lambda表达式,虽然可以通过(参数列表,函数主体、返回类型)三部分来表示一个 具体的函数操作,但是它必须是依托在一个接口才行,所以Lambda表达式就是对接口中抽象方法 的实现。
Lambda使用
-
用lambda表达式,接口中必须只有一个抽象方法,但是可以有其他的默认方法或者静态方法;
-
为了保证接口中只有一个冲向方法,可以添加一个注解
@FunctionalInterface interface Action1{ void test(); }
加了该注解,即表示位函数式接口,只能有一个抽象方法;
Thread t1=new Thread(()->{
System.out.println("run..方法中的内容");
});
//以上是lambda表达式的写法
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
System.out.println("run..方法中的内容");
}
});
lambda
表达式可以作为参数存在,但是不能作为对象,不能调用方法;
函数式接口
有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方 法。
例如
//这是函数式接口
public interface Action{
void action();
}
//这也是函数式接口,里边只有一个抽象的方法action
public interface DefaultAction{
void action();
default void test(){
System.out.println("我是默认实现");
}
}
//这是函数式接口
public interface Action{
void action();
}
//这也是函数式接口,里边只有一个抽象的方法action
public interface DefaultAction{
void action();
default void test(){
System.out.println("我是默认实现");
}
}
JDK1.8中,针对函数式接口,新增了一个注解 @FunctionalInterface
,用来检查被标注的接口,是 不是一个函数式接口,如果不是,那么编译器会报错。
但是,该注解不是必须要用的,它只是会让编辑器帮我们检查一下而已,以免出现接口中抽象方法的个 数不是1的情况。
例如,编译通过:
@FunctionalInterface
public interface Action{
void run();
}
例如,编译报错:
@FunctionalInterface
public interface Action{
void run();
void go();
}
Lambda语法
Lambda表达式的格式为:
() -> {}
()
表示参数列表,当只有一个参数时可以不写;->
后面跟的函数主体;{}
函数主体,表达式的返回值,有这个函数主体中的代码来决定,当只有一行代码时可以不写;
那么,一个Lambda表达式,到底该怎么编写,主要是看,在这个表达式所对应的函数式接口中,抽象方 法是怎么定义的,因为这个表达式就是对这个抽象方法的实现。
案例
package com.Lambda13.Case;
public class LamdaTesy1 {
public static void main(String[] args) {
/*Action1 a=new Action1() {
@Override
public void test() {
System.out.println("hello test");
}
};*/
/*Action1 a1=()->//{
System.out.println("hello test");//方法里头只有一行可以不写大括号
System.out.println("hi");
// };
a1.test();*/
// Action1 a=(t)->System.out.println("hello test"+t);
/* Action1 a=(t,s)->
System.out.println("hello"+t+""+s);
*/
/*Action1 a=name -> {
System.out.println("hello"+name);
return 0;
};*/
Action1 a=(s, n) -> s.length();//返回字符串的长度
System.out.println(a.test("hello", 10));
Thread t1=new Thread(()->{
System.out.println("run..方法中的内容");
});
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
System.out.println("run..方法中的内容");
}
});
}
}
@FunctionalInterface
interface Action1{
// void test();
// void test(int t);
// void test(int t,String s);
// int test(String name);
int test(String s,int n);
}
注意,Lambda表达式中的参数列表,里面的参数可以不写类型,因为JVM在运行时会自动推断, 当然,如果直接手动写上去,也完全没有问题。
常用接口:
JDK1.8中,已经定了一些会常用到的函数式接口,这些函数式接口都定义在 java.lang.function
包 中,例如 Predicate
、 Consumer
、 Function
、 Supplier
、 UnaryOperator
和 BinaryOperator
等。
注意,如果需要,也可以自己定义类似的函数式接口,并不是必须要使用这些定义好的接口。
1. Predicate
java.util.function.Predicate
接口定义了一个名叫 test
的抽象方法,它接受泛型T 对象, 并返回一个 boolean
类型的结果
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
例如:定义一个方法,用来过滤数组中所有符合要求的数据,选出大于50的数据
package com.Lambda13.Case;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTest {
public static Integer[] filter(Integer[] arr,Predicate<Integer> p){
//设置list数组来接收过滤后的数据
List<Integer> list=new ArrayList<>();
//遍历数组arr
for (Integer i :arr) {
//判断当前数据是否符合要求
if (p.test(i)){
list.add(i);
}
}
//把集合强转为Integer类型的数组
return list.toArray(new Integer[list.size()]);
}
public static void main(String[] args) {
Integer[] arr={12,66,18,19,21,56,112};
/*Integer[] filter = filter(arr, new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer > 50;
}
});*/
//以上是匿名内部类,以下是lambda表达式的写法
Integer[] filter = filter(arr, e -> e > 50);
System.out.println(Arrays.toString(filter));
}
}
//运行结果:
[66, 56, 112]
此外, Predicate
接口中,还定义了一些默认方法和静态方法:
and()
or()
negate()
在使用该接口来做判断的时候,经常需要几个条件同时成立,或者其中一个条件成立,或者求反。 在这种情况下,除了可以在代码中选择使用&&,||,!之外,也可以分别使用这三个方法来代替。
and():
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
例如:
package com.Lambda13.Case;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTest {
public static Integer[] filter(Integer[] arr,Predicate<Integer> p){
//设置list数组来接收过滤后的数据
List<Integer> list=new ArrayList<>();
//遍历数组arr
for (Integer i :arr) {
//判断当前数据是否符合要求
if (p.test(i)){
list.add(i);
}
}
//把集合强转为Integer类型的数组
return list.toArray(new Integer[list.size()]);
}
public static void main(String[] args) {
Integer[] arr={12,66,18,19,21,56,112};
Predicate<Integer> p1=e->e>10;
Predicate<Integer> p2=e->e<30;
Predicate<Integer> p=p1.and(p2);
Integer[] filter = filter(arr, p);
System.out.println(Arrays.toString(filter));
}
}
//运行结果:
[12, 18, 19, 21]
or()
方法:
- 与
and()
类似,不过只要满足其中一个条件即可;
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
negate()
:
default Predicate<T> negate() {
return (t) -> !test(t);
}
案例:
public static void main(String[] args) {
Integer[] arr={12,66,18,19,21,56,112};
Predicate<Integer> p1=e->e>20;
// Predicate<Integer> p2=e->e<30;
// Predicate<Integer> p=p1.and(p2);
Predicate<Integer> p=p1.negate();//不包含p1的
Integer[] filter = filter(arr, p);
System.out.println(Arrays.toString(filter));
}
//运行结果
[12, 18, 19]
JDK1.8中,给 Collection
集合增加了默认方法: removeIf
此方法就使用了 Predicate
作为自己的参数,来移除符合条件的数据,实现如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iSmnAYy-1638150957312)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210701171236067.png)]
案例:
public static void main(String[] args) {
Collection<String> coll=new ArrayList<>();
coll.add("abc");
coll.add("hello");
coll.add("world");
//如果字符串中包含l,则返回true,true则移除
coll.removeIf(s -> s.contains("l"));
System.out.println(coll);
}
//运行结果:
[abc]
2.Consumer
java.util.function.Consumer
接口:
@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); };
}
}
例如:定义一个方法,用来对学生对象进行操作
package com.Lambda13.Case;
import java.util.function.Consumer;
public class ConsumerTest {
public static void main(String[] args) {
Student stu=new Student("lucy");
//1 给stu对象的name属性值加上前缀 zyz
Consumer<Student> c1=student -> student.name="zyz_"+student.name;
//2 给stu对象的name属性值加上后缀;
Consumer<Student> c2=student -> student.name=student.name+
"_"+System.currentTimeMillis();
//3 给stu对象的name属性值,先加前缀再加后缀
Consumer<Student> c3=c1.andThen(c2);
//如果传入consumer1,表示只加前缀
//如果传入consumer2,表示只加后缀
//如果传入consumer3,表示先加前缀,再加后缀
optStu(stu,c3);
System.out.println(stu.name);
}
public static void optStu(Student stu,Consumer<Student> con){
con.accept(stu);
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
}
//运行结果
zyz_lucy_1625135362859
JDK1.8中,给Collection
集合增加了默认方法: forEach
用来遍历集合,定义如下:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
例如
public static void main(String[] args) {
Collection<String> coll=new ArrayList<>();
coll.add("hello");
coll.add("world");
coll.add("dancer");
//去掉中间变量,直接吧Lambda表达式当前参数传入到forEach方法中
coll.forEach(t-> System.out.println(t));
//遍历输出
}
//运行结果
hello
world
dancer
3.Function
java.util.function.Function
接口,
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
//Returns a function that always returns its input argument.
static <T> Function<T, T> identity() {
return t -> t;
}
}
例如
package com.Lambda13.Case;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
public class FunctionTest {
public static void main(String[] args) {
String str="a-b-c-a-b-c";
//传入字符串,返回数组,操作为把字符串按照 "-" 进行分割为字符串数组
// "a-b-c-a-b-c" 转换为 {"a","b","c","a","b","c"}
Function<String,String[]> f1=s->s.split("-");
/*Function<String,String[]> fun1=new Function<String, String[]>() {
@Override
public String[] apply(String s) {
return s.split("-");
}
};*/
//传入字符串数组,返回Set<String>集合,目的是去除数组中重复的数据,存放把结果存放到Set集合中
//{"a","b","c","a","b","c"} 转换为集合 [a, b, c]
Function<String[], Set<String>> f2=strings ->{
Set<String> set=new HashSet<>();
for (String string :strings) {
set.add(string);
}
return set;
};
//刚好,f1函数的结果,作为f2函数的参数,f1和f2组合成f3函数
//f3函数表示传入字符串,最后返回Set<String>集合
//其实内部是先将字符串交给f1函数转换数组,在将数组交给f2函数转换Set集合
//通过上面列出的andThen源码也可以看出是这个效果
Function<String,Set<String>> f3=f1.andThen(f2);
String[] s1 = f1.apply(str);
System.out.println("f1:"+Arrays.toString(s1));
Set<String> s2 = f2.apply(s1);
System.out.println("f2:" + s2);
Set<String> set = f3.apply(str);
System.out.println("f3:"+set);
}
}
//运行结果
f1:[a, b, c, a, b, c]
f2:[a, b, c]
f3:[a, b, c]
理解了andThen方法,那么compose方法也就理解:f1.andThen(f2),f1.compose(f2)
俩个方法的区别是,把f2操作放在f1操作之前还是之后的问题
静态方法 identity ,API中给出的注释为:
static <T> Function<T, T> identity() {
return t -> t;
}
可以看出,该方法可以直接返回一个Function对象,传入一个参数,直接把该参数返回,不做任何 操作
需要注意的是,这是一个泛型方法,因为泛型参数T是在这个方法上定义的
例如
public class Test {
public static void main(String[] args) {
Function<String,String> f = Function.identity();
//传入hello,返回hello
String result = f.apply("hello");
System.out.println(result);
}
}
//运行结果:
hello
JDK1.8中,对于Map接口中新增了默认方法: computeIfAbsent
,用来在指定key不存在时,计算一个 value值和指定key匹配,并将该key=value写入map中。代码如下:
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
例如
Map<String,String> map=new HashMap<>();
map.put("1","tom");
map.put("2","lucy");
Function<String,String> f=key->"default";
//如果key值为3的时候,对应的值不存在
//那么使用f函数计算出一个value值,并且当前这个key-value存入到map集合中
map.computeIfAbsent("3",f);
//也可以去掉中间变量f,直接将Lambda表达式传入
//map.computeIfAbsent("3",key->"default");
map.forEach((k,v)-> System.out.println(k+"-"+v));
//
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println(s+"-"+s2);
}
});
//运行结果
1-tom
2-lucy
3-default
1-tom
2-lucy
3-default
4.Supplier
java.util.function.Supplier
接口,
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
例如: 输出10个1-100之间的随机的奇数
public class SupplierTest {
public static void main(String[] args) {
//随机生成10个1-100以内的奇数
Supplier<Integer> s=()-> {
int num ;
do {
num=(int)(Math.random()*100);
}while (num%2==0||num==0);
return num;
};
for (int i = 0; i < 10; i++) {
System.out.println(s.get());
}
}
}
//运行结果:
79
51
69
63
31
89
81
17
97
9
可以看出,Supplier接口中的方法,不需要参数,可以根据我们指定的算法,返回一个数据
Predicate 、 Consumer , Function , Supplier
,这些接口都是带泛型的接 口,泛型的类型只能是引用类型,那么如果需要操作基本类型的数据,这时候就会做自动装箱和拆箱。 而大量的装箱和拆箱是比较消耗性能的,所以JDK1.8中,还专门定义了一些针对基本类型的函数式接 口,例如:
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
default IntPredicate and(IntPredicate other) {
Objects.requireNonNull(other);
return (value) -> test(value) && other.test(value);
}
default IntPredicate negate() {
return (value) -> !test(value);
}
default IntPredicate or(IntPredicate other) {
Objects.requireNonNull(other);
return (value) -> test(value) || other.test(value);
}
}
可以看出,该函数式接口,没有定义泛型,里面的方法和
Predicate
是一样的使用方法,只 不过这时候操作的数据直接就是int类型,就不需要再进行自动装箱和拆箱了,提高了运行效率。
public class IntPredicateTeat {
public static void main(String[] args) {
int[] arr=new int[100000000];
for (int i = 0; i <arr.length ; i++) {
arr[i]=i;
}
Predicate<Integer> p1=n->n%2!=0;
IntPredicate p=num->num%2==0;
long start=System.currentTimeMillis();
for (int i :arr) {
p.test(i);//耗时:5
// p1.test(i);//耗时:99
}
long end=System.currentTimeMillis();
System.out.println("共耗时" + (end - start));
}
}
当数据量比较大的时候,分别使用这俩个接口,多运行几次,可以明显看出IntPredicate的平均执 行效率较高
默认情况下,这些专门对基本类型数据进行操作的函数式接口,它们的名字都有一定的规律前缀,比如 DoublePredicate
、 IntConsumer
、 LongBinaryOperator
、 IntFunction
等。
其中, Function
接口还有针对输出参数类型的变种: ToIntFunction
、 IntToDoubleFunction
等
类型推断
使用Lambda
表达式,相当于给函数式接口生成一个实例,但是Lambda表达式本身,并不包含这个接口 的任何信息,例如:
public class Test {
public static void main(String[] args) {
Test t = new Test();
//()->{} 这个表达式中并没有包含任何Runnable接口的信息
//但是编译和运行都是成功的
t.test(()->{});
}
public void test(Runnable run){
}
}
之所以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配的上,那是因为:
- 我们在使用Lambda表达式的时候,JVM是会通过上下文自动推断它所属接口类型的
- 并且接口中只有一个抽象方法,自然也能匹配成功该表达式所对应实现的抽象方法
例如
public class Test {
public static void main(String[] args) {
Test t = new Test();
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Runnable
t.test1(()->{});
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Action
t.test2(()->{});
}
public void test1(Runnable run){
}
public void test2(Action action){
}
}
interface Action{
void run();
}
类似的,JVM还能自动推断出Lambda表达式中参数的类型,例如
public class Test {
public static void main(String[] args) {
Test t = new Test();
//这俩种写法的效果是一样的,JVM根据环境自动推断出参数a和b的类型
t.test((int a,int b)->a+b);
t.test((a,b)->a+b);
}
public void test(Action action){
}
}
interface Action{
int run(int a,int b);
}
重载解析
如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。
例如:
public class Test {
public static void main(String[] args) {
//编译报错,因为俩个方法都符合
test(1,num -> num>0);
}
public static void test(int a,Predicate<Integer> p){
}
public static void test(int a,Function<Integer,Boolean> f){
}
}
可以看出,这时候编译报错,因为表达式
num -> num>0
对于俩个方法都符合既符合
Predicate
的实现,也符合Function
的实现
这时候可以做类型转换,来解决这个问题:
public class Test {
public static void main(String[] args) {
//编译通过,用强转的方式指定了表达式的对应的接口类
test(1,(Predicate<Integer>)(num->num>0));
}
public static void test(int a,Predicate<Integer> p){
}
public static void test(int a,Function<Integer,Boolean> f){
}
}
但是,这种情况很少出现,我们应该在方法重载的时候就提前注意到这个问题,或者给方法起不同 的名字。
局部变量
如果在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使用 final 修饰符进行修饰, 这方面的语法要求,和之前学习的匿名内部类保持一致。
public class Test {
public static void main(String[] args) {
int a = 1;
Runnable run = ()->{
//这里访问局部变量a之后,a就自动变成了final修饰的常量(JDK1.8)
//也可以手动给局部变量a加上final修饰符
//变量a的值将不可被再次赋值,变为了常量
System.out.println(a);
};
}
}
注意,JDK1.8中,被匿名内部类、局部内部类、
Lambda
表示访问的局部变量,会默认加上final
修饰符
方法引用
方法引用:
使用Lambda表达式,可以表示一个函数,这个函数由 “
-
参数列表、
-
函数主体、
-
返回类型”
这三部分组 成,同时这个函数还是一个函数式接口中,唯一的抽象方法的实现,例如:
public class Test {
public static void main(String[] args) {
//()-{} 该表达式 代表了 一个函数,该函数无参,函数主体不执行任何代码,也不返回任何数据
//()-{} 该表达式 同时也是接口Action中,唯一的抽象方法run的具体实现
//因为run方法也是无参、无返回类型
Action a = ()->{};
}
}
interface Action{
void run();
}
其实只要一个函数是无参的,无返回类型的,他就可以作为run方法的引用
Lambda表达式中,提供了特殊的语法,能让我们直接引用已经存在的方法,作为当前要表示的函数,例 如
public class Test {
public static void main(String[] args) {
//使用Lambda表达式,直接表示一个函数
//这个函数就作为run方法的具体实现
Action a1 = ()-> System.out.println("hello!");
//使用Lambda表达式,引用一个已经存在的方法,作为当前要表示的一个函数
//这个被引用的方法,需要和Action接口中的抽象方法run保持一致:参数列表、返回类型
//这个方法就作为run方法的具体实现
Action a2 = Student::sayHello;
a1.run();//输出hello
a2.run();//输出hello
}
}
interface Action{
void run();
}
class Student{
public static void sayHello(){
System.out.println("hello!");
}
可以看出,以上代码中的a1和a2要表示的俩个函数,其实是一样的,它们的参数列表、函数主体、 返回类型都是一样的,只不过a1表示的函数自己直接写出来的,a2表示的函数是引用提前写好的
注意,对应一个函数来说,我们不关心它的名字是什么,它在什么地方定义的,我们只关心函数的 三要素:参数列表、函数主体、返回类型,只要能写出这个函数或者引用到这个函数,就可以直接 拿来使用。
1.1静态方法引用
使用Lambda表达式可以引用类中的静态方法
语法要求:
类名::静态方法名
注意,方法名字后面一定没有小括号,因为这里只是方法的引用,不是方法的调用
例如:
public class MethodTest {
public static void main(String[] args) {
//只要函数的参数列表是String类型的,函数的返回值是int类型,就可以作为Actions接口的具体实现
Actions a=str -> str.length();
System.out.println(a.run("nihao"));//输出5
//使用 类名::静态方法名 的形式来引用当前类中的sayHello方法
Actions a1=MethodTest::sayHello;
System.out.println(a1.run("zyz"));//输出3
}
public static int sayHello(String name){
return name.length();
}
}
interface Actions{
int run(String str);
}
1.2实例方法的引用
使用Lambda表达式可以引用类中的非静态方法,语法要求为:
类名::非静态方法名
注意这里也是通过类名来引用
例如,如果run方法是一个参数
package com.JDKchapter14.Case;
public class MethodTest {
public static void main(String[] args) {
/*Actions a=new Actions() {
@Override
public int run(String str) {
return str.length();
}
};*/
Actions a=str -> str.length();
System.out.println(a.run("nihao"));//输出5
//通过类名调用非静态方法
Actions a1=String::length;
System.out.println(a1.run("hello"));//输出5
}
}
interface Actions{
int run(String str);
}
对于int run(String str)
来讲,可以直接引用String
类型中的length()
方法来表示对run
方法的实现,这个时候需要按照以下要求:
- run方法的参数类型是String类型,才可以使用String引用他的非静态方法来对run进行实现;
- 使用String引用他的非静态方法,必须是无参的方法,因为run只有一个String类型的参数;
- 使用String引用它的静态方法必须是int返回类型,因为run方法的返回类型是int;
也就是说,如果你是想按照如下形式,来完成run方法的实现:
public int run(String str){
return str.xx();
}
这时候,可以使用
Action a = String::xxx;
来表示这种情况
例如,如果run方法是俩个参数
public class MethodTest {
public static void main(String[] args) {
Actions a=(str, i) -> str.length()+i;
System.out.println(a.run("hi", 10));//
Actions a1=String::indexOf;
//返回指定字符在字符串中第一次出现的索引
System.out.println(a1.run("hello", 'e'));//输出 1
Actions a2=String::lastIndexOf;
//返回指定字符最后一次出现再字符串中的索引
System.out.println(a2.run("abc-abc", 'a'));//输出 4
Actions a3=String::charAt;//String中的charAt方法
//返回指定索引处的值,这里index=5
char c = (char)a3.run("helloworld", 5);//输出 w
System.out.println(c);
}
}
interface Actions{
int run(String str,int i);
}
对于int run(String str,int i)
来讲,可以直接引用String
类型中的indexOf(int ch)
方法来表示对run
方法的实现,这是需要按照以下要求:
- run方法的第一个参数类型是
String
; - 使用String引用它的非静态方法,必须是有参的,并且参数类型是int,因为run方法的第二个参数是int类型;
- 使用String引用它的非静态方法,必须是int返回值类型的,因为run方法的返回类型是int;
也就是说,如果你是想按照如下形式,来完成run方法的实现:
public int run(String str,int i){
return str.xxx(i);
}
这时候,可以使用
Action a = String::xxx;
来表示这种情况
例如,如果run方法是三个参数
public class MethodTest {
public static void main(String[] args) {
/*Actions a=new Actions() {
@Override
public int run(String str, int i, int j) {
return i+j;
}
};*/
Actions a=(str, i, j) -> i+j;
System.out.println(a.run("accept", 'a', 1));//输出 98
//a:97 97+1=98
Actions a1=String::indexOf;
//从j位置开始搜索,搜索到与i匹配的字符,返回该字符的索引
System.out.println(a1.run("accept", 'e', 4));//输出 -1
System.out.println(a1.run("accept", 'e', 0));//输出 3
}
}
interface Actions{
int run(String str,int i,int j);
}
对于 int run(String str,int i,int j)
来讲,可以直接引用 String 类型中的 indexOf(int ch, int fromIndex)
方法来表示对 run 方法的实现,这时候需要按照以下要求:
- run方法的第一个参数类型是String类型,才可以使用String引用它的非静态方法来对run进行实现
- 使用String引用它的非静态方法,必须是俩参的,并且俩个参数类型都是int,因为run方法的第二个 参数是int类型,第三个参数的类型也是int类型
- 使用String引用它的非静态方法,必须是int返回类型,因为run方法的返回类型是int
也就是说,如果你是想按照如下形式,来完成run方法的实现:
public int run(String str,int i,int j){
return str.xxx(i,j);
}
run方法是四个参数的:
public class MethodTest {
public static void main(String[] args) {
Actions actions=Stu::test;
System.out.println(actions.run(new Stu(), "hi", 6, new ArrayList<>()));
}
}
interface Actions{
int run(Stu stu,String str,int i,List<Integer> list);
}
class Stu{
public int test(String str, int i, List<Integer> list){
return str.length()+i;
}
}
1.3使用对象引用法
语法要求:
对象::非静态方法
例如
public class MethodTest {
public static void main(String[] args) {
//创建对象
Stu stu=new Stu();
//实现接口方法
Actions a=str -> "hello"+str;
Actions a1=stu::test;//方法引用来实现接口方法
//因为接口中的方法与类中的方法:返回值类型,参数列表,函数主体一致
System.out.println(a.run("lucy"));//输出hellolucy
System.out.println(a1.run("lucky"));//输出hellolucky
}
}
interface Actions {
String run(String str);
}
class Stu{
public String test(String name){
return "hello"+name;
}
}
注意,其实这时候,任何一个对象的中的方法,只要是参数列表和返回类型和run方法保持一致, 都可以使用这个对象中的这个方法,来对Action接口进行实现
以上代码也可以改成
public class MethodTest {
public static void main(String[] args) {
//创建对象
Stu stu=new Stu();
//实现接口方法
// Actions a=str -> "hello"+str;
// Actions a1=stu::test;//方法引用来实现接口方法
//因为接口中的方法与类中的方法:返回值类型,参数列表,函数主体一致
Actions a2=Stu::test;
System.out.println(a2.run(stu, "luck"));
// System.out.println(a.run("lucy"));//输出hellolucy
// System.out.println(a1.run("lucky"));//输出hellolucky
}
}
interface Actions {
String run(Stu stu,String str);
}
class Stu{
public String test(String name){
return "hello"+name;
}
}
1.4构造方法引用
语法要求
类名::new
1.4.1无参构造器
public class MethodTest {
public static void main(String[] args) {
//实现接口中的抽象方法
Actions a=()->new Stu();
System.out.println(a.run());//输出地址值
Actions a1=Stu::new;
System.out.println(a1.run());//输出 地址值
}
}
interface Actions {
Stu run();
}
class Stu{
}
1.4.2有参构造器
public class MethodTest {
public static void main(String[] args) {
//实现接口中的抽象方法
Actions a=str->new Stu(str);
System.out.println(a.run("zyz"));//输出Stu{name='zyz'}
Actions a1=Stu::new;
System.out.println(a1.run("zyq"));//输出 Stu{name='zyq'}
}
}
interface Actions {
Stu run(String name);
}
class Stu{
private String name;
public Stu(String name) {
this.name = name;
}
@Override
public String toString() {
return "Stu{" +
"name='" + name + '\'' +
'}';
}
}
1.4.3数组构造
语法要求为
数组类型::new
例如:根据给定的长度,创建任意类型的数组对象
public class MethodTest2 {
public static void main(String[] args) {
Action a=len->new int[len];
System.out.println(a.run(10).length);
Action a1=int[]::new;
System.out.println(a1.run(8).length);
}
}
interface Action{
int[] run(int len);
}
可以看出,无论是构造器,还是普通方法,在Lambda表达式中,都有可以引用过来,当做一个函 数,这个函数有参数列表、函数主体、返回类型,并且把这个函数当做一个函数式接口中抽象方 法的实现!
Optional
java.util.Optional
类,是用来防止NullPointerException
异常的辅助类型, Optional 对 象中封装的值,可以是null
,也可以不是null
在Java8之前,一个函数可能因为代码逻辑问题,最终返回一个null,这时候程序中很可能出现空指针异 常。而在Java8中,不推荐返回 null ,而是返回 Optional
Optional
中常用的方法:
of
ofNullable
isPresent
ifPresent
get
orElse
orElseGet
map
faltMap
filter
案例:
package com.JDKchapter14.Case;
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
/**
* of方法 为非null的值创建一个Optional对象
* 如果传入参数为null,则抛出NullPointerException
*/
Optional<String> op1=Optional.of("hello");
/**
* ofNullable方法
* ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况
*/
Optional<Object> op2 = Optional.ofNullable(null);
//isPresent方法,如果值存在返回true,否则返回false
if (op1.isPresent()){
System.out.println(op1.get());
}
//get方法,如果Option有值则返回,否则抛出NoSuchElementException
if (op2.isPresent()){
System.out.println(op2.get());
}
/**
* ifPresent方法 如果Optional实例有值则为其调用Consumer接口中的方法,否则不做处理
* Consumer:
* * public void accept(T t);
* */
op1.ifPresent(s -> System.out.println("op1-"+s));//输出 opt1-hello
op2.ifPresent(s-> System.out.println(s));//这个不执行 因为op2里面的值是null
// orElse方法 如果有值则将其返回,否则返回指定的其它值
System.out.println(op1.orElse("如果opt1中的值为null则返回这句话"));
System.out.println(op2.orElse("如果opt2中的值为null则返回这句话"));
/**
* orElseGet方法 orElseGet与orElse方法类似,区别在于得到的默认值的方式不同
* Supplier:
* public T get();
*/
System.out.println(op1.orElseGet(() -> "opt1为null则返回这句话"));
System.out.println(op2.orElseGet(() -> "opt2为null则返回这句话"));
Optional<Integer> map1 = op1.map(s -> 1);
System.out.println(map1.orElse(0));
Optional<String> map2 = op2.map(Object::toString);
System.out.println(map2.orElse(null));
Optional<Double> map3 = op2.map(s -> 2.0);
System.out.println(map3.orElse(0d));
/** flatMap方法 如果有值,则调用mapper的函数返回Optional类型返回值,否则返回空 Optional
flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional
* 调用结束时,flatMap不会对结果用Optional封装,需要我们自己把返回值封装为Optional
* public <U> Optional<U> flatMap(Function<? super T,Optional<U>>
mapper);**/
Optional<String> fm = op1.flatMap(s -> Optional.of(s + "_zyz"));
System.out.println(fm.get());
Optional<String> fm1 = op2.flatMap(str -> Optional.of("_zyz"));
System.out.println(fm1.get());
Optional<String> op01 = op1.filter(s -> s.length() > 5);
System.out.println(op01.get());
}
}
Stream
3.1概述
java.util.stream.Stream
接口,表示能应用在一组元素上,一次执行的操作序列,也就是可 以对一组数据进行连续的多次操作。
Stream
在使用的时候,需要指定一个数据源,
比如 java.util.Collection
的子类, List
或者 Set
都可以,但是 Map
类型的集合不支持。
不适用于Map集合
主要用做数据处理
中间操作的返回值永远是Stream;
最后的操作只能有一个,因为Stream会失效;
Stream是对集合功能的增强,它提供了各种非常便利、高效的聚合操作,可以大批数据操作,同时再结合Lambda表达式,就可以极大的提高编程效率;
Stream的API提供了串行和并行两种模式进行操作数据。
Stream操作分为中间操作或者最终操作两种:
-
中间操作,返回Stream本身,这样就可以将多个操作依次串起来
例如:
map、flatMap、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered
; -
最终操作,返回一特定类型的计算结果
例如,
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、 anyMatch、allMatch、noneMatch、findFirst、findAny、iterator
;
例如
package com.JDKchapter14.Case;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class StreamTest1 {
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6);
list=list.stream() //把集合list变成stream
.filter(s->s%2==0)//过滤
.sorted((e1,e2)->e2-e1)//排序,倒序
.collect(Collectors.toList());
System.out.println(list);
}
}
//运行结果
[6, 4, 2]
可以看出,在list集合转为Stream后,可以经过连续的多次数据操作,最后返回我们想要的结果, 并且实现功能代码比之前更加优雅、简洁
Stream的操作,看起来很像生产车间的流水线作业:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNDdgQEa-1638151320533)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210704135919317.png)]
一个最初的产品(数据),经过中间多个连续的不同工序(操作),得到最终的产品。
3.2 其他转Stream
可以将现有的数据,转换为Stream对象,然后再使用Stream的API对数据进行一些系列操作
3.2.1 值
public static void main(String[] args) {
Stream<String> stream=Stream.of("a","b","c");
}
3.2.2 数组
public static void main(String[] args) {
String[] arr={"a","b","c"};
Stream<String> stream= Arrays.stream(arr);
Stream<String> stream1=Stream.of(arr);
}
3.2.3 集合
public static void main(String[] args) {
List<String> list=new ArrayList<>();
Collections.addAll(list,"1","2","3","4","5");
Stream<String> stream=list.stream();
stream.collect(Collectors.toList());
}
注意,只要是Collection类型的集合,都可以调用stream()方法,将集合转换为Stream对象
3.2.4 基本类型
对于基本数据类型,有专门的三种Stream类型:
IntStream
LongStream
DoubleStream
虽然也可以用Stream类型,并指定泛型:
Stream<Integer>
Stream<Long>
Stream<Double>
但是,在数据量较大的时候,自动拆箱/封装会比较消耗性能,所以提供了上面三种专门针对基本类型的Stream
例如,创建IntStream类型
public static void main(String[] args) {
IntStream stream =IntStream.of(new int[]{1,2,3});
//[1,3)
IntStream stream1=IntStream.range(1,3);
//[1,3]
IntStream stream2=IntStream.rangeClosed(1,3);
}
3.3 Stream转其他
使用Stream的API对数据操作后,还可以把结果转换为其他类型
3.3.1 数组
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
/**
* <A> A[] toArray(IntFunction<A[]> generator);
*
* public interface IntFunction<R> {
* R apply(int value);
* }
*/
String[] strArray =stream.toArray(String[]::new);
}
3.3.1 集合
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
// List<String> list=stream.collect(Collectors.toList());
// List<String> list1=stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> set3 = stream.collect(Collectors.toSet());
// System.out.println(list);
// System.out.println(list1);//[hello, world, zyz]
System.out.println(set3);//[world, zyz, hello]
}
注意,一个Stream在代码中,只能使用一次,再次使用就会报错
java.lang.IllegalStateException: stream has already been operated upon or closed
3.3.3字符串
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
String s = stream.collect(Collectors.joining("-"));
System.out.println(s);
}
//运行结果
hello-world-zyz
3.4 Stream操作系统
现在已经知道了,怎么把数据转为Stream对象,怎么把操作后的Stream转为其他数据,那么接下来 就看下,使用Stream的API可以对数据做哪些一系列的操作
Stream操作主要分为中间操作或者最终操作:
- 最终操作,就是把Stream处理完了,已经有结果了,之后就不能再使用这个Stream了
- 中间操作,对Stream进行一个中间操作后,还可以对其进行下一步的继续操作
3.4.1 最终操作
Stream中常用的最终操作,有以下几种:
1.iterator
,返回迭代器对象
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
Iterator<String> it=stream.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
hello
world
zyz
2.forEach
,将调Stream中的每个元素,交过一个Consumer函数处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z6WEmnBV-1638151320539)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210704153434732.png)]
public interface Consumer<T> {
void accept(T t);
}
例如:
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
// stream.forEach( s-> System.out.println(s));
stream.forEach( System.out::println);
}
3.count
:统计流中的元素数。并返回结果
例如:
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
System.out.println(stream.count());
}
4.max
:返回流中基于comparator所指定的比较规则,比较出最大值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVUOKvSO-1638151320542)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210704154423219.png)]
例如:
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
// Optional<String> max = stream.max(((o1, o2) -> o1.compareTo(o2)));
stream.max(String::compareTo);//String类中有compareTo()方法
System.out.println(max.get());
}
//为什么这使用String::compareTo来对抽象方法compare进行实现?
//可以参考上面“实例方法引用”的章节部分
//运行结果
zyz
5.min
,返回流中基于comparator所指定的比较规则,比较出的最小值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TFNFAOwB-1638151320546)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210704161106223.png)]
例如
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
// Optional<String> min = stream.min(((o1, o2) -> o1.compareTo(o2)));
// System.out.println(min.get());
Optional<String> min1 = stream.min(String::compareTo);
System.out.println(min1.get());//输出hello
}
6.toArray
:使用调用流中的元素,生成数组返回。
public static void main(String[] args) {
Stream<String> stream=Stream.of("hello","world","zyz");
String[] array = stream.toArray(String[]::new);
// String[] strings = stream.toArray(s -> new String[s]);
/*String[] strings1 = stream.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
});*/
System.out.println(Arrays.toString(array));
// System.out.println(Arrays.toString(strings));
collect
,将元素收集到一个可以修改的容器中,并返回该容器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AlgM2mni-1638151320548)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210704162727305.png)]
List<String> list = stream.collect(Collectors.toList());
// ArrayList<String> list1 = stream.collect(Collectors.toCollection(ArrayList::new));
// Set<String> set = stream.collect(Collectors.toSet());
// HashSet<String> set1 = stream.collect(Collectors.toCollection(HashSet::new));
String collect = stream.collect(Collectors.joining(":"));
System.out.println(collect);
public static void test1(){
Stream<String> stream =
Stream.of("test","hello","world","java","tom","C","javascript");
//把Stream中的元素,按照字符串长度进行分组,长度相同算是一组,并存放到同一个集合中
//map的key是字符串的长度,value是同一组的数据
Map<Integer,List<String>> map=
stream.collect(Collectors.groupingBy(s->s.length()));
map.forEach((k,v)-> System.out.println(k+":"+v));
}
//运行结果:
1 : [C]
3 : [tom]
4 : [test, java]
5 : [hello, world]
10 : [javascript]
public static void test2(){
Stream<String> stream =
Stream.of("test","hello","world","java","tom","C","javascript");
//把Stream中的元素,按照指定条件分割成俩组,条件返回true是一组,条件返回false是另一组
//map的key是true或者false,value是对应的数据
//按照数据中是否包含"java"字符串来进行划分
Map<Boolean, List<String>> map =
stream.collect(Collectors.partitioningBy(s -> s.indexOf("java") != -1));
map.forEach((k,v)-> System.out.println(k+":"+v));
}
//运行结果:
false : [test, hello, world, tom, C]
true : [java, javascript]
Match
,匹配操作,Stream中提供了多种匹配模式
public static void test3(){
Stream<String> stream =
Stream.of("test","hello","world","java","tom","C","javascript");
//任意一个匹配成功就返回true 否则返回false
boolean anyMatch = stream.anyMatch(s -> s.startsWith("j"));
//所有元素匹配成功才返回true 否则返回false
boolean allMatch = stream.allMatch(s -> s.startsWith("j"));
//没有一个匹配的就返回true 否则返回false
boolean noneMatch = stream.noneMatch(s -> s.startsWith("j"));
}
注意,这些操作不能同时执行,因为一个Stream只能使用一次
findFirst
,返回 Stream的第一个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream =
Stream.of("test","hello","world","java","tom","C","javascript");
Optional<String> first = stream.findFirst();
System.out.println(first.get());
}
}
3.4.2 中间操作
filter
, 过滤方法,返回满足predicate指定的条件的所有元素的一个新流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LEARLHhC-1638151320551)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210707154946342.png)]
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
stream.filter(e->e.contains("o")).forEach(System.out::println);
}
}
//运行结果:
hello
world
map
, 对调用流中的元素,应用Function所指定的操作,然后返回一个新流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fEus91q-1638151320553)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210707155208072.png)]
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
List<Integer> list = stream.map(str -> str.length())
.collect(Collectors.toList());
list.forEach(System.out::println);
}
}
//运行结果:
5
5
5
map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素
map
方法可以和 reduce
方法配合使用, reduce
方法是将一组数据俩俩合并,最后得出一个结 果:
public class Test {
public static void main(String[] args) {
//1~10之间的数字累加
IntStream stream = IntStream.rangeClosed(1, 10);
//reduce方法需要提供一个起始值(种子)
//然后依照运算规则,和Stream中的第一个数据进行操作,得出结果
//再将这个结果和Stream中的第二个数据进行操作,再得出结果,依次类推,直到得出最终
结果
int result = stream.reduce(0, (a, b) -> a + b);
System.out.println(result);
}
}
//运行结果:
55
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("tom","mary","lucy");
//map方法中,让每一个数据加上前缀
//reduce方法中,将每个数据使用|拼接合并在一起
//reduce方法,也可以没有起始值,直接对Stream中的数据进行俩俩操作
Optional<String> result = stream.map(str -> "briup_" +
str).reduce((s1, s2) -> s1 + "|" + s2);
System.out.println(result.get());
}
}
//运行结果:
briup_tom|briup_mary|briup_lucy
sorted
, 排序
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
//默认自然排序
stream.sorted().forEach(System.out::println);
}
}
//运行结果:
briup
hello
world
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
//比较器排序,注意Lambda表达式中返回的值前加了符号
stream.sorted((o1, o2) -> -
o1.compareTo(o2)).forEach(System.out::println);
}
}
//运行结果:
world
hello
briup
limit
,返回Stream的前面n个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream =
Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.limit(5).forEach(System.out::println);
}
}
//输出结果:
test
javap
hello
world
java
skip
跳过前n个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream =
Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.skip(5).forEach(System.out::println);
}
}
//运行结果:
tom
C
javascript
distinct
去除重复数据
public class Test {
public static void main(String[] args) {
Stream<String> stream =
Stream.of("test","test","hello","world","java","java","C","C");
stream.distinct().forEach(System.out::println);
}
}
//运行结果:
test
hello
world
java
C
3.4.2 静态方法
concat
,拼接两个流
public static void test3(){
Stream<String> stream1 = Stream.of("hello","world");
Stream<String> stream2 = Stream.of("tom","mary");
Stream<String> concat = Stream.concat(stream1, stream2);
concat.forEach(s -> System.out.println(s));
}
//运行结果:
hello
world
tom
mary
Stream.generate
,通过Supplier接口,可以自己来控制数据的生成
public static<T> Stream<T> generate(Supplier<T> s) {
//...
}
public static void test3(){
final Random random=new Random();
//生成100个随机数,并输出
Stream.generate(()->random.nextInt(100))//范围100以内
.limit(12)//限制个数
.forEach(System.out::println);
//生成100个随机数,并存放在集合中
List<Integer> list = Stream.generate(()->random.nextInt(100))
.limit(100)
.collect(Collectors.toList());
System.out.println(list);
}
Stream.iterate
,它跟 reduce
操作很像,需要接受一个起始值(种子),然后通过函数得出一个 结果,再把结果当做参数传给函数,再得出第二个结果,依次类推,其实就是一个递归操作。
假设起始值为seed
,函数为f,那么第一个元素是seed
,第二个元素是f(seed
),第三个元素是f(f(seed)) , 第四个元素是f(f(f(seed))),以此类推。
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
//...
}
public class Test {
public static void main(String[] args) {
//生成一个等差数列,公差为3,从0开始获取前10个数字
Stream.iterate(0, n -> n + 3)
.limit(10)
.forEach(System.out::println);
}
}
//运行结果:
0
3
6
9
12
15
18
21
24
27
3.5 IO流和Stream
在IO流中,也有方法,可以将读取到的数据转换为Stream对象
public class BufferedReader extends Reader {
//@since 1.8
public Stream<String> lines() {
//..
}
}
例如,从a.txt文件中,找出字符最长的一行,返回它的字符数
public static void test3() {
BufferedReader br=null;
try {
br=new BufferedReader(new FileReader("src/file/a.txt"));
int asInt = br.lines()//io流转为Stream
.mapToInt(s -> s.length())//每行字符串转换为它的字符长度
.max()//获取最大长度的数字
.getAsInt();;//返回int类型的结果
System.out.println(asInt);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.6 并行流
Stream有串行和并行两种。串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多 个线程上同时执行。
创建并行Stream的俩种方式:
-
调用串行
Stream
的parallel
() 方法,可以将其转换并行StreamStream<String> stream = Stream.of("hello","world","briup"); Stream<String> parallelStream = stream.parallel();
-
调用集合对象的
parallelStream
方法,之后获取并行StreamList<String> list = new ArrayList<>(); Collections.addAll(list,"hello","world","briup"); Stream<String> parallelStream = list.parallelStream();
例如,排序并统计200万个字符串
public static void main(String[] args) {
//生成200万个不同的字符串放到集合中
int max=20000000;
List<String> list=new ArrayList<>(max);
for (int i = 0; i <max ; i++) {
UUID uuid=UUID.randomUUID();
list.add(uuid.toString());
}
//注意,前面生成字符串和存放到List中,也需要一些时间
long start =System.currentTimeMillis();
//串行stream
long count= list.stream().sorted().count();
//串行
// long count = list.parallelStream().sorted().count();
long end = System.currentTimeMillis();
long time=end-start;
System.out.println("耗时:" + time);
}
可以看出,在数据量较大的特定场景下,并行Stream比串行Stream的效率要高一些