JDK8 新特性
1.Lambda表达式
public interface IMesage{
void getMessage();
}
public class Main{
pubilc static void main(String[] args){
IMessage message = () -> {
System.out.println("Hello world");
}
message.getMessage();
}
}
Lambda 说明:
好处:减少了代码量,在开发过程中不需要机接口中方法的名字,使接口名和函数名一并省略掉了。(整体来看Lambda就是一个完整的接口)
注意:要求接口中只有一个抽象方法。可用注解
@FunctionalInterface
格式 1. 方法没有参数 () —> {};
2.方法有参数(参数,参数)—> {};
3.方法只有一条语句 (参数,参数)—> 语句 ;
巧妙使用案例:
class MyStream<T>{
private List<T> list;
...
public void myForEach(ConsumerInterface<T> consumer){// 1
for(T t : list){
consumer.accept(t);
}
}
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str));// 使用自定义函数接口书写Lambda表达式(把整个当作一个接口去理解)
(1)匿名内部类(Anonymous Classes)与Lambda表达式比较
匿名内部类 | Lambda表达式 |
---|---|
匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。因此类编译之后将会产生两个class文件:(xxxxx&1.class) | Lambda表达式通过*invokedynamic*指令实现,书写Lambda表达式不会产生新的类。反编译之后我们发现Lambda表达式被封装成了主类的一个私有方法,并通过invokedynamic指令进行调用。 |
this只能调用该内部类中的元素 | this能够调用真个类中的元素 |
this的应用:
public class Hello {
Runnable r1 = () -> { System.out.println(this); };
// this默认调用的使toString方法,如果没有重写则调用Object类中的toString方法
Runnable r2 = () -> { System.out.println(toString()); };
public static void main(String[] args) {
new Hello().r1.run();
new Hello().r2.run();
}
public String toString() { return "Hello Hoolee"; }
}
(2)新增接口方法(集合框架)
为引入Lambda表达式,Java8新增了java.util.funcion
包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。
接口名 | java8新增方法 |
---|---|
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | goOrDefault() forEach() replaceAll() putIfAbsent() remove() raplace() computeIfAbsent() computeIfPresent() compute() merge() |
Collection
removeIf();
该方法签名为boolean removeIf(Predicate<? super E> filter)
,作用是删除容器中所有满足filter
指定条件的元素,其中Predicate
是一个函数接口,里面只有一个待实现方法boolean test(T t)
(后面我们会看到,这个方法叫什么根本不重要,你甚至不需要记忆它的名字)。
// 使用removeIf()结合匿名名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){ // 删除长度大于3的元素
@Override
public boolean test(String str){
return str.length()>3;
}
});
forEach();
该方法的签名为void forEach(Consumer<? super E> action)
,作用是对容器中的每个元素执行action
指定的动作,其中Consumer
是个函数接口,里面只有一个待实现方法void accept(T t)
。
// 使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
@Override
public void accept(String str){
if(str.length()>3)
System.out.println(str);
}
});
spliterator();
方法签名为Spliterator<E> spliterator()
,该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()
方法有点像,我们知道Iterator
是用来迭代容器的,Spliterator
也有类似作用,但二者有如下不同:
Spliterator
既可以像Iterator
那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。Spliterator
是可拆分的,一个Spliterator
可以通过调用Spliterator<T> trySplit()
方法来尝试分成两个。一个是this
,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。
可通过(多次)调用Spliterator.trySplit()
方法来分解负载,以便多线程处理。
stream();和parallelStream();
stream()
和parallelStream()
分别返回该容器的Stream
视图表示,不同之处在于parallelStream()
返回并行的Stream
。Stream
是Java函数式编程的核心类。
List
replaceAll();
该方法签名为void replaceAll(UnaryOperator<E> operator)
,作用是对每个元素执行operator
指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator
是一个函数接口,里面只有一个待实现函数T apply(T t)
。
// 使用匿名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<String>(){
@Override
public String apply(String str){
if(str.length()>3)
return str.toUpperCase();
return str;
}
});
sort();
该方法定义在List
接口中,方法签名为void sort(Comparator<? super E> c)
,该方法根据c
指定的比较规则对容器元素进行排序。Comparator
接口我们并不陌生,其中有一个方法int compare(T o1, T o2)
需要实现,显然该接口是个函数接口。
// 之前的写法
// Collections.sort()方法
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>(){
@Override
public int compare(String str1, String str2){
return str1.length()-str2.length();
}
});
// List.sort()方法结合Lambda表达式
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());
Map
forEach();
该方法签名为void forEach(BiConsumer<? super K,? super V> action)
,作用是对Map
中的每个映射执行action
指定的操作,其中BiConsumer
是一个函数接口。
@org.junit.Test
public void test1 (){
Map<Integer,String> map = new HashMap();
map.put(1,"adc");
map.put(2,"cde");
map.put(3,"dcef");
map.put(4,"dfsa");
// 以前的方法
for (Map.Entry<Integer,String> element: map.entrySet()) {
System.out.println(element.getKey()+"="+element.getValue());
}
// forEach方法
map.forEach(((integer, s) -> System.out.println(integer+"="+s)));
}
getOrDefault()
该方法跟Lambda表达式没关系,但是很有用。方法签名为V getOrDefault(Object key, V defaultValue)
,作用是按照给定的key
查询Map
中对应的value
,如果没有找到则返回defaultValue
。使用该方法程序员可以省去查询指定键值是否存在的麻烦。
// 查询Map中指定的值,不存在时使用默认值
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
// Java7以及之前做法
if(map.containsKey(4)){
System.out.println(map.get(4));
}else{
System.out.println("NoValue");
}
// Java8使用Map.getOrDefault()
System.out.println(map.getOrDefault(4, "NoValue"));
putIfAbsent()
该方法跟Lambda表达式没关系,但是很有用。方法签名为V putIfAbsent(K key, V value)
,作用是只有在不存在key
值的映射或映射值为null
时,才将value
指定的值放入到Map
中,否则不对Map
做更改.该方法将条件判断和赋值合二为一,使用起来更加方便。
remove()
我们都知道Map
中有一个remove(Object key)
方法,来根据指定key
值删除Map
中的映射关系;Java8新增了remove(Object key, Object value)
方法,只有在当前Map
中**key
正好映射到value
时**才删除该映射,否则什么也不做。
replace()
在Java7及以前,要想替换Map
中的映射关系可通过put(K key, V value)
方法实现,该方法总是会用新值替换原来的值.为了更精确的控制替换行为,Java8在Map
中加入了两个replace()
方法,分别如下:
replace(K key, V value)
,只有在当前Map
中**key
的映射存在时**才用value
去替换原来的值,否则什么也不做。replace(K key, V oldValue, V newValue)
,只有在当前Map
中**key
的映射存在且等于oldValue
时**才用newValue
去替换原来的值,否则什么也不做。
replaceAll()
该方法签名为replaceAll(BiFunction<? super K,? super V,? extends V> function)
,作用是对Map
中的每个映射执行function
指定的操作,并用function
的执行结果替换原来的value
,其中BiFunction
是一个函数接口,里面有一个待实现方法R apply(T t, U u)
.不要被如此多的函数接口吓到,因为使用的时候根本不需要知道他们的名字。
需求:假设有一个数字到对应英文单词的Map,请将原来映射关系中的单词都转换成大写。
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll((k, v) -> v.toUpperCase());
merge()
该方法签名为merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)
。
作用是:
- 如果
Map
中key
对应的映射不存在或者为null
,则将value
(不能是null
)关联到key
上; - 否则执行
remappingFunction
,如果执行结果非null
则用该结果跟key
关联,否则在Map
中删除key
的映射.
参数中BiFunction
函数接口前面已经介绍过,里面有一个待实现方法R apply(T t, U u)
。
map.merge(key, newMsg, (v1, v2) -> v1+v2);
compute()
该方法签名为compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
,作用是把remappingFunction
的计算结果关联到key
上,如果计算结果为null
,则在Map
中删除key
的映射。
map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));
computeIfAbsent()
该方法签名为V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
,作用是:只有在当前Map
中不存在key
值的映射或映射值为null
时,才调用mappingFunction
,并在mappingFunction
执行结果非null
时,将结果跟key
关联.
Function
是一个函数接口,里面有一个待实现方法R apply(T t)
。
computeIfAbsent()
常用来对Map
的某个key
值建立初始化映射.比如我们要实现一个多值映射,Map
的定义可能是Map<K,Set<V>>
,要向Map
中放入新值,可通过如下代码实现:
Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的实现方式
if(map.containsKey(1)){
map.get(1).add("one");
}else{
Set<String> valueSet = new HashSet<String>();
valueSet.add("one");
map.put(1, valueSet);
}
// Java8的实现方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("one");
computeIfPresent()
该方法签名为V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
,作用跟computeIfAbsent()
相反,即,只有在当前Map
中存在key
值的映射且非null
时,才调用remappingFunction
,如果remappingFunction
执行结果为null
,则删除key
的映射,否则使用该结果替换key
原来的映射。
2.函数式接口
我们知道Lambda表达式使用的前提是函数式接口,在使用Lambda表达式时不需要关心函数名和接口名字。只关心函数的参数和返回值。
为了让我们使用Lambda表达式的时候更加方便,JDK提供了大量的函数式接口供开发者使用,均在java.util.funcation包中。
(1)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); };
}
}
案例:
public class ConsumerTest {
public static void main(String[] args) {
fun("fuck",s -> { // 没有传入参数
System.out.println(s.toUpperCase(Locale.ROOT));
});
}
public static void fun(String str, Consumer<String> consumer) {
consumer.accept(str);
}
}
连续操作案例:
public class ConsumerTest {
public static void main(String[] args) {
fun(s -> {
System.out.println(s.toUpperCase(Locale.ROOT));
},s -> {
System.out.println(s.toLowerCase(Locale.ROOT));
});
}
public static void fun(Consumer<String> consumer,Consumer<String> consumer2 ){
String str = "Hello World" ;
consumer.andThen(consumer2).accept(str);
}
}
输出:
HELLO WORLD
hello world
(2)Supplier接口(生产者,没有参数,有返回值)
@FunctionalInterface
public interface Supplier<T> {
T get();
}
案例:
public class SupplierTest {
public static void main(String[] args) {
fun(() -> { // 无参数传入
return "jack" + "fuck";
});
}
public static void fun(Supplier supplier) {
System.out.println(supplier.get());
}
}
(3)Function接口(有参数,有返回值)
传入一个参数对其进行处理,并返回一个值(前者为参数的数据类型,后者为返回值的数据类型)。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
// andThen方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
// 与andThen运行干好相反
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 传过来什么就返回什么
static <T> Function<T, T> identity() {
return t -> t;
}
}
案例:
// 字符串长度计算
public class FunctionTest {
public static void main(String[] args) {
System.out.println(fun("jadasf", s -> {
return s.length();
}));
}
public static int fun(String str, Function<String, Integer> function) {
return function.apply(str);
}
}
andThen测试案例:
public class FunctionTest {
public static void main(String[] args) {
System.out.println(fun("jadasf", s -> {
return s.length();
}, integer -> {
return integer + 100;
}));
}
public static int fun(String str, Function<String, Integer> function, Function<Integer, Integer> function2) {
return function.andThen(function2).apply(str);
}
}
(4)Predicate接口(有参数,返回一个boolean型)
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
// 且
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// 取反
default Predicate<T> negate() {
return (t) -> !test(t);
}
// 或
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用 java.util.function.Predicate 接口。
案例:
public class PredicateTest {
public static void main(String[] args) {
fun(s -> {
return s.length() < 3;
});
}
public static void fun(Predicate<String> predicate) {
System.out.println(predicate.test("dfsafds"));
}
}
结果:
false
3.方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
- 方法引用通过方法的名字来指向一个方法。
- 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 方法引用使用一对冒号 :: 。
public class QuoteTest {
public static void main(String[] args) {
// 使用了方法引用
fun(10,QuoteTest::add);
}
public static int add(int i) {
int sum = 0;
for (int j = i; j > 0; j--) {
sum += j;
}
return sum;
}
public static void fun(int i, Function<Integer, Integer> function) {
int sum = 0;
sum = function.apply(i);
System.out.println(sum);
}
}
应用场景:当Lambda表达式所要表达的逻辑已经再别的类中实现过了(相同的方案),可以直接引用那个类中的方法。
使用的五种形式:
- instanceName::methodName 类名::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用构造器
- TypeName[]::new String[];;new 调用数组的构造器
调用要求:
- 引用方法的参数类型,个数要和就和接口中的抽象方法保持一致。
- 引用方法的返回值要和接口中抽象方法的返回值保持一致。
(1)对象名::方法名(最常见的用法)
如果一个类中存在成员方法,那么可以通过对象名引用方法。
public class FunctionRefTest1 {
public static void main(String[] args) {
Date now = new Date();
// Lambda表达式
Supplier supplier = () -> {
return now.getTime();
};
// 方法引用
Supplier supplier1 = now::getTime;
}
}
now 为一个具体的实例。
(2)类名::静态方法
public class FunctionRefTest1 {
public static void main(String[] args) {
String str = "jdfsafa" ;
Function<String,String> function = (s -> s.toUpperCase(Locale.ROOT));
System.out.println(function.apply(str));
// 使用方法引用
Function<String,String> function1 = String::toUpperCase;
System.out.println(function1.apply(str));
}
}
(3)类名::普通方法
public class FunctionRefTest2 {
public static void main(String[] args) {
Supplier supplier = () -> {return System.currentTimeMillis();};
System.out.println(supplier.get());
// 方法引用
Supplier supplier1 = System::currentTimeMillis;
System.out.println(supplier1.get());
}
}
(4)类名::构造器
由于构造器的名字与类名完全一致,所以构造器引用使用 :: new 的方式。
@Data
public class Student {
public int age;
public String name;
}
public class FunctionRefTest3{
public static void main(String[] args) {
Supplier<Student> supplier = () -> {return new Student();};
System.out.println(supplier.get().age);
// 引用方法
Supplier<Student> supplier1 = Student::new;
System.out.println(supplier1.get().name);
}
}
(5)数组::构造器
public class FunctionRefTest3{
public static void main(String[] args) {
Function<Integer,Integer[]> function = (len) -> {
return new Integer[len];
};
System.out.println("数组的长度是"+ function.apply(10).length);
// 方法引用
Function<Integer,Integer[]> function1 = Integer[]::new;
System.out.println("数组的长度是"+ function1.apply(10).length);
}
}
总结:
方法引用是对Lambda表达式在符合特定情况下的缩写,但是要求引用方法的参数和返回值符合接口方法的要求。
4.Stream API
当我们要对集合中元素进行操作的时候,除了最简单的增、删、改、查,最常用的就是对集合进行遍历。
普通的集合方法只能对数据进行一次处理,不能进行连贯性的处理,大大影响了卡法效率。而且几乎每一次对集合的处理都要进行一次遍历,使代码变得复杂化。
注意Stream和I/O流没有任何关系,Stream不是一种数据结构,Stream流式思想跟像是一种工厂车间的生产流水线。它不会保存数据,而是对数据进行加工处理,可以通过多个工序对数据进行处理。Stream能让我们快速完成一些复杂的操作,如筛选,切片,映射,查找,去重,统计和规约。
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
public class StreamTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("dfs", "fdsfd", "dsad","dfsal");
list.stream().filter(s -> s.startsWith("d")).filter(s -> s.length() == 3).forEach(System.out::println);
}
}
// 获取流,进行筛选(过滤),逐一打印
原来则要进行三次遍历。
步骤
(1)获取流
一.通过Collection获取Stream
在java.util.Collection接口中有一个默认方法stream()
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
但是Map接口并没有实现Collection接口,我们只能获取对应的key和value
Modifier and Type | Method |
---|---|
Collection<V> | values() |
Set<K> | keySet() |
转化为对应的集合框架
public class StreamTest {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap();
Stream<Integer> stream = map.keySet().stream();
Stream<String> stream1 = map.values().stream();
Stream<Map.Entry<Integer, String>> stream2 = map.entrySet().stream();
}
}
二.通过Stream中的of方法获取
对数组中的数据进行处理(不排除现转化为集合,再进行处理)。因为数组中不可能添加默认方法,所可以使用Stream中的静态方法of
public class StreamTest {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("dfs", "dfs");
String[] arr = {"dfsl","dla","dfs"};
Stream<String> stream2 = Stream.of(arr);
stream2.forEach(System.out::println);
int[] arr1 = {1,2,3,4};
Stream.of(arr1).forEach(System.out::println);
// 一定要用包装类
Integer[] arr2 = {1,2,3,4};
Stream.of(arr2).forEach(System.out::print);
}
}
运行结果:
dfsl
dla
dfs
[I@43a25848
1234
注意:这边一定要用包装类才可以进行转化。
(2)Stream常用API
方法名 | 方法作用 | 返回类型 | 方法种类 |
---|---|---|---|
count | 统计个数 | Lang | 终结 |
forEach | 遍历处理(数据) | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取前几个数 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
distinct | 去重 | Stream | 函数拼接 |
sorted | 排序 | Stream | 函数拼接 |
(一切以API文档为依据)
终结方法:返回值类型不再是Stream,不再支持链式调用。
非终结方法:返回值类型任然是Stream,还可以继续使用调用(除了终结方法,其余的都是非终结方法)。
注意:
- Stream操作时不可逆的。(如:已经做过过滤操作,那么就没有办法回到之前)
- Stream返回的时新的流,而不是再原来流的基础上做操作。
- Stream不调用终结方法,中间的操作不会执行。
部分API讲解
forEach
void forEach(Consumer<? super T> action);
count
统计元素个数
long count();
filter
过滤数据,返回符合条件的数据。
Stream<T> filter(Predicate<? super T> predicate);
limit
对流进行部分截取(截取前半段)
Stream<T> limit(long maxSize);
skip
(截取后半段)
Stream<T> skip(long n);
map
如果我们要把流中的数据映射到另一个流中(相当于对整个流进行统一的修改)。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
案例:
@org.junit.Test
public void test5(){
Stream.of("1","2","3").map(Integer::parseInt).forEach(System.out::println);
}
sorted
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
对流进行排序
@org.junit.Test
public void test5() {
Stream.of("21", "2", "8", "6")
.map(Integer::parseInt)
.sorted((num1, num2) -> {
return num1 - num2;
})
.forEach(System.out::println);
}
内部有个比较器,可以自定义排序。
distinct
去重 注意:自定义去重需要重写equals和hashcode方法。
Stream<T> distinct();
@org.junit.Test
public void test5() {
Stream.of("2", "2", "8", "6","6")
.distinct()
.forEach(System.out::println);
}
对于基本类型可以直接去重,但是对于自定义类型务必要重写equals和hashcode方法。
match
如果需要判断数据是否匹配指定的条件,可以使用match。
boolean allMatch(Predicate<? super T> predicate);
// 是否全部满足条件
boolean anyMatch(Predicate<? super T> predicate);
// 是否有任意一个满足条件
boolean noneMatch(Predicate<? super T> predicate);
// 是否都不满足条件
find
寻找一些元素。
Optional<T> findFirst(); // 返回一个描述此流的第一个元素的 Optional,如果流为空,则返回一个空的 Optional。
Optional<T> findAny(); // 返回一个描述流的某些元素的 Optional,如果流为空,则返回一个空的 Optional。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
Max,Min
获取最大值或最小值
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
reduce
将所有的数据进行归纳整合成为一个数据。
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
public class ReduceTest {
public static void main(String[] args) {
Integer sum = Stream.of(1, 23, 43, 45).reduce(0, (x, y) -> {
return x + y;
});
System.out.println(sum);
}
}
identity:默认值
x:前面的计算结果
y:后面的一个数的值
第一次将identity赋给x,将第一个数赋值给y
返回:112
在实际开发中,常常会把map和reduce一起来使用
concat
将两个流合并成为一个。
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
(3)收集结果集
将Stream收集到集合或者数组中。
collect方法
一.结果收集到集合中
@org.junit.Test
public void test6(){
List<String> list = Stream.of("2", "4", "45", "3").collect(Collectors.toList());
System.out.println(list);
} // 收集到List中
@org.junit.Test
public void test7(){
Set<String> set = Stream.of("1", "23", "1", "34").collect(Collectors.toSet());
System.out.println(set);
} // 收集到Set中
结果:
[1, 23, 34] // 少一个是因为Set不允许出现重复数据
// 将Stream转化为具体的实现
@org.junit.Test
public void test8() {
ArrayList<String> arrayList = Stream.of("fds", "df", "dfs")
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList);
} // 将其转化为ArrayList
@org.junit.Test
public void tset9(){
HashSet<String> hashSet = Stream.of("df", "dfs", "fdjs", "dfs")
.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);
} // 将其转化为具体的HashSet
二.结果收集到数组中
@org.junit.Test
public void test10(){
Object[] array = Stream.of("fds", "dsf", "dfsja", "dfjd").toArray();
System.out.println(Arrays.toString(array));
} // 返回时Object类型
@org.junit.Test
public void test12() {
Integer[] nums = Stream.of(2, 343, 5, 23).toArray(Integer[]::new);
System.out.println(Arrays.toString(nums));
}
@org.junit.Test
public void test11(){
String[] str = Stream.of("dd", "df", "dfjs", "df").toArray(String[]::new);
System.out.println(Arrays.toString(str));
} // 返回为自定义类型
(4)流数据做聚合运算
collect方法
可以像数据库聚合函数一样对某个字段进行处理。【sum() , avg() , max() , min() , count()】。
@org.junit.Test
public void test13(){
Double avgAge = Stream.of(new Student(13, "frank"), new Student(15, "Leon"), new Student(14, "Johnny"), new Student(18, "dfs")).collect(Collectors.averagingInt(Student::getAge));
System.out.println(avgAge);
}
结果:
15.0
@org.junit.Test
public void test13(){
Optional<Student> collect = Stream.of(new Student(13, "frank"), new Student(15, "Leon"), new Student(14, "Johnny"), new Student(18, "dfs")).collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println(collect.get());
} // 获取最大年龄的学生
(5)*对流中数据进行分组
根据某个属性对数据进行分组。
Collectors.groupingBy()
@org.junit.Test
public void test13(){
Map<Integer, List<Student>> collect = Stream.of(new Student(15, "frank"), new Student(15, "Leon"), new Student(14, "Johnny"), new Student(18, "dfs")).collect(Collectors.groupingBy(Student::getAge));
}
@org.junit.Test
public void test13() {
Map<String, List<Student>> map = Stream.of(new Student(15, "frank"), new Student(15, "Leon"), new Student(14, "Johnny"), new Student(18, "dfs")).collect(Collectors.groupingBy(student -> student.getAge() >= 15 ? "青年" : "小孩"));
map.forEach((s, students) -> System.out.println(s + students));
}
多级分组
// 通过嵌套的方式进行多级分组
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
@org.junit.Test
public void test13() {
Stream.of(new Student(15, "Johnny", 170), new Student(20, "Frank", 165), new Student(19, "Leon", 175), new Student(16, "Marco", 169), new Student(19, "Jack", 179)).collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(student -> student.getHeight() > 170 ? "发育良好" : "发育不好")));
}
(6)*流中数据分区
对流中的数据进行分区,一组为true一组为false。
Collectors.partitionBy();
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream) {
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Partition<A>, T> accumulator = (result, t) ->
downstreamAccumulator.accept(predicate.test(t) ? result.forTrue : result.forFalse, t);
BinaryOperator<A> op = downstream.combiner();
BinaryOperator<Partition<A>> merger = (left, right) ->
new Partition<>(op.apply(left.forTrue, right.forTrue),
op.apply(left.forFalse, right.forFalse));
Supplier<Partition<A>> supplier = () ->
new Partition<>(downstream.supplier().get(),
downstream.supplier().get());
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new CollectorImpl<>(supplier, accumulator, merger, CH_ID);
}
else {
Function<Partition<A>, Map<Boolean, D>> finisher = par ->
new Partition<>(downstream.finisher().apply(par.forTrue),
downstream.finisher().apply(par.forFalse));
return new CollectorImpl<>(supplier, accumulator, merger, finisher, CH_NOID);
}
}
案例:
@org.junit.Test
public void test13() {
Map<Boolean, List<Student>> map = Stream.of(new Student(15, "Johnny", 170), new Student(20, "Frank", 165), new Student(19, "Leon", 175), new Student(16, "Marco", 169), new Student(19, "Jack", 179)).collect(Collectors.partitioningBy(student -> student.getHeight() > 170));
map.forEach((aBoolean, students) -> System.out.println(aBoolean + " " + students));
}
结果:
false [Student(age=15, name=Johnny, height=170.0), Student(age=20, name=Frank, height=165.0), Student(age=16, name=Marco, height=169.0)]
true [Student(age=19, name=Leon, height=175.0), Student(age=19, name=Jack, height=179.0)]
(7)*对流中的数据进行拼接
根据指定连接符,将所有的数据连接成一个字符串。
Collectors.jion();
@org.junit.Test
public void test13() {
String collect = Stream.of(new Student(15, "Johnny", 170), new Student(20, "Frank", 165), new Student(19, "Leon", 175), new Student(16, "Marco", 169), new Student(19, "Jack", 179))
.map(Student::getName).collect(Collectors.joining(",","$","--"));
System.out.println(collect);
}
结果:
$Johnny,Frank,Leon,Marco,Jack--
并行Stream流
串行Stream流:
上述所讲的都是串行Stream流,也就是再一个线程中执行。
@org.junit.Test
public void test14(){
Stream.of(1,2,434,54,23)
.filter(integer -> {
System.out.println(Thread.currentThread()+""+integer);
return integer<3;
}).count();
}
结果:
Thread[main,5,main]1
Thread[main,5,main]2
Thread[main,5,main]434
Thread[main,5,main]54
Thread[main,5,main]23
只在单一线程中执行。(效率不高)
并行流
在处理的时候使用多线程(并行流比串行流效率更高)。
parallelStream
是一个并行执行的流,它通过默认ForkjoinPool,可以提高多线程任务的速度。
@org.junit.Test
public void test15() {
Stream.of(1, 23, 23, 4).parallel().forEach(integer -> System.out.println(Thread.currentThread()+""+integer));
}
通过List接口直接获取并行流(Collection)
// 通过List接口直接获取Stream并行流
List<Integer> list = new ArrayList<>();
Stream<Integer> streamStream = list.parallelStream();
将已有的串行流转化为并行流
// 通过现有串行流获取并行流
Stream<Integer> parallel = Stream.of(1, 23, 23, 4).parallel();
*线程安全问题
@org.junit.Test
public void tset16() {
// 通过单线程添加
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());
// 通过多线程添加
List<Integer> listNew = new ArrayList<>();
list.parallelStream().forEach(listNew::add);
System.out.println(listNew.size());
}
结果:
1000
789
// 出现了线程安全问题
解决方案:
- 加同步锁
@org.junit.Test
public void tset16() {
// 通过单线程添加
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println(list.size());
// 通过多线程添加
Object obj = new Object();
List<Integer> listNew = new ArrayList<>();
list.parallelStream().forEach(integer -> {
synchronized (obj){
listNew.add(integer);
}
});
System.out.println(listNew.size());
}
- 使用线程安全的容器
@org.junit.Test
public void test17(){
Vector vector = new Vector();
Object obj = new Object();
IntStream.rangeClosed(1,1000)
.parallel()
.forEach(integer->{
synchronized (obj){
vector.add(integer);
}
});
System.out.println(vector.size());
}
3.将线程不安全的容器转化为线程安全的容器
List<Integer> synchronizedList = Collections.synchronizedList(listNew);
4.通过Stream中的toArray方法和collect方法来操作,因为这些操作满足线程安全。
5.Fork/join框架
parallelStream使用的就是Fork/Jion框架。(从JDK7就已经引入)
拆为fork ,拼接为join
Fork/Join框架三大模块
- 线程池:ForkjoinPool
- 任务对象:ForkjoinTask
- 执行任务的线程:ForkjoinWorkerThread
工作窃取算法
将任务分成若干个线程,当有的线程完成任务而有的线程还没有时,干完活的线程会去帮助其他线程干活,于是它就去其他的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
**缺点:**当任务少的时候创建多个线程反而会占用大量的资源。
Fork/Join案例
需求:使用Fork/Join计算1-10000的和,当一个任务计算的数量大于三千的时候拆封任务。
实现:
import java.util.concurrent.RecursiveTask;
public class SumRecurSiveTask extends RecursiveTask<Long> {
private static final long THRESHOLD = 3000L;
private final long start ;
private final long end ;
public SumRecurSiveTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end -start;
if (length <= THRESHOLD){
// 任务不需要分割
long sum = 0;
for (long i = start; i < end ; i++) {
sum += i;
}
return sum;
} else {
// 大于预定的数量
long middle = (start +end)/2;
System.out.println("拆封左边"+start+"-"+middle+" "+"拆封右边为"+middle+1+"-"+end);
SumRecurSiveTask left = new SumRecurSiveTask(start,middle);
left.fork();
SumRecurSiveTask right = new SumRecurSiveTask(middle+1,end);
right.fork();
return left.join()+right.join();
}
}
}
@org.junit.Test
public void test18(){
SumRecurSiveTask sumRecurSiveTask = new SumRecurSiveTask(1,10000);
System.out.println(sumRecurSiveTask.compute());
}
结果:
拆封左边1-5000 拆封右边为50001-10000
拆封左边5001-7500 拆封右边为75001-10000
拆封左边1-2500 拆封右边为25001-5000
49980000
6.Optional类
主要时解决空指针问题。
对null的处理方法:
@org.junit.Test
public void test19(){
String name = null;
System.out.println(name.length());
}
运行结果:
java.lang.NullPointerException
// 处理方法(传统方法)
@org.junit.Test
public void test19() {
String name = null;
if (name == null) {
System.out.println("字符串为空");
} else {
System.out.println(name.length());
}
}
Optional类
是一个没有子类的工具类(final)。
是一个可以为null的容器对象,避免null的检查,防止出现空指针异常。
public final class Optional<T>
Optional创建方式
使用方式:
// of方法
Optional<String> op1 = Optional.of("jfdsk");
Optional<String> op2 = Optional.of(null); // of方法不支持传入null(会出现报错)
// ofNullable方法
Optional<String> op3 = Optional.ofNullable("dfsal");
Optional<String> op4 = Optional.ofNullable(null);
// 通过empty方法直接创建一个空的对象
Optional<String> op5 = Optional.empty();
Optional常用API
get方法:
获取对象中的值。但是获取的值为null时,就会发生报错(NoSuchElementException)
isPresent方法:
判断是否包含值。
orElse方法:
如果调用对象包含值,否则返回方法参数。
ifPresent方法:
如果存在值干什么。
public void ifPresent(Consumer<? super T> action) {
if (value != null) {
action.accept(value);
}
}
ifPresentOrElse方法:
如果存在干什么,如果不存在又干什么。
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
7.时间日期API
更新的原因:旧版日期时间存在巨大问题。
@org.junit.Test
public void test21(){
Date date = new Date(2022,7,2);
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-DD");
System.out.println(simpleDateFormat.format(new Date()));
}
结果:
Wed Aug 02 00:00:00 CST 3922 // 从1900年开始加
2022-07-183 // 线程不安全(java.util.Date)
新的时间API
jdk8提供了新的时间API是线程安全的。(位于java.time包中)
新的java类
LocalDate:表示日期,包含年月日,格式为2022-7-3。
LocalTime:包含时间,包含时分秒。
LocalDateTime:表示日期时间,包含年月日,时分秒。
DateTimeFormatter:日期时间格式化。
Instant:时间戳。
Period:计算两个时间之间的距离(LocalDate)。
Duration:计算两个时间之间的距离(LocalTime)。
ZonedDateTime:时区时间。
常用API使用
// 自生构造器被私有化了(LocalDate,LocalTime,LocalDateTime)
LocalDate date1 = LocalDate.now();
System.out.println(date1);
LocalTime time = LocalTime.now();
System.out.println(time);
结果:
2022-07-03
07:34:00.984112800
LocalDate 常用API:看API文档 😁
所有的修改时间日期的方法都是创建一个新的对象,对原来的时间对象不会发生修改
日期时间的格式化:
@org.junit.Test
public void test24(){
// 提够了一些默认的初始化方式(ofPattern自定义格式)
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
System.out.println(dateTimeFormatter.format(LocalDateTime.now()));
}
结果:
2022-07-03T08:15:17.4896914
Instant类:
从1970.1.1 00:00:00 到现在的时间(秒,纳秒)。
@org.junit.Test
public void test24(){
Instant instant = Instant.now();
System.out.println(instant);
System.out.println(instant.getEpochSecond()); // 获取秒
System.out.println(instant.getNano()); // 获取纳秒
}
计算日期时间差:
Period:计算两个时间之间的距离(LocalDate)。
Duration:计算两个时间之间的距离(LocalTime)。
两个用法相似
@org.junit.Test
public void test24(){
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2021,1,5);
Period between = Period.between(localDate, localDate1);
System.out.println(between.getDays());
System.out.println(between.getMonths());
}
时间校正:
TemporalAdjuster接口。(TemporalAdjuster(工具类))
@org.junit.Test
public void test25(){
LocalDateTime now = LocalDateTime.now();
TemporalAdjuster adjuster = (temporal) -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println(nextMonth);
return nextMonth;
};
LocalDateTime nextMonth = now.with(adjuster);
System.out.println(nextMonth);
}
之前的类都不带有时区,而ZonedDateTime,ZonedTime,ZonedDate带有时区。
每个时区都有不同的ID ,格式“区域/城市”。如 Asia/Shanghai
(8)其他特性
重复注解
之前同一个注解在同一个地方不能重复出现,jdk8引入了重复注解,允许在同一个地方,使用同一个注解
使用 @Repeatable注解来定义重复注解。
类型注解
@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE
-
TYPE_PARAMETER:表示该注解能卸载类型参数的声明语句中,类型参数声明:如
-
TYPE_USE:表示注解可以在任何用到类型的地方使用。
intln(dateTimeFormatter.format(LocalDateTime.now()));
}
结果:
2022-07-03T08:15:17.4896914
Instant类:
从1970.1.1 00:00:00 到现在的时间(秒,纳秒)。
```java
@org.junit.Test
public void test24(){
Instant instant = Instant.now();
System.out.println(instant);
System.out.println(instant.getEpochSecond()); // 获取秒
System.out.println(instant.getNano()); // 获取纳秒
}
计算日期时间差:
Period:计算两个时间之间的距离(LocalDate)。
Duration:计算两个时间之间的距离(LocalTime)。
两个用法相似
@org.junit.Test
public void test24(){
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2021,1,5);
Period between = Period.between(localDate, localDate1);
System.out.println(between.getDays());
System.out.println(between.getMonths());
}
时间校正:
TemporalAdjuster接口。(TemporalAdjuster(工具类))
@org.junit.Test
public void test25(){
LocalDateTime now = LocalDateTime.now();
TemporalAdjuster adjuster = (temporal) -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);
System.out.println(nextMonth);
return nextMonth;
};
LocalDateTime nextMonth = now.with(adjuster);
System.out.println(nextMonth);
}
之前的类都不带有时区,而ZonedDateTime,ZonedTime,ZonedDate带有时区。
每个时区都有不同的ID ,格式“区域/城市”。如 Asia/Shanghai
(8)其他特性
重复注解
之前同一个注解在同一个地方不能重复出现,jdk8引入了重复注解,允许在同一个地方,使用同一个注解
使用 @Repeatable注解来定义重复注解。
类型注解
@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE
-
TYPE_PARAMETER:表示该注解能卸载类型参数的声明语句中,类型参数声明:如
-
TYPE_USE:表示注解可以在任何用到类型的地方使用。