用了一年多的Java8之后,总体感觉Java8的两大功能更新:Lambda表达式和Stream API,是不错的,确实提高了开发效率。Java8是2014年3月份正式Release的,可以看成是自Java5以来最具革命性的版本。

总体来说Java 8 的新特性:

  • Lambda表达式
  • 函数式接口
  • 方法引用与构造引用
  • Stream API
  • 接口默认方法与静态方法
  • 新时间日期API
  • 其他特性

总体来说Java 8的优点:

  • 代码更少
  • 强大的Stream API
  • 速度更快
  • 最大化较少空指针异常:Optional
  • Nashron引擎,允许在JVM上运行JS应用
  • 便于并行

关于JVM运行JS,其实早在Java 6上就已经实现了,只不过是运行太慢了。Nashron就是为了改进性能而推出的。

1. 关于接口的改进

Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。

2. 注解的新特性

Java 8对注解处理提供了两点改进:可重复的注解可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解。

3. 集合的底层源码实现方式更新
jdk7:
ArrayList list = new ArrayList();//初始化一个长度为10的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);
list.add(345);
...
当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。
删除操作:如果删除某一个数组位置的元素,需要其后面的元素依次前移。
remove(Object obj) / remove(int index)
jdk8:
ArrayList list = new ArrayList();//初始化一个长度为0的Object[] elementData
sysout(list.size());//返回存储的元素的个数:0
list.add(123);//此时才创建一个长度为10的Object[] elementData
list.add(345);
...
当添加第11个元素时,需要扩容,默认扩容为原来的1.5倍。还需要将原有数组中的数据复制到新的数组中。

所以:
(1) 建议使用:ArrayList list = new ArrayList(int length);
(2) jdk8延迟了底层数组的创建:内存的使用率;对象的创建更快

HashMap:Map的主要实现类;线程不安全的,效率高;可以存储null的key和value
(存储结构:jdk7.0 数组+链表; jdk8.0 数组+链表+红黑树)

Hashtable:Map的古老实现类;线程安全的,效率低;不可以存储null的key和value

4. 新日期时间的API丰富

Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。

  • java.time – 包含值对象的基础包
  • java.time.chrono – 提供对不同的日历系统的访问
  • java.time.format – 格式化和解析时间和日期
  • java.time.temporal – 包括底层框架和扩展特性
  • java.time.zone – 包含时区支持的类

(1) LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
(2) Instant时间点
java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。(1 ns = 10-9 s) 1秒 = 1000毫秒 =106微秒=109纳秒
(3) 格式化与解析日期或时间
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE
本地化相关的格式。如:ofLocalizedDate(FormatStyle.FULL)
自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”)

5. Optional类的使用

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:
Optional.empty() : 创建一个空的 Optional 实例
Optional.of(T t) : 创建一个 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
T get(): 如果调用对象包含值,返回该值,否则抛异常
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

6. Lambda表达式

Lambda表达式就是匿名内部类的简化。
Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表
右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

(参数类型 参数名1,参数类型 参数名2,……) -> {方法体}

注意事项:

  • 参数类型可以省略,因为有“类型推断”
  • 如果只有一个参数,小括号可以省略
  • 如果方法体只有一句话,则方法体外面的花括号可以省略
  • 如果方法体中只有一句return,没有其他语句,则return也可以省略

另一个重要的概念是:函数式接口
只包含一个抽象方法的接口,称为函数式接口。在接口上可以加上@FunctionalInterface注解。
在java.util.function包下定义了java 8 的丰富的函数式接口
Java内置的四大核心函数式接口:

  • Consumer< T > 消费型接口
  • Supplier< T > 供给型接口
  • Function<T, R> 函数型接口
  • Predicate< T > 断定型接口
    当然还有其他接口。
    在这里插入图片描述
	// Consumer示例
	@Test
	public void testConsumer() {
		// 写法一:
		Consumer<Integer> consumer = new Consumer<Integer>() {
			@Override
			public void accept(Integer t) {
				System.out.println("T: " + t);
			}
		};
		consumer.accept(100);
		// 写法二
		Consumer<Integer> consumer1 = t -> System.out.println("T: " + t);
		consumer1.accept(1000);
		
//		Consumer<Integer> consumer2 = System.out::println("T: " + t);	// 错误,不能拼接
		// 写法三
		Consumer<Integer> consumer3 = System.out::println; // 对象::实例方法名
		consumer3.accept(1000000);
	}
	// Supplier示例
	@Test
	public void testSupplier() {
		String name = "HXK";
		Supplier<String> sup1 = new Supplier<String>() {
			@Override
			public String get() {
				return name.toLowerCase();
			}
		};
		Supplier<String> sup2 = () -> name.toLowerCase();
		Assert.assertEquals("hxk", sup1.get());
		Assert.assertEquals("hxk", sup2.get());
	}
	// Function示例
	@Test
	public void testFunction() {
		String name = "HXK";
		Function<String, Integer> fun1 = str -> str.length();
		Function<String, Integer> fun2 = new Function<String, Integer>(){
			@Override
			public Integer apply(String str) {
				return str.length();
			}
		};
		Assert.assertEquals(3, (int)fun1.apply(name));
		Assert.assertEquals(3, (int)fun2.apply(name));
	}
	// Predicate 示例
	@Test
	public void testPredicate() {
		Predicate<Employee> pre1 = new Predicate<Employee>() {
			@Override
			public boolean test(Employee employee) {
				return employee.getAge()>30;
			}
		};
		Predicate<Employee> pre2 = employee -> employee.getAge()>30;
		Assert.assertEquals(true,pre1.test(new Employee(5,"HXK", 35, 29000)));
		Assert.assertEquals(false,pre2.test(new Employee(5,"HXT", 20, 29000)));
	}

