先将一个故事,为什么父类的方法有用呢?**扪心自问一下父类我们真正用的多吗?**不多,我们大部分时间都是直接用子类对象的。把话说难听点就是——子类对父类真正的继承的只是他的方法名字,不是他的方法体。
由于多态的存在,**每个子类都可以覆写父类的方法,**例如:
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();
}
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:
```java
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();
}
在抽象类中,具有如下特征
如果—个类中有抽象方法,那么这个类—定是抽象类,也就是说,使用关键字 abstract 修饰的 方法—定是抽象方法,具有抽象方法的类—定是抽象类。实现类方法中只有方法具体的实现。
抽象类中不—定只有抽象方法,抽象类中也可以有具体的方法,你可以自己去选择是否实现这些方 法。
抽象类中的约束不像接口那么严格,你可以在抽象类中定义 构造方法、抽象方法、普通属性、方 法、静态属性和静态方法
抽象类和接口—样不能被实例化,实例化只能实例化 具体的类
如果一个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的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等。
抽象类和接口的对比如下:
一个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();
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是: