继承的概述
Java是对现实社会的模拟,现实中有儿子可以继承父亲的财产,因此有了富二代的。 java中多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是多态的前提。
继承的格式
通过 extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:
继承演示,代码如下:
现实中通用的属性和功能定义在父类中
/*
* 定义人类Person,做为父类
*/
public class Person {
//成员变量 对应 现实中的属性
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 成员方法 对应现实中的功能
public void showInfo(){
System.out.println("姓名:" +name +"年龄:" + age);
}
}
定义Student子类
/*
* 定义Student类继承 人类Person
*/
public class Student extends Person {
//独有的方法
public void study() {
System.out.println("学习..");
}
}
定义Teacher子类
/*
* 定义Teacher类继承 人类Person
*/
public class Teacher extends Person{
//独有的方法
public void teaching() {
System.out.println("教书..");
}
}
定义测试类
public class Test {
public static void main(String[] args) {
//创建子类Student对象
Student student = new Student();
//调用从父类继承的方法 和变量
student.setName("张三");
student.setAge(18);
student.showInfo(); //姓名:张三 年龄:18
//调用本类独有的方法
student.study(); //学习..
//创建子类Teacher对象
Teacher teacher = new Teacher();
//调用从父类继承的方法 和变量
teacher.setName("李四");
teacher.setAge(28);
teacher.showInfo(); //姓名:李四 年龄:28
//调用本类独有的方法
teacher.teaching(); //教书..
}
}
继承的特点
- Java只支持单继承,不支持多继承
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //ok
class C extends A,B... //error
- Java支持多层继承(继承体系)。顶层父类是Object类。所有的类默认继承Object,作为父类。
class A{}
class B extends A{}
class C extends B{}
- 子类和父类是一种相对的概念。例如:B类对于A来说是子类,但是对于C类来说是父类
- 一个父类可以同时拥有多个子类
继承的中成员变量的访问
- 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
- 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:
代码示例
/*
* 定义动物类Animal,做为父类
*/
class Animal {
// 定义name属性
private String name;
// 定义age属性
public int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
/*
* 定义猫类Cat 继承 动物类Animal
*/
class Cat extends Animal {
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
System.out.println("抓老鼠");
}
}
/*
* 定义测试类
*/
public class Test {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
//cat.name = "Tom";// 编译报错,不能直接访问父类中私有的成员
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫的catchMouse()方法
cat.catchMouse(); //抓老鼠
// 调用该猫继承来的eat()方法
cat.eat(); //2岁的null在吃东西
}
}
如果子父类中有重名的成员变量,子类中如何访问父类中的成员变量
- 当父类的成员变量私有化时,在子类中是无法直接访问的,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问。所以是否重名不影响
- 当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super."进行区别。
代码示例
class Animal {
private String name = "动物";
public int age = 118;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Cat extends Animal {
// private String name = "猫";
public int age = 18;
public void show() {
// System.out.println("种类:"+ super.name); 编译报错,父类私有的成员变量子类不能直接访问
//通过GetXxx方法访问
System.out.println("种类:" + getName()); //种类:动物
//直接访问变量名称使用的是本类中的
System.out.println("我的年龄:" + age); //我的年龄:18
// 使用super来访问父类中的非私有成员
System.out.println("我父亲的年龄是:" + super.age); //我父亲的年龄是:118
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.show();
}
}
继承的中成员方法的访问
我们说父类的所有方法子类都会继承,我们不能直接访问父类中私有的成员方法,但是可以间接访问。
class Animal {
private void introduce() {
System.out.println("我是动物老大");
}
public void info(){
introduce();
}
}
class Cat extends Animal {
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// cat.introduce(); 子类不能直接访问父类中私有的成员方法
//间接访问
cat.info(); //我是动物老大
}
}
我们也可以直接调用父类中,非私有的成员方法
class Animal {
public void eat() {
System.out.println("我是动物,要吃东西");
}
}
class Cat extends Animal {
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat(); //我是动物,要吃东西
}
}
方法重写
当子类中出现了与父类一模一样的方法时,我们称之为方法重写 (Override) 。应用场景:子类觉得父类原来的实现方法不适合于子类。比如新的手机增加来电显示头像的功能,代码如下
class Phone {
public void sendMessage() {
System.out.println("发短信");
}
public void call() {
System.out.println("打电话");
}
public void showNum() {
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum() {
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class Test {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
np.sendMessage();
// 调用子类重写的方法
np.showNum();
}
}
注意事项:
@Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
必须保证父子类之间方法的名称相同,参数列表也相同。大部分情况下,我们只要保证要重写的方法一模一样就好,除非有特殊要求。任何情况下也不能违背下面几点原则
返回值类型:
- 引用类型:子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
- 基本类型:如果返回值类型是基本数据类型和void,那么必须是相同
子类方法的访问权限必须【大于等于】父类方法的访问权限修饰符。 小扩展提示:public > protected > 缺省 > private
几种特殊的方法不能被重写
- 静态方法不能被重写
- 私有等在子类中不可见的方法不能被重写
- final方法不能被重写
继承的中构造方法的访问
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
-
构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。
代码示例
class Fu{
public Fu() {
System.out.println("我是父类中的无参数构造器");
}
}
class Zi extends Fu{
public Zi() {
System.out.println("我是Zi类中的无参数构造器");
}
}
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
/*
输出结果:
我是父类中的无参数构造器
我是Zi类中的无参数构造器
*/
}
}
如果父类没有无参构造怎么办?
class Fu{
public int number;
public Fu(int number) {
System.out.println("我是父类中的有参数构造器");
}
}
class Zi extends Fu{
public Zi() { //此时子类代码报错。
System.out.println("我是Zi类中的无参数构造器");
}
}
解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。
class Fu {
public int number;
public Fu(int number) {
System.out.println("我是父类中的有参数构造器: " + number);
}
}
class Zi extends Fu {
public Zi() {
super(1); //用super(实参列表),显示调用父类的有参构造解决
System.out.println("我是Zi类中的无参数构造器");
}
}
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
/*
输出结果:
我是父类中的有参数构造器: 1
我是Zi类中的无参数构造器
*/
}
}