前几天电脑上的JDK自动更新到1.8,这两天了解了一下JDK1.8的一些新特性。下面就来浅谈一下我所了解的。
我们都知道,Java是不能多继承的,但是可以多实现。它与C++不同,C++是可以多继承的。尽管Java可以实现多个接口,但是接口中只能有抽象方法,不能有具体的实现。但是在JDK1.8中,接口里新增了默认方法。可以对默认方法进行具体的实现,但是这个方法必须是default。看下面这段代码:
1 interface AA{ 2 public int add(int a,int b); //申明方法 3 default void sing(String name){ //默认方法 4 System.out.println("The song name is " + name); 5 } 6 } 7 8 public class CC implements AA{ 9 public static void main(String[] args){ 10 AA aa = new AA(){ //匿名内部类 11 public int add(int a,int b){ 12 return (a + b); 13 } 14 }; 15 aa.sing("The Bird And The Bee"); //方法调用 16 System.out.println(aa.add(5,3)); 17 } 18 }
在接口AA中,我们有两个方法,并且有一个默认方法。在main函数中,我们对AA中的add方法进行实现,之后调用AA中的两个方法,这段代码是正确的。现在来想一个问题,既然现在接口中可以有方法的实现,而且接口又是可以多继承的,那么如果两个接口中有同样的方法签名的两个方法,那么在实现这两个接口的实现类中,会有冲突吗?
接口C继承了接口A和B,而在A,B中有相同方法签名的函数。注:函数的方法签名包括:函数名,参数个数,参数类型以及顺序,不包括返回值。代码如下:
1 interface A1{ 2 default int add(int a,int b){ 3 return (a + b); 4 } 5 default void say(){ 6 System.out.println("Hello,I am A1"); 7 } 8 } 9 10 interface B1{ 11 default int subtraction(int a,int b){ 12 return (a - b); 13 } 14 default void say(){ 15 System.out.println("Hello,I am B1"); 16 } 17 } 18 19 interface C1 extends A1,B1{}
上面这段代码在编译时候会出现错误。
之后我们稍稍修改一下我们的接口C1,让它覆盖父接口的say()方法。并完善我们代码:
1 interface A1{ 2 default int add(int a,int b){ 3 return (a + b); 4 } 5 default void say(){ 6 System.out.println("Hello,I am A1"); 7 } 8 } 9 10 interface B1{ 11 default int subtraction(int a,int b){ 12 return (a - b); 13 } 14 default void say(){ 15 System.out.println("Hello,I am B1"); 16 } 17 } 18 19 interface C1 extends A1,B1{ 20 @Override 21 default void say(){ 22 //B1.super.say(); 指定调用父接口中的say() 23 System.out.println("Hello,I am C1"); //有自己的实现 24 } 25 } 26 27 public class Test implements C1{ 28 public static void main(String[] args){ 29 Test t = new Test(); 30 t.say(); 31 System.out.println(t.add(3,5)); 32 System.out.println(t.subtraction(5,2)); 33 } 34 }
我们使用覆盖父类的方式来避免上面错出现的冲突。在覆盖方法中,我们可以有自己的实现,也可以指定实现某个父接口中的方法。好,解决了这个问题,我们来继续思考另外一个问题,如果一个子类继承一个基类并实现了一个接口,并且不幸的事,基类中有一个方法是接口中的默认方法,此时又会出现冲突吗?
代码如下:
1 interface A1{ 2 default int add(int a,int b){ 3 System.out.println("add() in interface A1 ---> "); 4 return (a + b); 5 } 6 default void say(){ 7 System.out.println("Hello,I am interface A1"); 8 } 9 } 10 11 class A2{ 12 public int add(int a,int b){ 13 System.out.print("add() in class A2 ---> "); 14 return (a + b); 15 } 16 public void say(){ 17 System.out.println("Hello,I am class A2"); 18 } 19 } 20 21 public class Test extends A2 implements A1{ 22 public static void main(String[] args){ 23 Test t = new Test(); 24 t.say(); 25 System.out.println(t.add(3,5)); 26 } 27 }
运行结果如下:
从结果可以看出,当基类和接口中的默认方法具有相同方法签名的函数时,子类优先继承基类中的函数实现,而不是父接口中的默认方法。如果父类没有相同的方法签名,子类才会继承接口中的默认方法。说了这么多关于默认方法,想一想JDK1.8为什么要新增加这一特性呢?由于Collection库需要为批处理操作添加新的方法,如forEach(),stream()等,但是不能修改现有的Collection接口——如果那样做的话所有的实现类都要进行修改,包括很多客户自制的实现类。拿forEach()方法来说,以前我们遍历一个集合的时候,都是自己写遍历操作的代码,相信下面这段代码我们都是很熟悉的:
1 import java.util.*; 2 3 public class Newlist{ 4 public static void main(String[] args){ 5 List<String> strList = new ArrayList<String>(); 6 strList.add("123"); 7 strList.add("234"); 8 strList.add("345"); 9 strList.add("456"); 10 strList.add("567"); 11 12 for(String list : strList){ 13 System.out.println(list); 14 } 15 } 16 }
但是现在不必要这么麻烦了,我们可以直接使用现成的方法进行遍历。使用下面这行代码,代替上面的for循环,即可。
list.forEach(o->{System.out.println(o);});
forEach()方法在List接口中就是一个默认方法,所以所有实现了这个接口,继承这接口的类或接口都具有这个方法,这就是新增默认方法的原因。
这行代码又引出了一个新特性-->lambda表达式。lambda表达式就是一个匿名函数,主要用于替换以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。Java8有一个短期目标和一个长期目标。短期目标是:配合“集合类批处理操作”的内部迭代和并行处理(上面将已经讲到);长期目标是将Java向函数式编程语言这个方向引导(并不是要完全变成一门函数式编程语言,只是让它有更多的函数式编程语言的特性,关于函数式编程语言和命令式编程语言请参看这篇博客http://blog.jobbole.com/42178/),也正是由于这个原因,Oracle并没有简单地使用内部类去实现λ表达式,而是使用了一种更动态、更灵活、易于将来扩展和改变的策略(invokedynamic)。下面举个简单例子了解lambda表达式:
1 interface A{ //函数接口 2 public int add(int a,int b); 3 boolean equals(Object obj); 4 default void say(){ 5 System.out.println("Hello,I am A"); 6 } 7 } 8 public class Lambda implements A{ 9 public static void main(String[] args){ 10 /* A a = new A(){ 匿名内部类实现add() 11 public int add(int a,int b){ 12 return (a+b); 13 } 14 }; */ 15 A a = (x,y)->{return (x+y);}; 16 a.say(); 17 System.out.println(a.add(5,3)); 18 } 19 }
在上面的main中我们使用了lambda表示式对接口A中add()方法进行了实现,上面注释是我们不使用表达式而是使用匿名内部类去实现接口中的非默认方法。可以看出,λ表达式是由(参数)、->、{}三部分构成的。左边是接口定义方法的参数,可以省略参数类型,并且只有当方法仅唯一参数时,小括号才能省略,没有参数应该写成()。表达式右边是代码块,也就是我们平时书写函数体中的内容。请注意:能够使用λ表达式的目标类型必须是函数接口,函数接口的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。说的简单一点就是在一个接口中只有一个显示申明的函数。在上面的例子中,我们的接口A就是函数接口,因为只有一个显示申明的add()。你可能疑惑,那equals方法呢?equals方法是Object的,所有的接口都会声明Object的public方法——它是隐式的。下面举一个线程的例子:
1 public class Lambda{ 2 public static void main(String[] args){ 3 Thread tt = new Thread(new Runnable(){ //一般做法 4 public void run(){ 5 for(int i=0;i<100;i++){ 6 System.out.println("normal function!"); 7 } 8 } 9 }); 10 11 Thread tt = new Thread(()->{ //使用lambda表示式 12 for(int i=0;i<100;i++){ 13 System.out.println("lambda expression!"); 14 } 15 }); 16 tt.start(); 17 } 18 }
JDK1.8有很多新特性,我列出的只是很小一部分,作为Java语言忠实的粉丝,感觉学习Java越来越有趣。
参考博客:http://blog.csdn.net/ioriogami/article/details/12782141