1、关于JDK的升级
JDK5之前JDK的变化并没有翻天覆地的变化,在JDK的升级过程JDK5是一个里程碑,然后就是今天我们要讲的JDK8同样也是一个里程碑。从JDK5开始引入众多特性之后,JDK8同样引入了更加灵活和轻巧便捷的代码编写方式。今天就给大家介绍关于JDK8中的:
- 日期时间处理类
- 新的注解
- 接口中的默认方法
- Lambda表达式
- 函数式接口
- 方法引用
- Stream编程
2、JDK8中日期的新类
JDK8将joda-time中的重要api集成到JDK中。主要有:
LocalDate:本地日期
LocalTime:本地时间
LocalDateTime:本地日期时间
ZonedDateTime:时区
Duration:持续时间
在JDK1.0的Date类添加toInstant方法(用于将Date转成新的表示形式)。
相关的包:
2.1、LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime它们都是操作时间的,与Calender有点类似,但更人性化。
LocalDate:拥有年月日
LocalTime:拥有时分秒
LocalDateTime:拥有年月日时分秒
2.1.1、获取对象
使用静态的方法可以直接得到LocalDate、LocalTime、LocalDateTime的对象。
使用静态方法now,得到当前系统的对应的时间。
使用of方法,可以指定具体的年月日。
// 获取对象
LocalDate now = LocalDate.now();
System.out.println(now);
// 使用of方法,可以指定具体年月日
LocalDate of = LocalDate.of(2020, 11, 11);
System.out.println(of);
2.1.2、其他的方法
getXxx方法可以获取到具体时间信息。
使用withXxxx可以设置时间信息。
使用plusXxxx可以在当前的时间点加上具体的时间值。
使用minusXxx可以在当前的时间点上减少具体的时间值。
2.2、Instant
Instant类,它类似于java.util.Date类。代表的是具体一个时间点的毫秒值。
// 获取对象,这里获取到的本初子午线的时间点,并不是系统时间
Instant now = Instant.now();
System.out.println(now);
// 设置时间偏移量,中国在东八区
OffsetDateTime atOffset = now.atOffset(ZoneOffset.ofHours(8));
System.out.println(atOffset);
// 获取系统毫秒值
long second = atOffset.toEpochSecond();
System.out.println(second);
// 设置毫秒值
Instant milli = Instant.ofEpochMilli(1554627592);
System.out.println(milli);
其他的方法与LocalDate类似。
2.3、DateTimeFormatter
DateTimeFormatter:类似于SimpleDateFormat,可以进行日期和字符串的相互转换。
有三种方式得到DateTimeFormatter对象,然后对日期和字符串进行转换。
2.3.1、调用静态成员变量
这种方式的操作,导致格式化和解析的时候都是按照默认方式进行的。
// 第一种方式
DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
String str1 = dtf.format(LocalDateTime.now());
System.out.println(str1); // 2019-04-07T17:20:07.628
2.3.2、本地化相关
按照内置的格式进行格式化和解析。
// 第二种方式
DateTimeFormatter dtf2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
String str2 = dtf2.format(LocalDateTime.now());
System.out.println(str2); // 2019年4月7日 星期日
2.3.3、自定义方式
按照自定义格式进行日期格式化和解析。
// 第三种方式
DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str3 = dtf3.format(LocalDateTime.now());
System.out.println(str3); // 2019-04-07 17:39:14
3、新的注解
3.1、可重复注解
在JDK8之前每个注解只能在对应的位置上使用一次。不能多次重复使用。
/**
* JDK8的注解演示
* @author QuBo
* 联系方式:18066841007
* @2019年4月7日 东软睿道
*/
// 自定义一个注解
@Target(ElementType.TYPE) // 定义注解可以书写在类上
@Retention(RetentionPolicy.RUNTIME) // 定义注解的存活时间
@interface Anno{
String value() ;
}
// 使用自定义注解
@Anno("neuedu")
public class AnnotationTest {
}
针对上面的程序,自定义注解名称为Anno,并且声明存活于运行时间(RetentionPolicy.RUNTIME),只能使用在类上(ElementType.TYPE)。其中定义value属性。
在AnnotationTest类上测试使用Anno注解。并指定属性值为neuedu。
上面这种用语法是最常见的使用,但是有问题,如果需要给注解的属性指定多个值,那么就不能在类上同时书写将注解书写多次。
上面的自定义的注解不能重复,在JDK8之前,要想给一个注解指定多个直接,需要使用数组完成。
在JDK8中引入Repeatable注解来标注当前的注解可以重复,但需要重新定义新的注解,并在其中定义可以被重复的注解数组属性。
3.2、ElementType的新属性
ElementType. TYPE_PARAMETER:表示该注解能写在类型变量的声明语句中(例如:泛型定义)。
ElementType. TYPE_USE:表示该注解能写在使用类型的任何语句中。
在JDK8之前,注解不能放在如下的位置:
在JDK8中为ElementType添加的TYPE_USE属性,可以指定注解书写在任何有类型参与的位置。
4、接口中默认方法
在JDK8之前,在接口中可以定义成员变量和成员方法。
在接口中定义的成员变量其实是一个常量,它的修饰符是public static final。
在接口中定义的成员方法都是抽象方法,它的修饰符是public abstract。
JDK8之后允许在接口中定义非抽象方法,但是这个方法必须使用default或static关键字来修饰。
4.1、接口中的default默认方法
/**
* 测试接口中的默认方法
* @author QuBo
* 联系方式:18066841007
*/
interface Inter{
// 接口中的抽象方法
public void demo();
// 接口中可以定义非抽象的方法,需要使用default修饰
public default void defaultMethod() {
System.out.println("接口中的默认方法");
}
}
class InterImpl implements Inter{
@Override
public void demo() {
System.out.println("复写接口中的抽象方法");
}
}
public class InterfaceDefaultMethodTest {
public static void main(String[] args) {
Inter inter = new InterImpl();
// 调用接口中的抽象方法
inter.demo();
// 调用接口中的默认方法
inter.defaultMethod();
}
}
4.2、接口中的static静态方法
interface StaticMethod{
public void demo(); // 接口中的抽象方法
// 接口中的静态的方法
public static void func(int a , int b) {
System.out.println("a = " + a + ", b = " + b);
}
}
public class InterfaceStaticMethod {
public static void main(String[] args) {
// 可以通过接口名.静态方法名,调用接口中的静态方法
StaticMethod.func(1, 3);
}
}
4.3、接口中方法细节
- 接口中定义的静态方法,只能通过接口来调用。
- 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时执行的是重写以后的方法。
- 类优先:如果子类(或实现类)继承的父类(或实现的接口中)定义的同名、同参数的默认方法,那么在没有重写此方法时,默认调用的是父类中的同名、同参数方法,而不会是接口中的同名、同参数方法。
- 接口冲突:如果实现类实现了多个接口,而在多个接口中都定义同名、同参数默认方法,那么在实现类没有重写此方法的情况会报错。那么就必须在实现类中重写此方法。
- 接口名.super关键字:在实现类中调用多个接口中同名的默认方法,需要使用接口名.super.默认方法名。
/**
* 接口中的默认方法和静态方法使用细节
* @author QuBo
* 联系方式:18066841007
* @2019年4月2日
*/
interface InterFunc{
// 抽象方法
public void demo();
// 默认方法
public default void func() {
System.out.println("InterFunc default func");
}
// 静态方法
public static void method() {
System.out.println("InterFunc static method");
}
}
// 在定义一个接口
interface InterFunc2{
// 抽象方法
public void demo();
// 默认方法
public default void func() {
System.out.println("InterFunc2 default func");
}
// 静态方法
public static void method() {
System.out.println("InterFunc2 static method");
}
}
// 在定义一个接口,继承上面已经存在的接口
interface InterFunc3 extends InterFunc , InterFunc2{
/*
* 需要在接口中显式指定需要复写具体那个父接口中的方法
*/
@Override
default void func() {
InterFunc.super.func();
}
}
// 定义类,实现接口接口
class InterFuncImpl implements InterFunc , InterFunc2{
@Override
public void demo() {
}
/*
* 需要在接口中显式指定需要复写具体那个父接口中的方法
*/
@Override
public void func() {
InterFunc2.super.func();
}
}
注意这里有坑:
在接口定义的默认方法和静态方法不能使用private关键字修饰。
5、Lambda表达式
5.1、lambda表达式引入
lambda表达式也可以称为匿名函数,它可以包含一个表达式和语句。JDK8中的lambda拥有自己强大的功能。
在Java世界里,万物皆对象,既然Java把所有事物都看作对象,那么同样可以把函数看作对象,代码块也可以看作对象了。
lambda表达式的格式:
(表达式) -> 执行语句
在程序中可以把Lambda表达式作为参数或者返回值进行传递,使用它Lambda可以写出更简洁、更灵活的代码。Lambda作为一种更紧凑的代码风格。使Java的语法表达能力得到提升。
/**
* lambda 表达式的简单演示
* @author QuBo
* 联系方式:18066841007
* @2019年4月2日
*/
public class LambdaDemo1 {
public static void main(String[] args) {
// 演示比较器 , 使用匿名内部类的方式
Comparator<String> com = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
System.out.println(com.compare("neusoft", "neuedu"));
//
// 使用lambda表达式方式使用比较器
Comparator<String> com2 = ( o1 , o2 )-> o1.compareTo(o2) ;
System.out.println(com2.compare("neusoft", "neuedu"));
}
}
5.2、Lambda表达式详解
5.2.1、Lambda表达式语法分析
Lambda表达式是主要是针对接口的使用简化操作,语法更加的简洁简单。
语法格式:
( 参数 , 参数 , …… ) ->{ 方法体代码 }
解释1:( ) 它代表接口中的抽象方法的那对小括号。
解释2:参数 它代表接口中的抽象方法的参数列表,若没有参数可省略。若多个参数,是可省略参数类型的。
解释3:-> 表示当前的一个lambda表达式,可以理解为导向作用。
解释4:{ 方法体 } 它代表复写接口中抽象方法的具体逻辑。
5.2.2、无参数、无返回值
接口中的抽象方法不接收任何参数,同时不需要任何返回值。
/**
* Lambda表达式的详细介绍
* @author QuBo
* 联系方式:18066841007
* @2019年4月2日 东软睿道
*/
public class LambdaDemo2 {
public static void main(String[] args) {
// 演示创建线程的任务
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "....");
}
};
// 开启线程
new Thread(run).start();
///使用Lambda表达式///
Runnable run2 = ()->{ System.out.println(Thread.currentThread().getName() + "...."); };
new Thread(run2).start();
}
}
通过上面的代码,其实我们可以发现,Lambda表达式的本质是接口Runnable接口的实例对象。
5.2.3、需要参数,但没有返回值
/**
* Lambda需要参数,但没有返回值的情况
* @author QuBo
* 联系方式:18066841007
* @2019年4月2日 东软睿道
*/
interface Inter{
public void demo(int a);
}
public class LambdaDemo4 {
public static void main(String[] args) {
// 采用匿名内部类
Inter i = new Inter() {
public void demo(int a) {
System.out.println( a % 2 == 0 ? "偶数" : "奇数" );
}
};
/
// 采用lambda表达式
Inter i2 = ( int a )->{ System.out.println( a % 2 == 0 ? "偶数" : "奇数" ); };
i2.demo(12);
}
}
5.2.4、参数类型推断
什么是参数类型推断呢?当接口中的抽象方法有参数的时候,使用Lambda表达式书写的时候,参数类型是可以让自动进行推断,进而可以省略参数的类型。
// 采用lambda表达式,省略参数的类型,可以进行自动的推断
Inter i2 = ( a )->{ System.out.println( a % 2 == 0 ? "偶数" : "奇数" ); };
5.2.5、一个参数,省略小括号
// 省略小括号
Inter i3 = a ->{ System.out.println( a % 2 == 0 ? "偶数" : "奇数" ); };
5.2.6、只有一条语句,并有返回值时,可以更简化
// 方法体不简化
Comparator<Integer> com = (o1,o2)-> {
return o1 - o2;
};
// 方法体只有一句话,并有返回值,可以省略大括号和return关键字
Comparator<Integer> com2 = (o1,o2)-> o1 -o2 ;
6、函数式接口
6.1、函数式接口介绍
函数式接口在Java指的是:在接口中仅有一个抽象方法的接口。
函数式接口也就是适用于函数式编程场景的接口,而Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以用于Lambda表达式的接口。
6.2、函数式接口演示
/*
* 定义接口,在接口中有且只有一个抽象方法
* 为了保证定义的接口一定是函数式接口,可以在接口上使用
* JDK中提供的FunctionInterface注解,进行标注
*/
@FunctionalInterface
public interface FunctionInterfaceDemo {
public void demo();
}
/*
* 测试函数式接口
*/
public class FunctionInterfaceTest {
// 定义方法,接受一个函数式接口
public static void test(FunctionInterfaceDemo fid) {
fid.demo();
}
public static void main(String[] args) {
// 给test方法传递一个函数式接口,采用匿名内部类方式
test(new FunctionInterfaceDemo() {
@Override
public void demo() {
System.out.println("采用匿名内部类");
}
});
// 采用lambda表达式
test(()->{
System.out.println("采用lanmbda表达式");
});
}
}
6.3、关于lambda的性能问题
6.3.1、关于编译生成class文件
当在程序使用lambda表达式和匿名内部类的时候它们在编译生成class文件的时候是有差异性的。
匿名内部类会生成独立的class文件,而lambda表达式并没有生成任何的class文件。也就是在程序中采用匿名内部类JVM就需要多加载class文件,而lambda表达式,并不需要加载额外的class文件,从class文件加载方面lambda表达式更加友好。
6.3.2、关于lambda执行的延时性
/*
* 此程序的目的是为了引出lambda延时执行的效果对比分析
*/
public class LazyTest {
public static void print(int age , String msg) {
if( age >= 18 ) {
System.out.println("您已成年,您的信息:"+msg);
}
}
public static void main(String[] args) {
int age = 23;
String sex = "性别:男";
String addr = "地址:陕西西安";
print(age , sex + "," + addr);
}
}
上面的程序是在年龄大于等于18岁的时候,才打印个人的一些信息数据,但在main方法中,已经将字符串拼接完成,并传递给了打印的方法。也就是不管打印方法中是否真正会打印数据,字符串都会被拼接,如果年龄不满足18岁,其实拼接字符串就是一种浪费。
针对上面的问题,我们可以采用Lambda表达式有效的避免这种浪费。
/*
* 测试Lambda表达式的演示加载问题
*/
interface Inter {
// 使用接口中的方法拼接数据
public String print();
}
public class LambdaTest {
public static void main(String[] args) {
int age = 3;
String sex = "性别:男";
String addr = "地址:陕西西安";
test(age, () -> {
System.out.println("当年龄小于18岁,lambda是不执行");
return sex + "," + addr;
});
}
public static void test(int age, Inter inter) {
if (age >= 18) {
System.out.println("您已成年,您的信息:" + inter.print());
}
}
}
当程序改成lambda表达式之后,如果判断不成立,其实lambda表达式根本就没有执行,那么在lambda表达式中拼接的字符串也不会执行。进而达到提升性能,减少操作的目的。
6.4、函数式接口作为方法参数
@FunctionalInterface
interface FuncParamer{
public void demo();
}
public class FuncParamerTest {
@Test
public void test() {
// 可以直接传递lambda表达式作为参数
method( ()->{System.out.println("函数式接口作为参数");});
}
// 方法需要接受一个函数式接口
public void method( FuncParamer paramer ) {
paramer.demo();
}
}
6.5、函数式接口作为方法返回值
@Test
public void test() {
// 函数接口作为返回值,其实返回的是一个函数式接口的实现类对象
FuncParamer paramer = method2();
paramer.demo();
}
// 方法返回函数式接口对象
public FuncParamer method2() {
return ()->{System.out.println("函数式接口作为参数返回");};
}
6.6、常用的函数式接口
在JDK8中的java.util.function包下,提供大量函数式接口。其中非常重要的有: Supplier、Consumer、Predicate、Function等
6.6.1、Supplier接口
Supplier:它是一个提供者(也可以理解为生产者)接口。其中提供get方法,返回类型为T的对象。
/**
* Consumer接口演示
* @author QuBo
* 联系方式:18066841007
* @2019年4月11日 东软睿道
*/
public class ConsumerTest {
@Test
public void demo() {
cons(100,(num)->{System.out.println("您的得分为:"+num);});
}
public void cons( Integer num , Consumer<Integer> con ) {
con.accept(num);
}
}
6.6.2、Consumer接口
Consumer:它是一个消费者(消费型)接口。其中提供accept方法,需要接受一个T类型的对象。
/**
* Consumer接口演示
* @author QuBo
* 联系方式:18066841007
* @2019年4月11日 东软睿道
*/
public class ConsumerTest {
@Test
public void demo() {
cons(100,(num)->{System.out.println("您的得分为:"+num);});
}
public void cons( Integer num , Consumer<Integer> con ) {
con.accept(num);
}
}
6.6.3、Predicate接口
Predicate:断定型接口,其中的test,需要接受T类型的对象,最终评定T是否满足某种要求,并返回boolean结果。
6.6.4、Function接口
Function:函数型接口,其中apply方法需要接受T类型的对象,进行操作,最终返回R类型的对象。
7、方法引用
7.1、什么是方法引用
方法引用:当要传递的Lambda体的操作已经有具体的实现的时候,这时就可以使用方法引用。
方法引用的格式:
第一种:对象 :: 方法名;
第二种:类名 :: 静态方法名;
第三种:类名 :: 方法名;
7.2、对象 :: 方法名
7.3、类名 :: 静态方法名
7.4、类名 :: 方法名
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数( 或无参数) 时,可以使用:类名::非静态方法名。
7.5、构造方法引用
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,
语法格式:ClassName::new
要求:构造器参数列表要与接口中抽象方法的参数列表一致,并且方法的返回值即为构造器对应类的对象。
7.6、数组引用
语法格式:type[] :: new
8、Stream API操作
8.1、Stream API 介绍
JDK8的升级中,Lambda表达式是其中第一个非常重要的改变,而Stream API则是第二个非常重要的给表。Stream API是真正的使用函数式编程风格的API。可以提供我们编码的效率、简化代码。
在Java中,我们使用集合主要存储数据,而使用Stream API可以对已经在集合中的数据进行各种复杂操作。
Stream API主要针对的集合的操作,可以对集合完成各种超级繁琐和复杂的查找、过滤、映射等数据操作。
简单的说:
集合存数据,Stream API处理集合中的数据。
注意细节:
Stream API是处理数据的,它不是容器,不能存储数据。Stream 操作完集合之后,会返回更改后的新集合,原集合中的数据不会被改变。并且Stream可以做到延时操作(用的时候才执行)。
Stream操作集合的数据共计分为三步:
- 数据序列化
- 对数据各种操作
- 终止操作(结束操作)
8.2、获取Stream的方式
获取Stream有四种方式
8.2.1、通过集合获取
在Collection接口中:
8.2.2、通过数组获取
在Arrays工具类中提供大量重载静态的stream方法,可以获取Stream。
8.2.3、通过Stream中的of方法获取
8.2.4、创建无限流
无限流可以理解为造数据。
8.3、Stream的各种操作
Stream的中间操作比较多,下面一个一个的来说:
8.3.1、filter过滤操作
8.3.2、limit限制操作
8.3.3、skip跳过操作
8.3.4、distinct去除重复元素
8.3.5、map映射操作
map(Function f):接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
8.3.6、flatMap映射操作
flatMap(Function f):接受一个函数作为参数,将流中的每个值都转成另一个流,然后把所有流连成一个新的流。
@Test
public void demo6() {
List<String> list = new ArrayList<>();
Collections.addAll(list, "neuedu","neusoft");
// 将集合中的每个字符串转成对应的字符 map存在流嵌套
Stream<Stream<Character>> stream = list.stream().map(this::string2char);
stream.forEach(c ->{
c.forEach(System.out::println);
});
// flatMap相当于把多个流合并成一个流。
Stream<Character> flatMap = list.stream().flatMap(this::string2char);
flatMap.forEach(System.out::println);
}
// 接受一个字符串,返回Stream对象
public Stream<Character> string2char(String s){
List<Character> list = new ArrayList<>();
for(Character c : s.toCharArray()) {
list.add(c);
}
return list.stream();
}
总结:
map:如果遇到流嵌套的情况,不会将多个流进行拆分合并,依然是Stream中嵌套Stream
flatMap:可以将嵌套的流,合并之后,转成一个流进行操作。
8.3.7、sorted排序
8.4、Stream的终止操作
8.4.1、allMatch操作
allMatch:判断所有的数据是否都满足某个条件。
8.4.2、anyMatch操作
anyMatch判断所有的数据中是否最少有一个满足给定的添加
8.4.3、noneMatch操作
noneMatch:判断所有数据中是否一个匹配的都没有
8.4.4、findFirst和findAny操作
8.4.5、count、max、min操作
8.4.6、forEach遍历操作
8.4.7、reduce规约操作
8.4.8、collect收集操作
collect:将数据最后在转成List、或Set等集合。
在collect中需要接受Collector收集器,而收集器需要通过Collectors工具类得到不同形式的收集器。