接口是常量和抽象方法的集合,是所有实现接口的子类的“协议”和“规范”。
既然接口是常量和抽象方法的集合,那么咱们先来看看抽象类及其抽象方法和非抽象方法。下面来看Demo1。
Demo1:Animal类的常规类
无测试类
package com.shenqi.xiaobaiyang;
/*
* (父类Animal中描述动物类型的变量type
和 一个输出动物叫声信息的方法sound()。
*/
public class Animal {
private String type;
public Animal(String type){
this.type = type;
}
public String toString(){
/*
* toString()方法
* 在Object类里面定义toString()方法的时候返回的对象
的哈希code码(对象地址字符串)。
可以通过重写toString()方法来表示出对象的属性。
*/
return "这是一只" + type;
}
public void sound(){
System.out.println("不知道是什么声音...");
}
}
package com.shenqi.xiaobaiyang;
public class Cat extends Animal {
public Cat(String type){
super(type);//对父类的构造方法进行初始化
}
public void sound(){
System.out.println("喵 喵 喵...");
}
}
package com.shenqi.xiaobaiyang;
public class Duck extends Animal {
public Duck(String type) {
super(type);
}
public void sound(){
System.out.println("嘎 嘎 嘎...");
}
}
分析:看父类Animal类中的sound()方法,发现每个子类都重写了父类的Animal中sound()方法,即各子类都没有采用父类该方法的具体实现。
实际上,一个抽象的动物是没有具体的叫声的,也就是说对于Animal类,给出的sound()方法的具体实现本身是没有任何意义的。父类的方法只是充当了一个“占位符”的作用,其作用在于确保子类一定拥有一个sound()方法。【说白了,就是只是为子类提供了一个框架,而把方法的具体实现下放到每个具体子类中】。
Demo2:Animal类的抽象类
Animal类的抽象类与Animal类的常规类:之所以可以使用抽象类来替代常规类,是因为事实上,一个抽象的东西是没有具体的叫声的。在常规类中,Animal类,给出的sound()方法的具体实现本身是没有任何意义的。父类的方法只是充当了一个“占位符”的作用,其作用在于确保子类一定拥有一个sound()方法。
现在使用抽象方法和抽象类改写Animal类。
package com.qiji.xiaobaiyang;
abstract class Animal {
private String type;
public Animal(String type){
this.type = type;
}
public String toString(){
return "这是一只" + type;
}
public abstract void sound();
}
package com.qiji.xiaobaiyang;
public class Cat extends Animal {
public Cat(String type){
super(type);//对父类的构造方法进行初始化
}
public void sound(){
System.out.println("喵 喵 喵...");
}
}
package com.qiji.xiaobaiyang;
public class Duck extends Animal {
public Duck(String type) {
super(type);
}
public void sound(){
System.out.println("嘎 嘎 嘎...");
}
}
程序中把sound()方法定义成了抽象方法。
抽象方法是指只有方法头部定义,没有方法实现体的方法,定义为抽象方法时需要在方法中加入abstract关键字。包含有抽象方法的类必须被定义为抽象类。
知识点:
① 定义为抽象方法和抽象类的基本格式如下:
【限定修饰符】abstract class类名{
【限定修饰符】abstract void 方法名();
}
② 使用抽象方法与抽象类时应遵循的相应规则
1. 任何包含抽象方法的类必须被定义为抽象类,抽象类中也可包含非抽象方法【常规类中除构造方法以外的所有方法为非抽象方法】,甚至没有包含抽象方法的类也能定义为抽象类。
2.抽象类不可以被实例化。如下述代码:
Animal animal = new Animal();
抽象类之所以不能被实例化为对象是因为对象必须具有具体的属性(变量)状态和行为(方法)。
3.虽然抽象类不能被实例化,但可以将抽象类的非抽象子类的实例对象赋给抽象类类型的引用,并通过该引用调用子类的相应方法。如下述代码:
package com.qiji.xiaobaiyang;
public class AnimalText {
public static void callSound(Animal animal){//方法参数为Animal类型
animal.sound();
}
public static void main(String[] args) {
Cat cat = new Cat("宠物猫");
Duck duck = new Duck("小鸭子");
System.out.println(cat);
callSound(cat);//调用时实参为子类实例化对象
System.out.println(duck);
callSound(duck);//调用时实参为子类实例化对象
}
}
注意:
1.若子类没有实现抽象父类中的所有抽象方法,则子类也必须被定义为抽象类,并且不能被实例化。反之,子类就是一个具体类,能被实例化。
2.static、private、final方法不能是抽象的,因为这些类型的方法是不能被覆盖的。
使用抽象类与抽象方法的优势【相对于常规类】:能够将实现下放到具体子类中,进一步实现了类的接口和实现的分离,能够有效地提高程序的扩展性。
接下来,来看接口
1. 在Java中,一个类只能够有一个直接的父类,但是一个类可以实现多个接口,Java采用这种方式实现多继承;
2. 接口的概念:
a.接口明确地描述了系统对外提供的所有服务,清晰地把系统的实现细节与接口分离,系统的实现细节由接口的实现类负责实现,接口负责对外描述系统提供的服务,对内描述系统应该具备的功能;
b.接口和抽象类都不能够被实例化,但是抽象类中可以包含具体的实现,这样可以提高代码的重用性,而接口不能包含任何具体的实现;
3.接口的特点
(1)接口中的成员变量必须全部是public,static,final类型(编译时常量),必须被显式地初始化;
(2)接口中的方法必须全部是public,abstract类型;
综合(1)(2)两点,接口中不能够出现:
A.实例变量(因为对象中只保存实例变量,而接口不能够有实例,因此没有实例变量)
B.非抽象的实例方法
C.静态方法
(3)接口中没有构造方法,因此不能够创建接口的实例对象;
(4)一个接口不能够实现另外一个接口,只能够继承另外多个接口(类可以实现多个接口,接口可以继承多个接口),如果接口C同时继承接口A和B,则接口C成为复合接口;
(5)接口必须通过类实现它的抽象方法,当类实现某个接口时,它必须实现接口中所有的抽象方法,否则这个类必须被定义为抽象类
(6)不能够创建接口的实例,但可以创建接口类型的引用变量,该变量可以引用实现这个接口的类的实例
4.抽象类与接口
4.1 相同点
(1)抽象类和接口都位于继承树的上层;
这里有一个设计思想:当一个系统(一个类)使用一棵继承树上的类时,应该尽可能地把引用变量声明为继承树的上层抽象类型,引用变量最好声明为接口类型,因为接口是作为系统和外界的交互窗口,这样的好处是可以实现两个系统之间的松耦合;
(2)抽象类和接口都不能够被实例化;
(3)抽象类和接口都可以包含抽象方法;
4.2 不同点
(1)抽象类中可以为部分方法提供默认的实现,从而避免在子类中重复实现这些方法,提高代码的可重用性,而接口中只能够定义抽象的方法
这里有一个扩展性的问题,抽象类比较容易被扩展,因为可以在抽象类中加入具体的方法,加入具体的方法并不会影响到它的任何子类,子类默默继承了这些方法,而对于接口而言,一旦接口被公布,就不能随意在接口中添加方法,因为这样,实现接口的类需要实现新加入的方法,否则需要将类定义为抽象类
(2)一个类只能够继承一个直接的父类,这个父类有可能是抽象类,但是一个类可以实现多个接口
原理思想:接口中只有抽象方法,只有接口的实现类会实现接口的抽象方法,一个类即使有多个接口,也不会增加JVM进行动态绑定的复杂度,因为JVM不会把方法与接口绑定,只会把方法和实现类绑定。
(3)Java程序的设计思想:
对于一棵已经存在的继承树,可以方便地从类中抽象出新的接口,接口更有利于软件系统的维护和重构,对于一棵继承树而言,如果B继承A,B和C提供一种相同的服务S,这样如果将B和C共同的服务抽象成为抽象类O,因为Java不允许多继承,需要A继承类O,这样A就需要实现O中定义的服务S,否则需要将A定义成为抽象类,这显然是不合理的。
因此这时需要接口,将服务S抽象出来封到一个接口中,让B和C都实现这个接口,这样不会影响到其他类
(4)一种解决方案是:
对已经存在的系统进行自下而上的抽象时,需要借助于接口,对于任何两个类,不管它们的类型是否相同,只要它们存在相同的功能,提供相同的服务,就可以将这个服务抽象出来,封装到一个接口中,让这两个类都实现这个接口,这样,这两个类就可以对外提供服务了。
(5)一种Java程序的设计原则是:
系统之间的耦合都通过接口实现,即将引用变量定义成为接口类型,这样可以保证系统之间的松耦合(继承树的上层抽象类型)
4.3 何时使用抽象类??何时使用接口??
(1)将接口作为系统与外界交互的窗口,对外,接口向使用者说明系统能够提供的服务,对内,接口指定系统必须实现的服务,
下面这句话很重要:
接口是一个系统中最高层次的抽象类型,这里的系统可以是一个很大的系统,也可以是一个局部系统,任何系统之间,都应该通过接口进行交互,这样可以实现系统之间的松耦合。
(2)外界只能够通过接口访问系统提供的服务,而不能够修改接口,接口一旦定义,不要轻易修改,否则对外对内都会造成很大的影响。
(3)抽象类可以作为系统的扩展点,将抽象类视为介于“抽象”和“实现”之间的半成品,它力所能及地完成了部分功能,还有一些功能有待于它的子类去实现。
例如一个系统A中包括接口i和抽象类1,系统B中包括四个类abcd,系统B中的类通过与接口i建立组合和依赖关系,访问系统A中的服务,通过继承系统A中的抽象类1,扩展系统A的功能
当一个软件系统对外发布时,会在文档中说明哪些接口允许使用者实现,哪些类允许被继承;
时刻牢记,系统之间通过接口进行耦合,访问系统提供的服务,通过继承抽象类实现系统功能的扩展。
抽象类与接口的对比
参数 | 抽象类 | 接口 |
默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与常规类的区别 | 除了你不能实例化抽象类之外,它和常规JAVA类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其他修饰符 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
说了这么多,看一个实例吧。
Demo3 接口
package com.shenqiqiji.xiaobaiyang;
public interface Qiji {
final float PI=3.14159f;//定义用于表示圆周率的常量PI
float getArea(float r);//定义一个用于计算面积的方法getArea()
float getCircumference(float r);//定义一个用于计算周长的方法getCircumference()
//与Java的类文件一样,接口文件的文件名必须与接口名相同。
}
package com.shenqiqiji.xiaobaiyang;
public class Cire implements Qiji {
@Override
public float getArea(float r) {
float area=PI*r*r; //计算圆面积并赋值给变量area
return area; //返回计算后的圆面积
}
@Override
public float getCircumference(float r) {
//计算圆周长并赋值给变量circumference
float circumference=2*PI*r;
//返回计算后的圆周长
return circumference;
}
public static void main(String[] args) {
Cire c = new Cire();
//控制格式化输出
float f = c.getArea(2.0f);
System.out.println(Float.toString(f));
}
}