三、继承
继承:
继承是类与类的一种关系,是一种“is a”的关系。比如说,狗是一种动物(dog is an animal),汽车是一种交通工具(car is a vehicle),在这里动物和交通工具就称为父类(基类),狗和汽车就称为子类(派生类)。就像日常生活中我们所说的继承一样,儿子会继承父亲的一些相貌上或者性格上的特点,还会耳濡目染、有样学样地继承父亲的一些行为方式;而在Java中,子类则会继承父类所有的属性和方法(当然private修饰的属性和方法不可以继承哦)。
继承的关键字为extends
具体定义语法为:
[权限修饰符] class 类名 extends 父父类名{
//定义或修改属性
//定义或重写方法
}
注意:类是单继承的,即每个类只能有一个父类。
方法的重写与方法的重载:
上面说过子类可以继承父类的方法,但如果父类中的方法不够完善或者不能满足子类的要求,这时候子类就可以修改父类的方法,拓展功能或者重新定义,这就是方法的重写。
在同一个类中,我们可以定义一些名称相同的方法,但这些方法的参数或者返回值类型却不同。在调用方法时,虽然这些方法都是同名的,但Java会根据不同的参数列表来选择正确的方法进行调用,这就是方法的重载。
方法的重写和重载从字面上来看只有一字之差,但是本质上却有很大的区别,以下是需要注意的几点:
- 子类中重写的方法与父类中的方法有完全相同的返回值类型、方法名、参数个数以及参数类型,只是方法体不同
- 重载的方法必须具有不同的参数列表,即不同的参数类型或者不同的参数个数或者不同的参数顺序,因此方法体也就不同了。除此之外,权限修饰符和返回值类型也可以不同,但方法名必须要相同
- 方法的重写和重载都可以改变权限修饰符,但都只能将范围扩大(关于权限修饰符的作用范围上文有介绍)
- 一般来说,重载方法时,方法之间需要存在一定的联系,例如功能相似,因为这样才能使方法的重载有意义,并且提高程序的可读性
super关键字:
如果子类已经将父类中的方法重写了,调用的时候肯定是调用被重写过的方法,那如果现在一定要调用父类中的方法,此时就可以通过使用super关键字:super.方法名(参数列表)
来实现
下面以狗类(子类)和动物类(父类)为例具体演示一下上面介绍的几个概念,先定义Dog类和Animal类:
package com.project;
public class Animal{//定义Animal类
int legs = 2;//定义legs属性
public void eat(){//定义eat方法
System.out.println("动物具有吃东西的能力");
}
package com.project;
public class Dog extends Animal{//定义Dog类
}
修改项目测试类的代码:
public class Test {
public static void main(String[] args) {
Dog obj = new Dog();//创建Dog类对象obj
System.out.println(obj.legs);//输出obj的legs属性值
obj.eat();//调用obj的eat()方法
}
}
控制台界面会输出以下信息:
说明子类可以继承父类的属性和方法。
修改Dog类的代码:
public class Dog extends Animal{
int legs = 4;
public void eat(){//重写eat()方法
super.eat();//用super关键字调用Animal类中的eat()方法
System.out.println("狗具有吃骨头的能力");
}
}
其他代码不作改动,控制台界面会输出以下信息:
说明子类可以修改父类的属性和重写父类的方法。
四、多态
多态:
多态,从字面上理解就是多种形态,Java中指的是对象的多种形态。
多态分为两种:
- 引用多态:父类的引用既可以指向本类的对象,也可以引用子类的对象
- 方法多态:当通过父类创建本类对象时,调用的方法为本类的方法;创建子类对象时,调用的方法为子类继承的方法或重写的方法(即不能调用子类独有的方法)
还是以Dog类和Animal类为例,修改Dog类和项目测试类主方法的代码:
public class Dog extends Animal{
public void eat(){
System.out.println("狗具有吃骨头的能力");
}
}
public class Test {
public static void main(String[] args) {
Animal obj1 = new Animal();//通过Animal类创建Animal类对象obj1
Animal obj2 = new Dog();//通过Animal类创建Dog类对象obj2(通过构造方法可以区别创建的是哪类对象)
obj1.eat();//调用obj1的eat()方法
obj2.eat();//调用obj2重写的eat()方法
}
}
控制台界面会输出以下信息:
再次修改Dog类和项目测试类主方法的代码:
public class Dog extends Animal{
public void eat(){
System.out.println("狗具有吃骨头的能力");
}
public void watchDoor(){//定义watchDoor方法
System.out.println("狗具有看门的能力");
}
}
public class Test {
public static void main(String[] args) {
Animal obj1 = new Animal();
Animal obj2 = new Dog();
obj1.eat();
obj2.eat();
obj2.watchDoor();//调用obj2的独有的watchDoor方法
}
}
此时,obj2.watchDoor();
这个地方程序会报错:
因为父类中并没有定义watchDoor()方法,通过父类创建子类对象并调用这个子类独有的方法时就会报错。
抽象类:
抽象类只是规定子类必须拥有的方法,但并不规定这些方法是如何实现的。因此,可以从多个具有相同特征的类中抽象出一个抽象类,这个抽象类作为子类的模版,从而避免子类设计的随意性。需要注意的是:
- 抽象类和抽象方法都是用
abstract
关键字来修饰 - 当
abstract
定义抽象方法时,只需要声明即可,不需要定义具体实现(因为抽象类并不关注这些方法是如何实现的),即abstract 类名();//没有方法体
- 抽象类中既可以有普通方法,也可以有抽象方法
- 包含抽象方法的类一定是抽象类
下面以狗类(子类)、猫类(子类)和动物类(父类,同时也是抽象类)为例具体演示一下上面介绍的几个概念,先定义Dog类、Cat类和Animal类:
public abstract class Animal{//定义Animal抽象类
public abstract void eat();//定义eat抽象方法
public abstract void voice();//定义voice抽象方法
}
public class Dog extends Animal{
public void eat(){//定义eat()方法在Dog类中的具体实现
System.out.println("狗具有吃骨头的能力");
}
public void voice(){//定义voice()方法在Dog类中的具体实现
System.out.println("狗“汪汪汪”地叫");
}
}
public class Cat extends Animal{
public void eat(){//定义eat()方法在Cat类中的具体实现
System.out.println("猫具有吃鱼的能力");
}
public void voice(){//定义voice()方法在Cat类中的具体实现
System.out.println("猫“喵喵喵”地叫");
}
}
修改项目测试类的代码:
public class Test {
public static void main(String[] args) {
Dog obj1 = new Dog();
Cat obj2 = new Cat();
obj1.eat();
obj1.voice();
obj2.eat();
obj2.voice();
}
}
控制台界面会输出以下信息:
接口:
接口定义的是某一批类所共同遵守的规范,既不关心这些类中的数据,也不关心这些类中方法的具体实现,只规定这些类中必须包含的方法(这一点与抽象类很相似)。
定义接口的关键字为interface
。接口不同于类,它可以继承多个父接口。具体的定义语法为:
[权限修饰符] [abstract] interface 接口名 [extends 父接口1, 父接口2, ...]{
//定义一个或多个常量
//定义一个或多个抽象方法
}
注意:
- 因为接口中包含的是常量和抽象方法,所以定义接口的时候系统会自动添加
abstract
关键字 - 接口中的属性均为静态常量,因此在定义属性的时候系统会自动添加
public static final
修饰符 - 接口中的方法均为抽象方法,因此在定义方法的时候系统会自动添加
public abstract
修饰符 - 接口是用来被继承和被实现的,因此不能使用
private
和protected
修饰(这样会使得接口没有意义),一般使用public
修饰
使用implements
关键字实现接口。而当类既继承父类又实现接口时,定义语法为:
[权限修饰符] class 类名 extends 父类 implements 接口1, 接口2, ...{//一个类可以实现多个接口
//如果继承了抽象类,要包含继承的抽象方法;此外还要包含接口的抽象方法
}
注意:
- 在给接口命名的时候通常在名字前面加上一个“I”
- 继承父类必须要在实现接口之前
还是以狗类、猫类为例,现在要他们都继承哺乳动物类。狗和猫都有吃东西和叫的能力,但狗能游泳而猫却不能。在大自然中,鸭子也是会游泳的,但很明显,鸭子并不属于哺乳动物,即不能继承哺乳动物类。因此要想描述狗和鸭子都具有游泳的能力,就要用到接口。
先定义ISwim接口:
package com.project;
public interface ISwim{
public void swim();
}
定义Mammal类(定义的代码与Animal类一样,只是类名不同),修改Dog类和Cat类的代码,:
public class Dog extends Mammal implements ISwim{//实现ISwim接口
public void eat(){
System.out.println("狗具有吃骨头的能力");
}
public void voice(){
System.out.println("狗“汪汪汪”地叫");
}
public void swim(){//定义swim()方法在Dog类中的具体实现
System.out.println("狗具有游泳的能力");
}
}
public class Cat extends Mammal{
public void eat(){
System.out.println("猫具有吃鱼的能力");
}
public void voice(){
System.out.println("猫“喵喵喵”地叫");
}
}
定义Duck类:
public class Duck implements ISwim{//实现ISwim接口
public void swim(){//定义swim()方法在Duck类中的具体实现
System.out.println("鸭子具有游泳的能力");
}
}
修改项目测试类主方法的代码:
public class Test {
public static void main(String[] args) {
Dog obj1 = new Dog();
Cat obj2 = new Cat();
Duck obj3 = new Duck();
obj1.eat();
obj1.voice();
obj1.swim();
obj2.eat();
obj2.voice();
obj3.swim();
}
}
控制台界面会输出以下信息:
后记
以上的内容是我通过看书和看视频以及上网找资料归纳、总结出来的,目的在于为我今后复习Java基础知识和编写Java程序时提供可查阅的笔记,我也希望能够给阅读本文的Java初学者一些帮助。
参考资料
《Java——从入门到精通》
慕课网(IMOOC)