目录
一、简介
Java 8 (⼜称为 jdk 1.8) 是 Java 语⾔开发的⼀个主要版本。 Java 8 是oracle公司于2014年3⽉发布,可以看成是⾃Java 5 以来最具⾰命性的版本。Java 8为Java语⾔、编译 器、类库、开发⼯具与JVM带来了⼤量新特性。
速度更快
• 代码更少(增加了新的语法:Lambda 表达式)
• 强⼤的 Stream API
• 便于并⾏
• 最⼤化减少空指针异常:Optional
• Nashorn引擎,允许在JVM上运⾏JS应⽤
二、函数式接口
1. 什么是函数式(Functional)接口
• 只包含⼀个抽象⽅法的接口 ,称为函数式接口。
• 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出⼀个受检异常(即:⾮运⾏时 异常),那么该异常需要在⽬标接口的抽象⽅法上进⾏声明)。
• 我们可以在⼀个接口上使⽤ @FunctionalInterface 注解,这样做可以检查它是否是⼀个函数式接口。
• 在java.util.function包下定义了Java 8 的丰富的函数式接口
2.函数式接口的特点
• 接口有且仅有⼀个抽象⽅法
• 允许定义静态⽅法
• 允许定义默认⽅法
• 允许java.lang.Object中的public⽅法
• 该注解不是必须的,如果⼀个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够 更好地让编译器进⾏检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会 报错
3. 理解函数式接口
• Java从诞⽣⽇起就是⼀直倡导“⼀切皆对象”,在Java⾥⾯⾯向对象(OOP)编程是⼀切。但是随着python、 scala等语⾔的兴起和新技术的挑战,Java不得不做出调整以便⽀持更加⼴泛的技术要求,也即 java不但可 以⽀持OOP还可以⽀持OOF(⾯向函数编程)
• 在函数式编程语⾔当中,函数被当做⼀等公⺠对待。在将函数作为⼀等公⺠的编程语⾔中,Lambda表达式 的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附 于⼀类特别的对象类型⸺函数式接口。
• 简单的说,在Java8中,Lambda表达式就是⼀个函数式接口的实例,这就是Lambda表达式和函数式接口 的关系。也就是说,只要⼀个对象是函数式接口的实例,那么该对象就可以⽤Lambda表达式来表⽰。 以前⽤匿名实现类表⽰的现在都可以⽤Lambda表达式来写。
1)Runnable接口
packagejava.lang;
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
原始方法实现:
public class RunnableLambda implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
public static void main(String[] args) {
RunnableLambda runnableLambda = new RunnableLambda();
new Thread(runnableLambda).start();
}
}
lambda⽅式实现:
public class RunnableLambdaNew{
public static void main(String[] args) {
newThread(() -> {
System.out.println("helloworld");
}).start();
}
}
三、Lambda表达式
1.概念
Lambda是⼀个匿名函数,可以理解为⼀段可以传递的代码(将代码像数据⼀样传递);可以写出更简洁、更灵活的 代码;作为⼀种更紧凑的代码⻛格,是Java语⾔表达能⼒得到提升。
2.匿名内部类
public class Person{
private String name;
private int 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;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
",age=" + age +
'}';
}
}
public class Test{
public static void main(String[] args) {
Person person1 = new Person(11, "andy");
Person person2 = new Person(8, "jack");
Person person3 = new Person(3, "lucy");
TreeSet<Person> treeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Persono1, Persono2) {
if (o1.getId() > o2.getId()) {
return 1;
}
if (o1.getId() < o2.getId()) {
return -1;
}
return 0;
}
});
treeSet.add(person1);
treeSet.add(person2);
treeSet.add(person3);
for (Person person : treeSet) {
System.out.println(person.toString());
}
}
}
3. lambda
Lmabda表达式的语法总结:() -> {};
λ表达式有三部分组成:参数列表,箭头(->),以及⼀个表达式或语句块。
public class Person2 {
private String name;
private int age;
public Person2() {
}
public Person2(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 +
'}';
}
}
public class TreeSetTest {
public static void main(String[] args) {
Person2 p1=new Person2("andy",38);
Person2 p2=new Person2("jack",24);
Person2 p3=new Person2("lucy",48);
/*TreeSet<Person2> personSet=new TreeSet<>(new Comparator<Person2>(){
@Override
public int compare(Person2 o1,Person2 o2){
return-(o1.getAge()-o2.getAge());
}
});*/
//TreeSet<Person2> personSet=new TreeSet<>((Person2 person1,Person2 person2)->{return-(person1.getAge()-person2.getAge());});
//类型推断
//TreeSet<Person2> personSet=new TreeSet<>((person1,person2)->{return-(person1.getAge()-person2.getAge());});
//⽅法体只有⼀⾏语句可以省略⼤括号,且有return都可以可以省略
TreeSet<Person2> personSet=new TreeSet<>((person1,person2)->person1.getAge()- person2.getAge());
//⽅法引⽤
//TreeSet<Person2> personSet=new TreeSet<>(Comparator.comparingInt(Person2::getAge));
personSet.add(p1);
personSet.add(p2);
personSet.add(p3);
for(Person2 person:personSet){
System.out.println("person="+person);
}
}
}
以下是lambda表达式的重要特征:
• 可选类型声明:不需要声明参数类型,编译器可以统⼀识别参数值。
• 可选的参数圆括号:⼀个参数⽆需定义圆括号,但多个参数需要定义圆括号。
• 可选的⼤括号:如果主体包含了⼀个语句,就不需要使⽤⼤括号。
• 可选的返回关键字:如果主体只有⼀个表达式返回值则编译器会⾃动返回值,⼤括号需要指定 表达式返回 了⼀个数值。
//1.不需要参数,返回值为 5
()->5
//2.接收⼀个参数(数字类型),返回其2倍的值
x->2*x
//3.接受2个参数(数字),并返回他们的差值
(x,y)->x–y
//4.接收2个int型整数,返回他们的和
(intx,inty)->x+y
//5.接受⼀个 string对象,并在控制台打印,不返回任何值(看起来像是返回void)
(Strings)->System.out.print(s)
实战案例,计算两数之和,之差等。
步骤⼀:定义⼀个接口,内部包含⼀个calc的⽅法
public interface CalcInterface{
int calc(inta,intb);
}
public class Calc{
public static int calc(int x,int y,CalcInterface calcInterface){
return calc Interface.calc(x,y);
}
}
步骤⼆:测试类
public class CalcTest {
public static void main(String[] args) {
int m = 1;
int result2 = Calc.calc(1, 2, new CalcInterface() {
@Override
public int calc(int x, int y) {
System.out.println("m=" + m);
//m=1;
return x + y;
}
});
System.out.println("result2=" + result2);
int i = 1;
int result = Calc.calc(10, 2, (intx, inty) -> {
//可以取值
System.out.println("i=" + i);
//不能修改⼀个局部变量的值,像匿名内部类⼀样,局部都是final的
//i=2;
System.out.println("i=" + i);
return x + y;
});
System.out.println("result=" + result);
result = Calc.calc(10, 2, (int x, int y) -> x - y);
System.out.println("result=" + result);
result = Calc.calc(10, 2, (int x, int y) -> x * y);
System.out.println("result=" + result);
result = Calc.calc(10, 2, (int x, int y) -> x / y);
System.out.println("result=" + result);
}
}
4. 变量作用域 lambda
表达式只能引⽤标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变 量,否则会编译错误。
int aaa=200;
CalcInterface addCounter=(int a,int b)->{
int c=a+b;
System.out.println("引⽤局部变量aaa:"+aaa);
//lambda标识内部⽆法修改外部的局部变量的值
/* System.out.println("修改局部变量aaa:"+ aaa + +);*/
return c;
};
四、内置函数式接口
java.util.function 它包含了很多类,⽤来⽀持 Java的 函数式编程,该包中的函数式接口有4类函数式接口:
1. supplier接口
生产型接口,没有参数,有返回值
@FunctionalInterface
public interface Supplier<T> {
/**
*Getsaresult.
*
*@returnaresult
*/
T get();
}
获取系统时间:
public static Date testSupplierDate(Supplier<Date> supplier){
return supplier.get();
}
//获取当前的系统时间
System.out.println(MyFunctionTest.testSupplierDate(()->{
return new Date();
}));
2.consumer消费型接口
消费一个数据,没有返回值
@FunctionalInterface
public interface Consumer<T> {
/**
*Performs this operation on the given argument.
*
*@param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
accept(t);
after.accept(t);
};
}
}
输入一个用户名,将用户名转大写,打印用户名
/**
*2、消费型接⼝,接收⼀个参数,没有返回值
*@paramusername
*@paramconsumer
*/
public static void testConsumer(String username,Consumer<String> consumer){
consumer.accept(username);
}
MyFunctionTest.testConsumer("Iloveyou",(str)->{
String s=str.toUpperCase();
System.out.println(s);
});
3. predicate推断型接口
根据⼊参做判断,并返回boolean值的时候使⽤
例如根据性别判断是否符合⼥朋友标准
public static boolean judgeSex(Person person, Predicate<Person> predicate) {
return predicate.test(person);
}
Person person=new Person();
person.setId(123);
person.setName("路西");
person.setSex("⼥");
boolean sex=MyFunctionTest.judgeSex(person,(p)->{
if(p.getSex().equals("⼥")){
return true;
}
return false;
});
System.out.println(sex);
4. function函数型接口
此接口接收⼀个参数,返回⼀个结果,参数和结果的数据类型根据实际情况稳定。
例如:输⼊⼀个字符串,返回⼀个整形
public static Integer testFunction(String s , Function<String,Integer> function){
return function.apply(s);
}
Integer integer=MyFunctionTest.testFunction("12345",(str) -> {
return Integer.parseInt(str);
});
System.out.println(integer.getClass());
五、方法引用
1.什么是方法引用
⽅法引⽤提供了⾮常有⽤的语法,可以直接引⽤已有Java类或对象(实例)的⽅法或构造器。与lambda联合使⽤, ⽅法引⽤可以使语⾔的构造更紧凑简洁,减少冗余代码。
语法:
2. ⽅法引⽤和lambda表达式
六、 Stream API
1.简介
Java 8 API添加了⼀个新的抽象称为流Stream,可以让你以⼀种声明的⽅式处理数据。
Stream 使⽤⼀种类似⽤ SQL 语句从数据库查询数据的直观⽅式来提供⼀种对 Java 集合运算和表达的⾼阶抽 象。
Stream API可以极⼤提⾼Java程序员的⽣产⼒,让程序员写出⾼效率、⼲净、简洁的代码。
这种⻛格将要处理的元素集合看作⼀种流,流在管道中传输,并且可以在管道的节点上进⾏处理,⽐如筛选,排 序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前 ⾯处理的结果。
Stream(流)是⼀个来⾃数据源的元素队列并⽀持聚合操作
• 元素是特定类型的对象,形成⼀个队列。Java中的Stream并不会存储元素,而是按需计算。
• 数据源 流的来源。可以是集合,数组,I/O channel,产⽣器generator 等。
• 聚合操作 类似SQL语句⼀样的操作,⽐如filter, map, reduce, find, match, sorted等。 和以前的Collection操作不同,Stream操作还有两个基础的特征:
• Pipelining: 中间操作都会返回流对象本⾝。这样多个操作可以串联成⼀个管道,如同流式⻛格(fluent style)。这样做可以对操作进⾏优化,⽐如延迟执⾏(laziness)和短路( short-circuiting)。 • 内部迭代:以前对集合遍历都是通过Iterator或者for-Each的⽅式, 显式的在集合外部进⾏迭代,这叫做 外部迭代。Stream提供了内部迭代的⽅式,通过访问者模式(Visitor)实现。
2.生成流
在 Java 8 中, 集合接口有两个⽅法来⽣成流:
stream() − 为集合创建串⾏流。适合存在线程安全问题、阻塞任务、重量级任务,以及需要使⽤同⼀事务的 逻辑
parallelStream() − 为集合创建并⾏流。适合没有线程安全问题、较单纯的数据处理任务。
public class MethodUse{
public static void main(String[] args) {
List<String> string List = new ArrayList<>();
stringList.add("zhangsan");
stringList.add("gaojieling");
stringList.add("chengqingqing");
stringList.add("zhanghaotian");
/* for(String s : stringList){
System.out.println(s);
}*/
/*stringList.forEach(System.out::println);*/
//⽣成串⾏流
Stream<String> stream = stringList.stream();
//⽣成并⾏流
Stream<String> parallelStream = stringList.parallelStream();
}
}
3.forEach
Stream 提供了新的⽅法 forEach 来迭代流中的每个数据
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
for(
String string:strings)
{
System.out.println(string);
}
System.out.println("=============");
strings.stream().
forEach(System.out::println);
4. map
map ⽅法⽤于映射每个元素到对应的结果,以下代码⽚段使⽤ map 输出了元素对应的平⽅数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//Function<Integer,Integer>myFunction=(Integera)->{returna*a;};
Stream<Integer> integerStream = numbers.stream()
5. filter
ilter ⽅法⽤于通过设置的条件过滤出元素。以下代码⽚段使⽤ filter ⽅法过滤出空字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("没有过滤空字符串之前的");
strings.stream().forEach(System.out::println);
System.out.println("过滤空字符串之后的");
//Predicate<String>myFunction=(Stringstr)->{returnstr.length()>0;};
//lambda表达式优化之前
strings.stream().filter((Stringstr)->{returnstr.length() > 0;}).forEach(System.out::println);
//lambda表达式优化之后
strings.stream().filter(str -> str.length()>0).forEach(System.out::println);
6. sort
sorted ⽅法⽤于对流进⾏排序。
List<String>strings=Arrays.asList("abc","","bc","efg","abcd","","jkl");
System.out.println("排序之前");
strings.stream().filter(str->str.length()>0).forEach(System.out::println);
System.out.println("排序之后");
strings.stream().filter(str->str.length()>0).sorted().forEach(System.out::println);
7. Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可⽤于返回列表或字符串:
//collector将流转换成集合
Stream<String>stringStream=strings.stream().filter(str->str.length()>0);
System.out.println(stringStream);
List<String>list=strings.stream().filter(str->str.length()>0).collect(Collectors.toList());
System.out.println(list);
//collector将流转换成字符串
StringmyString=strings.stream().filter(str->str.length()>0).collect(Collectors.joining(":"));
System.out.println(myString);
七、接口中的默认⽅法
1.默认方法简介
Java 8 新增了接口的默认⽅法。java8之前的接口中的⽅法都是抽象⽅法。
简单说,默认⽅法就是接口可以有实现⽅法,而且不需要实现类去实现其⽅法。
我们只需在⽅法名前⾯加个 default 关键字即可实现默认⽅法。
2.语法
public interface ComputerInteface{
void playGame();
void movie();
default void coding(){
System.out.println("此处是实现");
};
}
3. 多个默认方法
⼀个接口有默认⽅法,考虑这样的情况,⼀个类实现了多个接口,且这些接口有相同的
默认⽅法,以下实例说明了这种情况的解决⽅法
如果⼀个实现类实现了两个接口,并且两个接口中的默认实现同名,
public interface ComputerInteface{
voidplayGame();
voidmovie();
default void coding(){
System.out.println("此处是电脑的实现");
}
}
public interface PadInteface{
void playGame();
void movie();
default void coding(){
System.out.println("此处是pad的实现");
}
}
public class MySuperComputer implements
ComputerInteface,PadInteface{
@Override
public void playGame(){
}
@Override
public void movie(){
}
/**
*⽅案⼀:⾃⼰实现的
*/
/* @Override
public void coding(){
System.out.println("我的超级电脑的编程实现");
}*/
/**
*⽅案⼆:需要选择⼀个接⼝中的默认实现
*/
@Override
public void coding(){
PadInteface.super.coding();
}
}
4. 静态默认方法
Java 8 的另⼀个特性是接口可以声明(并且可以提供实现)静态⽅法。例如:
public interface ComputerInteface{
void playGame();
void movie();
default void coding(){
System.out.println("此处是电脑的实现");
};
static void homework(){
System.out.println("此处是静态⽅法");
}
}
八、 Java8日期时间
Java 8 在 java.time 包下提供了很多新的 API。它的⽬标是克服旧的⽇期/时间API实现中所有的缺陷,新的⽇期/ 时间API的⼀些设计原则如下:
•不变性:新的⽇期/时间API中,所有的类都是不可变的,这种设计有利于并发编程。
•关注点分离:新的API将⼈可读的⽇期时间和机器时间(unix timestamp)明确分离,它为⽇期(Date)、时 间(Time)、⽇期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
•清晰:在所有的类中,⽅法都被明确定义⽤以完成相同的⾏为。举个例⼦,要拿到当前实例我们可以使⽤ now()⽅法,在所有的类中都定义了format()和parse()⽅法,而不是像以前那样专⻔有⼀个独⽴的类。为了 更好的处理问题,所有的类都使⽤了⼯⼚模式和策略模式,⼀旦你使⽤了其中某个类的⽅法,与其他类协同 ⼯作并不困难。
•实⽤操作:所有新的⽇期/时间API类都实现了⼀系列⽅法⽤以完成通⽤的任务,如:加、减、格式化、解析、从 ⽇期/时间中提取单独部分等操作。
•可扩展性:新的⽇期/时间API是⼯作在ISO-8601⽇历系统上的,但我们也可以将其应⽤在⾮IOS的⽇历上。
新的java.time包涵盖了所有处理⽇期,时间,⽇期/时间,时区,时刻(instants),过程(during)与时钟(clock)的 操作。
//可以使⽤ of⽅法构建它们的实例,如下⾯创建了⼀个 2019-9-2221:42:59东⼋区 的时间对象
LocalDate localDate = LocalDate.of(2019, Month.SEPTEMBER, 22);
LocalTime localTime = LocalTime.of(21, 42, 59);
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
//获取现在的时间,这是⼀个静态⽅法
LocalDate now = LocalDate.now();
//每个实例可以获取它们的 part信息,如获取年
int year = localDate.getYear();
//可以修改 part信息,这将返回⼀个新对象,如增加⼀年
LocalDate localDatePlus = localDate.plusYears(1);
//设置 part信息,也会返回新的对象,如设置为 2017年
LocalDate localDateWithYear = localDate.withYear(2017);
//⽐较两个⽇期 isAfter,isBefore
boolean after = localDate.isAfter(LocalDate.now());
//格式化⽇期时间
//yyyy-MM-dd
System.out.println(now.format(DateTimeFormatter.ISO_DATE));
//yyyy-MM-ddTHH:mm:ss
System.out.println(now.format(DateTimeFormatter.ISO_DATE_TIME));
//yyyy-MM-ddHH:mm:ss
System.out.println(now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
//⽇期解析
System.out.println(LocalDate.parse("2019-09-22"));
System.out.println(LocalDateTime.parse("2019-09-22T21:05:22"));
System.out.println(LocalDateTime.parse("2019-09-22
21:05:22",DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));