1),接口是一种规范,就是告诉外界这个东东可以做什么。
2),抽象类是一种模板,就是告诉外界这个东西的一部分公共功能。
3),类和上面的接口,抽象类最大的区别就是类可以维护状态信息。
- 在以前的接口中
2,接口只能继承接口,不能继承类
3,接口里没有普通方法,方法全是抽象的
4,接口里的方法默认修饰符是public abstract
5,接口里的字段全是全局常量,默认修饰符是public static final
java8中的接口在原来的基础上增加了一些功能,主要是2个:默认方法,静态方法。
- 1,默认方法
for(int i = 0; i<list.size();i++) { System.out.println(list.get(i)); }
现在我们使用函数函数式编程,代码如下:
public static void main(String[] args) { List<Integer> list = Lists.newArrayList(1, 2, 3); Stream<Integer> stream = list.stream(); stream.forEach(System.out::println); }现在假设我们自己不用流,我们想直接在list集合上面提供一个forEach方法,用来将一个函数应用到集合的每一个元素上面。下面是使用这种方式编写的代码:
list.forEach(System.out::println);上面的效果很好,如果我们自己定义也就是说重新设计集合当然是没有问题的,但是java的集合是许多年前设计的,这就会带来一个问题,我们为List接口增加了一个方法,那么所有的实现类都要改过,这样子代码才不会报错,这基本是无法想象的。所以呢,java设计者希望通过允许接口包含具体实现的方法来一劳永逸的解决这个问题。当然,为接口提供默认方法还有别的好处,我们一会会整理到。
java8中允许在接口中定义默认方法,默认方法必须使用default来修饰。先来看一个例子,熟悉下java8中默认方法的语法:
public class Test implements TestInterface { public static void main(String[] args) { Test test = new Test(); //使用自己类中的方法 System.out.println(test.getId()); //使用接口中的默认方法 System.out.println(test.getName()); } @Override public Integer getId() { return 25; } } interface TestInterface { Integer getId(); default String getName() { return "默认方法里面的输出。。。"; } }
上面的代码使用了TestInterface接口中定义的默认方法getName(),程序执行没一点问题。当然我们也可以自己来重写接口中默认方法中的默认方法。
public class Test implements TestInterface { public static void main(String[] args) { Test test = new Test(); //使用自己类中的方法 System.out.println(test.getId()); //使用接口中的默认方法 System.out.println(test.getName()); } @Override public Integer getId() { return 25; } @Override public String getName() { //默认使用接口中的默认方法,注意下面的使用父类接口中方法的语法 //return TestInterface.super.getName(); return "自己的类中重写了接口中的默认方法"; } } interface TestInterface { Integer getId(); default String getName() { return "默认方法里面的输出。。。"; } }在上面的使用过程中,我们发现了现在使用java8接口的默认方法很好的解决了前面我说的问题了,我们不必在每个实现类中都去实现接口中新加的方法了,只需要在接口中增加一个default修饰的默认方法就好了。
另外,在上面的编码过程中,我们不难还能发现一个使用默认方法带来的好处,我们现在可以很方便的在接口中指定本质上可选的方法,根据接口的使用方式来选择使用的方法。举个例子吧,比如我们定义了一个接口,里面有一个删除元素的方法remove(),但是实际情况是这个接口应该支持可修改和不可修改2种系列,在可修改系列中没有问题,我们直接实现remove()方法就好了,不可修改系列我们不需要这个方法,但是也只能定义一个remove()的空实现,最好抛出一个异常。现在我们使用java8中的默认方法,就避免了用于不可修改系列的类也必须定义自己的,占位符性质的多余的方法,总结一下就是说默认方法让一些类要去实现的方法变成了可选方法。
- 总结一下:
1,优雅的随时间演化接口。也就是代码的向后兼容性
2,提供可选功能,类不必在不需要该功能的时候提供占位符实现。
- 2,多级继承的问题
不慌的,解决上面的问题很简单,就是定义一组规则就好了,按照一个约定来定义解决上面的冲突。具体的规则如下:
1),在所有的情况,类实现的优先级高于接口的默认实现,也就是先使用自己类中定义的方法或者是父类中的方法。类优先是解决上面系列问题的最主要的规则。
2),当类同时实现了2个接口,2个接口中如果包含相同的默认方法,那么这个类中就必须重写这个接口,不然代码报错。
3),如果是一个接口继承了另外一个接口,2个接口中也包含相同的默认方法,那么继承接口的版本具有更高的优先级。比如A扩展了B接口,那么优先使用A类里面的test方法。
4),通过使用super,可以显式的引用被继承接口的默认实现,语法如下:InterfaceName.super.methodName()。
相关代码如下:
public class Test extends C { public static void main(String[] args) { Test test = new Test(); System.out.println(test.getName()); } } class C implements A, B { @Override public Integer getId() { // TODO Auto-generated method stub return null; } //不管接口A和接口B中的这个方法是不是默认方法,有没有提供实现都必须重写, 不然代码报错 @Override public String getName() { System.out.println(A.super.getName()); return "自己类中必须重写这个方法。。。"; } } interface A { Integer getId(); default String getName() { return "接口A。。。"; } } interface B { default String getName() { return "接口B。。。"; } }
- 3,在接口中使用静态方法
接口名字.静态方法名。
public class Test implements A { public static void main(String[] args) { System.out.println(A.getName()); } } interface A { static String getName() { return "接口A。。。"; } }
注意,实现接口的类或者子接口不会继承接口中的静态方法。static不能和default同时使用。在java8中很多接口中都增加了静态方法,比如下面代码:
public class Test { public static void test(List<String> list) { //直接使用Comparator的静态方法 list.sort(Comparator.comparing(String::length)); } public static void main(String[] args) { List<String> list = Lists.newArrayList("122","2","32"); test(list); for (String str : list) { System.out.println(str); } } }