继承的概述
Java是对现实社会的模拟,现实中有儿子可以继承父亲的财产,因此有了富二代的。 java中多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
总结:
- 父类所有的变量和方法,是静态的还是非静态的,是私有的还是公开的,是 final 还是非 final,都会被继承到子类中。当子类继承父类后,就可以直接使用父类公共的属性和方法了
- 继承设计规范:子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。
- 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
- 在子类方法中访问成员(成员变量、成员方法)满足:就近原则
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错。
继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是多态的前提。
继承的格式
通过 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类中的无参数构造器
*/
}
}
this关键字
- 含义:this代表当前对象
this使用位置
- this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
- this在非静态实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
- this不能出现在静态代码块和静态方法中
this的三种用法
this.成员变量名
- 方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this区分成员变量和局部变量,没有重名问题,就可以省略this.
- this.成员变量会先从本类声明的成员变量列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员变量列表中查找
class Fu {
public String name ="父类的名字";
public int age =22;
}
class Zi extends Fu {
public int age = 18;
public void test() {
int age = 20;
//当方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this.
System.out.println(this.age); //18
/*
this.成员变量会先从本类声明的成员变量列表中查找,
如果未找到,会去从父类继承的在子类中仍然可见的成员变量列表中查找
*/
System.out.println(this.name); //父类的名字
}
}
public class Demo01{
public static void main(String[] args) {
Zi zi = new Zi();
zi.test();
}
}
this.成员方法
- 调用当前对象的成员方法时,都可以加"this.",也可以省略,实际开发中都省略
- 当前对象的成员方法,先从本类声明的成员方法列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员方法列表中查找
class Fu {
public void demo() {
System.out.println("我是父类可见成员方法");
}
}
class Zi extends Fu {
public void test() {
//调用当前对象的成员方法时,都可以加"this.",也可以省略,实际开发中都省略
this.show(); //张三
this.demo(); //我是父类可见成员方法
}
public void show() {
System.out.println("张三");
}
}
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.test();
}
}
this()或this(实参列表)
- 只能调用本类的其他构造器
- 必须在构造器的首行
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环
class Zi {
private String name;
private int age;
private char sex;
public Zi() {
}
public Zi(String name, int age) {
this.name = name;
this.age = age;
}
public Zi(String name, int age, char sex) {
//只能调用本类的其他构造器且必须在构造器的首行
this(name, age);
this.sex = sex;
}
}
super关键字
含义:super代表当前对象中从父类中继承的资源的引用的
super使用的前提
- 通过super引用父类的xx,都是在子类中仍然可见的
- 不能在静态代码块和静态方法中使用super
super的三种用法
super.成员变量
在子类中访问父类的成员变量,特别是当子类的成员变量与父类的成员变量重名时。
class Father{
int a = 10;
}
class Son extends Father{
int a = 20;
public void test(int a){
//在子类中访问父类的成员变量,特别是当子类的成员变量与父类的成员变量重名时。
System.out.println(super.a);//10
//访问本类的成员变量
System.out.println(this.a);//20
//访问本方法的局部变量
System.out.println(a);//30
}
}
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test(30);
}
}
super.成员方法
在子类中调用父类的成员方法,特别是当子类重写了父类的成员方法时
class Father{
public void method(){
System.out.println("父类方法");
}
}
class Son extends Father{
public void test(){
//调用子类方法
method();//子类方法
super.method();//父类方法
}
public void method(){
System.out.println("子类方法");
}
}
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
}
}
super()或super(实参列表)
在子类的构造器首行,用于表示调用父类的哪个实例初始化方法。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
class Father {
private String name;
public Father(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Son extends Father {
public Son( String name) {
super(name);//调用父类中的构造方法
}
public void show(){
System.out.println("名字:" + super.getName());
}
}
public class Test {
public static void main(String[] args) {
Son s = new Son("张三");
s.show(); //名字:张三
}
}
就近原则和追根溯源原则之找变量
没有super和this
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
- 如果不是局部变量,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类的(非private,跨包还不能是缺省的)
存在this
-
通过this找变量时,先从当前执行代码的本类中的成员变量开始找,没有的会往上找父类的(非private,跨包还不能是缺省的)。
存在super
- 通过super找成员变量,直接从当前执行代码所在类的父类找
- super()或super(实参列表)只能从直接父类找
- 通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)
注意:super和this都不能出现在静态方法和静态代码块中,因为super和this都是存在与对象中的
就近原则和追根溯源原则之找方法
没有super和this
-
先从当前对象(调用方法的对象)的本类找,如果没有,再从父类继承的可见的方法列表中查找,再没有,继续往上追溯
存在this
-
先从当前对象(调用方法的对象)的本类找,如果没有,再从父类继承的可见的方法列表中查找,再没有,继续往上追溯
存在super
-
直接从当前对象(调用方法的对象)的父类继承的可见的方法列表中查找,再没有,继续往上追溯
找构造器
- this()或this(实参列表):只从本类中,不会再往上追溯
- super()或super(实参列表):只从直接父类找,不会再往上追溯