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()
线程池。
操作流
-
过滤
通过
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循环中打印
-
映射
如果想通过某种操作把一个流中的元素转换成新的流中的元素,可以使用
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
2map()
方法接收的是一个Function(Java 8新增的函数式接口,接受一个输入参数T,返回一个结果R)类型的参数,此时参数为String类的length方法,即把Stream<String>
的流转成一个Stream<Integer>
的流
-
匹配
Stream类提供了三个方法来进行元素匹配
anyMatch()
,只要有一个元素匹配传入条件,即返回trueallMatch()
,只要有一个元素不匹配传入条件即返回falsenoneMatch()
,只要有一个元素匹配传入条件返回false,否则返回true
他们接收的是一个Predicate类型的参数。
-
组合
reduce()
方法的主要作用是把Stream中的元素组合起来Optional<T> reduce(BinaryOperator<T> accumulator)
,没有初始值,只有一个操作即运算规则,此时返回OptionalT 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对象
-
静态方法
empty()
创建空的Optional对象Optional<String> empty = Optional.empty(); System.out.println(empty); // 输出:Optional.empty
-
静态方法
of()
创建非空的Optional对象Optional<String> opt = Optional.of("ab"); System.out.println(opt); // 输出:Optional[ab]
传入
of()
中的参数必须非空 -
静态方法
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);
}
}
}
}
解决方法:
-
把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 -
把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 -
使用数组
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关键字和其所在的类实例相同