方法引用:就是Lambda表达式,就是函数式接口的一个实例,通过方法的名字来指向一个方法。方法引用相当于对某种具备特殊要求 的Lambda表达式的代替,目的是为了语法更加简洁!
使用方法引用的要求:实现抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
语法:类名或对象名::方法名
语法分类:以下三种情况

  • 对象::实例方法名
  • 类 :: 静态方法名
  • 类 :: 实例方法名
// 第一个示例:对象::实例方法名
Consumer<String> con=(x)->System.out.println(x);
// 等同于
Consumer<String> con=System.out::println;
// 正常调用
con.accept(100);

// 第二个示例:类 :: 静态方法名
		Comparator<Double> comp = new Comparator<Double>() {
			@Override
			public int compare(Double o1, Double o2) {
				return Double.compare(o1, o2);
			}
		};
		// 使用Lambda表达式一
		Comparator<Double> comp2 = (Double o1, Double o2) -> {return Double.compare(o1, o2);};
		// 使用Lambda表达式二
		Comparator<Double> comp21 = (o1,o2) -> Double.compare(o1, o2);
		// 使用方法引用
		Comparator<Double> comp3 = Double::compare;	// 类::静态方法名
		System.out.println(comp3.compare(23.1, 45.4));

// 第三个示例:类 :: 实例方法名
		Comparator<String> comp = new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.compareTo(o2);
			}
		};
		// 使用Lambda表达式一
		Comparator<String> comp2 = (String o1, String o2) -> {return o1.compareTo(o2);};
		// 使用Lambda表达式二
		Comparator<String> comp21 = (o1, o2) -> o1.compareTo(o2);
		// 使用方法引用
		Comparator<String> comp3 = String::compareTo;	// 类::实例方法
		System.out.println(comp.compare("BB", "Aa"));

构造器引用
格式:ClassName::new
要求:方法体中返回的就是一个对象,这时可以使用构造器引用。

  • 方法体中,仅仅就有一个语句而且是返回语句
  • 返回的就是一个new出来的对象
  • 函数式接口的抽象方法的参数列表=对象构造器的参数列表+对象类型
// 示例
Function<Integer, MyClass> fun = (n) -> new MyClass(n);
// 等同于
Function<Integer, MyClass> fun = MyClass::new;

数组引用
代替一部分具有特殊要求的Lambda表达式
格式:type[] :: new
要求:

  • 方法的实现中仅仅只有一个return语句
  • return 的只能是一个数组对象
  • 函数式接口的抽象方法的参数列表=数组参数类型+数组类型
	@Test
	public void testArrayRef() {
		Function<Integer, Integer[]> fun1 = (i)->new Integer[i];
		// 数组引用
		Function<Integer, Integer[]> fun2 = Integer[]::new;
	}
7. Stream API

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

简单的理解Stream就是:数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
“集合讲的是数据,Stream讲的是计算”

注意
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream操作的三大步骤:
(1) 创建Stream

  • 通过集合创建
  • 通过数组创建
  • 通过Stream的of()方法创建
  • 创建无限流(静态方法迭代Stream.iterate()和生成Stream.generate())

