Jdk8新特性目录总结
---------------------------------------
Lambda体验
Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果
* 从匿名类到Lambda表达式的转变
*/
@Test public void testLambdaHelloWorld() {
// 匿名类01
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
}).start(); // lambda01
new Thread(() -> System.out.println("Hello")).start();
}
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
Lambda的优点:简化匿名内部类的使用,语法更加简单。
Lambda的标准格式
Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
(参数类型 参数名称) -> {
代码体;
}
无参数无返回值的Lambda
@Test
public void test(){
goSwimming(new Swimmable(){
@Override
public void swimming() {
System.out.println("我要游泳");
}
});
goSwimming( () -> {
System.out.println("我要游泳");
}); } // 练习无参数无返回值的Lambda
public static void goSwimming(Swimmable s) {
s.swimming();
}
/**
* @author WGR
* @create 2020/3/23 -- 23:29
*/
public interface Swimmable {
public abstract void swimming();
}
有参数有返回值的Lambda
@Test
public void test1(){
goEatting( name ->{
System.out.println("吃"+name);
return 0 ;
});
} public static Integer goEatting(Eatable e){
return e.eatting("饭");
}
/**
* @author WGR
* @create 2020/3/23 -- 23:29
*/
public interface Swimmable {
public abstract void swimming();
}
@Test
public void test3(){
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178)); Collections.sort(persons, (o1, o2) -> {
return o2.getAge() - o1.getAge(); // 降序
}); persons.forEach((t) -> {
System.out.println(t);
Lambda的实现原理
匿名内部类的class文件
Lambda表达式的断点
关于这个方法 lambda$test$1 的命名:以lambda开头,因为是在test()函数里使用了lambda表达式,所以带有$test表示,因为是第二个,所以$01
如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加
上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文
件中。使用java命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
小结 :
匿名内部类在编译的时候会一个class文件
Lambda在程序运行的时候形成一个类
1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2. 还会形成一个匿名内部类,实现接口,重写抽象方法
3. 在接口的重写方法中会调用新生成的方法.
Lambda省略格式
/**
* lambda语法: 能省则省
*/
@SuppressWarnings("unused")
@Test public void testLambdaSyntax() {
// 语法
BinaryOperator<Integer> bo = (Integer x, Integer y) -> {
return x+y;
}; // 简化1: 由于类型推断(编译器javac根据上下文环境推断出类型), 可以省略参数的类型
bo = (x,y) -> {
return x+y;
}; // 简化2: 当Lambda体只有一条语句的时候可以省略return和大括号{}
bo = (x,y) -> x + y; // 简化3: 当参数只有一个时, 可以省略小括号
Consumer<String> fun = args -> System.out.println(args); // 简化4: 当参数个数为零时, 使用()即可
Runnable r1 = () -> System.out.println("Hello Lambda"); // 简化5: 方法引用(下个新特性)
Consumer<String> fun02 = System.out::println;
}
Lambda的前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用Lambda
2. 接口中有且仅有一个抽象方法
/**
* @author WGR
* @create 2020/3/24 -- 0:11
*/
public class LambdaCondition {
public static void main(String[] args) {
// 方法的参数或局部变量类型必须为接口才能使用Lambda
test(() -> {
}); Flyable f = () -> {
System.out.println("我会飞啦");
};
} public static void test(Flyable a) {
new Person() { };
} } // 只有一个抽象方法的接口称为函数式接口,我们就能使用Lambda
@FunctionalInterface // 检测这个接口是不是只有一个抽象方法
interface Flyable {
// 接口中有且仅有一个抽象方法
public abstract void eat();
// public abstract void eat2();
}
JDK 8接口增强介绍
interface 接口名 {
静态常量;
抽象方法;
}
JDK 8对接口的增强,接口还可以有默认方法和静态方法
JDK 8的接口:
interface 接口名 {
静态常量;
抽象方法;
默认方法;
静态方法;
}
接口引入默认方法的背景
在JDK 8以前接口中只能有抽象方法。存在以下问题:
如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。不利于接口的扩展。
interface A {
public abstract void test1();
// 接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
public abstract void test2();
}
class B implements A {
@Override
public void test1() {
System.out.println("BB test1");
}
// 接口新增抽象方法,所有实现类都需要去重写这个方法
@Override
public void test2() {
System.out.println("BB test2");
}
}
class C implements A {
@Override
public void test1() {
System.out.println("CC test1");
}
// 接口新增抽象方法,所有实现类都需要去重写这个方法
@Override
public void test2() {
System.out.println("CC test2");
}
}
以前假如一个接口有很多的抽象方法的时候,可以写一个装饰类
例如,JDK 8 时在Map接口中增加了 forEach 方法:
接口默认方法的使用
方式一:实现类直接调用接口默认方法
方式二:实现类重写接口默认方法
public class Demo02UseDefaultFunction {
public static void main(String[] args) {
BB bb = new BB();
bb.test01(); CC cc = new CC();
cc.test01();
}
} interface AA {
public default void test01() {
System.out.println("我是接口AA默认方法");
}
}
// 默认方法使用方式一: 实现类可以直接使用
class BB implements AA {
} // 默认方法使用方式二: 实现类可以重写接口默认方法
class CC implements AA {
@Override
public void test01() {
System.out.println("我是CC类重写的默认方法");
}
}
接口静态方法
为了方便接口扩展,JDK 8为接口新增了静态方法。
public class Demo03UseStaticFunction {
public static void main(String[] args) {
BBB bbb = new BBB();
// bbb.test01();
// 使用接口名.静态方法名();
AAA.test01();
}
} interface AAA {
public static void test01() {
System.out.println("我是接口静态方法");
}
} class BBB implements AAA {
// @Override
// public static void test01() {
// System.out.println("我是接口静态方法");
// }
}
接口默认方法和静态方法的区别
1. 默认方法通过实例调用,静态方法通过接口名调用。
2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
如果方法要被继承或者重写就用默认方法,如果不需要被继承就使用静态方法
函数式接口介绍
它们主要在 java.util.function 包中。下面是最常用的几个接口。
1. Supplier接口
@FunctionalInterface
public interface Supplier<T> { /**
* Gets a result.
*
* @return a result
*/
T get();
}
2. Consumer接口
@FunctionalInterface
public interface Consumer<T> { /**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t); }
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); }
4. Predicate接口
@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);
}
Supplier 接口
java.util.function.Supplier<T> 接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
/**
* @author WGR
* @create 2020/3/24 -- 21:46
*/
public class Demo02Supplier {
// 使用Lambda表达式返回数组元素最大值
public static void main(String[] args) {
System.out.println("开始了");
printMax(() -> {
int[] arr = {11, 99, 88, 77, 22};
Arrays.sort(arr); // 升序排序
return arr[arr.length - 1];
});
} public static void printMax(Supplier<Integer> supplier) {
System.out.println("aa");
int max = supplier.get();
System.out.println("max = " + max);
}
}
Consumer 接口
java.util.function.Consumer<T> 接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
public class Demo03Consumer { public static void main(String[] args) {
printHello( str ->{
//HELLO JDK8
System.out.println(str.toUpperCase());
});
} public static void printHello(Consumer<String> consumer) {
consumer.accept("hello jdk8");
}
}
public class Demo04ConsumerAndThen { public static void main(String[] args) {
printHello( str1 ->{
System.out.println(str1.toUpperCase());
},str2 ->{
System.out.println(str2.toLowerCase());
});
} public static void printHello(Consumer<String> c1, Consumer<String> c2) {
String str = "Hello Jdk8";
// default Consumer<T> andThen(Consumer<? super T> after) {
// Objects.requireNonNull(after);
// return (T t) -> { accept(t); after.accept(t); };
// }
c2.andThen(c1).accept(str);
}
}
由于c2在前面,所以先操作c2再去操作c1,结果如图所示。
Function 接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。
public class Demo05Function { public static void main(String[] args) {
getNumber( str ->{
return Integer.parseInt(str);
});
} public static void getNumber(Function<String, Integer> function) {
String num = "20";
Integer apply = function.apply(num);
System.out.println(apply);
}
}
public class Demo06FunctionAndThen { public static void main(String[] args) {
getNumber(str -> {
return Integer.parseInt(str);
}, num -> {
return num * 5;
});
} public static void getNumber(Function<String, Integer> f1, Function<Integer, Integer> f2) {
String num = "20";
Integer integer = f1.andThen(f2).apply(num);
System.out.println(integer); //100
}
}
Predicate 接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T> 接口。
public class Demo07Predicate {
public static void main(String[] args) {
isLongName( str -> {
return str.length() > 3;
});
} public static void isLongName(Predicate<String> predicate) {
boolean test = predicate.test("新年快乐");
System.out.println(test);
}
}
public class Demo08Predicate_And_Or_Negate {
// 使用Lambda表达式判断一个字符串中即包含W,也包含H
// 使用Lambda表达式判断一个字符串中包含W或者包含H
// 使用Lambda表达式判断一个字符串中不包含W
public static void main(String[] args) {
test((String str) -> {
// 判断是否包含W
return str.contains("W");
}, (String str) -> {
// 判断是否包含H
return str.contains("H");
});
} public static void test(Predicate<String> p1, Predicate<String> p2) { String str = "Hello World";
boolean b = p1.and(p2).test(str);
if (b) {
System.out.println("即包含W,也包含H");
} // 使用Lambda表达式判断一个字符串中包含W或者包含H
boolean b1 = p1.or(p2).test(str);
if (b1) {
System.out.println("包含W或者包含H");
}
// 使用Lambda表达式判断一个字符串中不包含W
boolean b2 = p1.negate().test("Hello W");
// negate相当于取反 !boolean
if (b2) {
System.out.println("不包含W");
}
}
}
方法引用的格式
符号表示 : ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景 : 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用。
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
1. instanceName::methodName 对象::实例方法
2. ClassName::staticMethodName 类名::静态方法
3. ClassName::methodName 类名::普通方法
4. ClassName::new 类名::new 调用的构造器
5. TypeName[]::new String[]::new 调用数组的构造器
对象名 ::实例方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为:
// 对象::实例方法
@Test
public void test01() {
Date d = new Date();
Supplier<Long> getTime = d::getTime;
Long time = getTime.get();
System.out.println(time);
}
方法引用的注意事项
1. 被引用的方法,参数要和接口中抽象方法的参数一样
2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
类名 ::引用静态方法
由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis ,所以当我们需要通过Lambda来调用该
方法时,可以使用方法引用 , 写法是:
// 类名::静态方法
@Test
public void test02() {
//1585110320763
Supplier<Long> timeMillis = System::currentTimeMillis;
System.out.println(timeMillis.get());
}
类名 ::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。
// 类名::实例方法
@Test
public void test03() {
//1
Function<String, Integer> length = String::length;
System.out.println(length.apply("hello")); // 5 Function<String, Integer> length1 = s1 -> {
return s1.length();
}; //2
BiFunction<String, Integer, String> substring = String::substring;
String s = substring.apply("hello", 3); //lo
System.out.println(s); BiFunction<String, Integer, String> substr = (String s1, Integer i1) -> {
return s1.substring(i1);
};
String hello = substr.apply("hello", 3);
System.out.println(hello);
}
类名 ::new引用构造器
由于构造器的名称与类名完全一样。所以构造器引用使用 类名称 ::new 的格式表示。首先是一个简单的 Person 类:
// 类名::new引用类的构造器
@Test
public void test04() {
Supplier<Person> p1 = Person::new;
Person person = p1.get();
System.out.println(person);
BiFunction<String, Integer, Person> p2 = Person::new;
Person wgr = p2.apply("wgr", 27);
System.out.println(wgr);
}
数组 ::new 引用数组构造器
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。
// 类型[]::new
@Test
public void test05() {
Function<Integer, int[]> f1 = int[]::new; int[] arr1 = f1.apply(10);
System.out.println(Arrays.toString(arr1));
}
Stream流式思想概述
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工
处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
tream API 能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。
小结
Stream是流式思想,相当于工厂的流水线,对集合中的数据进行加工处理
获取 Stream流的两种方式
方式1 : 根据Collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
public interface Collection { default Stream<E> stream() }
public class Demo04GetStream {
public static void main(String[] args) {
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
}
}
public class Demo05GetStream {
public static void main(String[] args) {
// Map获取流
Map<String, String> map = new HashMap<>();
// ...
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
方式2 : Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:
public class Demo06GetStream {
public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
}
}
Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
方法名 | 方法作用 | 返回值类型 | 方法种类 |
count | 统计个数 | long | 终结 |
forEach | 逐一处理 | void | 终结 |
filter | 过滤 | Stream | 函数拼接 |
limit | 取用前几个 | Stream | 函数拼接 |
skip | 跳过前几个 | Stream | 函数拼接 |
map | 映射 | Stream | 函数拼接 |
concat | 组合 | Stream | 函数拼接 |
终结方法 :返回值类型不再是 Stream 类型的方法,不再支持链式调用。
非终结方法 :返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
Stream的3个注意事项:
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法,中间的操作不会执行
Stream 流的forEach方法
/**
* @author WGR
* @create 2020/3/26 -- 21:52
*/
public class StreamTest { @Test
public void test(){
List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
strings.stream().forEach(System.out::print); // 对象 + 实例方法
}
}
Stream 流的count方法
Stream流提供 count 方法来统计其中的元素个数:
public class StreamTest { @Test
public void test(){
List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
long count = strings.stream().count();
System.out.println(count); //6
}
}
Stream 流的filter方法
filter用于过滤数据,返回符合过滤条件的数据
可以通过 filter 方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
Stream流中的 filter 方法基本使用的代码如:
public class StreamTest { @Test
public void test(){
List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
strings.stream().filter( s -> s.length() ==2 ).forEach(System.out::println);
}
}
在这里通过Lambda表达式来指定了筛选的条件:姓名长度为2个字。
Stream 流的limit方法
limit 方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:
public class StreamTest { @Test
public void test(){
List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
strings.stream().limit(3).forEach(System.out::println);
}
}
Stream 流的skip方法
如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:
public class StreamTest { @Test
public void test(){
List<String> strings = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
strings.stream().skip(2).forEach(System.out::println);
}
}
Stream 流的map方法
如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Stream流中的 map 方法基本使用的代码如:
@Test
public void test(){
Stream<String> stringStream = Stream.of("11", "22", "33");
stringStream.map(Integer::parseInt).forEach(System.out::println); }
这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。
Stream 流的sorted方法
如果需要将数据排序,可以使用 sorted 方法。方法签名:
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
Stream流中的 sorted 方法基本使用的代码如:
@Test
public void test(){
Stream<String> stringStream = Stream.of( "22", "33","11");
stringStream.map(Integer::parseInt).sorted( (o1,o2) -> o1 -o2 ).forEach(System.out::println); }
这段代码中, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。
Stream 流的distinct方法
如果需要去除重复数据,可以使用 distinct 方法。方法签名:
Stream<T> distinct();
Stream流中的 distinct 方法基本使用的代码如:
@Test
public void test(){
Stream<String> stringStream = Stream.of( "11","22", "33","11");
stringStream.map(Integer::parseInt).distinct().forEach(System.out::println); }
@Test
public void test(){
Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("张学友", 56),
new Person("黎明", 52))
.distinct()
.forEach(System.out::println);
}
/**
* @author WGR
* @create 2020/3/26 -- 22:07
*/
public class Person {
private String name;
private int age; public Person() {
} public Person(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
} @Override
public int hashCode() {
return Objects.hash(name, age);
}
}
自定义类型是根据对象的hashCode和equals来去除重复元素的。
Stream 流的match方法
如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。方法签名:
boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Stream流中的 Match 相关方法基本使用的代码如:
@Test
public void test(){
Stream<String> stringStream = Stream.of( "11","22", "33","11");
boolean b = stringStream.map(Integer::parseInt).allMatch(i -> i > 10);
System.out.println(b); //true 全部大于十
boolean b1 = stringStream.map(Integer::parseInt).anyMatch(i -> i < 10);
System.out.println(b1); // 没有比10小的 //false
boolean b3 = stringStream.map(Integer::parseInt).noneMatch(i -> i < 20);
System.out.println(b3); // 没有比20小的 //false
}
Stream 流的find方法
如果需要找到某些数据,可以使用 find 相关方法。方法签名:
Optional<T> findFirst();
Optional<T> findAny();
Stream流中的 find 相关方法基本使用的代码如:
@Test
public void testFind(){
Optional<Integer> first = Stream.of(5, 3, 6, 1).findFirst();
System.out.println("first = " + first.get()); //first = 5
Optional<Integer> any = Stream.of(5, 3, 6, 1).findAny();
System.out.println("any = " + any.get()); // any = 5
}
Stream 流的max和min方法
如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
Stream流中的 max 和 min 相关方法基本使用的代码如:
@Test
public void testFind(){
Optional<Integer> max = Stream.of(5, 3, 6, 1).max( (o1,o2) -> o1 - o2); // 取升序的最后一个
System.out.println("max = " + max.get());
Optional<Integer> min = Stream.of(5, 3, 6, 1).min( (o1,o2) -> o1 - o2); // 取升序的第一个
System.out.println("min = " + min.get());
}
Stream 流的reduce方法
如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。方法签名:
T reduce(T identity, BinaryOperator<T> accumulator);
Stream流中的 reduce 相关方法基本使用的代码如:
@Test
public void testFind(){
Integer reduce = Stream.of(5, 3, 6, 1).reduce(0, Integer::sum);
System.out.println(reduce); //15
}
Stream 流的map和reduce组合使用
@Test
public void testFind(){
int totalAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map(Person::getAge)
.reduce(0, Integer::sum);
System.out.println("totalAge = " + totalAge); int maxAge = Stream.of(
new Person("刘德华", 58),
new Person("张学友", 56),
new Person("郭富城", 54),
new Person("黎明", 52))
.map(Person::getAge)
.reduce(0, Math::max);
System.out.println("maxAge = " + maxAge);
}
@Test
public void testFind(){
// 统计 数字2 出现的次数
int count = Stream.of(1, 2, 2, 1, 3, 2)
.map(i -> i == 2 ? 1 : 0)
.reduce(0, Integer::sum);
System.out.println("count = " + count);
}
Stream 流的mapToInt
如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:
IntStream mapToInt(ToIntFunction<? super T> mapper);
@Test
public void testFind(){
// Integer占用的内存比int多,在Stream流操作中会自动装箱和拆箱
Stream<Integer> stream = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
IntStream intStream = stream.mapToInt(Integer::intValue);
int reduce = intStream
.filter(i -> i > 3)
.reduce(0, Integer::sum);
System.out.println(reduce); }
Stream 流的concat方法
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
该方法的基本使用代码如:
@Test
public void testFind(){
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
Stream<String> result = Stream.concat(streamA, streamB);
result.forEach(System.out::println);
}
Stream流中的结果到集合中
Stream流提供 collect 方法,其参数需要一个 java.util.stream.Collector<T,A, R> 接口对象来指定收集到哪
种集合中。java.util.stream.Collectors 类提供一些方法,可以作为 Collector`接口的实例:
public static <T> Collector<T, ?, List<T>> toList() :转换为 List 集合。
public static <T> Collector<T, ?, Set<T>> toSet() :转换为 Set 集合。
下面是这两个方法的基本使用代码:
// 将流中数据收集到集合中
@Test
public void testStreamToCollection() {
Stream<String> stream = Stream.of("aa", "bb", "cc");
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
}
Stream流中的结果到数组中
Stream提供 toArray 方法来将结果放到一个数组中,返回值类型是Object[]的:
@Test
public void testStreamToArray() {
Stream<String> stream = Stream.of("aa", "bb", "cc");
String[] strings = stream.toArray(String[]::new);
for (String str : strings) {
System.out.println(str);
}
}
对流中数据进行聚合计算
当我们使用 Stream流处理数据后,可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值,获取最小
值,求总和,平均值,统计数量。
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
//1. 最大值
Optional<Student> collect = studentStream.collect(Collectors.maxBy(Comparator.comparing(Student::getSocre)));
//studentStream.collect(Collectors.maxBy((o1,o2) ->( o1.getSocre() - o2.getSocre())));
System.out.println(collect.get()); //Student{name='迪丽热巴', age=56, socre=99} }
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
//最小值
Optional<Student> collect = studentStream.collect(Collectors.minBy(Comparator.comparing(Student::getSocre)));
//studentStream.collect(Collectors.minBy((o1,o2) ->( o1.getSocre() - o2.getSocre())));
System.out.println(collect.get()); //Student{name='柳岩', age=52, socre=77} }
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
// studentStream.map(Student::getSocre).reduce(Integer::sum);
Integer collect = studentStream.collect(Collectors.summingInt(Student::getSocre));
System.out.println(collect);
}
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
Double collect = studentStream.collect(Collectors.averagingDouble(Student::getSocre));
System.out.println(collect);
}
对流中数据进行分组
当我们使用Stream流处理数据后,可以根据某个属性将数据分组:
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
Map<Integer, List<Student>> collect = studentStream.collect(Collectors.groupingBy(Student::getAge));
collect.forEach( (k,v) -> {
System.out.println(k + "::" + v);
});
}
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 58),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 47));
Map<String, List<Student>> collect = studentStream.collect(Collectors.groupingBy(s -> s.getSocre() > 60 ? "及格" : "不及格"));
collect.forEach( (k,v) -> {
System.out.println(k + "::" + v);
});
}
对流中数据进行多级分组
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
Map<Integer, Map<String, List<Student>>> map =
studentStream.collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(s -> {
if (s.getSocre() >= 90) {
return "优秀";
} else if (s.getSocre() >= 80 && s.getSocre() < 90) {
return "良好";
} else if (s.getSocre() >= 80 && s.getSocre() < 80) {
return "及格";
} else {
return "不及格";
}
}))); map.forEach((k, v) -> {
System.out.println(k + " == " + v);
});
}
对流中数据进行分区
Collectors.partitioningBy 会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
// partitioningBy会根据值是否为true,把集合分割为两个列表,一个true列表,一个false列表。
Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(s ->
s.getSocre() > 90));
map.forEach((k, v) -> {
System.out.println(k + " == " + v);
});
}
对流中数据进行拼接
Collectors.joining 会根据指定的连接符,将所有元素连接成一个字符串。
@Test
public void test(){
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77));
String collect = studentStream
.map(Student::getName)
.collect(Collectors.joining(">_<", "^_^", "^v^"));
System.out.println(collect);
}
/**
* 测试flatMap
*/
@Test public void testFlatMap() {
List<String> strs = Arrays.asList("孙悟空", "猪八戒");
String collect = strs.stream()
.map(s -> {
char[] arr01 = s.toCharArray();
Character[] arr02 = new Character[arr01.length];
for (int i = 0; i < arr02.length; i++) {
arr02[i] = arr01[i];
}
return arr02;
})
.flatMap(Arrays::stream) // 扁平化
.map(String::valueOf)
.collect(Collectors.joining(","));
System.out.println(collect); // 孙,悟,空,猪,八,戒
}
/**
* SQL与Stream
*
*/
@Test public void testStreamCompSql() {
// select name, max(age) from person where name in ('孙悟空','猪八戒') and age is not null group by name order by name
LinkedHashMap<String, Optional<Integer>> map =
plist.stream()
.filter(p -> Arrays.asList("孙悟空","猪八戒").contains(p.getName())) // name in ('孙悟空','猪八戒')
.filter(p -> nonNull(p.getAge())) // age is not null
.sorted(comparing(Person::getName,nullsLast(naturalOrder()))) // order by name, 注意空值问题
//.collect(groupingBy(Person::getName) // Map<String, List<Person>>, 此处搜集到的还是人,但需要的是年龄,继续downstream搜集
.collect(groupingBy(Person::getName,LinkedHashMap::new,mapping(Person::getAge, maxBy(Integer::compare)))) // group by name
;
System.out.println(map); // {孙悟空=Optional[18], 猪八戒=Optional[28]} // select * from person where age = (select max(age) from person) limit 1
Optional<Person> first = plist.stream().sorted((p1,p2) -> p2.getAge() - p1.getAge()).findFirst();
System.out.println(first.get()); // Person [name=沙悟净, age=48, salary=40000.0]
}
对比一下串行流和并行流的效率:
/**
* @author WGR
* @create 2020/3/31
*/
public class Demo07Parallel { private static final int times = 500000000;
long start;
@Before
public void init() {
start = System.currentTimeMillis();
} @After
public void destory() {
long end = System.currentTimeMillis();
System.out.println("消耗时间:" + (end - start));
} // 并行的Stream : 消耗时间:431
@Test
public void testParallelStream() {
LongStream.rangeClosed(0, times).parallel().reduce(0, Long::sum);
} // 串行的Stream : 消耗时间:623
@Test
public void testStream() {
// 得到5亿个数字,并求和
LongStream.rangeClosed(0, times).reduce(0, Long::sum);
}
}
我们可以看到parallelStream的效率是最高的。
Stream并行处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作。
下面具体来做一下实验:
@Test
public void test0Serial() {
long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
.filter(s -> {
System.out.println(Thread.currentThread() + ", s = " + s);
return true;
})
.count();
System.out.println("count = " + count);
}
并行流:
@Test
public void test0Parallel() {
long count = Stream.of(4, 5, 3, 9, 1, 2, 6)
.parallel() // 将流转成并发流,Stream处理的时候将才去
.filter(s -> {
System.out.println(Thread.currentThread() + ", s = " + s);
return true;
})
.count();
System.out.println("count = " + count);
}
获取并行流有两种方式:
直接获取并行流: parallelStream()
将串行流转成并行流: parallel()
并行流的线程安全问题:
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<>();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
list.add(i);
});
System.out.println("list = " + list.size());
}
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<>();
// IntStream.rangeClosed(1, 1000)
// .parallel()
// .forEach(i -> {
// list.add(i);
// });
// System.out.println("list = " + list.size()); // 解决parallelStream线程安全问题方案一: 使用同步代码块
Object obj = new Object();
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronized (obj) {
list.add(i);
}
});
System.out.println("list = " + list.size());
}
// parallelStream线程安全问题
@Test
public void parallelStreamNotice() {
ArrayList<Integer> list = new ArrayList<>();
// IntStream.rangeClosed(1, 1000)
// .parallel()
// .forEach(i -> {
// list.add(i);
// });
// System.out.println("list = " + list.size()); // 解决parallelStream线程安全问题方案一: 使用同步代码块
// Object obj = new Object();
// IntStream.rangeClosed(1, 1000)
// .parallel()
// .forEach(i -> {
// synchronized (obj) {
// list.add(i);
// }
// }); // 解决parallelStream线程安全问题方案二: 使用线程安全的集合
Vector<Integer> v = new Vector();
List<Integer> synchronizedList = Collections.synchronizedList(list);
IntStream.rangeClosed(1, 1000)
.parallel()
.forEach(i -> {
synchronizedList.add(i);
});
System.out.println("list = " + synchronizedList.size());
}
// parallelStream线程安全问题
@Test
public void parallelStreamNotice() { List<Integer> collect = IntStream.rangeClosed(1, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println("collect.size = " + collect.size());
}
旧版日期时间 API 存在的问题
1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
public static void main(String[] args) {
// 旧版日期时间 API 存在的问题
// 1.设计部合理
Date now = new Date(1985, 9, 23);
System.out.println(now); // 2.时间格式化和解析是线程不安全的
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 50; i++) {
new Thread(() -> {
try {
Date date = sdf.parse("2019-09-09");
System.out.println("date = " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 线程安全的 DateTimeFormatter
*/
@Test public void testDateFormatter() throws InterruptedException, ExecutionException {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd"); // 定义线程池, 任务, 提交100个解析任务并输出任务的执行情况
ExecutorService pool = Executors.newFixedThreadPool(10);
Callable<LocalDate> task = () -> LocalDate.parse("20190305",dtf);
List<Future<LocalDate>> results = new ArrayList<>();
IntStream.rangeClosed(0, 100).forEach(i -> results.add(pool.submit(task)));
for (Future<LocalDate> future : results) {
println(future.get());
}
}
新日期时间 API介绍
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包
中,下面是一些关键类。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant:时间戳,表示一个特定的时间瞬间。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
- ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:
- ThaiBuddhistDate :泰国佛教历
- MinguoDate :中华民国历
- JapaneseDate :日本历
- HijrahDate :伊斯兰历
JDK 8 的日期和时间类
LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO -8601 日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
@Test
public void testLocalDate() {
// LocalDate: 表示日期,有年月日
LocalDate date = LocalDate.of(2020, 4, 1);
System.out.println("date = " + date); LocalDate now = LocalDate.now();
System.out.println("now = " + now); System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());
}
@Test
public void testLocalTime() {
// LocalTime: 表示时间,有时分秒
LocalTime time = LocalTime.of(22, 00, 39);
System.out.println("time = " + time); LocalTime now = LocalTime.now();
System.out.println("now = " + now); System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
@Test
public void testLocalDateTime() {
// LocalDateTime: LocalDate + LocalTime 有年月日 时分秒
LocalDateTime dateTime = LocalDateTime.of(2020, 4, 1, 13, 28, 59);
System.out.println("dateTime = " + dateTime); LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getHour());
System.out.println(now.getSecond());
}
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
// LocalDateTime 类: 对日期时间的修改
@Test
public void test05() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
System.out.println( now.withYear(2021).withMonth(1).withHour(11).withMinute(11));
// 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
}
now = 2020-03-31T22:10:02.710
修改年份: 2078-03-31T22:10:02.710
now == setYear: false
修改月份: 2020-06-30T22:10:02.710
修改小时: 2020-03-31T09:10:02.710
修改分钟: 2020-03-31T22:11:02.710
2021-01-31T11:11:02.710
5天后: 2020-04-05T22:10:02.710
now == localDateTime: false
10年后: 2030-03-31T22:10:02.710
20月后: 2021-11-30T22:10:02.710
20年前: 2000-03-31T22:10:02.710
5月前: 2019-10-31T22:10:02.710
100天前: 2019-12-22T22:10:02.710 Process finished with exit code 0
日期时间的比较
// 日期时间的比较
@Test
public void test06() {
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2020, 4, 1);
System.out.println(now.isBefore(date)); //true
System.out.println(now.isAfter(date)); // false
}
JDK 8 的时间格式化与解析
通过 java.time.format.DateTimeFormatter 类可以进行日期时间解析与格式化。
@Test
public void test04(){
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = now.format(dtf);
System.out.println(format); //2020-03-31 22:15:50 // 将字符串解析为日期时间
LocalDateTime parse = LocalDateTime.parse("1985-09-23 10:12:22", dtf);
System.out.println("parse = " + parse); }
JDK 8 的 Instant 类
Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
@Test
public void test07() {
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now);
// 获取从1970年1月1日 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
Instant instant = Instant.ofEpochSecond(5);
System.out.println(instant);
}
JDK 8 的计算日期时间差类
Duration/Period类: 计算日期时间差。
1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
2. Period:用于计算2个日期(LocalDate,年月日)的距离
// Duration/Period 类: 计算日期时间差
@Test
public void test08() {
// Duration计算时间的距离
LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(14, 15, 20);
Duration duration = Duration.between(time, now);
System.out.println("相差的天数:" + duration.toDays());
System.out.println("相差的小时数:" + duration.toHours());
System.out.println("相差的分钟数:" + duration.toMinutes());
// Period计算日期的距离
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1998, 8, 8);
// 让后面的时间减去前面的时间
Period period = Period.between(date, nowDate);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());
}
相差的天数:0
相差的小时数:8
相差的分钟数:492
相差的年:21
相差的月:7
相差的天:23
JDK 8 的时间校正器
有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
TemporalAdjuster : 时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
/**
* TemporalAdjuster
*/
@Test public void testTemporalAdjuster() {
LocalDate ld = LocalDate.now();
println(ld.with(firstDayOfMonth())); // 2020-03-01 月初
println(ld.with(firstDayOfNextYear())); // 2021-01-01 年初
println(ld.with(lastDayOfMonth())); // 2020-03-31 月末
println(ld.with(lastDayOfYear())); // 2020-12-31 年末
println(ld.with(next(SUNDAY))); // 2020-04-05 下周日
println(ld.with(previousOrSame(MONDAY))); // 2020-03-30 前一个或相同的周一
} private void println(Object obj) {
System.out.println(obj);
}
JDK 8 设置日期时间的时区
Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别
为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:该类中包含了所有的时区信息。
// 设置日期时间的时区
@Test
public void test10() {
// 1.获取所有的时区ID
// ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时间,获取计算机的当前时间
LocalDateTime now = LocalDateTime.now(); // 中国使用的东八区的时区.比标准时间早8个小时
System.out.println("now = " + now); //now = 2020-03-31T22:43:42.289 // 2.操作带时区的类
// now(Clock.systemUTC()): 创建世界标准时间
ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());
System.out.println("bz = " + bz); //bz = 2020-03-31T14:43:42.290Z // now(): 使用计算机的默认的时区,创建日期时间
ZonedDateTime now1 = ZonedDateTime.now();
System.out.println("now1 = " + now1); // now1 = 2020-03-31T22:43:42.290+08:00[Asia/Shanghai] // 使用指定的时区创建日期时间
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Vancouver"));
System.out.println("now2 = " + now2); // now2 = 2020-03-31T07:43:42.293-07:00[America/Vancouver] // 修改时区
// withZoneSameInstant: 即更改时区,也更改时间
ZonedDateTime withZoneSameInstant = now2.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
System.out.println("withZoneSameInstant = " + withZoneSameInstant); //withZoneSameInstant = 2020-03-31T22:43:42.293+08:00[Asia/Shanghai] // withZoneSameLocal: 只更改时区,不更改时间
ZonedDateTime withZoneSameLocal = now2.withZoneSameLocal(ZoneId.of("Asia/Shanghai"));
System.out.println("withZoneSameLocal = " + withZoneSameLocal); // withZoneSameLocal = 2020-03-31T07:43:42.293+08:00[Asia/Shanghai] }
重复注解的使用
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。
/**
* @author WGR
* @create 2020/4/1 -- 19:02
*/
@MyTest("ta")
@MyTest("tb")
@MyTest("tc")
public class Test {
@org.junit.Test
@MyTest("ma")
@MyTest("mb")
public void test() {
} public static void main(String[] args) throws NoSuchMethodException {
// 4.解析重复注解
// 获取类上的重复注解
// getAnnotationsByType是新增的API用户获取重复的注解
MyTest[] annotationsByType = Test.class.getAnnotationsByType(MyTest.class);
for (MyTest myTest : annotationsByType) {
System.out.println(myTest);
}
MyTests[] annotationsByType1 = Test.class.getAnnotationsByType(MyTests.class);
for ( MyTests myTests : annotationsByType1){
System.out.println(myTests);
} System.out.println("----------");
// 获取方法上的重复注解
MyTest[] tests = Test.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test);
} MyTests[] test = Test.class.getMethod("test").getAnnotationsByType(MyTests.class);
for (MyTests t : test) {
System.out.println(t);
}
}
} // 1.定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests { // 这是重复注解的容器
MyTest[] value();
} // 2.定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}
@com.topcheer.annotation.MyTest(value=ta)
@com.topcheer.annotation.MyTest(value=tb)
@com.topcheer.annotation.MyTest(value=tc)
@com.topcheer.annotation.MyTests(value=[@com.topcheer.annotation.MyTest(value=ta), @com.topcheer.annotation.MyTest(value=tb), @com.topcheer.annotation.MyTest(value=tc)])
----------
@com.topcheer.annotation.MyTest(value=ma)
@com.topcheer.annotation.MyTest(value=mb)
@com.topcheer.annotation.MyTests(value=[@com.topcheer.annotation.MyTest(value=ma), @com.topcheer.annotation.MyTest(value=mb)])
类型注解的使用
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T> 、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
@MyTest("src1")
public int show01(@MyTest("src")String src) {
@MyTest("123")int i = 10;
System.out.println(i + ": " + src);
return i;
} /**
* 类型参数读取
*/
@org.junit.Test public void testTypeParameter() throws NoSuchMethodException, SecurityException {
Class<?> clazz = this.getClass();
Method method01 = clazz.getDeclaredMethod("show01", String.class);
AnnotatedType[] types = method01.getAnnotatedParameterTypes();
for (AnnotatedType at : types) {
MyTest anno = at.getAnnotation(MyTest.class);
System.out.println(anno);
System.out.println(anno.value());
} }
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
内嵌类
序号 | 内嵌类 & 描述 |
---|---|
1 | static class Base64.Decoder
该类实现一个解码器用于,使用 Base64 编码来解码字节数据。 |
2 | static class Base64.Encoder
该类实现一个编码器,使用 Base64 编码来编码字节数据。 |
方法
序号 | 方法名 & 描述 |
---|---|
1 | static Base64.Decoder getDecoder()
返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。 |
2 | static Base64.Encoder getEncoder()
返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。 |
3 | static Base64.Decoder getMimeDecoder()
返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。 |
4 |
static Base64.Encoder getMimeEncoder() 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。 |
5 | static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)
返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。 |
6 | static Base64.Decoder getUrlDecoder()
返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。 |
7 | static Base64.Encoder getUrlEncoder()
返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。 |
注意:Base64 类的很多方法从 java.lang.Object 类继承。
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.UUID; /**
* @author WGR
* @create 2020/4/8 -- 20:53
*/
public class Java8Tester {
public static void main(String args[]){
try {
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString); // 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString); System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
} byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString); }catch(UnsupportedEncodingException e){
System.out.println("Error :" + e.getMessage());
}
}
}
Base64 编码字符串 (基本) :cnVub29iP2phdmE4
原始字符串: runoob?java8
Base64 编码字符串 (URL) :cnVub29iP2phdmE4
Base64 编码字符串 (MIME) :NWY0ZGRjNzQtYzhlOS00MTY4LWFkNWYtNTlkZGVhMTBhNDNjYmJmNDJhY2ItOTA1Ni00MDNlLThh
YTEtYTExMTFiOTU2Njg5OGQ3NWZmMDUtODBkMy00YjFhLTg3ODktN2I4N2FmMGVmZGRjNTZiZGFh
YjgtMzllMS00OGQ0LTkyMWItNTEwNTVhYzNjNGQwN2IwMTFjYzktOWM0Zi00YzVjLWE4OWMtOGJk
MDE3MGZlOWRkYTg2MDhmYTItOTU5Mi00Yjg4LWIxMDQtMzkxZDJmMGI3NjMwYmJkZWExYjgtOTJk
ZS00OGI2LWE0MmYtNDcwNWMxZDM3MDU4ZTcyNjFkMjgtN2MwOC00ZDI4LTk3MDAtOTcxZWJlMDUx
NmQ2ZjRkNzU0ZjEtMWVjMi00OGJmLTkxODYtN2ZiNzIwMjg4ZWU4MGVjZDAyNjctYzUxNi00YjNh
LTgzZjYtMTViMTI2YjgwMmEw Process finished with exit code 0
Comparator提供的方法
/**
* @author WGR
* @create 2020/4/13 -- 16:12
*/
public class Person { private String name;
private Integer age;
private Double salary; public Person(String name, Integer age, Double salary) {
super();
this.name = name;
this.age = age;
this.salary = salary;
} public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
} @Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", salary=" + salary + "]";
} }
排序实现
/**
* @author WGR
* @create 2020/4/13 -- 16:14
*/
public class _Comparator {
List<Person> personList = new ArrayList<>(); @Before
public void before() {
personList.add(new Person("hepengju", 28, 20000.0));
personList.add(new Person("lisi", 44, 40000.0));
personList.add(new Person("wangwu", 55, 50000.0));
personList.add(new Person("zhaoliu", 66, 60000.0));
personList.add(new Person("zhangsan", 33, 33333.0));
personList.add(new Person("zhangsan", 23, 30000.0));
} //按照名字排序
@Test
public void test() {
personList.stream().sorted(Comparator.comparing(Person::getName)).forEach(System.out::println);
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
} //按照名字排序降序
@Test
public void test4() {
personList.stream().sorted(Comparator.comparing(Person::getName).reversed()).forEach(System.out::println);
// Person [name=zhaoliu, age=66, salary=60000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=hepengju, age=28, salary=20000.0]
} // 先按照名字排序, 名字一样再按照年龄排序, 年龄一样再按照薪资排序
@Test
public void test2(){
personList.stream().sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge).thenComparing(Person::getSalary)).forEach(System.out::println);
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
} // 4. 处理所有空值问题(null都到最后)
@Test
public void test3(){
// personList.add(null);
personList.add(new Person(null, 33, 30000.0));
personList.add(new Person("zhangsan", null, 30000.0));
personList.add(new Person("zhangsan", 33, null));
personList.add(new Person(null, null, null));
personList.stream().sorted(Comparator.comparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Person::getName,Comparator.nullsLast(Comparator.naturalOrder()))
).forEach(System.out::println);
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=zhangsan, age=null, salary=30000.0]
// Person [name=zhangsan, age=33, salary=null]
// Person [name=zhaoliu, age=66, salary=60000.0]
// Person [name=null, age=33, salary=30000.0]
// Person [name=null, age=null, salary=null] }
//jdk8 lambda排序
@Test
public void test5() {
personList.stream().sorted((p1,p2) -> p1.getName().compareTo(p2.getName()) ).forEach(System.out::println);
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
}
@Test
public void test6() {
personList.stream().sorted((p1,p2) -> p1.getAge() - p2.getAge() ).forEach(System.out::println);
// Person [name=zhangsan, age=23, salary=30000.0]
// Person [name=hepengju, age=28, salary=20000.0]
// Person [name=zhangsan, age=33, salary=33333.0]
// Person [name=lisi, age=44, salary=40000.0]
// Person [name=wangwu, age=55, salary=50000.0]
// Person [name=zhaoliu, age=66, salary=60000.0]
}
}
Jdk8新特性目录总结的更多相关文章
- JDK8 新特性
JDK8 新特性目录导航: Lambda 表达式 函数式接口 方法引用.构造器引用和数组引用 接口支持默认方法和静态方法 Stream API 增强类型推断 新的日期时间 API Optional 类 ...
- java进阶一之jdk8新特性
1.官方发布的jdk8新特性 2.51CTO相关专题
- JDK8新特性一览
转载自:http://blog.csdn.net/qiubabin/article/details/70256683 官方新特性说明地址 Jdk8新特性.png 下面对几个常用的特性做下重点说明. 一 ...
- 一次电话Java面试的问题总结(JDK8新特性、哈希冲突、HashMap原理、线程安全、Linux查询命令、Hadoop节点)
面试涉及问题含有: Java JDK8新特性 集合(哈希冲突.HashMap的原理.自动排序的集合TreeSet) 多线程安全问题 String和StringBuffer JVM 原理.运行流程.内部 ...
- jdk8新特性:在用Repository实体查询是总是提示要java.util.Optional, 原 Inferred type 'S' for type parameter 'S' is not within its bound;
jdk8新特性:在用Repository实体查询是总是提示要java.util.Optional 在使用springboot 方法报错: Inferred type 'S' for type para ...
- 深入理解java虚拟机---jdk8新特性(二)
1.jdk8新特性 1.新特性 2.lambda函数表达式的作用 A: 替换内部类 B:对集合的操作并行化
- JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序
大家对java接口Comparator和Comparable都不陌生,JDK8里面Comparable还和以前一样,没有什么改动:但是Comparator在之前基础上增加了很多static和defau ...
- jdk8新特性
JDK8新特性(JDK8的新特性) * 接口中可以定义有方法体的方法,如果是非静态,必须用default修饰 * 如果是静态的就不用了 class Test { public void run() { ...
- JDK8新特性之一Lambda
JDK8的新特性之一Lambda能将函数作为方法里面的参数使用. /** * JDK8新特性Lambda */ public class Test { public static void main( ...
- JDK8新特性关于Stream流
在Java1.8之前还没有stream流式算法的时候,我们要是在一个放有多个User对象的list集合中,将每个User对象的主键ID取出,组合成一个新的集合,首先想到的肯定是遍历,如下: 1 2 3 ...
随机推荐
- 在昇腾Ascend 910B上运行Qwen2.5推理
目前在国产 AI 芯片,例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求,然而当前的生态还远未成熟.从底层芯片的算力性能.计算架构的算子优化,到上层推理框架对各种模型的支持及推理加速,仍有很多需要 ...
- QT5.15.2 连接MySQL 驱动问题解决方案,无论菜鸟🐦️还是老鸟🦜,解决了就是好鸟🦚
最新在学QT,现在QT只能在线安装了,用了几天,看到数据库时,需要用MySQL,结果出现了问题. QSqlDatabase: QMYSQL driver not loaded. QSqlDatabas ...
- hyperf使用session
在hyperf里面使用session的时候可以先安装组件包 composer require hyperf/session Session 组件的配置储存于 config/autoload/sess ...
- 修复Bug好比钓鱼
作者: Jim Bird 来源: CSDN 发布时间: 2012-09-13 10:43 阅读: 4224 次 推荐: 18 原文链接 [收藏] 英文原文:Fixing a Bug ...
- OneForAll - 功能强大的子域收集工具
OneForAll,是 shmilylty 在 Github 上开源的子域收集工具,目前版本为 v0.4.3. 收集能力强大,利用证书透明度收集子域.常规检查收集子域.利用网上爬虫档案收集子域.利用D ...
- Redis集群之常用操作
Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境.直接使用red ...
- windows电脑在线生成ios p12证书工具和生成教程
使用hbuilderx开发ios APP的时候,打包APP提示需要IOS的打包证书 而hbuilderx本身是不能生成证书的,因为生成证书需要在苹果开发者中心生成.而在苹果开发者中心生成证书的时候,提 ...
- springboot 参数注解 注入参数
什么时注解参数 说明 我们在 使用spring mvc 的时候会使用这样的注解 @ResponseBody 这样,spring mvc 会将 客户端传来的数据,自动构建成 相应类型的对象. 有些情况下 ...
- Ant Design Pro项目ProTable怎么实现单元格合并效果
前情 公司有经常需要做一些后台管理页面,我们选择了Ant Design Pro,它是基于 Ant Design 和 umi 的封装的一整套企业级中后台前端/设计解决方案. 产品效果图 最新接到的一个后 ...
- 【Amadeus原创】Docker容器的备份与还原
主要作用: 就是让配置好的容器,可以得到复用,后面用到得的时候就不需要重新配置. 其中涉及到的命令有: docker commit 将容器保存为镜像 docker save -o 将镜像备份为tar文 ...