1.接口中可以有默认方法实现
interface A { public void sayString(String str); default public void SayInt(int i) {// 接口内默认实现,不需要子类实现,子类可以覆盖或子接口或子抽象类可以覆盖为抽象方法 System.out.println("sayInt--->" + i); } } abstract class B implements A { public abstract void SayInt(int i); } class C extends B { @Override public void sayString(String str) { System.out.println("sayString--->" + str); } @Override public void SayInt(int i) { System.out.println("sayInt2--->" + i); } } public class Test { public static void main(String[] args) { C c = new C(); c.SayInt(21); c.sayString("senssic"); } }
2.Lambada表达式
每一个lambda都能通过一个特定的接口,与一个给定的类型进行匹配,即所谓函数式接口必须要有且仅有一个抽象方法声明,每个与之对应的lambda表达式必须与抽象方法声明相匹配(即传入的参数类型,和返回类型相一致)。由于默认方法不是抽象的,所以在函数式接口里你可以添加任意的默认方法。
任意一个包含一个抽象方法的接口,都可以做成lambda表达式,为了让你定义的接口满足要求,应当在接口前面加上@FunctionalInterface,编译器便会注意到此标注,如果接口中定义了第二个抽象方法,编译器会报错。
// 标注此interface为函数式接口,最好写上 @FunctionalInterface interface A { public String fLambada(String str);// 函数式接口有且仅有一个抽象方法 default public void sayInt(int i) {// 函数式接口可以声明多个默认方法 System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class B { public void invoke(A a) { System.out.println(a.fLambada("senssic")); } } public class Test { public static void main(String[] args) { B b = new B(); // 以下三中lambada均可,都是通过lambada来省略代码直接实现接口抽象方法 b.invoke((String str) -> { System.out.println(str);// 使用大括号写实现代码 return "hello " + str; }); b.invoke((String str) -> "hello " + str);// 直接返回 b.invoke(str -> "hello " + str);// 省略类型直接返回 } }
2.1 方法和构造方法的引用
使用静态方法实现接口抽象方法
@FunctionalInterface interface A { public String fLambada(String str); default public void sayInt(int i) { System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class B { public void invoke(A a) { System.out.println(a.fLambada("senssic")); } } public class Test { public static void main(String[] args) { B b = new B(); // 通过静态方法来实现接口的抽象方法,类型必须匹配, // 比如此处,valueOf返回的必须为String类型,valueOf传入的参数必须为一个且为String类型 A a = String::valueOf; b.invoke(a); } }
使用普通方法实现抽象接口
@FunctionalInterface interface A { public String fLambada(String str); default public void sayInt(int i) { System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class B { public void invoke(A a) { System.out.println(a.fLambada("senssic")); } } class C { public String invSay(String str) { return "world" + str; } } public class Test { public static void main(String[] args) { B b = new B(); C c = new C(); A a = c::invSay;//使用非静态方法实现抽象接口,此处C必须先实例化才能调用其invSay方法 b.invoke(a); } }
使用构造方法实现抽象接口
@FunctionalInterface interface A { public Person fLambada(String name, int age); default public void sayInt(int i) { System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class Person { private String name; private int age; public Person(String name, int age) { this.setName(name); this.setAge(age); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "姓名:" + name + "--->年龄:" + age; } } public class Test { public static void main(String[] args) { A a = Person::new;// 此处使用构造方法来实现抽象接口,注意传入参数和返回类型要与接口一致此处构造方法无返回,但jvm会解析为对应的对象 Person person = a.fLambada("senssic", 21);// 再也不用写那么多代码来实现工厂了 System.out.println(person.toString()); } }
3.lambda的访问范围
对于lambda的访问权限与匿名对象的方式非常类似,能够访问局部对应外部区域的final变量,以及成员变量和静态变量
3.1 访问局部变量
@FunctionalInterface interface A { public String fLambada(String str); default public void sayInt(int i) { System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class B { public void invoke(A a) { System.out.println(a.fLambada("senssic")); } public void partVab() { // 局部变量 // 可以省略final 也正确,编译器会隐式的认为是final修饰的 String name = "qiyu";// 或 final String name = "qiyu"; this.invoke(str -> { // name = "德玛西亚";// 此处报错,name不能被修改因为隐式为final的 return "hello " + str + "and " + name; }); } } public class Test { public static void main(String[] args) { B b = new B(); b.partVab(); } }
3.2 访问成员变量和静态变量
@FunctionalInterface interface A { public String fLambada(String str); default public void sayInt(int i) { System.out.println("sayInt--->" + i); } default public void sayString(String str) { System.out.println("sayInt--->" + str); } } class B { private String cname = "初始化的成员变量"; static private String sname = "初始化的静态变量"; public void invoke(A a) { System.out.println(a.fLambada("senssic")); } public void partVab() { // 局部变量 // 可以省略final 也正确,编译器会隐式的认为是final修饰的 String name = "qiyu";// 或 final String name = "qiyu"; // static String sr="";//方法内不能声明静态变量 this.invoke(str -> { // name = "德玛西亚";// 此处报错,name不能被修改因为隐式为final的 cname = "德玛西亚";// 可以访问有读写权限 sname = "皮城女警";// 可以访问有读写权限 return "hello " + str + "and " + name + "and " + cname + "and " + sname; }); } } public class Test { public static void main(String[] args) { B b = new B(); b.partVab(); } }
3.3 访问默认接口方法
不能被访问到,将出现编译错误
4.内置的函数式接口
JDK8中提供了很多的内置函数式接口,位于java.util.function包中
4.1Predicates
predicates是一个布尔类型的函数,该函数只有一个输入参数。predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and or negate)
import java.util.function.Predicate; public class Test { public static void main(String[] args) { Predicate<String> pre = str -> str.length() == 4; pre = pre.and(p -> p.contains("f")); System.out.println(pre.test("qiyu")); } }
4.2 Suppliers
Suppliers接口产生一个给定类型的结果,需要类有无参构造函数
import java.util.function.Supplier; 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() { // TODO Auto-generated method stub return "名字:" + name + "年龄:" + age; } } public class Test { public static void main(String[] args) { Supplier<Person> pSupplier = Person::new; Person person = pSupplier.get(); System.out.println(person.toString()); } }
4.3 Consumers
Consumer代表了在一个输入参数上需要进行的操作。Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
4.4 Comparators
Comparator接口在早期的Java 版本中非常著名。Java 8 为这个接口添加了不同的默认方法。Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
4.5 Optionals
Optional不是一个函数式接口,而是一个精巧的工具接口,用 NullPointerEception产生。这个概念在下一节会显得很重要,所以我们在这里快速地浏览一下Optional的工作原理。
Optional是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返
回一个non-null的值,也可能返回一个空值。为了不直接返回 null,我们在 Java 8中就返回一个Optional.
回一个non-null的值,也可能返回一个空值。为了不直接返回 null,我们在 Java 8中就返回一个Optional.
Optional<String> optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
5. Streams
java.util.Stream表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream 操作可以是中间操作,也可以是完结操作。完 结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你
可以通过多次调用 StringBuffer 的append方法一样)。Stream是在一个源的基础上创建出来的,例如java.util.Collection中的list或者 set
(map不能作为Stream的源)。Stream 操作往往可以通过顺序或者并行两种方式来执行。
import java.util.ArrayList; import java.util.List; import java.util.Optional; public class Test { public static void main(String[] args) { List<String> sList = new ArrayList<String>(); sList.add("a1"); sList.add("b2"); sList.add("ab"); sList.add("acc"); sList.add("bc"); sList.add("cf6"); sList.add("a7"); sList.add("17"); sList.add("236"); sList.add("589"); sList.add("5898955"); // Filter 流过滤,中间操作返回流本身 sList.stream().filter(str -> str.length() == 3) .filter(str -> str.contains("c")).forEach(System.out::println);// acc // cf6 // sorted 流排序,中间操作返回流本身 sList.stream().filter(str -> str.contains("c")) .sorted((str1, str2) -> { if (str1.length() == str2.length()) { return 0; } else if (str1.length() > str2.length()) { return 1; } else { return -1; } }).forEach(System.out::println);// bc acc cf6 // map 流转换对象,中间操作返回流本身 sList.stream().filter(str -> str.matches("\\d+")) .map(str -> Integer.parseInt(str)).forEach(System.out::println);// 12 // 236 // 589 // match 流匹配,终结操作 System.out.println(sList.stream().allMatch(str -> str.length() == 3));// false System.out.println(sList.stream().anyMatch(str -> str.length() > 5));// true // count 返回当前流数量,终结操作 long l = sList.stream().filter(str -> str.length() > 3).count(); System.out.println(l);// 1 // reduce 减少数量合并流对象,终结操作 Optional<String> reOptional = sList.stream() .filter(str -> str.length() == 3) .reduce((str, str2) -> str + "-->" + str2); reOptional.ifPresent(System.out::println);// acc-->cf6-->236-->589 // parallelStream 并行排序,效率更快 Optional<String> reOptiona = sList.parallelStream() .filter(str -> str.length() == 3) .reduce((str, str2) -> str + "-->" + str2); reOptiona.ifPresent(System.out::println);// acc-->cf6-->236-->589 } }
6.Time API
Java 8 包含了全新的时间日期API,这些功能都放在了java.time包下
Clock
Clock提供了对当前时间和日期的访问功能。Clock是对当前时区敏感的,并可用
System.currentTimeMillis() 方法来获取当前的毫秒时间。当前时间线上的时刻可以用 Instance类来表示。
Instance也能够用 java.util.Date对象。
Clock提供了对当前时间和日期的访问功能。Clock是对当前时区敏感的,并可用
System.currentTimeMillis() 方法来获取当前的毫秒时间。当前时间线上的时刻可以用 Instance类来表示。
Instance也能够用 java.util.Date对象。
Timezones
时区类可以用 ZoneId 来表示。时区类的对象可以通过静态工厂方法方便地获取。时区类还定义
了一个偏移量,用 来在当前时刻或某时间与目标时间进行转换
了一个偏移量,用 来在当前时刻或某时间与目标时间进行转换
LocalTime
本地时间类表示一个没有指定时区的时间,例如,10 p.m.或者17:30:15,下面的例子会用上面例
子定义的时区创建两个本地时间对象。然后我们会比较两个时间,并计算它们之间的小时和分钟的不同。
本地时间类表示一个没有指定时区的时间,例如,10 p.m.或者17:30:15,下面的例子会用上面例
子定义的时区创建两个本地时间对象。然后我们会比较两个时间,并计算它们之间的小时和分钟的不同。
LocalDate
本地时间表示了一个独一无二的时间,例如:2014-03-11。这个时间是不可变的,与LocalTime 是
同源的。下面的例子演示了如何通过加减日,月,年等指标来计算新的日期。记住,每一次操作都会返回
一个新的时间对象。
本地时间表示了一个独一无二的时间,例如:2014-03-11。这个时间是不可变的,与LocalTime 是
同源的。下面的例子演示了如何通过加减日,月,年等指标来计算新的日期。记住,每一次操作都会返回
一个新的时间对象。
LocalDateTime
LocalDateTime表示的是日期-时间。它将刚才介绍的日期对象和时间对象结合起来,形成了一个对
象实例。LocalDateTime是不可变的,与LocalTime和LocalDate的工作原理相同。我们可以通过调用
方法来获取日期时间对象中特定的数据域。
LocalDateTime表示的是日期-时间。它将刚才介绍的日期对象和时间对象结合起来,形成了一个对
象实例。LocalDateTime是不可变的,与LocalTime和LocalDate的工作原理相同。我们可以通过调用
方法来获取日期时间对象中特定的数据域。