Java中的继承、多态和接口
1、用类比引入继承概念
众所周知,Java是一门面向对象的语言。如果我们要设计多种多样的交通工具,比如汽车、火车、飞机,虽然这些工具功能不同、形态不同,但是他们很多的基本功能相同,比如——载人,行驶,是否能飞...,于是我们把class Vehicle(交通工具类)当作设计交通工具的标准,再通过这个标准创建不同的交通工具,包括汽车、火车、飞机等等(class Car,class Train,class Air...),这些工具的共有属性都是继承设计标准(class Vehicle)中的属性。所谓继承,就是将Vehicle类中的部分属性和功能改变,但是大体框架相同,构成新的汽车类(class Car)、火车类(class Train)、飞机类(class Air)。
2、用问题深入什么是继承?
现在有一个交通工具制造厂; 制造场可以制造汽车、火车、飞机三类交通工具; 制造场可以展示三类交通工具的信息; 汽车:name,capacity,length,tyreNum(型号,载客量,长度,轮胎的个数) 火车:name,capacity,length,haveTrack(型号,载客量,长度,是否有轨道) 飞机:name,capacity,length,haveWing(型号,载客量,长度。是否有机翼)
3、用代码演示继承的作用
- 首先,观察问题,汽车、火车、飞机都是交通工具,它们的信息都有name,capacity,length,所以我们构建一个class Vehicle(交通工具类),Vehicle类中包含它们三者共有的信息。并且最后都是要输出信息的,所以我们在Vehicle类中也构建一个showInfo的方法。
public class Vehicle {
protected String name;
protected int capacity;
protected int length;
public Vehicle(String name , int capacity , int length){
this.name = name;
this.capacity = capacity;
this.length = length;
}
public void showInfo(){
System.out.print(name+"的最大载客量是:"+capacity+",长度是:"+length+"米");
}
}
- Vehicle类构建完了,接下来构建一个class Car(汽车类)。在这个Car类中,重点来了,我们用extends关键字把Car作为Vehicle类的子类。然后,我们再在Car类中给一个tyreNum整形变量,代表汽车轮胎个数。然后给Car类一个构造器,包括name,capacity,length,tyreNum。重点来了,用super关键字,将形参name,capacity,length,给定到父类Vehicle中,而Car类专属的tyreNum就在本类用。再给定一个showInfo方法,用来展示汽车的信息。重点来了,在showInfo方法中,用super关键字调用Vehicle中的showInfo方法。
public class Car extends Vehicle //extends关键字,表示Car类继承Vehicle类,即Car是Vehicle的子类
{
protected int tyreNum;
public Car(String name , int capacity , int length , int tyreNum){
super(name, capacity, length);//super关键字,将形参传给父类
this.tyreNum=tyreNum;
}
@Override
public void showInfo(){
super.showInfo();//super关键字,调用父类Vehicle中的showInfo方法
System.out.println(",轮胎数量是:"+tyreNum);
}
}
- 在Car类构建完毕之后,同理,构建出Train类和Air类,分别由自己的专属实例变量,haveTrack和haveWing。代码如下:
public class Train extends Vehicle{
protected boolean haveTrack;
public Train(String name , int capacity , int length , boolean haveTrack){
super(name, capacity, length);
this.haveTrack=haveTrack;
}
@Override
public void showInfo(){
super.showInfo();
System.out.println(",是否有轨道:"+haveTrack);
}
}
public class Air extends Vehicle{
protected boolean haveWing;
public Air(String name , int capacity , int length , boolean haveWing){
super(name, capacity, length);
this.haveWing=haveWing;
}
@Override
public void showInfo(){
super.showInfo();
System.out.println(",是否有机翼:"+haveWing);
}
}
- 在完成以上构建类的步骤之后,我们创建一个交通工具制造厂(class Database),用来进行制造交通工具,并输出交通工具的信息。我们用ArrayLise的方式new一个容器vehicles。构建一个增加交通工具的add方法,目的是将交通工具放入vehicles容器中。再建立一个输出交通工具信息的list方法,用来展现交通工具的信息。
import java.util.ArrayList;
public class Database {
ArrayList<Vehicle>vehicles=new ArrayList<Vehicle>();
public void add(Vehicle vehicle){
vehicles.add(vehicle);
}
public void list(){
for(Vehicle v:vehicles){
v.showInfo();
}
}
}
- 在完成上述四步之后,我们可以进入测试环节,创造一个测试类用于测试,代码如下:
public class Test {
public static void main(String[] args) {
Database db=new Database();
db.add(new Car("宝马",5,3,4));
db.add(new Train("和谐号",1800,500,true));
db.add(new Air("客机",500,28,true));
db.list();
}
}
- 运行代码,结果如下: 我们可以看到,成功运行,并且把信息成功输出!
4、继承的更多和多态的概念
在上述例子中,我们可以看到继承的基本操作,那么与继承关系紧密的另一个概念是什么呢?那就是多态了。面向对象的三个基本特征就是:封装,继承,多态。
封装最好理解了。 封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承的基本概念在上面我已经演示过了,现在详细说说。 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”,被继承的类称为“父类”。在Java语言中,默认继承,Object父类。继承的过程,就是从一般到特殊的过程。
接下来到了新概念——多态。 多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 实现多态,有二种方式,覆盖,重载。 覆盖,是指子类重新定义父类的虚函数的做法。 重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
继承的机制,引用学长上培训课给我们的总结就是:
- 子类调用方法时优先调用自己的,如果自己没有在调用父类的;
- 子类构造对象时,需要用到super();方法向父类传递对应的参数,并且符合一下顺序:父类构造->子类的定义初始化->构造器初始化
- 如果不调用super,构造器会自动在父类中寻找不带任何参数的构造器
- 要在子类的方法中调用父类的方法,需要用到super关键字
多态的机制,引用学长上培训课给我们的总结就是:
- 子类的对象可以被当作父类的对象来使用
- 赋值给父类的变量
- 传递给需要父类对象的函数
- 放进存放父类对象的容器里
- Java的所有对象变量都是多态的,它能保存不止一种类型的对象
- 它们可以保存的是声明类型的对象(静态类型),也可以是声明类型的子类的对象(动态类型)
- 当我们把一个子类的对象交给父类的变量管理时就发生了向上造型;
那何为造型Cast?
- 把一个类的实例对象赋值给另外一个类的变量的过程叫做造型
- 父类的变量可以管理子类的对象(向上造型)
- 子类的变量不能管理父类的对象
除非用向下造型,c=(Car)v; 但是前提是v管理的的确是一个Car的对象Vehicle v=new Vehicle(); Car c=new Car(); v=c; //可以 c=v; //编译错误!会爆红
- 向上造型总是安全的,是默认的,不需要加括号
- 向下造型是强制的,是不安全的,可能会发生异常
- 造型不是类型转换
5、由继承引入抽象类和抽象方法(abstract)
思考下面一个问题:
- 现在需要做三个类
- 矩形,三角形,圆形
- 每个类里面都需要有计算周长的方法getPerimeter(); 和计算面积的方法getArea();
是不是想到了些什么?这三个类如果向上抽取出共同特点,那就是,都有面积,都有周长。而且每个类都要有计算面积、计算周长的方法。那么我们如何让这三个类建立联系呢?其实可以通过建立抽象类和抽象方法实现。
- 我们首先创建矩形类(class Rectangle),在矩形类中创建构造器和计算周长的方法getPerimeter();和计算面积的方法getArea()。
public class Rectangle extends Shape{
protected double length;
protected double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double getPerimeter(){
return (length+width)*2;
}
@Override
public double getArea(){
return length*width;
}
}
- 同理,我们随之建立三角形类(class Triangle)和圆类( class Circular),在类中创建构造器和计算周长的方法getPerimeter();和计算面积的方法getArea()。
public class Triangle extends Shape{
protected double sideOne;
protected double sideTwo;
protected double sideThree;
public Triangle(double sideOne, double sideTwo, double sideThree) {
this.sideOne = sideOne;
this.sideTwo = sideTwo;
this.sideThree = sideThree;
}
@Override
public double getPerimeter(){
return sideOne+sideTwo+sideThree;
}
@Override
public double getArea(){
double p=(sideOne+sideTwo+sideThree)/2.0;
return Math.sqrt(p*(p-sideOne)*(p-sideTwo)*(p-sideThree));
}
}
public class Circular extends Shape{
double R;
public Circular(double r) {
R = r;
}
@Override
public double getPerimeter(){
return 2*R*Math.PI;
}
@Override
public double getArea(){
return Math.pow((R*Math.PI),2);
}
}
- 你会发现上面三段代码的共同点 extends Shape,那么这个class Shape是何方神圣呢?
public abstract class Shape {
public abstract double getPerimeter();
public abstract double getArea();
}
- 非常简短的代码,只不过多了一个关键字abstract。这个关键字是抽象的意思,在abstract的类中,不能有具体的方法。我们上面的三种形状都是Shape的子类。那么我们创造一个test类来测试一下!
public class test {
public static void main(String[] args) {
Shape R=new Rectangle(10,15);
Shape T=new Triangle(3,4,5);
Shape C=new Circular(5);
System.out.println(R.getArea());
System.out.println(R.getPerimeter());
System.out.println(T.getArea());
System.out.println(T.getPerimeter());
System.out.println(C.getArea());
System.out.println(C.getPerimeter());
}
}
这里要注意的问题是,抽象类不能实例化!
Shape shape=new Shape();
//这种情况是不允许存在的,因为抽象的类不能实例化!!!
以上的例子就是由继承引出抽象类和抽象方法,各位可以细品。
6、抽象的接口
在上面了解了抽象之后,我们引入接口的概念:
- 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口在IDE中有自己的创建(我这里用idea举例)
为了更了解接口,下面由一个问题引入:
- 现在有一个手机类Phone,和人类Person
- 两者都有发出声音的方法Sound();
- 我们想把这个公共的方法提取出来但是又不违和;
如何构建呢?
- 我们建立人类和手机类
public class Person implements Soundable {
@Override
public void sound(){
System.out.println("你好");
}
}
public class Phone implements Soundable{
@Override
public void sound(){
System.out.println("叮铃铃");
}
}
- 建立Soundable接口
public interface Soundable {
public void sound();
}
- 创建测试类
public class test {
public static void main(String[] args) {
Soundable person=new Person();
Soundable phone=new Phone();
person.sound();
phone.sound();
}
}
结果如下:
此外,又因为接口本身就是抽象的,所以下面两个没区别(有无abstract)
public abstract interface Soundable {
public abstract void sound();
}
public interface Soundable {
public void sound();
}
还有,当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
7、接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
接着举个例子吧,下面的Sports接口被Hockey和Football接口继承:
public interface Sports
{
public void setHomeTeam(String name);
public void setVisitingTeam(String name);
}
public interface Football extends Sports
{
public void homeTeamScored(int points);
public void visitingTeamScored(int points);
public void endOfQuarter(int quarter);
}
public interface Hockey extends Sports
{
public void homeGoalScored();
public void visitingGoalScored();
public void endOfPeriod(int period);
public void overtimePeriod(int ot);
}
Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
此外,接口可以多继承,在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
public interface Hockey extends Sports, Event
以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可能定义或是继承相同的方法。
8、接口的总结
这些篇幅就直接引用学长上课时候的总结吧:
接口的一些机制
- 接口里的所有方法都是抽象方法,即使前面没有加abstract声明,系统会自动默认
- 接口里的所有变量都是静态变量
- 接口可以继承接口,但不能继承类
- 接口不能有实体
- 一个类可以实现多个接口,实现 用关键字 implements
接口存在的意义
- 将某个功能抽象出来,提高可扩展性
- 是Java成功的关键之一,因为极适合多人写一个大程序
- 也是Java被批评的要点之一,因为容易造成代码膨胀
继承类(不管是实体类还是抽象类)侧重于规则和概念 实现接口侧重于功能(很多able结尾的接口,如Cloneable,Readable,Runable)
9、总结与感想
- 其实这篇博文,很多概念我都是在写的时候更进一步的弄清楚的,继承、多态、接口,许许多多的知识在查阅的时候才会更多的灌输到脑子里。当然,敲代码举例子也是很不错的过程。如果对我这篇文章很多概念还是没有弄清楚的,可以参考 Java继承 和 Java多态 还有 Java接口 ,或者是看看其他的有关博文。
- 当然,这篇博文对我而言不仅仅是一项任务,更像是一次自我提升,在查资料、敲代码、写博文,这三步之后,我感觉我的基础框架构建出来了。古人云,授人以鱼不如授人以渔。而我发现,在授人以渔的过程中,自己的进步是真的很大,比起刷网课视频,自己实践动手给人更多的提升感觉,尽管过程很苦很累,但是一切都是值得的!写下这篇博文,更像是给自己做一份笔记,让自己了解更细致,这钟感觉针不戳~
- 还望未来的自己再接再厉,继续前行!