Java学习笔记——Java新特性

Java学习笔记——Java新特性

菜鸟教程

Stream流

Java 8新增的Stream是为了解放程序员操作集合时的生产力,很大一部分原因是因为出现了Lambda表达式。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

创建流

如果是数组需要使用Arrays.stream()或者Stream.of()来创建流,如果是集合的话,可以直接使用stream()方法创建流,还可以用parallelStream()来创建并行流,默认是使用ForkJoinPool.commomPool()线程池。

操作流

  1. 过滤

    通过filter()方法从流中筛选元素

    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class A {
        public static void main(String[] args) {
            List<String> ls = new ArrayList<>();
            ls.add("ab");
            ls.add("cd");
            ls.add("ef");
            Stream<String> stream = ls.stream().filter(element -> element.contains("c"));
            stream.forEach(System.out::println);
        }
    }
    

    需要 JDK8 以上

    在这里插入图片描述

    运行结果是

    cd

    • filter()方法接收的是一个Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数,本例将一个Lambda表达式传给该方法,即筛选出带有"c"的字符串
    • forEach()方法接收的是一个Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回值的操作)类型的参数,classname::functionname是Java 8引入的新语法,System.out返回PrintStream类,然后stream.forEach(System.out::println)相当于在for循环中打印
  2. 映射

    如果想通过某种操作把一个流中的元素转换成新的流中的元素,可以使用map()方法

    public class A {
        public static void main(String[] args) {
            List<String> ls = new ArrayList<>();
            ls.add("ab");
            ls.add("cd");
            ls.add("ef");
            Stream<Integer> stream = ls.stream().map(String::length);
            stream.forEach(System.out::println);
        }
    }
    

    运行结果为:

    2
    2
    2

    • map()方法接收的是一个Function(Java 8新增的函数式接口,接受一个输入参数T,返回一个结果R)类型的参数,此时参数为String类的length方法,即把Stream<String>的流转成一个Stream<Integer>的流
  3. 匹配

    Stream类提供了三个方法来进行元素匹配

    • anyMatch(),只要有一个元素匹配传入条件,即返回true
    • allMatch(),只要有一个元素不匹配传入条件即返回false
    • noneMatch(),只要有一个元素匹配传入条件返回false,否则返回true

    他们接收的是一个Predicate类型的参数。

  4. 组合

    reduce()方法的主要作用是把Stream中的元素组合起来

    • Optional<T> reduce(BinaryOperator<T> accumulator),没有初始值,只有一个操作即运算规则,此时返回Optional
    • T reduce(T identify, BinaryOperator<T> accmulator),有初始值,有运算规则,两个参数,此时返回的类型和初始值的类型一致
    public class A {
        public static void main(String[] args) {
            Integer[] ints = {0,1,2,3};
            List<Integer> ls = Arrays.asList(ints);
    
            Optional<Integer> op1 = ls.stream().reduce((a,b)->a+b);
            Optional<Integer> op2 = ls.stream().reduce(Integer::sum);
            System.out.println(op1.orElse(0));
            System.out.println(op2.orElse(0));
    
            int reduce1 = ls.stream().reduce(6,(a,b)->a+b);
            System.out.println(reduce1);
            int reduce2 = ls.stream().reduce(6,Integer::sum);
            System.out.println(reduce2);
        }
    }
    

    运行结果如下:

    6
    6
    12
    12

    • 运算规则可以是Lambda表达式,也可以是classname::functionname

转换流

将流转换为数组或者集合,collect()方法

public class A {
    public static void main(String[] args) {
        List<String> ls = new ArrayList<>();
        ls.add("ab");
        ls.add("cd");
        ls.add("ef");

        // 流转换成数组
        String[] strArray = ls.stream().toArray(String[]::new); // Can be replaced with 'collection.toArray()'
        System.out.println(Arrays.toString(strArray));

        List<Integer> ls1 = ls.stream().map(String::length).collect(Collectors.toList());
        List<Integer> ls2 = ls.stream().map(String::length).collect(Collectors.toCollection(ArrayList::new));
        System.out.println(ls1);
        System.out.println(ls2);

        String str = ls.stream().collect(Collectors.joining(",")).toString();
        System.out.println(str);
    }
}

