尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JavaSE 基础语法 - 继承 - ②

JavaSE 基础语法 - 继承 - ② 接上节 JavaSE 基础语法 - 继承 - ① 的内容。子类不仅可以拥有父类的成员变量也可以拥有父类的方法。那么新的问题来了。如果子类自己也有String name 这时候怎么办如果子类自己也有eat()又怎么办如果子类构造对象的时候需要先初始化父类应该怎么办Java 为了解决这些问题引入了super关键字。8、为什么需要 super第一次学习继承的时候应该都有一个疑惑子类已经继承了父类为什么还需要 super其实super的出现并不是为了增加一种新的语法而是为了解决继承过程中成员冲突和父类初始化的问题。8.1 子类为什么需要访问父类假设有一个 Person 类classPerson{Stringname张三;publicvoideat(){System.out.println(Person 正在吃饭);}}定义 StudentclassStudentextendsPerson{publicvoidstudy(){System.out.println(学习);}}测试StudentstunewStudent();System.out.println(stu.name);stu.eat();输出张三Person正在吃饭这是没有问题的。因为 Student 没有 name 也没有 eat() 。Java自然向父类查找。但是如果 Student 自己也定义classStudentextendsPerson{Stringname李四;// 自己也定义有了name}测试StudentstunewStudent();System.out.println(stu.name);输出李四为什么因为成员变量遵循就近原则。那么问题来了如果我就是想访问 Person 里面那个 name 怎么办Java于是设计了super专门表示父类对象部分。8.2 为什么会出现成员重名再来看一个例子。PersonclassPerson{Stringname张三;}StudentclassStudentextendsPerson{Stringname李四;}对象实际上应该长这样很多同学看到这里会问一个对象里面怎么会有两个 name答案是可以。因为父类部分属于 Person 子类部分属于 Student 。它们分别存储。只是访问的时候遵循先找子类再找父类。因此 stu.name 得到李四而父类那个张三仍然存在只是访问不到所以Java设计super。表示访问父类中的 name。8.3 super 诞生的原因现在我们已经知道对象实际上包含父类部分 子类部分。因此Java需要一种方式告诉 JVM不要找子类请直接访问父类。于是有了super可以把 super 理解成父类对象部分的引用。例如super.name 表示 Person中的name不是 Student自己的name。同样 super.eat() 表示调用父类的方法。而不是子类自己的方法。另外super() 表示调用父类构造方法。所以 super 实际上有三种用途super.成员变量super.成员方法super()9、super 关键字9.1 什么是 supersuper是 Java 提供的一个关键字。它表示当前对象中的父类部分。很多人会把 super 理解成父类对象这种说法并不准确。因为程序中并没有重新创建一个父类对象。真正存在的是只有一个对象这个对象里面存着父类部分还有子类部分。所以更准确的理解super 用来引用当前对象中的父类部分。例如classPerson{Stringname张三;}classStudentextendsPerson{Stringname李四;publicvoidprint(){System.out.println(name);System.out.println(super.name);}}输出李四 张三说明name 访问子类而 super.name 访问的是父类。9.2 super 访问成员变量这是 super 最简单的用法。例如classPerson{Stringname张三;}classStudentextendsPerson{Stringname李四;publicvoidshow(){System.out.println(name);System.out.println(super.name);}}测试StudentstunewStudent();stu.show();输出李四 张三因此super 可以解决成员变量隐藏的问题。9.3 super 访问成员方法不仅成员变量可以重名成员方法也可以。例如classPerson{publicvoideat(){System.out.println(Person 吃饭);}}classStudentextendsPerson{Overridepublicvoideat(){System.out.println(Student 吃饭);}publicvoidtest(){eat();super.eat();}}测试StudentstunewStudent();stu.test();输出Student吃饭Person吃饭解释eat()调用当前类Student的实现。super.eat()调用父类Person的实现。这种写法在方法重写中非常常见例如子类希望在保留父类逻辑的基础上扩展功能Overridepublicvoideat(){super.eat();// 执行父类逻辑System.out.println(饭后散步);}9.4 super 调用父类构造方法除了访问成员变量和成员方法super 还有第三种用途调用父类构造方法例如classPerson{publicPerson(){System.out.println(Person 构造方法);}}classStudentextendsPerson{publicStudent(){super();System.out.println(Student 构造方法);}}测试newStudent();输出Person构造方法Student构造方法可以看到创建子类对象时父类构造方法一定先执行。为什么因为子类对象中包含父类部分。只有先完成父类部分初始化才能继续初始化子类部分。9.5 super 使用注意事项使用 super 时需要记住以下几点1、super 可以访问父类成员变量、成员方法和构造方法super.name;super.eat();super();2、super() 只能出现在构造方法中错误示例publicvoidtest(){// 这是一个普通的的方法不是构造方法super();}编译错误。3、super() 必须是构造方法中的第一条语句正确publicStudent(){super();System.out.println(Student);}错误publicStudent(){System.out.println(Hello);super();}编译错误。4、this() 与 super() 不能同时出现例如publicStudent(){this(Tom);super();}编译错误。原因是this() 用于调用本类其他构造方法。super() 用于调用父类构造方法。两者都必须位于构造方法第一行因此不能同时出现。10、this 与 super学习完 super 之后都会有一个疑问this 和 super 到底有什么区别例如this.name和super.name到底有什么不同再比如this();和super();为什么都必须写在构造方法第一行10.1 什么是 this在前面我们已经讲过this表示当前对象的引用。例如classStudent{Stringname张三;publicvoidprint(){System.out.println(this.name);}}这里的this.name表示当前Student对象中的name10.2 什么是 super而super表示当前对象中的父类部分。例如classPerson{Stringname父类;}classStudentextendsPerson{Stringname子类;}this.name得到的是子类而super.name得到父类10.3 this 与 super 相同点两者都有引用作用。都不能脱离对象。例如下面都可以访问成员变量this.name;super.name;都可以访问成员方法this.eat();super.eat();都可以调用构造方法this();super();因此它们都属于Java提供的特殊引用。10.4 this 与 super 不同点虽然写法非常相似。但是作用完全不同。this表示当前对象。例如this.name的查找会先在子类中查找子类没有才去父类。super.name的查找会直接在父类中查找不会在子类。因此两者访问对象不同。10.5 this 与 super 对比表对比项thissuper表示对象当前对象当前对象中的父类部分访问成员变量✔✔访问成员方法✔✔调用构造方法✔✔调用位置普通方法、构造方法普通方法、构造方法super()仅构造方法是否必须第一行this()必须super()必须是否可同时出现✘✘注意this 和 super成员访问可以同时出现publicvoidtest(){System.out.println(this.name);System.out.println(super.name);// 这样是可以同时使用}但 this() 和 super() 不能同时出现因为两者都必须位于构造方法第一行。10.6 使用场景场景一访问当前对象成员this.name场景二访问父类成员super.name场景三调用当前类其他构造方法this(张三);场景四调用父类构造方法super();这是最常见也是最重要的使用方式。11、protected 访问权限之前我们介绍了四种访问权限publicprotected默认defaultprivate但是当时并没有详细介绍protected。因为它真正发挥作用是在继承。11.1 为什么需要 protected先来看一个例子。PersonclassPerson{privateStringname;}StudentclassStudentextendsPerson{publicvoidshow(){System.out.println(name);}}编译报错因为 private 只能本类访问。如果改成protected String name;Student 立即可以访问。因为 protected 就是专门为继承设计。11.2 protected 访问规则protected 允许下面两种情况同一个包不同包例如1、都在同一个包中可以访问2、分别在不同包但是需要继承关系才可以访问3、如果分别在不同包也没有继承关系的话就不能访问。11.3 同包访问例如protectedintage;同一个包任何类都可以访问。例如StudentstunewStudent();System.out.println(stu.age);合法。11.4 不同包继承访问例如Person 位于 com.personStudent 位于 com.student只要class Student extends Person仍然可以访问age这也是 protected 最大的作用。11.5 四种访问权限总结这是Java面试经常考的。修饰符本类同包不同包子类其他包public✔✔✔✔protected✔✔✔✘default✔✔✘✘private✔✘✘✘12、构造方法我们已经讲过构造方法。那么在继承以后构造方法又会发生什么变化来看下面例子。12.1 父类构造方法classPerson{publicPerson(){System.out.println(Person);}}12.2 子类构造方法classStudentextendsPerson{publicStudent(){System.out.println(Student);}}测试newStudent();很多人猜输出Student实际上输出PersonStudent因为创建Student对象。首先必须完成Person部分初始化。因此父类构造方法一定先执行。12.3 默认 super()我们发现代码没有写super()父类构造方法还是会执行。原因Java自动补充publicStudent(){super();System.out.println(Student);}因此即使没有写仍然调用父类构造方法。12.4 显示调用 super()如果父类只有有参构造。例如classPerson{publicPerson(Stringname){}}那么必须自己写super(张三);否则编译报错。12.5 为什么 super() 必须放第一行这是Java规定的。原因只有父类初始化完成子类才能继续初始化。否则子类可能使用一个尚未初始化完成的父类成员。因此Java从语言层面规定super() 必须位于构造方法第一行。13、对象初始化过程前面我们已经知道子类对象中实际上包含两部分父类部分子类部分例如classPerson{Stringname;intage;}classStudentextendsPerson{intscore;}创建StudentstunewStudent();此时 stu 所代表的对象中可以理解为包含Person部分 name ageStudent部分 score那么问题来了创建 Student 对象时父类部分和子类部分到底谁先初始化答案是先初始化父类部分再初始化子类部分。也就是说创建Student对象 ↓ 初始化Person部分 ↓ 执行Person构造方法 ↓ 初始化Student部分 ↓ 执行Student构造方法 ↓ 对象创建完成但是真正的初始化过程比这个更加详细。13.1 成员变量默认初始化当 JVM 为对象分配内存后对象中的成员变量会先获得默认值。例如classPerson{Stringname;intage;booleanflag;}创建PersonpnewPerson();此时成员变量会先获得默认值name-nullage-0flag-false注意这里说的是成员变量。局部变量不会自动进行默认初始化。例如publicstaticvoidmain(String[]args){intage;System.out.println(age);}会编译错误。所以成员变量-有默认值 局部变量-必须手动初始化13.2 成员变量显式初始化如果成员变量在定义的时候进行了初始化classPerson{Stringname张三;intage18;}那么在默认初始化之后显式初始化按照成员变量在类中定义的顺序依次执行。可以简单理解为name →nullage →0先执行默认初始化 然后再到下边的赋值 name →张三age →1813.3 父类部分先初始化假设classPerson{StringnamePerson;publicPerson(){System.out.println(Person 构造方法);}}classStudentextendsPerson{StringschoolJava;publicStudent(){System.out.println(Student 构造方法);}}执行StudentstunewStudent();首先处理的是Person部分也就是name 默认初始化 ↓ name 显式初始化为Person↓Person构造方法然后才开始处理Student部分13.4 子类部分再初始化接下来处理 Student 部分StringschoolJava;先进行默认初始化school-null然后进行显式初始化school-Java最后执行 Student 构造方法publicStudent(){System.out.println(Student 构造方法);}所以整个过程可以理解为Person部分 默认初始化-显式初始化-Person构造方法Student部分 默认初始化-显式初始化-Student构造方法14、综合案例下面通过一个完整案例把 super、构造方法和对象初始化过程结合起来。14.1 定义 PersonclassPerson{protectedStringnamePerson;protectedintage20;publicPerson(){System.out.println(Person 无参构造方法);}publicPerson(Stringname,intage){this.namename;this.ageage;System.out.println(Person 有参构造方法);}publicvoideat(){System.out.println(Person 吃饭);}}14.2 定义 StudentclassStudentextendsPerson{privatedoublescore100;publicStudent(Stringname,intage,doublescore){super(name,age);this.scorescore;System.out.println(Student 构造方法);}Overridepublicvoideat(){super.eat();System.out.println(Student 吃饭);}publicvoidshow(){System.out.println(姓名this.name);System.out.println(年龄this.age);System.out.println(成绩this.score);}}14.3 测试publicclassTest{publicstaticvoidmain(String[]args){StudentstunewStudent(张三,18,95);stu.show();stu.eat();}}运行结果Person有参构造方法Student构造方法 姓名张三 年龄18成绩95.0Person吃饭Student吃饭14.4 分析对象创建过程执行StudentstunewStudent(张三,18,95);首先创建 Student 对象然后进入 Student 构造方法publicStudent(Stringname,intage,doublescore){super(name,age);this.scorescore;System.out.println(Student 构造方法);}第一条语句super(name,age);调用Person(Stringname,intage)所以父类部分得到name张三age18然后返回到 Student 构造方法又执行this.scorescore;得到score95最后输出Student构造方法14.5 分析 super.eat()执行stu.eat();Student 中重写了publicvoideat(){super.eat();System.out.println(Student 吃饭);}首先super.eat();调用父类Person.eat()输出Person吃饭然后执行System.out.println(Student 吃饭);输出Student吃饭最终Person吃饭Student吃饭这是一种非常典型的继承使用方式子类重写父类方法同时通过 super 保留父类原有功能再增加自己的功能。
返回列表