先将一个故事,为什么父类的方法有用呢?扪心自问一下父类我们真正用的多吗?⬇️不多,我们大部分时间都是直接用子类对象的。把话说难听点就是——子类对父类真正的继承的只是他的方法名字,不是他的方法体。
由于多态的存在,每个子类都可以覆写父类的方法,例如:⬇️
class Person {
public void run() { … }
}
class Student extends Person {
@Override
public void run() { … }
}
class Teacher extends Person {
@Override
public void run() { … }
}
从Person类派生的Student和Teacher都可以覆写run()方法。
如果父类Person的run()方法没有实际意义,能否去掉方法的执行语句?⬇️
class Person {
public void run(); // Compile Error!
}
答案是不行,会导致编译错误,因为定义方法的时候,必须实现方法的语句。
能不能去掉父类的run()方法?⬇️
答案还是不行,因为去掉父类的run()方法,就失去了多态的特性。例如,runTwice()就无法编译:⬇️
public void runTwice(Person p) {
p.run(); // Person没有run()方法,会导致编译错误
p.run();
}
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:⬇️
class Person {
public abstract void run();
}
把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的(没有函数体的函数无法执行),所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。
必须把Person类本身也声明为abstract,才能正确编译它:⬇️
abstract class Person {
public abstract void run();
}
'下面是一个经典的例子'
public interface Dog {
void FurColor();
}
abstract class WhiteDog implements Dog{
public void FurColor(){
System.out.println("Fur is white");
}
abstract void SmallBody();
}
在抽象类中,具有如下特征
2.抽象类中不—定只有抽象方法,抽象类中也可以有具体的方法,你可以自己去选择是否实现这些方 法。
3.抽象类中的约束不像接口那么严格,你可以在抽象类中定义 构造方法、抽象方法、普通属性、方 法、静态属性和静态方法
4.抽象类和接口—样不能被实例化,实例化只能实例化 具体的类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。
因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。
使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:
Person p = new Person(); // 编译错误
无法实例化的抽象类有什么用?
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
例如,Person类定义了抽象方法run(),那么,在实现子类Student的时候,就必须覆写run()方法:♏
// abstract class
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
定义:接口(Interface)在Java中是一种提供抽象方法定义的机制,是一种行为规范的协定。接口中只能包含抽象方法和常量(静态final变量),而不能包含普通的成员属性(非常量的实例变量)。
接口(高度抽象——抽象到他就不是一个正常的类了,没有实例,也没有方法体,连class都没有了)
接口相当于就是对外的—种约定和标准,这里拿操作系统举例子,为什么会有操作系统?就会为了屏蔽 软件的复杂性和硬件的简单性之间的差异,为软件提供统—的标准。
在 Java 语言中,接口是由 interface 关键字来表示的,比如我们可以向下面这样定义—个接口♏
public interface CxuanGoodJob {}
比如我们定义了—个 CxuanGoodJob 的接口,然后你就可以在其内部定义 cxuan 做的好的那些事情, 比如 cxuan 写的文章不错。
public interface CxuanGoodJob {
void writeWell();
}
这里隐含了—些接口的特征:
interface 接口是—个完全抽象的类,他不会提供任何方法的实现,只是会进行方法的定义。
接口中只能使用两种访问修饰符, —种是 public ,它对整个项目可见; —种是 default 缺省 值,它只具有包访问权限。
接口只提供方法的定义,接口没有实现,但是接口可以被其他类实现。也就是说,实现接口的类需 要提供方法的实现,实现接口使用 implements 关键字来表示, —个接口可以有多个实现。
class CXuanWriteWell implements CxuanGoodJob{
@Override
public void writeWell() {
System .out .println("Cxuan write Java is vary well");
}
}
接口不能被实例化,所以接口中不能有任何构造方法,你定义构造方法编译会出错。
接口的实现比如实现接口的全部方法,否则必须定义为 抽象类 ,这就是我们下面要说的内容
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract String getName();
}
就可以把该抽象类改写为接口:interface
在Java中,使用interface可以声明一个接口:♏
interface Person {
void run();
String getName();}
所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
当一个具体的class去实现一个interface时,需要使用implements关键字。举个例子:
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}
♏我们知道,在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个
interface,例如:
class Student implements Person, Hello { // 实现了两个interface
...
}
注意区分术语:
Java的接口特指interface的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
抽象类和接口的对比如下:
abstract classinterface继承只能extends一个class可以implements多个interface字段(成员属性)可以定义实例字段不能定义实例字段抽象方法可以定义抽象方法可以定义抽象方法非抽象方法可以定义非抽象方法可以定义default方法
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。例如:
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
Person接口继承自Hello接口,因此,Person接口现在实际上有3个抽象方法签名,其中一个来自继承的Hello接口。
合理设计interface和abstract class的继承关系,可以充分复用代码。一般来说,公共逻辑适合放在abstract class中,具体逻辑放到各个子类,而接口层次代表抽象程度。可以参考Java的集合类定义的一组接口、抽象类以及具体子类的继承关系:
┌───────────────┐
│ Iterable │
└───────────────┘
▲ ┌───────────────────┐
│ │ Object │
┌───────────────┐ └───────────────────┘
│ Collection │ ▲
└───────────────┘ │
▲ ▲ ┌───────────────────┐
│ └──────────│AbstractCollection │
┌───────────────┐ └───────────────────┘
│ List │ ▲
└───────────────┘ │
▲ ┌───────────────────┐
└──────────│ AbstractList │
└───────────────────┘
▲ ▲
│ │
│ │
┌────────────┐ ┌────────────┐
│ ArrayList │ │ LinkedList │
└────────────┘ └────────────┘
在使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象:
List list = new ArrayList(); // 用List接口引用具体子类的实例 Collection coll =
list; // 向上转型为Collection接口 Iterable it = coll; // 向上转型为Iterable接口
在接口中,可以定义default方法。例如,把Person接口的run()方法改为default方法:
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
Person s = new Student();
Person t = new Teacher();
这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
// 不关心Person变量的具体子类型:
s.run();
t.run();
同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:
// 同样不关心新的子类是如何实现run()方法的:
Person e = new Employee();
e.run();
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
用抽象类给一个有工资收入和稿费收入的小伙伴算税