(2) 中间操作

  • 筛选与切片
    filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
    limit(long n)——截断流,使其元素不超过给定数量。
    skipn(long n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
    distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
	@Test
	public void test1() {
		// 1.获取Stream对象
		Stream<Employee> stream = list.stream();
		// 2.中间操作 --- Filter
		Stream<Employee> filter = stream.filter(e->e.getAge()>30);
		
		// 3.终止操作 方式一
		filter.forEach(new Consumer<Employee>() {
			@Override
			public void accept(Employee t) {
				System.out.println(t);
			}
		});
		// 3.终止操作 方式二
		list.stream().filter(e->e.getAge()>30).forEach(t->System.out.println(t));
		// 3.终止操作 方式三
		filter.forEach(System.out::println);
	}
  • 映射
    map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    练习:获取员工姓名长度大于2的员工的姓名。
    flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    常用的是map和flatmap,其他还有mapToDouble、mapToInt、mapToLong。
    map是将每一个元素映射成一个普通类型的元素。
    flatMap是将每一个元素映射成一个Stream
// MAP  Demo-1
		// 1.获取Stream对象
		Stream<Employee> stream = list.stream();
		// 2.中间操作 --- map
		Stream<String> map = stream.map(e->e.getName());
		// Stream<String> map = stream.map(Employee::getName);
		// 3.终止操作
		map.forEach(System.out::println);
// MAP  Demo-2  将List中每个student变换成Map<>后放回list中
		Student student1 = new Student("H", 10);
		Student student2 = new Student("X", 20);
		Student student3 = new Student("K", 20);
		
		List<Student> list = new ArrayList<Student>();
		list.add(student1);
		list.add(student2);
		list.add(student3);

		List<Map<String, Object>> collect2 = list.stream().map(t->{
			Map<String, Object> map = new HashMap<String, Object>();
			Field[] declaredFields = t.getClass().getDeclaredFields();
			for (Field f : declaredFields) {
				f.setAccessible(true);
			}
			for (Field f : declaredFields) {
				String field = f.toString().substring(f.toString().lastIndexOf(".") + 1);
				try {
					map.put(field, f.get(t));
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			return map;
		}).collect(Collectors.toList());
// FLATMAP
	// 定义字符转char Stream函数
	public static Stream<Character> fromStreamToChar(String str) {
		char[] charArray = str.toCharArray();
		ArrayList<Character> list = new ArrayList<>();
		for(int i=0; i<charArray.length; i++) {
			list.add(charArray[i]);
		}
		return list.stream();
	}

	@Test
	public void testFlatMap() {
		// 实现将String转为char Stream
		ArrayList<String> list = new ArrayList<>();
		list.add("john");
		list.add("lucy");
		list.add("lily");
		// 1.获取Stream对象
		Stream<String> stream = list.stream();
		// 2.中间操作 --- flatMap 
		Stream<Character> flatMap = stream.flatMap(TestStreamMiddle::fromStreamToChar);
		// 3.终止操作
		flatMap.forEach(System.out::println);
	}
  • 排序
    sorted()——自然排序
    sorted(Comparator com)——定制排序

(3) 终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
流进行了终止操作后,不能再次使用。

  • 匹配与查找
    allMatch(Predicate p)——检查是否匹配所有元素
    anyMatch(Predicate p)——检查是否至少匹配一个元素
    noneMatch(Predicate p)——检查是否没有匹配的元素
    findFirst——返回第一个元素
    findAny——返回当前流中的任意元素
    count——返回流中元素的总个数
    max(Comparator c)——返回流中最大值
    min(Comparator c)——返回流中最大值
    forEach(Consumer c)——内部迭代使用(使用Collection接口需要用户去做迭代,成为外部迭代,相反,Stream API使用内部迭代——它帮你把迭代做了)

  • 归约
    reduce(T identity, BinaryOperator b)——可以将流中元素反复结合起来,得到一个值。返回 T
    reduce(BinaryOperator b) ——可以将流中元素反复结合起来,得到一个值。返回 Optional

	@Test
	public void testReduce() {
		List<Employee> list = EmployeeData.getData();
		
		Optional<Integer> reduce1 = list.stream().map(new Function<Employee, Integer>() {
			@Override
			public Integer apply(Employee t) {
				// TODO Auto-generated method stub
				return t.getAge();
			}
		}).reduce(new BinaryOperator<Integer>() {
			@Override
			public Integer apply(Integer t, Integer u) {
				// TODO Auto-generated method stub
				return t+u;
			}
		});
		System.out.println(reduce1);
		
		// 简单写法
		Optional<Integer> reduce2 = list.stream().map(Employee::getAge).reduce((t,u)->t+u);
		System.out.println(reduce2);
	}
  • 收集
    collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、set、map)
	@Test
	public void testCollector() {
		Set<Employee> set = list.stream().collect(Collectors.toSet());
		set.forEach(System.out::println);
	}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