一、接口与抽象类的设计理念
不同点的意义:抽象类是对对象的抽象;接口是实现功能的封装。
其本身的设计目的就是不同的。下面引用至阿里新零售事业群CBU技术部招Java高级&专家,感觉说的很好。
大家讲的都很详细了,我说说我自己的一点浅薄的理解。
我一直认为,工科的知识有个很明显的特点:“以用为本”。在讨论接口和抽象类的区别时,我也想从“用”的角度试着总结一下区别,所以我想到了设计目的。
接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。对“接口为何是约束”的理解,我觉得配合泛型食用效果更佳。
而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。
二、extends与implements的区别
extends(继承)是针对抽象类,implements(实现)是针对接口类。Java只能单继承,接口弥补单继承的不足。
从Java编程思想来说,继承解决的是代码复用以及对象的关系,接口解决的是解耦,这是两种不同的理念。
1、在类的声明中,通过关键字extends来创建一个类的子类。
一个类通过关键字 implements 声明自己使用一个或者多个接口。
extends 是继承某个类,继承之后可以使用父类的方法,也可以重写父类的方法。
implements 是实现多个接口,接口的方法一般为空的,必须重写才能使用
2、extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承
Java中不支持多重继承,但是可以用接口来实现,这样就要用到 implements,继承只能继承一个类,但 implements 可以实现多个接口,用逗号分开就行了。比如 :class A extends B implements C,D,E
3、接口实现的注意点:
(1)实现一个接口就是要实现该接口的所有的方法(抽象类除外)
(2)接口中的方法都是抽象的
(3)多个无关的类可以实现同一个接口,一个类可以实现多个无关的接口
4、与Extends的不同
extends 可以实现父类,也可以调用父类初始化 this.parent(),而且会覆盖父类定义的变量或者函数。这样的好处是:架构师定义好接口,让工程师实现就可以了,整个项目开发效率和开发成本大大降低。
implements 实现父类,子类不可以覆盖父类的方法或者变量。即使子类定义与父类相同的变量或者函数,也会被父类取代掉。
这两种实现的具体使用,是要看项目的实际情况,需要实现,不可以修改implements,只定义接口需要具体实现,或者可以被修改扩展性好,用extends。
三、extends的向上转型
父类与子类继承关系上的不同:A a = new B(); 结果a是一个A类的实例,只能访问A中的方法,那么又和A a = new A();有什么区别呢?
class B extends A,继承过后通常会在 B 类上定义一些父类 A 没有的成员或者方法。
A a = new B(); 这样是可以的,向上转型。
a 是一个父类对象的实例,因而不能访问子类定义的新成员或方法。
假如这样定义:
class A {
int i;
void f(){}
}
class B extends A {
int j;
void f(){} //重写
void g(){}
}
然后:B b = new B(); 那么 b 就是子类对象的实例,不仅能够访问自己的属性和方法,也能够访问父类的属性和方法。诸如b.i,b.j,b.f(),b.g()都是合法的。此时b.f() 是访问的B中的 f()。
A a = new B(); a 虽然是用的B的构造函数,但经过upcast,成为父类对象的实例,不能访问子类的属性和方法。a.i,a.f()是合法的,而a.j,a.g()非法。此时访问a.f()是访问B中的f()。
四、向上转型的过程
A a = new B(); 这条语句,实际上有三个过程:
1、A a;
将 a 声明为父类对象,只是一个引用,未分配空间
2、B temp = new B();
通过B类的构造函数建立了一个B类对象的实例,也就是初始化
3、a = (A)temp;
将子类对象 temp 转换为父类对象,并赋给a,这就是上转(upcast),是安全的。
经过以上3个过程,a就彻底成为了一个A类的实例。
子类往往比父类有更多的属性和方法,上转只是舍弃,是安全的;而下转(downcast)有时会增加,通常是不安全的。
4、需要注意的是:
a.f() 对应的应该是B类的方法 f(),因为调用构造函数建立实例过后,对应方法的入口已经确定了。
如此一来,a 虽被上转为 A类,但其中重写的方法f()仍然是B的方法f()。也就是说,每个对象知道自己应该调用哪个方法。
A a1 = new B();
A a2 = new C();
a1,a2两个虽然都是A类对象,但各自的 f() 不同,这正是多态性的体现。