目录
为什么需要接口?
和普通类相比,抽象类最大的优势可以实现对子类重写方法的控制,即可以强制子类重写某一个方法。
但是,在抽象类中,依然会保留一些普通方法,而普通方法中会涉及到一些安全以及隐私操作的问题,这样一来,数据的安全性便无法得到保证,因为至少对于抽象类而言,还是可以使用set()方法来修改一个类的成员变量。
那么,若是在开发过程中对外隐藏全部的细节,将私有成员实现真正的私有,那么便可以使用接口来进行描述。
可以将接口理解为一个抽象层级比抽象类更高的存在,可以更加规范地对子类进行约束,全面实现规范和实现的分离。
怎么定义接口?
接口的定义,和类的定义很相似,只是接口定义时使用的是interface关键字。
例如这样便定义了一个内容为空的接口:
public interface Demo {
}
定义接口时需要注意些什么?
- 接口是一个十分纯粹的抽象类,因此,在接口中,使用public对一个方法进行修饰时,便不用再加上abstract关键字,因为在接口中会默认一个public权限的方法是抽象的,此时再使用abstract关键字进行修饰便冗余了。当然了,在接口中不加任何权限修饰时定义的抽象方法默认为public权限,因为接口是一个公共的规范。
- 在接口中,可以定义:
- 抽象方法。只能使用public定义抽象方法,此时不用加上abstract关键字,默认是一个抽象方法。
- 全局变量。包括静态的全局变量和非静态的全局变量,但是必须要在定义时进行初始化。
- 普通方法。只能使用default关键字和private关键字对普通方法进行修饰。但是在接口中禁止使用set()和get()方法,因此外部类是无法访问接口内的成员变量的(因为接口不允许被实例化,因此接口中的成员变量无法被直接访问,并且由于接口禁止get()方法,因此接口中的成员变量也无法通过get()方法间接访问)。这样一来,接口内部的成员变量便只能在接口内部使用,确保了隐私数据的安全性。当然了,普通方法包括了普通的静态方法和普通的动态方法。
- 常量。包括静态常量和非静态的常量。
下面来看一个接口定义的例子:
public interface Demo {
public static final String var1 = "111";//这是一个全局的静态常量
public final String var2="222";//这是一个全局的非静态常量
public static int var3=0;//这是一个全局的静态变量(实际上会自动使用final关键字转换为常量)
public int var4=0;//这是一个全局的非静态变量(实际上会自动使用final关键字转换为常量)
public abstract String method1();//这是一个使用abstract关键字进行修饰的抽象方法
public String method2();//这是一个没有使用abstract关键字进行修饰但是默认是abstract的抽象方法
//这是一个使用default修饰的非静态的普通方法
default void method3(){
System.out.println("method3");
method4();
}
//这是一个使用private修饰的非静态普通方法,只能在接口内部出现而不能在接口外部出现
private void method4(){
System.out.println("method4");
}
//这是一个使用private修饰的静态普通方法
private static void method5(){
System.out.println("method5");
}
//这是一个使用public修饰的静态普通方法
public static void method6(){
System.out.println("method6");
}
}
tip:实际上,接口中定义的无论是静态变量、非静态变量,最后都会自动的转化为public static final修饰的常量,这也是为什么即使是非静态变量也要在定义时就进行赋值(因为通常情况下非静态变量是不用在定义时人为进行赋值的)。那么,既然是常量,便不可以被修改,即使在类内部也无法被重新赋值,这就保证了数据的安全性。
实现接口时需要注意些什么?
- 接口不能直接产生实例化对象,因此接口是需要被实现的,因而接口需要子类来实现。
- 如果一个子类是一个普通类,那么这个子类需要实现接口中的所有抽象方法,但是可以自由选择是否重写接口中的普通方法。如果一个子类是一个抽象类,那么这个子类可以自由选择是否实现接口中的抽象方法,也可以自由选择是否重写接口中的普通方法。
- 一个子类可以继承一个类但可以实现多个接口,子类对接口的实现使用implements关键字进行。
- 接口虽然不能进行实例化,但是可以通过子类对象的向上转型来实现。(因为接口中存在的抽象方法在子类中必定会被实现,因此完全可以使用多态进行,但是需要注意的是,此时使用多态时,接口中的静态方法和私有方法是不能通过子类对象调用的,因为子类只能实现接口中所有的抽象方法以及继承接口中的普通方法。)
下面来看一个接口实现的例子:
//因为子类可以继承多个接口,这边用实现来和单继承进行区别,因此使用的是implements
public class DemoImpl implements Demo {
//实现了接口中的method1()方法
@Override
public String method1() {
return null;
}
//实现了接口中的method2()方法
@Override
public String method2() {
return null;
}
//重写了接口中的method3()方法,当然也可以选择不重写
@Override
public void method3() {
System.out.println("重写后的method3");
}
}
tip:当子类是普通类时,接口中的抽象方法是一定要被实现的,但是接口中的普通方法是可以由子类自由选择是否重写,并且若是一个接口允许子类重写自己的方法,那么只能在接口中将该方法使用default关键字定义为非静态方法,只有这样子类才有可能重写接口中的普通方法。其余的方法:含有private或static关键字修饰的方法便不允许被子类重写。
接口的使用
- 由于接口不存在构造器,因此接口是不能直接进行实例化的。
- 可以通过多态的方式来创建接口的对象(但实际上创建的还是子类的对象)
- 因为接口不存在构造器,因此接口也不会直接或间接地继承Object类。
- Object类不可以直接实例化接口,但是可以存放接口对象,因为两者存放的实际上都是子类对象在堆中的地址(引用数据类型),因此完全可以使用Object类进行接收。
下面来看一个接口使用的例子:
public class Test {
public static void main(String[] args) {
DemoImpl obj1 = new DemoImpl(); //最简单的实例化方式
Demo obj2 =new DemoImpl(); //使用多态进行的实例化方式1,利用接口类接收实例化对象
Object obj3=obj2; //虽然接口不是Object类的子类,接口接收的对象也是一个引用数据类型,那么该对象同样可以使用Object类进行接收
Demo obj4= (Demo) obj3; //此时可以将之前保存在Object类中的引用数据类型的对象还给接口类
obj1.method1();
obj1.method2();
obj1.method3(); //唯独这个方法是非静态非私有的普通方法,虽然在接口中但是可以被直接调用
obj2.method1();
obj2.method2();
obj2.method3(); //唯独这个方法是非静态非私有的普通方法,虽然在接口中但是可以被直接调用
obj4.method1();
obj4.method2();
obj4.method3(); //唯独这个方法是非静态非私有的普通方法,虽然在接口中但是可以被直接调用
}
}
tip1:Object类的对象可以接收所有的数据类型,包括:基本数据类型、类的对象、接口的对象(实际上还是类的对象)、数组。
tip2:如果,想要获取一个接口的内部成员变量的内容,是否可以直接获取呢?答案是可以的。接口对数据的保护只存在于将全部的成员变量封装为常量,但是并没有阻止在外部类中对接口的成员进行访问,下面便是一个直接获取接口常量成员的例子(变量成员在默认常量化后也变成了常量成员):
public class Test {
public static void main(String[] args) {
String var1 = Demo.var1;
String var2 = Demo.var2;
int var3 = Demo.var3;
int var4 = Demo.var4;
System.out.println(var1);
System.out.println(var2);
System.out.println(var3);
System.out.println(var4);
}
}
这样做是可以获取接口的常量成员的,但是由于是常量成员,那便只能获取而无法直接修改。
接口的多实现
一个类不能多继承多个类,但是一个类可以多实现多个接口,这便是接口的多实现。
下面来看一个多实现的例子:
//此时还存在一个Demo1接口
public interface Demo1 {
void demo1Method1();
}
//一个类同时实现了Demo接口和Demo1接口的抽象方法
public class DemoImpl implements Demo,Demo1 {
//实现了Demo接口中的method1()方法
@Override
public String method1() {
return null;
}
//实现了Demo接口中的method2()方法
@Override
public String method2() {
return null;
}
//实现了Demo1接口中的demo1Method1()方法
@Override
public void demo1Method1() {
}
}
接口的继承
接口是可以继承的,并且接口在继承时存在一定的继承规则:
接口在继承时,只能继承接口而不能继承类,继承接口时可以一次继承多个接口,这就是接口的多继承,多继承时仍然使用extends关键字。
一个类可以同时继承一个抽象类和若干接口,顺序是先继承再实现。但是,这一条规格看一眼,直到可以这么做便足矣,因为这样继承是毫无意义的。
当一个接口多继承了其他接口时,那么在子类实现该接口时,需要将该接口所继承的其他接口的抽象方法一并实现。
下面来看一个多继承的例子:
//此时Demo接口没变,但是Demo1接口继承了Demo接口
public interface Demo1 extends Demo{
void demo1Method1();
}
//此时虽然只实现了Demo1接口,但是由于Demo1接口继承了Demo接口,因此需要将Demo接口的抽象方法一并实现
//当然了,由于存在继承关系,因此Demo接口中的普通方法在此也是可以被重写的
public class DemoImpl implements Demo1 {
//实现了Demo接口中的method1()方法
@Override
public String method1() {
return null;
}
//实现了Demo接口中的method2()方法
@Override
public String method2() {
return null;
}
//实现了Demo1接口中的demo1Method1()方法
@Override
public void demo1Method1() {
}
}
当接口和抽象类都可以使用时,优先考虑使用接口。一方面是接口的抽象程度更高,另一方面是接口的安全性比抽象类来得高。