15--JDK8特性

1、关于JDK的升级

JDK5之前JDK的变化并没有翻天覆地的变化,在JDK的升级过程JDK5是一个里程碑,然后就是今天我们要讲的JDK8同样也是一个里程碑。从JDK5开始引入众多特性之后,JDK8同样引入了更加灵活和轻巧便捷的代码编写方式。今天就给大家介绍关于JDK8中的:

  1. 日期时间处理类
  2. 新的注解
  3. 接口中的默认方法
  4. Lambda表达式
  5. 函数式接口
  6. 方法引用
  7. 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有点类似,但更人性化。

扫描二维码关注公众号,回复: 17280544 查看本文章

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、接口中方法细节

  1. 接口中定义的静态方法,只能通过接口来调用。
  2. 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时执行的是重写以后的方法。
  3. 类优先:如果子类(或实现类)继承的父类(或实现的接口中)定义的同名、同参数的默认方法,那么在没有重写此方法时,默认调用的是父类中的同名、同参数方法,而不会是接口中的同名、同参数方法。
  4. 接口冲突:如果实现类实现了多个接口,而在多个接口中都定义同名、同参数默认方法,那么在实现类没有重写此方法的情况会报错。那么就必须在实现类中重写此方法。
  5. 接口名.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操作集合的数据共计分为三步:

  1. 数据序列化
  2. 对数据各种操作
  3. 终止操作(结束操作)

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工具类得到不同形式的收集器。

猜你喜欢

转载自blog.csdn.net/baozi7263/article/details/105580338