继承extends(也叫扩展)
引入
首先写两个类:
//定义学生类
class Student {
//成员变量
private String name;
private int age;
//空构造
public Student(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
//定义教师类
class Teacher {
//成员变量
private String name;
private int age;
//空构造
public Teacher(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
我们观察上面两个类代码:
发现name,age成员变量,以及getXxx()/setXxx(),还有eat()等都是相同的。如果我们后来继续定义类,比如,工人类,运动员类。他们肯定也具备这些内容。那么,我们每一次定义这样的类的时候,都要把这些重复的内容都重新定义一遍。
太麻烦,重复的代码太多,所以,要考虑改进
如何改进呢?
能不能把这些相同的内容给定义到一个独立的类中。然后,让这多个类和这个独立的类产生一个关系,有了这个关系后,这多个类就可以具备这个独立的类的功能。
为了实现这个效果,Java提供了一个技术:继承(也叫“扩展”)
举例:
父亲:4个儿子
继承怎么表示呢?继承的格式是什么样子的呢?
class Father {...} //定义一个Father类,被继承的类
class Son extends Father { //定义一个Son类继承(或叫扩展)Father类
}
修改我们的代码:
将Student,Teacher类共同的东西抽取出来,单独放到一个类中,叫Person
class Person {
String name;
int age;
public Person(){}
//getXxx()/setXxx()
public void eat() {
System.out.println("吃饭");
}
}
//自定义类继承自抽象出来的Person类,这样,Person中定义的方法就不用再次定义了
class Student extends Person {
//空参构造方法,构造方法不能被继承,所以子类的构造还是需要定义
public Student(){}
//子类在继承的基础上,再增加自己特有的方法
public void study(){}
}
class Teacher extends Person {
public Teacher(){}
public void teach(){}
}
继承概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
通过extends关键字可以实现类与类的继承,继承的格式如下:
class 子类名 extends 父类名 {}
单独的这个类,也就是被继承的类,称为父类,基类或者超类;这多个类称为子类或者派生类。
有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
继承的案例和继承的好处
通过一个具体案例来演示代码
案例1:学生类和教师,定义两个功能(吃饭,睡觉)
案例2:加入人类后改进
/*
继承概述:
把多个类中相同的内容给提取出来定义到一个类中。
格式:
class 子类名 extends 父类名 {}
好处:
A:提高了代码的复用性
B:提高了代码的维护性
C:让类与类之间产生了关系,是多态的前提
*/
//使用继承前
/*
class Student {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Teacher {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
*/
//使用继承后
class Person {
public void eat() {
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉");
}
}
class Student extends Person {}
class Teacher extends Person {}
class ExtendsDemo {
public static void main(String[] args) {
Student s = new Student();
s.eat();
s.sleep();
System.out.println("-------------");
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
继承的好处
提高了代码的复用性
多个类相同的成员可以放到同一个类中
提高了代码的维护性
如果功能的代码需要修改,修改一处即可,继承的类中自动都被修改了
让类与类之间产生了关系,是多态的前提(后面讲)
Java中继承的特点
- Java只支持单继承,不支持多继承。
即:一个类只能有一个直接父类,不可以有多个直接父类。
class SubDemo extends Demo{} //ok
class SubDemo extends Demo1,Demo2...//error不能继承多个类
- Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
/*
class Father {}
class Mother {}
class Son exnteds Father {} //正确的
class Son extends Father,Mother {} // 错误的,不能多继承
*/
class GrandFather {
public void show() {
System.out.println("GrandFather");
}
}
class Father extends GrandFather {
public void method(){
System.out.println("Father");
}
}
class Son extends Father {}
class ExtendsDemo2 {
public static void main(String[] args) {
Son s = new Son();
s.method(); //使用从Father继承的方法
s.show(); //使用从Father继承的方法
}
}
Java中继承的注意事项
子类只能继承父类所有非私有的成员(成员方法和成员变量)
子类不能继承父类的构造方法,但是可以通过super(后面讲)关键字去访问父类构造方法。
不要为了部分功能而去继承
我们到底在什么时候使用继承呢?
继承中类之间体现的是:"is a"的关系。
/*
继承的注意事项:
A:子类只能继承父类所有非私有的成员(成员方法和成员变量)
B:子类不能继承父类的构造方法,但是可以通过super(马上讲)关键字去访问父类构造方法。
C:不要为了部分功能而去继承
class A {
public void show1(){}
public void show2(){}
}
class B {
public void show2(){}
public void show3(){}
}
//我们发现B类中出现了和A类一样的show2()方法,所以,我们就用继承来体现
class B extends A {
public void show3(){}
}
这样其实不好,因为这样你不但有了show2(),还多了show1()。
有可能show1()不是你想要的。
那么,我们什么时候考虑使用继承呢?
继承其实体现的是一种关系:"is a"。
Person
Student
Teacher
水果
苹果
香蕉
橘子
采用假设法。
如果有两个类A,B。只有他们符合A是B的一种,或者B是A的一种,就可以考虑使用继承。
*/
class Father {
private int num = 10;
public int num2 = 20;
//私有方法,子类不能继承
private void method() {
System.out.println(num);
System.out.println(num2);
}
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
class Son extends Father {
public void function() {
//num可以在Father中访问private
//System.out.println(num); //子类不能继承父类的私有成员变量
System.out.println(num2);
}
}
class ExtendsDemo3 {
public static void main(String[] args) {
// 创建对象
Son s = new Son();
//s.method(); //子类不能继承父类的私有成员方法
s.show();
s.function();
}
}
继承中成员变量的关系
案例演示
子父类中同名和不同名的成员变量
子类方法中寻找变量的顺序
/*
类的组成:
成员变量:
构造方法:
成员方法:
而现在我们又讲解了继承,所以,我们就应该来考虑一下,类的组成部分的各自关系。
继承中成员变量的关系:
A:子类中的成员变量和父类中的成员变量名称不一样,这个太简单。
B:子类中的成员变量和父类中的成员变量名称一样,这会出现什么情况呢?
在子类方法中访问一个变量的查找顺序:
a:在子类方法的局部范围找,有就使用
b:在子类的成员范围找,有就使用
c:在父类的成员范围找,有就使用
d:如果还找不到,就报错。
*/
class Father {
public int num = 10;
public void method() {
int num = 50;
}
}
class Son extends Father {
public int num2 = 20;
public int num = 30;
public void show() {
int num = 40;
System.out.println(num);
System.out.println(num2);
// 找不到符号
System.out.println(num3);
}
}
class ExtendsDemo4 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();
}
}
结论
在子类方法中访问一个变量
首先在子类局部范围找,也就是方法内部
然后在子类成员范围找,也就是子类的成员变量
最后在父类成员变量范围找(肯定不能访问到父类局部范围)
如果还是没有就报错。
super关键字
super的用法和this很像
this 代表本类对象的引用
super 代表父类存储空间的标识(可以理解为父类对象的引用)
用法(this和super均可如下使用)
- 访问成员变量
this.成员变量
super.成员变量(访问父类的成员变量,不能访问父类的private变量)
访问静态成员时,也可以用:父类名.静态成员
- 访问构造方法(子父类的构造方法问题再讲)
this(…) super(…) //
- 访问成员方法(子父类的成员方法问题再讲)
this.成员方法() super.成员方法()
/*
问题是:
我不仅仅要输出局部范围的num,还要输出本类成员范围的num。怎么办呢?
我还想要输出父类成员范围的num。怎么办呢?
如果有一个东西和this相似,但是可以直接访问父类的数据就好了。
恭喜你,这个关键字是存在的:super。
this和super的区别?
this代表本类对应的引用。
super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)
A:调用成员变量
this.成员变量 调用本类的成员变量
super.成员变量 调用父类的成员变量
B:调用构造方法
this(...) 调用本类的构造方法
super(...) 调用父类的构造方法
C:调用成员方法
this.成员方法 调用本类的成员方法
super.成员方法 调用父类的成员方法
*/
class Father {
public int num = 10;
}
class Son extends Father {
public int num = 20;
public void show() {
int num = 30;
System.out.println(num); //30
System.out.println(this.num); //20
System.out.println(super.num); //10
}
}
class ExtendsDemo5 {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
继承中构造方法的关系
1.子类中所有的构造方法默认都会访问父类中空参数的构造方法,除非显式使用super/this调用了父类或者是本类的其他构造方法。
2.在类中对本类或者是父类构造方法的调用,只能是在构造方法中,不能在实例方法中调用构造方法(更不能在类方法中调用构造方法),原因:
- 实例方法被调用时,说明实例对象已经被创建完了,此时不能再使用this/super去初始化本实例或者是父类实例
- 类方法是在本类加载的时候就已经加载的,这时实例对象还没有被创建出来,是不能使用this或者super的
class A {
public A(int i){
this(1 ,2);//
}
public A(int a ,int b){
System.out.println("hello");//默认在这行之上,调用super();父类的空参构造
}
}
因为子类会继承父类中的数据(成员变量),可能还会使用父类的数据。
所以,子类初始化之前,一定要先完成父类数据的初始化。
换句话说,一个对象的创建意味着它的所有的父类都会被创建出来。
子类构造方法的第一条语句:
如果是this(...)表明调用的是本类的另一个构造方法,在另一个构造方法中还可以继续使用this(...)调用本类其他的构造方法,如果有多个构造方法的话,可以继续调用下去,但是不能递归调用,最终总会有一个构造方法,第一条语句不是this()了。
这时,可以显式的调用父类的构造方法super(...);或者什么都不写,这时系统默认调用父类的空参构造方法。
总之,当子类对象被创建的时候,总是会先调用父类的构造方法,直到Object这个最上层的对象被创建出来之后,其下的子类对象才会被创建出来。
构造方法不能递归调用
class A {
public A(int i){
this(1 ,2);
}
public A(int a ,int b){
this(2);
}
}
/*
继承中构造方法的关系
子类中所有的构造方法默认都会访问父类中空参数的构造方法
注意:子类每一个构造方法的第一条语句默认都是:super();除非显式this/super
*/
class Father {
int age;
public Father() {
System.out.println("Father的无参构造方法");
}
public Father(String name) {
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
//super();
System.out.println("Son的无参构造方法");
}
public Son(String name) {
//super();
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo6 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
System.out.println("------------");
Son s2 = new Son("tom");
}
}
考察父类,子类代码块的执行顺序
从这个案例中能看到有继承关系的类的实例初始化的过程,以及为什么在类方法中不能使用this或者是super关键字
class Father{
static{
System.out.println("Father类的静态代码块");
}
{
System.out.println("Father类的构造代码块1");
}
public Father(){
System.out.println("Father的无参构造方法");
}
public Father(int x){
System.out.println("Father的有参构造方法");
}
{
System.out.println("Father类的构造代码块2");
}
}
class Son extends Father{
static{
System.out.println("Son类的静态代码块");
}
{
System.out.println("Son类的构造代码块1");
}
public Son(){
System.out.println("Son的无参构造方法");
}
public Son(int x){
System.out.println("Son的有参构造方法");
}
{
System.out.println("Son类的构造代码块2");
}
}
class TestBlock{
public static void main(String[] args){
Son s = new Son();
}
}
如果父类中没有空参构造方法,怎么办?
1.子类可以通过super去显式调用父类其他的带参的构造方法
2.子类可以通过this去调用本类的其他构造方法,但是本类其他构造也必须首先用super(...)访问了父类的带参构造(因为父类没有空参构造)
总结
如果父类没有空参构造,子类的构造方法中就必须显式调用父类带参构造super(...);
一定要注意:
super(…)或者this(…)必须出现在构造方法第一条语句上
否则,就会有父类数据的多次初始化
/*
如果父类没有无参构造方法,那么子类的构造方法会出现什么现象呢?
报错。
如何解决呢?
A:在父类中加一个无参构造方法
B:通过使用super关键字去显示的调用父类的其他带参构造方法
C:子类通过this去调用本类的其他构造方法
子类中一定要有一个去访问了父类的构造方法,否则父类数据就没有初始化。
注意事项:
this(...)或者super(...)必须出现在第一条语句上。
如果不是放在第一条语句上,就可能对父类的数据进行了多次初始化,所以必须放在第一条语句上。
*/
class Father {
/*
public Father() {
System.out.println("Father的无参构造方法");
}
*/
public Father(String name) {
System.out.println("Father的带参构造方法");
}
}
class Son extends Father {
public Son() {
super("随便给");
System.out.println("Son的无参构造方法");
//super("随便给");
}
public Son(String name) {
//super("随便给");
this();
System.out.println("Son的带参构造方法");
}
}
class ExtendsDemo7 {
public static void main(String[] args) {
Son s = new Son();
System.out.println("----------------");
Son ss = new Son("tom");
}
}