【jdk8的新特性】

JDK1.8新特性

接口方法

接口中的属性默认有:static final 修饰符修饰;

定义的方法默认是抽象方法;

现在可以写默认方法

由于JDK1.8的API,在已有的接口上,新增了很多的新方法,这种新增变化带来的问题,正如上述的情况 一样,也会给使用这些接口的老用户带来很多的不便。

为了解决这个问题,JDK1.8中引入了一种新的机制:接口可以支持在声明方法的同时,提供实现。 主要通过两种方式可以完成这种操作:

  1. 默认方法
  2. 静态方法

1.1默认方法

定义语法:

interface InterfaceName{

​ default returnType methodName(arg-list){ }

}

默认的访问修饰符是public

例如:

interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
两大优势:
  1. 可以让接口更优雅的的升级,减少使用人员操作的负担;

    不必随着接口方法的实现,从而修改实现代码,因为默认方法在子类用可以不用实现;

  2. 可以让实现类中省略很多不必要的方法的空实现;

接口继承与默认方法的冲突:

例如:一个类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 包 中,例如 PredicateConsumerFunctionSupplierUnaryOperatorBinaryOperator 等。

注意,如果需要,也可以自己定义类似的函数式接口,并不是必须要使用这些定义好的接口。

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的平均执 行效率较高

默认情况下,这些专门对基本类型数据进行操作的函数式接口,它们的名字都有一定的规律前缀,比如 DoublePredicateIntConsumerLongBinaryOperatorIntFunction 等。

其中, Function 接口还有针对输出参数类型的变种: ToIntFunctionIntToDoubleFunction

类型推断


使用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表达式,可以表示一个函数,这个函数由 “

  1. 参数列表、

  2. 函数主体、

  3. 返回类型”

    这三部分组 成,同时这个函数还是一个函数式接口中,唯一的抽象方法的实现,例如:

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 中常用的方法:

  1. of
  2. ofNullable
  3. isPresent
  4. ifPresent
  5. get
  6. orElse
  7. orElseGet
  8. map
  9. faltMap
  10. 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));
  1. 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] 
  1. 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只能使用一次

  1. 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的俩种方式:

  • 调用串行Streamparallel() 方法,可以将其转换并行Stream

    Stream<String> stream = Stream.of("hello","world","briup");
    Stream<String> parallelStream = stream.parallel();
    
  • 调用集合对象的 parallelStream 方法,之后获取并行Stream

    List<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的效率要高一些

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值