接口是Java中最为抽象的定义
查看jdk源码可以发现其很多类,最顶层总是接口,下一层则是抽象类,底层才是具体实现类。
体现简单的设计思想是:
接口是一种规范,是在设计代码时设计出其主体功能,就像对于集合来说,不论是ArrayList,还是LinkedList都有其其通用的功能,添加,删除,修改等,这些在设计接口的时候都设计好了。
关于抽象类,就是把能够通用的功能做实现,不能通用的功能继续抽象,到其实体自类中根据实体类的特征去做实现,
就比如有一个接口I,它里面有两个抽象方法add(),put(),其有两个实体类A,B继承了该接口,做了实现,然后你发现在实体类中A,B代码中add()是一样的,但put()是不一样的,考虑代码设计不要冗余和做重复的工作,那你是不是应该让add()代码能够只写一遍,但是对于put()方法还必须分开写,这时候你还不能在接口中写,是不是需要抽象类了。
接口设计主体功能,抽象类继承接口选择实现相同的功能,具体实现类继承抽象类实现不同的功能。
接口不能被实例化,于class的区别:1、没有属性 2、没有构造方法 3、不能有任何实现方法 4、类可以通过implement实现接口中的抽象方法(一个类可以有多个实现)
interface和package
在Java封装体系中 包(package),其跨包之间的访问只能访问public的内容。而接口中所有方法都是public,所以interface就是package对外开放的“接口”。
接口中的成员都为 静态(static)、final以及公开(public):为什么是这样?
知识点1:一个类,首先他会在内存里面有一个类对象,然后由类对象生成类的对象。 (类对象是类加载时生成的,存在于方法区)
知识点2:为什么接口Interface里面的值必须是常量呢? 因为类可以被实例化,实例化的类的对象里面的变量就会被赋初始值。比如String 是 null int是0,double是0.0。但是接口呢?接口不能被实例化,所以接口里面如果是变量的话不会被赋初始值这样就会出问题。所以接口里面的值必须是常量final而且一定是static不管写不写都是。
知识点3:那为什么它要是静态的呢?因为static是什么?是所有对象可以访问,而且可以直接通过类名访问。接口有对象么?显然没有,必须通过类名来访问所以是要静态的。
接口的用法
在Java中,接口的用法非常丰富:可以嵌套包含其他接口、类、枚举和注解(枚举和注解将在枚举和注解的使用中介绍)以及常量,如下:
package com.javacodegeeks.advanced.design;
public interface InterfaceWithDefinitions {
String CONSTANT = "CONSTANT";
enum InnerEnum {
E1, E2;
}
class InnerClass {
}
interface InnerInterface {
void performInnerAction();
}
void performAction();
}
针对上面的复杂场景,Java编译器强制为嵌套的类对象构造和方法声明提供了一组隐式的要求。首当其冲的便是接口中的每个声明必须是public(即便不指定也是public,并且不能设置为非public,详细规则可参考可见性部分介绍)。所以下面代码中的用法与上面看到的声明是等价的:
public void performAction();
void performAction();
另外,接口中定义的每个方法都被默认声明为abstract的,所以下面的声明都是等价的:
public abstract void performAction();
public void performAction();
void performAction();
对于常量字段,除了隐式的public
外,也被加上了static
和final
修饰,所以下面的声明也是等价的:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
对于嵌套的类、接口或枚举的定义,也隐式的声明为static
的,所以下面的声明也是等价的:
class InnerClass {
}
static class InnerClass {
}
根据个人偏好可以使用任意的声明风格,不过了解上面的约定倒是可以减少一些不必要的代码编写。
标记性接口
标记性接口是接口的一种特殊形式:即没有任何方法或其他嵌套定义。
一个广泛使用的标记性接口是Serializable
:
public interface Serializable {
}
这个接口声明类可以被序列化或反序列化,同样它并未指定序列化过程中使用的方法。
尽管标记性接口并不满足接口作为契约的主要用途,不过在面向对象设计过程种仍然有一定的用武之地。
函数式接口,默认方法及静态方法
伴随着Java 8的发布,接口被赋予了新的能力:静态方法、默认方法以及从lambda表达式的自动转换(函数式接口)。
在上面的接口部分,我们强调过在Java中接口只能作为声明但不能提供任何实现。但默认方法打破了这一原则:在接口中可以为default
标记的方法提供实现,如下:
package com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
void performAction();
default void performDefaulAction() {
// Implementation here
}
}
从对象实例层次看,默认方法可被任何的接口实现者重载;除此之外,接口还提供了另外的静态方法,如下:
package com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
static void createAction() {
// Implementation here
}
}
也许你会认为在接口中提供实现违背了基于契约的开发过程,不也你也可以列出很多Java把这些特性引入其中的理由。不管是带来了帮助还是困扰,它们已然存在,你也可以使用它们。
函数式接口有着不同的场景,并被认为是对编程语言的一种强大的扩展。本质上,函数式接口也是接口,不过包含一个抽象的方法声明。Java 标准库中的Runnable
接口就是这种理念的绝佳范例:
@FunctionalInterface
public interface Runnable {
void run();
}
Java 编译器在处理函数式接口时有所不同,并能把lamdba表达式转化为函数式接口的实现。我们先看一下下面方法的定义:
public void runMe( final Runnable r ) {
r.run();
}
在Java 7及以前的版本中,必须要提供Runnable
接口的具体实现(例如使用匿名类),但在Java 8中却可以通过传递lambda表达式来运行run()
方法:
runMe( () -> System.out.println( "Run!" ) );
最后,可以使用@FunctionalInterfact
注解(注解会在枚举和注解的使用章节进行详细介绍)告知编译器以在编译阶段验证函数式接口中仅包含了一个抽象方法声明,从而保证未来任何变更的引入不会破坏该接口的函数式特性。