输出结果:

[ab, cd, ef]
[2, 2, 2]
[2, 2, 2]
ab,cd,ef

  • toArray()方法可以将流转换成数组,String[]::new一个可以产生所需的新数组的函数。相当于返回了一个指定长度的字符串数组
  • 当我们需要把一个集合按照某种规则转换成另一个集合的时候,可以配套使用map()collect()方法。
    • stream()方法创建集合流
    • map(String::length)将集合流映射为字符串长度的一个新流
    • collect()方法将其转换为新的集合

Optional

主要用于解决的问题是臭名昭著的空指针异常(NullPointerException|)的一个类。

Optional类提供了一种用于表示可选值而非空引用的类级别解决方案,避免没必要的null检查

创建Optional对象

  1. 静态方法empty()创建空的Optional对象

    Optional<String> empty = Optional.empty();
    System.out.println(empty); // 输出:Optional.empty
    
  2. 静态方法of()创建非空的Optional对象

    Optional<String> opt = Optional.of("ab");
    System.out.println(opt); // 输出:Optional[ab]
    

    传入of()中的参数必须非空

  3. 静态方法ofNullable()创建既可空又可非空的Optional对象

    String name = null;
    Optional<String> optOrNull = Optional.ofNullable(name);
    System.out.println(optOrNull); // 输出:Optional.empty
    

    原理是ofNullable()内部有一个三元表达式,如果参数为null则返回私有常量EMPTY;否则使用new关键字创建一个新的Optional对象,所以不会抛出NPE异常

判断值是否存在

isPresent()判断一个Optional对象是否存在,如果存在返回true;否则返回false。这一条性质取代了obj!=null的判断

Optional<String> opt = Optional.of("a");
System.out.println(opt.isPresent()); //true

Optional<String> optNull = Optional.ofNullable(null);
System.out.println(optNull.isPresent()); // false

Java 11以后还可以用isEmpty()判断,其结果与isPresent()相反

非空表达

ifPresent()允许使用函数式编程的方式执行一些代码

Optional<String> opt = Optional.of("a");
opt.ifPresent(str -> System.out.println(str.length()))

Java 9以后可以通过ifPresentOrElse(action,emptyAction)来执行两种结果

Optional<String> opt = Optional.of("a");
opt.ifPresentOrElse(str -> System.out.println(str.length()),()-> System.out.println("Empty."))

设置(获取)默认值

orElse()方法用于返回包裹在Optional对象中的值,如果该值不为null,返回;否则返回默认值。该方法的参数类型和值的类型需要保持一致

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("default");
System.out.println(name); // 输出:default

orElseGet()类似,但是参数类型不同。如果Optional对象中的值为null,执行参数中的函数

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"default");
System.out.println(name); // 输出:default

使用get()方法获取值的时候,当Optional对象为空时可能会报错

过滤值

filter()方法的参数类型为Predicate(Java 8新增的一个函数式接口),即将一个Lambda表达式作为参数传给该方法作为条件,如果表达式结果输出为false,返回一个empty的Optional对象;否则返回过滤后的Optional对象

public class A {
    public static void main(String[] args) {
        String password = "12345";
        Optional<String> opt = Optional.ofNullable(password);

        Predicate<String> len6 = pwd -> pwd.length()>6;
        Predicate<String> len10 = pwd -> pwd.length()<10;

        boolean result = opt.filter(len6.and(len10)).isPresent();
        System.out.println(result);
    }
}

转换值

map()方法可以按照一定规则将原有Optional对象转换为一个新的Optional对象,且原有的Optional对象不会改变

public class A {
    public static void main(String[] args) {
        String password = "123459";
        Optional<String> opt = Optional.ofNullable(password);

        Optional<Integer> optInt = opt.map(String::length);
        System.out.println(optInt.orElse(0)); // 输出:6
    }
}

