详细的整理了接口和抽象类的区别。
抽象类和接口的区别
当我们遇到这样一个面试提问的时候首先应该考虑的是:
- 什么是抽象类,作用是什么
- 什么接口,作用是什么
抽象类及其作用
抽象类在我们实际开发当中扮演了一个什么样的角色?当我们在开发或者设计一些功能和属性大部分差不多的Activity或者是class的时候,为了避免大量重复的工作,最好的做法就是抽取一个公共的基类,这样做的目的即可以减少重复的代码,又让代码变得简洁,简单。
因此抽象类就是用于抽取,捕捉子类通用共性的一种类。只能用于作为父类,提供给子类继承并且不能被实例化,作为被用来创建继承层级的一种模板。也是多态特性的一种重要表现形式。
下面举个简单的例子帮助理解
class Demo3Abstract {
public static void main(String[] args) {
Zi z = new Zi();
z.print(); //直接调用子类中的print方法
z.method(); //也可以拿到从父类继承的method方法
Fu f= new Zi(); //父类指向子类对象。
f.print(); //编译看左边,运行看右边。
System.out.println(f.i); //out i0
Zi zx=(Zi)f; //向下转型
zx.print1111(); //才能拿到子类特有的方法。
System.out.println(zx.i); // out 20
}
}
abstract class Fu {
int i=10;
public abstract void print(); //抽象方法必须有子类重写后使用
public void method() { //非抽象方法子类可以直接继承用
System.out.println("Hello World!");
}
}
class Zi extends Fu {
int i=20;
public void print() {
System.out.println("Zi");
}
public void print1111(){
System.out.println("Zi111");
}
}
上述例子说明了,抽象类最重要的特点之一,就是可以Zi类拿到Fu提供的method方法,又拥有了自己的特性(print方法)。这样就大大减少了代码的冗余。但是有有一点需要注意的是当我们面向接口编程的时候,当重写了父类中的抽象方法的时候,编译时,找的父类的方法体,倒是实际上在运行过程中,使用的是子类的实现方法。这是一种动态绑定实现机制,也是Java语言的重要基石。
多态的弊端在于,当采用面向接口编程的过程中,当需要到子类的特性的时候,就必须向下转型,这样才能拿到子类的特性(特有的方法)。这其实也很好理解,因为在内存的存储中,Zi类继承Fu,在Zi类的内存中,有一部分是保存有Fu类的相关数据的地址值的,所以我们在才能对代码实现复用,但是面向接口编程的时候,是从Fu类的内存中去找值的,所以f.1
和zx.i
值完全是不一样,当我们想要拿到子类值的时候,只能向下转型才能拿到20,否则就只能拿到父类的成员变量里面的10。换句话说,只有父类和子类二者重写方法之间,存在动态绑定的过程,当时其他方面(比如说成员变量),是不存在动态绑定的,他们是有各自存有的区域。这点需要特别注意。
抽象类的一些特性
- 抽象类不能被实例化,但可以有构造函数
- 抽象方法必须由子类进行重写
- 只要包含一个抽象方法的类,就必须定义为抽象类,不管是否还包含其他方法
- 抽象类中可以包含具体的方法,也可以不包含抽象方法
- 抽象类可以包含普通成员变量,其访问类型可以任意
- 抽象类也可以包含静态成员变量,其访问类型可以任意
- 子类中的抽象方法不能与父类的抽象方法同名
- abstract不能与private、static、final或native并列修饰同一个方法
接口的及其作用
接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情
下面同样是用一个例子来说明
class Demo1Interface {
public static void main(String[] args) {
Inter i = new Demo();
i.print();
}
}
interface Inter {
public abstract void print();
public static final int num = 10; //接口中所有的变量都是常量
public abstract void print11(); //接口中所有的方法都是抽象的
/*public void method() { //错误: 接口方法不能带有主体
System.out.println("aaa"); //接口中所有的方法都是抽象的
}*/
}
class Demo implements Inter {
public void print() {
System.out.println("print");
}
}
正如上面提到的一样,接口之中的方法,所有都是抽象方法并且修饰符只能是public,因为接口本身就是提供的一种规范和约束。当你实现一个接口的时候就必须实现里面所有的方法。这里需要注意的仍然是上述的多态的弊端,再次就不再赘述了。并且不能定义变量,只能定义常量。
接口的一些特性
- 接口中不能有构造方法。
- 接口的所有方法自动被声明为public,而且只能为
public
,如果使用protected
、private
,会导致编译错误。 - 接口可以定义”成员变量”,而且会自动转为
public final static
,即常量,而且必须被显式初始化。 - 接口中的所有方法都是抽象方法,不能包含实现的方法,也不能包含静态方法
- 实现接口的非抽象类必须实现接口的所有方法,而抽象类不需要
- 不能使用
new
来实现化接口,但可以声明一个接口变量,它必须引用一个实现该接口的类的对象,可以使用instanceOf来判断一个类是否实现了某个接口,如if (object instanceOf ClassName){doSth()}
; - 在实现多接口的时候一定要注意方法名的重复
抽象类和接口的区别
有了上述的知识储备,我想我们终于可以来回答一下这二者之间的区别了。
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
关键字 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 |
多继承 | 抽象类只可以继承一个类和实现多个接口 | 接口和接口之间是可以多继承或者单继承多实现的。 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
设计理念 | is-a的关系,体现的是一种关系的延续 | like-a体现的是一种功能的扩展关系 |
具体使用的场景
- 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
- 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
- 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
- 多用组合,少用继承。