Java_继承
概述和格式
面向对象的三大特征:封装,继承,多态。
继承是Java中一般到特殊的关系,是一种子类到父类的关系。
例如:学生类继承了人类。 猫类继承了动物类。被继承的类称为:父类/超类。
继承父类的类称为:子类。继承的作用?
“可以提高代码的复用”,相同代码可以定义在父类中。然后子类直接继承父类,就可以直接使用父类的这些代码了。(相同代码重复利用)子类更强大:子类不仅得到了父类的功能,它还有自己的功能。
继承的特点:
子类继承了一个父类,子类就可以直接得到父类的属性(成员变量)和行为(方法)了。继承的格式:
子类 extends 父类{}
小结:
继承是子类到到父类的一种关系。
子类继承了一个父类,子类就可以直接得到父类的属性和行为了。
在Java中继承是 “is a” 的关系。Cat extends Animal:猫是一个动物。
在Java中,子类是更强大的,子类不仅继承了父类的功能,自己还可以定义自己的功能。
class animal{
}
class cat extends animal{
}
继承使用案例
案例(教务系统)
学生类(姓名,年龄,吃饭,特有功能:学习)
老师类(姓名,年龄,吃饭,特有功能:授课)
班主任(姓名,年龄,吃饭,特有功能:管理)问题:
如果直接定义类会出现大量相同属性相同行为的重复代码。
企业开发中不允许太多冗余代码。
解决思路:
把相同的属性和行为定义在一个父类中,然后让子类继承即可。父类:People类(姓名,年龄,吃饭)
学生类(特有功能:学习)
老师类(特有功能:授课)
班主任(特有功能:管理)小结:
继承的优势可以把相同的代码定义在父类中,子类可以直接继承使用。
这样就可以提高代码的复用性:相同代码只需要在父类中写一次就可以了。
定义People类
public class People {
private String name;
private int age ;
public void eat(){
System.out.println(name + ",在吃饭!");
}
public People() {
}
public People(String name, int age) {
this.name = name;
this.age = 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 String toString() {
return "People{name = " + name + ", age = " + age + "}";
}
}
定义老师类
public class Teacher extends People {
//特有功能
public void teach(){
System.out.println(getname() + "老师授课");
}
}
定义实现的main
public class TestMain {
public static void main(String[] args) {
Teacher boNiu = new Teacher();
boNiu.setName("播妞"); // 调用继承自父类的
boNiu.setAge(18); // 调用继承自父类的
System.out.println(boNiu.getName()); // 调用继承自父类的
System.out.println(boNiu.getAge()); // 调用继承自父类的
boNiu.eat(); // 调用继承自父类的
boNiu.teach(); // 调用子类自己的!
}
}
子类不能继承的成员
- 子类继承父类,子类就得到了父类的属性和行为。
但是并非所有父类的属性和行为等子类都可以继承。- 子类不能继承父类的东西:
1. 子类不能继承父类的构造器:子类有自己的构造器。
2. 有争议的观点(拓展):
- 子类是否可以继承父类的私有成员?
子类是可以继承父类的私有成员的,只是不能直接访问而已。
– 以后可以暴力去访问继承自父类的私有成员~~~- 子类是否可以继承父类的静态成员?
– 我认为子类是不能继承父类的静态成员的,
– 子类只是可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问。 共享并非继承。
- 子类和父类构造器是分开的,子类不能继承父类构造器。
- 子类可继承父类私有成员,但不能直接访问。
- 子类不能继承父类的静态成员,子类只是可以访问。
成员变量的继承
就近原则:
子类有找子类,子类没有找父类,父类没有就报错。小结:
this代表了当前对象的引用,可以用于访问当前子类对象的成员变量。
super代表了父类对象的引用,可以用于访问父类中的成员变量。
public class TestDemo {
public static void main(String[] args) {
Cat cat = new Cat();
cat.show();
}
}
class Animal{
public String name = "动物名称";
}
class Cat extends Animal{
public String name = "子类名称";
public void show(){
String name = "局部名称";
System.out.println(name); // 局部名称
System.out.println(this.name); // 子类名称
System.out.println(super.name); // 父类名称
// System.out.println(name2); // 报错了!
}
}
成员方法的继承
就近原则:
子类有找子类,子类没有找父类,父类没有就报错。
小结:
子类对象优先使用子类已有的方法。
public class Main {
public static void main(String[] args){
Cat cat = new Cat();
Cat.eat();
animal.run();
}
public static class animal{
public static void run(){
System.out.println("动物在跑。。。");
}
public static void eat(){
System.out.println("动物在吃...");
}
}
static class Cat extends animal{
public static void eat(){
System.out.println("小猫在不停的吃");
}
}
}
方法重写
方法重写的概念:
子类继承了父类,子类就得到了父类的某个方法
但是子类觉得父类的这个方法不好用或者无法满足自己的需求
子类重写一个与父类申明一样的方法来覆盖父类的该方法,子类的这个方法
就进行了方法重写。方法重写的校验注解: @Override
Java建议在重写的方法上面加上一个@Override注解。
方法一旦加了这个注解,那就必须是成功重写父类的方法,否则报错!
@Override优势:可读性好,安全,优雅!!
方法重写的具体要求:
1.子类重写方法的名称和形参列表必须与父类被重写方法一样。
2.子类重写方法的返回值类型申明要么与父类一样,要么比父类方法返回值类型范围更小。(以后再了解)
3.子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者更大。(以后再了解)
4.子类重写方法申明抛出的异常应该与父类被重写方法申明抛出的异常一样或者范围更小!(以后再了解)方法重写的规范:
1. 加上@Override注解。
2 .建议“申明不变,重新实现”。小结:
方法重写是子类重写一个与父类申明一样的方法覆盖父类的方法。
方法重写建议加上@Override注解。
方法重写的核心要求:方法名称形参列表必须与被重写方法一致!!
建议“申明不变,重新实现”。
public class ExtendsDemo {
public static void main(String[] args) {
Wolf w = new Wolf();
w.run();
}
}
class Wolf extends Animal{
// 进行了方法重写!!
// 子类重写方法的名称和形参列表必须与父类被重写方法一样
// 子类重写方法的返回值类型申明要么与父类一样,要么比父类方法返回值类型范围更小
// 子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者更大
@Override
public void run(){
System.out.println("🐺跑的贼快~~~");
}
}
class Animal{
public void run(){
System.out.println("动物可以跑步~~~");
}
}
静态和私有方法的重写 都不可以
Super访问父类方法
super:代表了父类引用。
super可以用在子类的实例方法中调用父类被重写的方法。
static class People extends Animals{
@Override
public void eat(){
System.out.println("I'm eating dinner");
}
public void eatrice(){
super.eat();
}
}
}
一般重新在子类中定义一个方法,super.父类的方法
调用。
继承后的构造器
特点:
子类的全部构造器默认一定会先访问父类的无参数构造器,再执行子类自己的构造器。
为什么子类构造器会先调用父类构造器?
1.子类的构造器的第一行默认有一个super()调用父类的无参数构造器,写不写都存在!
2.子类继承父类,子类就得到了父类的属性和行为。
当我们调用子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为啊。
public class Main {
public static void main(String[] args){
student st = new student();
}
static class teacher{
public teacher(){
System.out.println("父类的构造");
}
}
static class student extends teacher{
public student(){
System.out.println("子类的构造");
}
}
}
在执行子类对象的时候,先执行父类的构造,原因是在子类第一行代码有定义super();
super调用父类构造器
子类构造器的第一行可以写一个:super(…)调用父类的指定构造器
小结:
子类构造器可以通过super(…)根据参数去调用父类构造器以便初始化继承自父类的成员变量数据!!
首先,定义父类,定义有参构造器
public static class Animal{
private String name;
private int age;
private char sex;
public Animal(){ //无参构造器
}
public Animal(String name,int age,char sex){ //有参构造器
this.name = name;
this.age = age;
this.sex = sex;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public String toString() {
return "Animal{name = " + name + ", age = " + age + ", sex = " + sex + "}";
}
}
然后,定义子类,以及子类构造器,用super();
将父类有参构造器的内容传进来。
public static class Monkey extends Animal{
public Monkey(String name, int age, String sex) {
super(name,age,sex);
}
public void eatBanana(){
System.out.println(getName()+ "-->" + getAge() + "-->" + getSex() + "在吃香蕉");
}
}
过程如图所示:
super调用父类构造器内存图
This和Super的总结
- this代表了当前对象的引用(继承中指代子类对象):
this.子类成员变量。
this.子类成员方法。- this(…):可以根据参数匹配访问本类其他构造器。
- super代表了父类对象的引用(继承中指代了父类对象空间)
super.父类成员变量。
super.父类的成员方法。
super(…):可以根据参数匹配访问父类的构造器。拓展:this(…)根据参数匹配访问本类其他构造器。
注意:
this(…)借用本类其他构造器。
super(…)调用父类的构造器。
this(…)和super(…)必须放在构造器的第一行,否则报错!
所以this(…)和super(…)不能同时出现在构造器中!!!
问题:创建一个学生类,以及3个参数的构造器,想要达到调用两个参数,第三个不调用但默认有值的情况。
class Student{
private String name ;
private int age ;
private String schoolName ;
public Student() {
}
public Student(String name , int age){
// 借用兄弟构造器的功能!
this(name , age , "黑马");
}
public Student(String name, int age, String schoolName) {
this.name = name;
this.age = age;
this.schoolName = schoolName;
}
}
public static void main(String[] args) {
// 需求:希望如果不写学校默认就是”黑马“!
Student zbj = new Student("天蓬元帅", 1000 );
System.out.println(zbj.getName());
System.out.println(zbj.getAge());
System.out.println(zbj.getSchoolName());
Student swk = new Student("齐天大圣", 2000, "清华大学" );
System.out.println(swk.getName());
System.out.println(swk.getAge());
System.out.println(swk.getSchoolName());
}
这种情况就要用this调用兄弟构造器。
主要代码:
public Student(String name , int age){
// 借用兄弟构造器的功能!
this(name , age , "黑马");
}
public Student(String name, int age, String schoolName) {
this.name = name;
this.age = age;
this.schoolName = schoolName;
}
}
在两个参数构造器,用this使用三个参数的构造器,提供默认值。
继承的特点
- 单继承:一个类只能继承一个直接父类。 (代码反证)
- 多层继承:一个类可以间接继承多个父类。(家谱)
- 一个类可以有多个子类。
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类!!
引用类型
作为方法的参数和返回值
引用类型作为Java的数据类型,自然可以作为方法的参数类型和返回值类型。
除了基本数据类型都是引用数据类型了。小结:
引用类型作为数据类型可以在一切可以使用类型的地方使用!!
public static void go(Dog a){
System.out.println("比赛开始。。。");
a.run(); //方法回调
System.out.println("比赛结束。。。");
}
调用顺序:
创建对象的一般创建方法,在里面可以初始化,这是封装的功能。
// 引用类型作为方法的返回值:创建一个狗对象返回!
public static Dog createDog(){
// Dog taiDi = new Dog();
// return taiDi;
return new Dog();
}
作为成员变量的类型
- 定义address类
public static class Address{
private String code;
private String name;
private double x;
private double y;
public Address(){
}
public Address(String code,String name,double x,double y){
this.code = code;
this.name = name;
this.x = x;
this.y = y;
}
}
- 定义学生类
public static class Student{
private String name;
private int age;
private Address address; //定义地址类型的变量
public Student() {
}
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
}
- main函数中的使用,地址不能直接调用要用对象调用!!!
public static void main(String[] args){
Student westrong = new Student();
westrong.setName("沐鑫");
westrong.setAge(22);
Address addr = new Address("111","222",20,20);
westrong.setAddress(addr);
Address addr1 = westrong.getAddress();
System.out.println(addr1.getCode() + "->" + addr1.getName());
System.out.println(westrong.getName());
System.out.println(westrong.getAge());
}
笔记鸣谢
https://www.bilibili.com/video/BV1TE41177mP?p=29&spm_id_from=pageDriver&vd_source=eddd1a6c8490ec4358db736f38ae13e6