String::length,将原有的字符串类型的Optional按照字符串长度重新生成一个类型为Integer的Optional对象

Lambda表达式

Lambda表达式描述了一个代码块(匿名方法),可以将其作为参数传递给构造方法或者普通方法

// eg1.
() -> System.out.println("Hello world!");
  • ()为Lambda表达式的参数列表,本例中没有参数
  • ->标识这串代码为Lambda表达式
  • System.out.println("Hello world!")为要执行的代码
// eg2. 创建线程
public class LamadaTest{
    public static void main(String[] args){
        new Thread(new Runnable(){
            @Override
            public void run(){
                System.out.println("Hello world!");
            }
        }).start();
    }
}
// 利用Lambda表达式
public class LamadaTest{
    public static void main(String[] args){
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
( params ) -> {expression-or-statements}
  • ()中的参数以逗号 “,” 分隔,可以指定参数也可以不指定,编译器会根据上下文进行推断
  • ->Lambda表达式的标识符
  • {}是Lambda表达式的主体,可以是一行也可以是多行

Lambda表达式作用域范围

和匿名内部类一样,不要在Lambda表达式主体中对方法内的局部变量进行修改,否则编译不通过。Lambda表达式中使用的变量必须是final的。

Lambda表达式中要用到的,但又未在Lambda表达式中声明的变量,必须声明为final或者effectively final,否则会出现编译错误

// final关键字修饰,赋值一次就不能修改的变量
final int a;
a = 1;

int b;
b = 1;
// 往后b不会被更改,此时b就是effectively final

int c;
c = 1;
c = 2;
// 此时c就不是effectively final

举个例子

// eg.错误的例子,编译器会爆错:变量limit已被定义
public class A {
    public static void main(String[] args) {
        int limit = 10;
        Runnable r = () ->{
            // 此处报错
            int limit = 5;
            for(int i=0;i<limit;i++){
                System.out.println(i);
            }
        }
    }
}

解决方法:

  1. 把limit变量设置为static

    public class A {
        static int limit = 10;
        public static void main(String[] args) {
            Runnable r = () ->{
                limit = 5;
                for(int i=0;i<limit;i++){
                    System.out.println(i);
                }
            };
            r.run();
        }
    }
    

    输出结果:

    0
    1
    2
    3
    4

  2. 把limit变量声明为AtomicInteger

    AtomicInteger可以保证int值的修改是原子的,可以使用set()方法设置一个新的int值,get()方法获取当前int值

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class A {
    
        public static void main(String[] args) {
            final AtomicInteger limit = new AtomicInteger(10);
            Runnable r = () ->{
                limit.set(5);
                for(int i=0;i<limit.get();i++){
                    System.out.println(i);
                }
            };
            r.run();
        }
    }
    

    输出结果:

    0
    1
    2
    3
    4

  3. 使用数组

    public class A {
        public static void main(String[] args) {
            final int[] limit = {10};
            Runnable r = () ->{
                limit[0] = 5;
                for(int i=0;i<limit[0];i++){
                    System.out.println(i);
                }
            };
            r.run();
        }
    }
    

    使用final修饰数组,欺骗编译器。final修饰数组的时候,该引用变量limit不能被修改,但是数组内容可以被修改

    输出结果:

    0
    1
    2
    3
    4

Lambda和this关键字

Lambda表达式不会引入新的作用域,即Lambda表达式主体内部使用this关键字和其所在的类实例相同

public class A {
    public static void main(String[] args) {
        A a = new A();
        a.work();
    }
    public void work(){
        System.out.printf("this = %s%n",this);
        // 匿名内部类创建线程
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.printf("this = %s%n",this);
            }
        };
        new Thread(r).start();
        // Lambda表达式创建线程
        new Thread(()->System.out.printf("this = %s%n",this)).start();
    }
}

输出结果:

this = A@448139f0
this = A$1@5000d44
this = A@448139f0

可以看到,利用Lambda表达式和主体内使用的this关键字和其所在的类实例相同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mighty-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值