2020.10.19收获(动手动脑四)
**************************************蓝色粗体为动手动脑内容*********************************************************
一、继承
继承可以理解为一个类从另一个类获取成员函数的过程。例如类B继承于类A,那么B就拥有A的成员变量和成员函数。被继承的类称为父类或基类,继承的类称为子类或派生类。
使用场景:
1)当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能
2)当你需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也可以方便后续修改成员。
1、对于继承过程中构造方法的理解(TestInherits.java)
package day02; class Grandparent { public Grandparent() { System.out.println("GrandParent Created."); } public Grandparent(String string) { System.out.println("GrandParent Created.String:" + string); } } class Parent extends Grandparent { public Parent() { //super("Hello.Grandparent."); System.out.println("Parent Created"); //super("Hello.Grandparent."); } } class Child extends Parent { public Child() { System.out.println("Child Created"); } } public class TestInherits { public static void main(String args[]) { Child c = new Child(); } }
当取消第二行的注释时,运行报错
结论:通过 super 调用基类构造方法,必须是子类构造方法中的第一个语句。
分析:
在创建子类的对象时,Java虚拟机首先执行父类的构造方法,然后再执行子类的构造方法。在多级继承的情况下,将从继承树的最上层的父类开始,依次执行各个类的构造方法,这可以保证子类对象从所有直接或间接父类中继承的实例变量都被正确地初始化。
思考:为什么子类的构造方法在运行之前,必须调用父类的构造方法?
构造函数(constructor)是一种特殊的方法 ,主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们,即构造函数的重载。构造函数的功能主要用于在类的对象创建时定义初始化的状态。构造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。不能反过来调用也是这个原因,因为父类根本不知道子类有什么变量,而且这样一来子类也得不到初始化的父类变量,导致程序运行出错。
2、不允许继承的类:final
- 以final声明的方法不允许覆盖
- 以final声明的变量不允许更改
- 利用final,我们可以设计出一种特殊的“只读”的“不可变类”
创建“不可变的类”的对象后,此对象的属性不可改,而且也无法从此类派生出新的子类。String就是一个典型的例子
不可变类的实例:Address.java
package day02; public final class Address { private final String detail; private final String postCode; // 在构造方法里初始化两个实例属性 public Address() { this.detail = ""; this.postCode = ""; } public Address(String detail, String postCode) { this.detail = detail; this.postCode = postCode; } // 仅为两个实例属性提供getter方法 public String getPostCode() { return this.postCode; } public String getDetail() { return this.detail; } // 重写equals方法,判断两个对象是否相等 public boolean equals(Object obj) { if (obj instanceof Address) { Address ad = (Address) obj; if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())) { return true; } } return false; } public int hashCode() { return detail.hashCode() + postCode.hashCode(); } }
4、ExplorationJDKSource.java
public class ExplorationJDKSource { public static void main(String[] args) { System.out.println(new A()); } } class A{}
运行结果:day02.A@15db9742
分析:
main方法实际上调用的是:public void println(Object x)
这一方法内部调用了 String类 的 valueOf 方法,valueOf 方法内部又调用 Object.toString 方法
public String toString() { return getClass().getName()+"@"+Integer.toHexString(hashCode()); }
hashCode方法是本地方法,由JVM设计者实现
5、方法覆盖
Fruit.java( 神奇的“+”号 )
public class Fruit { public String toString() { return "Fruit toString."; } public static void main(String args[]) { Fruit f=new Fruit(); System.out.println("f="+f); // System.out.println("f="+f.toString()); } }
最后一句,一个字串和一个对象“相加”,Fruit类覆盖了Object类的toString方法
结论:在“+”运算中,当任何一个对象与一个String对象,连接时,会隐式地调用其toString()方法,默认情况下,此方法返回“类名 @ + hashCode”。为了返回有意义的信息,子类可以重写toString()方法。
方法覆盖的要点:
(1)方法覆盖要求子类与父类的方法一模一样,否则就是方法重载
(2)覆盖方法的允许访问范围不能小于原方法
(3)覆盖方法所抛出的异常不能比原方法更多
(4)不能覆盖静态方法
(5)声明为final方法不允许覆盖