文章目录
- 一、 继承
- 1. 为什么要继承
- 2. 如何继承
- 3. 情况一:子父类成员变量重名
- 4. 情况二:子父类成员方法重名
- 5. 子父类构造方法问题
- 6. 继承中代码块调用顺序
- 7. protected关键字
- 7. 继承方式
- 8. final关键字
- 9. 继承和组合
一、 继承
1. 为什么要继承
假设一种场景,你是一个富二代,你继承了家里的遗产,这样你就获得了父辈的遗产
而你作为下一代,就继承了上一代的财产
又比如你是一个大学生,首先最基础的你是人,其次你是学生,再其次你才是大学生
那当我是学生这一层身份的时,我继承了人的属性,那我还需要再定义一遍人的属性吗,不用
那进一步当我是大学生这一层身份的时候,我继承了学生的属性,我还需要把学生和人的属性再定义一遍吗,不用
-
这就是继承的意义:避免冗余代码,把共同属性组织起来,实现代码复用
-
父类(基类,超类):继承的上一代,子类(派生类):继承的下一代
对于子类间共有的属性,我们可以抽象出来,组成一个共同的父类,而子类则可以在父类原有的基础上进行扩展延伸
就比如学生类,我是在人这个父类进行拓展延伸的,定义了一些学生才有的属性,比如年级,学校等等
- 既然是继承,那是不是可以调用父类的方法和属性呢,答案是可以的
- 而你继承后不能完全跟父类一样吧,因此需要添加独属于子类特有的属性和方法
2. 如何继承
格式:定义类的时候,通过
extends (你要继承的类名)
关键字
举个例子,我定义了一个“人”类,同时定义了一个“学生”类,我让“学生”类继承“人”类
然后在学生类中输出学生类的成员变量和父类中的成员变量//“人”类文件 public class Person {public int age = 18;public double high = 180.5;public char sex = '男'; }//“学生”类文件 public class Student extends Person{public String colleague = "清华大学";//学校...public String subject = "Java编程语言";//主修学科...public int stage = 1;//大一、大二...@Overridepublic String toString() {return "Student{" +"colleague='" + colleague + '\'' +", subject='" + subject + '\'' +", stage=" + stage +", sex=" + sex +'}';} }//测试类文件 public class Try {public static void main(String[] args) {Student stu = new Student();System.out.println(stu);} }
我们可以看到当我们去输出“学生”类的时候,因为继承了“人”类
我在输出类成员方法的时候是可以输出“人”类中的成员变量(性别是男)
- 但是倘若成员变量或者方法命名不规范,会出现一些问题
3. 情况一:子父类成员变量重名
比如我在刚刚代码基础上在“学生”这个类中添加与“人”类相同的成员变量
char sex = '女'
当我输出类的成员变量的时候,我输出的是“人”类中的性别还是“学生”类的性别
很显然是子类(“学生”类的变量),因此我们得出一条结论:当父子类成员变量重名,优先访问子类
但是你就是倔,就想要访问父类的怎么办,Java提供了super关键字,顾名思义就是超级访问权限
格式:super.父类的成员变量
举个例子,我们将“学生”类中输出成员变量的部分稍微做了下改变,对于性别加了个super关键字@Overridepublic String toString() {return "Student{" +"colleague='" + colleague + '\'' +", subject='" + subject + '\'' +", stage=" + stage +", sex=" + super.sex +'}';}
我们再看打印结果,这就符合我们的预期了
还有一种情况,就是如果子类中没有你想要找的成员变量,那么就会在父类中寻找,如果父类没有就会报错
就拿我们之前的代码举例,我们在测试类中尝试打印以下成员变量public class Try {public static void main(String[] args) {Student stu = new Student();System.out.println(stu.colleague);System.out.println(stu.high);System.out.println(stu.money);} }
发现我们找不到money
这个成员变量,我们把其注释掉再找执行一次代码
发现不管是子类还是父类中的成员变量都访问到了,因此寻找的顺序是:
子类优先 --> 子类不存在 --> 父类其次 —> 父类不存在 --> 报错
如果你使用this关键字访问当前对象的成员变量,默认访问的还是子类中的
以下是图解
this
关键字和super
关键字
4. 情况二:子父类成员方法重名
- 假如你虽然重名,但是参数列表不同,构成了重载,在实例化对象的时候会自动根据传的参数来匹配子类或者是父类的成员方法
//“人”类中定义了一个方法
public void eat (){System.out.println("人正在吃饭");}//“学生”类中定义了一个方法
public void eat(String colleague){System.out.println(colleague+"的大学生正在吃饭");}//测试类中定义了一个方法
public static void main(String[] args) {Student stu = new Student();stu.eat("北京大学");stu.eat();}
我们可以看到虽然都是同一个“学生”类,但是我访问成员方法的时候的参数列表不同,访问的结果也不一样
- 那假如子父类成员方法重名且参数列表相同,则优先访问子类中的成员方法
//在“人”类中定义了一个成员方法
public void sleep(){System.out.println("人在睡觉");}//在“学生”类中定义了一个相同的成员方法
public void sleep(){System.out.println("大学生}//在测试类中再调用
stu.sleep();
我们可以看到优先访问的是子类,若果想访问父类,则利用super关键字
:stu.super.sleep
注意:只能在非静态方法中使用,切记切记!!!
public void sleep(){super.sleep();System.out.println("大学生在睡觉");}
5. 子父类构造方法问题
若在父类中定义了构造方法,子类继承了父类,在子类完成构造前,先要帮父类进行构造,不然报错
//“人”类中定义构造方法
public class Person {public int age;public double high;public char sex;Person(int age,double high,char sex){this.age = age;this.high = high;this.sex = sex;}
}//“学生”类中定义构造方法
public class Student extends Person{public String colleague;//学校...public String subject;//主修学科...public int stage;//大一、大二...public char sex;Student(String colleague,String subject,int stage,char sex){this.colleague = colleague;this.subject = subject;this.stage = stage;this.sex = sex;}
}//测试方法中实例化对象
Student stu = new Student("北京大学","Java数据结构",2,'男');
直接报错,提示没有给父类应给的类型,因此我们要在子类构造方法中初始化父类中的成员变量
//“人”类构造方法
public Person(int age,double high,char sex){this.age = age;this.high = high;this.sex = sex;
}//“学生”类构造方法,注意super关键字传的内容
public Student(String colleague,String subject,int stage,char sex){super(18,180.5,'男');this.colleague = colleague;this.subject = subject;this.stage = stage;this.sex = sex;
}//测试类方法中实例化对象
Student stu = new Student("北京大学","Java数据结构",2,'男');
- 若父类和子类都没写构造方法,就像我们只看演示一样,都没写构造方法,为什么不会报错呢
- 因为Java会默认添加无参构造方法,可以自己都写上调试下,这里就不过多演示了
6. 继承中代码块调用顺序
我们先给出示例代码,我实例化类两个子类的对象
//Person类
public class Person {public int age;public double high;public char sex;static{System.out.println("Person类的静态代码块被调用");}{System.out.println("Person类的构造代码块被调用");}public Person(int age,double high,char sex){this.age = age;this.high = high;this.sex = sex;System.out.println("Person类构造方法被调用");}
}//Student类
public class Student extends Person{public String colleague;//学校...public String subject;//主修学科...public int stage;//大一、大二...public char sex;static{System.out.println("Student类的静态代码块被调用");}{System.out.println("Student类的构造代码块被调用");}public Student(String colleague,String subject,int stage,char sex){super(18,180.5,'男');this.colleague = colleague;this.subject = subject;this.stage = stage;this.sex = sex;System.out.println("Student类构造方法被调用");}
}//测试类,注意我实例化类两个对象
public class Try {public static void main(String[] args) {Student stu = new Student("北京大学","Java数据结构",2,'男');System.out.println("============================");Student stus = new Student("浙江大学","Java微服务",3,'男');}
}
- 通过观察我们看到静态代码块最先被调用,并且是先父类再子类
- 其次是父类的构造代码块和构造方法,其次才是子类的构造代码块和构造方法
- 而且静态代码块才执行一次(这个上次讲过,不过多解释)
- 为什么,我认为:肯定静态代码块优先,重点在于构造代码块,既然子类是继承父类的
- 那实例化子类对象的时候,在子类中先进入来自父类的区域,之后在进入子类都有的区域
7. protected关键字
解释:你肯定发现在定义成员变量的时候我都是加的public关键字,指的是哪里都可以访问,不管是不同类还是不同包
- 但是,这样是不是权限太大了,不安全,如果是private,只能在当前类中访问,权限又太小了
- 因此,我们定义了protected关键字,来表明这不大不小的权限
- protected规则:同个包甭管子类还是非子类都可访问,不同包继承得来的子类可以访问,非子类不可访问
- 给出示例代码
//“Person”类
protected int hands = 2;//“Student”类
protected int money = 200;//测试类
System.out.println(stu.money);
System.out.println(stu.hands);
-
但是如果是不同的包呢?由于代码过多,我采用截图的方法给大家演示
-
这里插一嘴:除了private,protected,public外,还有个default(默认),它只能在同个包中访问
7. 继承方式
- 单继承(继承一个)
- 多层继承(传宗接代)
- 不同类继承自同一个类(几个孩子有个共同父亲)
它们各有的性质我相信你都应该大致了解类,在Java中没有菱形继承(一个子类继承两个父类)
8. final关键字
你可以把它理解成C语言的const,如果加在变量
如果加载类前面,表示不可被继承
而且Srting类也是一个final类
9. 继承和组合
- 继承:B继承与A,那B就是A,换句话来说继承后拥有了被继承方全部内容
- 组合:把继承拆开,把各个部分拆成一个一个模块,到时候再组合起来
- 为什么推崇组合?
- 我们把每一个成员方法都写成一个类,再在其他的一个大的类中将这些小的类组合起来,相比于继承每个类之间都联系紧密,组合让结构更加松散,这样不管哪个类修改,都不会影响到其他类,更灵活
- 假设一个场景,你定义了个动物的类,类中有个
run()
方法,你派生出三个类:狗、猫、鱼,此时狗和猫继承了动物这个类,拥有run()
这个方法,但是你的鱼也有了run()
这个方法,合理吗?不合理- 如果采用组合。我们就把各个方法定义成一个个小的类,我们在派生子类的时候根据子类的情况把各个小的类适当选择然后组合,这样就可以避免比如上面的鱼具有
run()
这个方法的尴尬- 组合最大的好处:避免了牵一发而动全身的痛苦