本篇文章為大家帶來了關於java的相關知識,其中主要介紹了關於抽象類別和介面的相關問題,包括了什麼是抽象類別、抽象類別再實現多態中的意義等等內容,下面一起來看一下,希望對大家有幫助。
推薦學習:《java影片教學》
我們之前學過什麼是類,那麼抽象類別是不是也是類別的一種呢?
聽名字就覺得好抽象呀!說對了,他就是抽象的,不是具體的。在類別中沒有包含足夠的資訊來描繪一個具體的對象,這樣的類別稱為抽象類別。
來看一個抽象類別的範例
// 抽象类和抽象方法需要被 abstract 关键字修饰 abstract class Shape { // 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体 abstract void draw(); }
大家覺得這個抽象類別是不是什麼也沒幹,他唯一的方法draw()還是空的。
像這樣的類別是不是就沒有包含足夠的資訊來描繪一個具體的對象,自然也就不能實例化對象了。不信你看:
那既然一個類別不能實例化,那這種抽象類別存在的意義是什麼?
#抽象類別存在的一個最大意義就是被繼承,當被繼承後就可以利用抽象類實現多態。
來看一段程式碼
// 抽象类和抽象方法需要被 abstract 关键字修饰 abstract class Shape { // 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体 abstract void draw(); } // 当一个普通类继承一个抽象类后,这个普通类必须重写抽象类中的方法 class Cycle extends Shape { @Override void draw() { // 重写抽象类中的draw方法 System.out.println("画一个圆圈"); } } public class Test4 { public static void main(String[] args) { //Shape shape = new Shape(); 抽象类虽然不能直接实例化 // 但可以把一个普通类对象传给一个抽象类的引用呀,即父类引用指向子类对象 Shape shape = new Cycle(); // 这称作:向上转型 /*Cycle cycle = new Cycle(); Shape shape = cycle // 这是向上转型的另一种写法 */ shape.draw(); // 通过父类引用调用被子类重写的方法 } }
運行之後你會發現神奇的一幕:
##大家在看完了程式碼可能會有很多疑問,別急咱們一個一個的說,
什麼是向上轉型:一句話總結就是「父類別引用指向子類別物件」
向轉型後的變化
向上轉型的作用
這樣的話就我們上面的程式碼就可以理解了
#
##看來,我們可以透過子類別對抽象類別的繼承和重寫,抽象類別還真有點用呀!但這和多態有什麼關係呢,抽象類別用起來這麼麻煩,我還不如直接用普通類,也能達到這樣的效果,還不用再寫一個子類呢?
那行,你再看下面的程式碼,你就知道抽象類別在實作多型時的好處了。
abstract class Shape {
public abstract void draw(); // 抽象方法不能里有具体的语句
}
// 当一个普通类继承一个抽象类的时候,再这个子类中必须重写抽象类中的抽象方法
class Cycle extends Shape {
@Override // 如果不重写会报错,但如果继承的是普通类则不会报错,用抽象类更安全
public void draw() {
System.out.println("画一个圆圈");
}
}
class Flower extends Shape { // 不同的子类对父类的draw方法进行了不同的重写
@Override
public void draw() {
System.out.println("画一朵花");
}
}
class Square extends Shape {
@Override
public void draw() {
System.out.println("画一个正方形");
}
}
public class Test4 {
public static void main(String[] args) {
Cycle cycle = new Cycle(); // 子类引用cycle
Flower flower = new Flower(); // 子类引用flower
Square square = new Square();
// 数组的类型是Shape,即数组中每一个元素都是一个父类引用
// 在这个过程其实也发生了向上转型,对抽象类中的方法进行了重写
Shape[] shapes = {cycle, flower, square}; // 父类引用引用不同的子类对象
for (int i = 0; i < shapes.length; i++) {
Shape shape = shapes[i]; // 父类引用shape指向—>当前所对应的子类对象
shape.draw(); // 通过父类引用调用子类重写的draw方法
}
}
}
呼叫同一個方法竟然印出了不同的結果,這難道就是所謂的多型
是不是有點懵,下面我們來解釋一下// 对上面的代码补充一下
// 可能你对 Shape[] shapes = {cycle, flower, square};不太理解
// 但上面的代码就相当于
Shape[] shapes1 = new Shape[3]; // 有三个不同的子类对象呀!数组大小为3
// (将指向->子类对象)的子类引用赋值给父类对象,不就相当于该夫类引用指向->所对应的子类对象吗
//这是向上转型的另一种写法,应为前面已经实例化了子类对象 Cycle cycle = new Cycle();
shapes1[0] = cycle; // 如果前面没实例化子类对象,就要写成shape1[0] = new Cycle
shapes1[1] = flower;
shapes1[2] = square;
當我們的父類別引用指向不同的子類別物件時,當我們呼叫同一個draw方法時卻輸出了不同的結果。 (其實就是該方法再子類別中被重寫成了不同形式)這就叫做多型 。
嘻嘻,其實只要要結合例子來看,多型也沒那麼難理解呀
那為啥一定要用抽象類別呢?我一個普通類別繼承普通類別來實現多態不可以嗎當然可以,但不太安全有風險;
但如果是抽象类的话,就不一样了
从这我们也可以看出,当用抽象类的时候,编译器自动就对我们是否重写进行了校验,而充分利用编译器的校验, 在实际开发中是非常有意义的 。所以说抽象类还是有用的
好了,相信到这里你对抽象类也有了一个大概的认识,下面来简单做一下总结
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。
接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
如何定义一个接口呢?下面我们来看一个栗子
//接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口 public interface 接口名称{ // 定义变量 int a = 10; // 接口当中的成员变量默认都是public static final // 抽象方法 public abstract void method1(); // public abstract 是固定搭配,可以不写 void method2(); // 接口当中的成员方法默认都是public abstract, 更推荐用第二种来定义方法 }
可以看到接口和类其实还是有很多相似点:
接口中也包含抽象方法,所以也不能直接实例化接口,那么我们怎么用接口呢?
哈哈,很简单,我们再用一个普通类实现这个接口不就行了吗,不同的是抽象类是被子类来继承而实现的,而接口与类之间则是用关键字implements来实现。
就像普通类实现实现抽象类一样,一个类实现某个接口则必须实现该接口中的抽象方法,否则该类必须被定义为抽象类。
铁汁们!刚才我们是用抽象类来实现多态,那么现在我们可以尝试用接口来实现多态,
接口可以看成是一种特殊的类,只能用 interface 关键字修饰 interface IShape { int a = 10; 接口当中的成员变量默认都是public static final int b = 23; void draw(); 接口当中的成员方法一般只能是抽象方法,默认是public abstract(JDK1.8以前) default void show() { System.out.println("接口中的其他方法");//接口中的其他方法也可以实现,但要用default修饰 } public static void test() { System.out.println("这是接口当中的一个静态的方法"); } } // 一个普通的类要想实现接口,可以用implement, //因为接口也是抽象方法的,所以实现接口的这个类也要重写抽象方法 class Cycle implements IShape { @Override public void draw() { System.out.println("画一个圆圈"); } } class Square implements IShape { @Override public void draw() { System.out.println("画一个正方形"); } } class Flower implements IShape { @Override public void draw() { System.out.println("画一朵花"); } } public class Test4 { public static void main(String[] args) { // IShape iShape = new IShape(); 接口也不能直接实例化 Cycle cycle = new Cycle(); Square square = new Square(); Flower flower = new Flower(); // 这里的IShape接口就相当与抽象类中父类,接口类型也是一种引用类型 IShape[] iShapes = {cycle, square, flower}; // 这个过程其实就发生了向上转型 for (IShape iShape : iShapes) { // 增强型的for—each循环,也可以写成普通的for循环形式 iShape.draw(); // 通过重写实现了多态 } } } 引用变量cycle和square都赋值给了Shape类型的引用变量shape, 但当执行shape.draw()时,java虚拟机到底要调用谁重写的的draw方法, 就看此时接口引用的是那个对象的,是shape的、还是cycle的
看一下运行结果
看完代码你可能有点晕,但没关系。一般接口咱也不这么用,直接使用抽象类不就好了(我只是演示一下用接口也能实现多态)
下面我们来总结一下Java中接口的几个主要特点
那么接口一般用在什么地方呢?
下面就让我们来看看接口的正确用法:帮助java实现“ 多继承 ”
由于 Java 中单继承的特性,导致一个类只能继承一个类,但是可以实现一个或多个接口,此时可以使用接口。 class Animal { String name; // 不能使用private,后面的子类也要用 public Animal(String name) { // 父类的自定义的构造方法 this.name = name; } } interface IFlying { // 自定义多种接口 void fly(); } interface IRunning { void run(); } interface ISwimming { void swimming(); } // 小鸭子,不仅会跑,还会游泳、飞行 一个类继承父类,并实现多个接口,间接的解决java中不能多继承的问题 class Duck extends Animal implements IRunning, ISwimming, IFlying { public Duck(String name) { // 子类构造方法 super(name); // 必须在子类构造方法的第一行 // 在给实现子类的构造方法前,先要用super()调用实现父类的构造方法,比较先有父后有子呀! // 因为父类自己定义了构造方法,编译器不会自动给给子类构造方法中添加super();来实现父类的构造方法,需要我们自己实现 } // 对接口中的抽象方法进行重写 @Override public void fly() { System.out.println(this.name + "正在用翅膀飞"); } @Override public void run() { System.out.println(this.name + "正在用两条腿跑"); } @Override public void swimming() { System.out.println(this.name + "正在漂在水上"); } } public class 接口的使用 { // 不用学我用中文名作为类名,我只是为演示方便 public static void main(String[] args) { Duck duck = new Duck("第一个小鸭子"); // 实例化鸭子对象 duck.fly(); // 通过引用 变量名.方法名 输出重写后的方法 duck.run(); duck.swimming(); } } 有人可能会说干嘛用接口,我直接在父类Animal中实现fly、run、swimming这些属性, 然后不同的动物子类再继承父类这些方法不行吗? 但问题是,鸭子会fly、swimming,那猫会飞和游泳吗?你再写个其他动物的子类是不是就不行了 而用接口呢?我们只是把这种飞、游泳的行为给抽象出来了, 只要一个子类有这种行为,他就可以实现相对应的接口,接口是更加灵活的
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多个接口。
继承表达的含义是 is - a 语义, 而接口表达的含义是 具有 xxx 特性 ,能实现接口的类和该接口并不一定有is_a的关系,只要该类有这个接口的特性就行
猫是一种动物, 具有会跑的特性.
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,只要这个类有有这个特性就好。
举个栗子
class Robot implements IRunning { private String name; public Robot(String name) { this.name = name; } // 对run方法进行重写 @Override public void run() { System.out.println("机器人" + this.name + "正在跑"); } } public class Test4 { public static void main(String[] args) { Robot robot1 = new Robot("图图"); robot1.run(); } } // 执行结果 机器人图图正在跑
只要能跑就行,管他是机器人还是动物呢
推荐学习:《java视频教程》
以上是完全掌握Java中的抽象類別和接口的詳細內容。更多資訊請關注PHP中文網其他相關文章!