类的继承和派生

  • 继承
  • 继承的关键字
  • Object类
  • 对象实例化的内存情况
  • jvm
  • 方法区
  • 子类和父类的构造方法
  • 一个常见的错误
  • 类的派生
  • 类的派生
  • 成员变量的隐藏和方法重写
  • 属性的隐藏
  • 方法重写(覆盖)
  • 方法重载和方法重写的区别
  • 注意点
  • super
  • 构造函数
  • 调用被隐藏的成员
  • final
  • final描述成员变量
  • final描述成员方法
  • final描述类
  • 多态
  • 上转型
  • 下转型


继承

Java中的继承:子类就是享有父类的属性和方法,并且还存在一定的属性和方法的扩展。

继承的关键字

在java中,如果声明一个类继承另一个类,需要使用extends关键字。
格式修饰符 class B extends A就是B类继承A类,称A是B的父类,B是A的子类。

public  class  A {}
public   class  B  extends A{}

java 派生类调用基类函数 java类的派生_java 派生类调用基类函数


java 派生类调用基类函数 java类的派生_System_02


java语言中不支持多继承(一个类继承多个类)。

Object类

java.lang.Object类: 所有类的祖先
在定义一个类的时候不用加extends Object默认都是继承的Object类的。

class A{
}
// 相当于
class A extends Object{
}

对象实例化的内存情况

jvm

1、首先JVM运行一个class文件时,使用类加载器先将Phone类加载到方法区,然后main方法压栈(入栈)。

·2、在栈中运行main方法,当看到局部变量p时,会在栈中开辟一块空间;当看到new Phone()时,会在堆内存中开辟空间,并将堆内存中的对应地址0x123赋值给p;还会拿到方法区的地址值指向方法区。

·3、在main方法中运行到给对象p的属性赋值时,通过地址去堆内存中找到相应属性并赋值,运行p.sendMessage()这一步时,也是根据地址值去堆内存中找到相应的对象,再用对象去方法区中找到sendMessage()方法,然后将sendMessage()方法压到栈中(入栈),调用完毕sendMessage()方法会出栈。

·4、main方法运行结束后会出栈。

java 派生类调用基类函数 java类的派生_父类_03

堆区: 存储new出来的对象,每个对象都包含一个与之对应的class的信息。

栈区: 栈中只保存基础数据类型的值和对象以及基础数据的引用

方法区

方法区: 包含所有的class和static变量。方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

子类和父类的构造方法

两者执行的先后顺序:
执行顺序:先执行父类构造方法,再执行子类构造方法。在多层继承层时,编译器会一直上溯到最初类,再从“上”到“下”依次执行。

class Super {
    String s;
    public Super() {
        System.out.println("Super");
    }
}

class Sub extends Super {
    public Sub() { // 在子类中没有显示调用父类的构造方法,那么编译器就会自动地调用父类的默认的构造方法。
        System.out.println("Sub");
    }

}

public class Q4 {
    public static void main(String[] args) {
        Sub s = new Sub();
    }
}
/*
Super
Sub
*/

一个常见的错误

class Sup{
    int a;
    Sup(int a) {
        this.a = a;
        System.out.println("Sup");
    }
}
class Sub extends Sup{
    int b;
    Sub() { // 编译错误
        System.out.println("Sub");
    }
}

出现这种错误的原因是父类中的构造方法不再是默认的构造方法,因为Sup(int a)所以会出现这种情况,在这种情况下解决方法就是:

class Sup{
    int a;
    Sup(int a) {
        this.a = a;
        System.out.println("Sup");
    }
}
class Sub extends Sup{
    int b;
    Sub(int a, int b) {
        super(a); // 在子类的构造方法加上super(a);就可以避免。
        System.out.println("Sub");
    }
}

类的派生

由于子类继承父类就继承了父类所有的属性和方法,但是这种情况显然是不合适的,如何解决这个问题呢?

类的派生

可以用修饰符来将父类的某些属性或者方法进行隐藏,使得子类无法再继续进行继承这部分属性和方法。

java 派生类调用基类函数 java类的派生_java_04


若子类和父类在同一个包内,子类可以继承父类中访问权限设定为public、 protected、 default的成员变量和方法。

若子类和父类不在同一个包内,子类可以继承父类中访问权限设定为public、 protected的成员变量和方法。

成员变量的隐藏和方法重写

属性的隐藏

当子类中的属性名和父类的属性名字相同的时候,父类的该属性就会被隐藏。只要是属性名相同就会被隐藏,这一属性的类型没有什么关系。

class Main{
    public static void main(String [] args) {
        Sub sub = new Sub();
        System.out.println(sub.a);
    }
}
class Sup{
    int a = 15;
    void p() {
        System.out.println(a);
    }
}
class Sub extends Sup{
    int a = 14;
}
/*
14
*/

上述代码中的父类中的属性a被隐藏,因为在子类中又重复地定义了一遍。
子类对象在寻找方法或成员变量的时候,先搜索子类独有空间,在搜索父类空间

方法重写(覆盖)

要求:
两同:方法名相同 参数列表一致
两小:
子类返回值类型应该更小或者相等
子类的抛出的异常小于等于父类的抛出的异常类
一大:
子类的访问权限比父类访问权限要大和相等

class Sup{
    int a = 15;
    byte p() {
        System.out.println("Super");
        return 1;
    }
}
class Sub extends Sup{
    float a = 14.0f;
    int p() { // 编译报错,没有保证子类的返回值类型小于等于父类的返回值类型
        System.out.println("Super");
        return 1;
    }
}
class Sup{
    int a = 15;
    Sub p() {
        System.out.println("Super");
        Sub o = new Sub()
        return o;
    }
}
class Sub extends Sup{
    float a = 14.0f;
    Sup p() { // 编译报错,因为子类的返回值类型是小于父类的返回值类型的。
        System.out.println("Super");
        Sub o = new Sub()
        return o;
    }
}

方法重载和方法重写的区别

(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

注意点

因子类会隐藏父类的属性和重写父类的方法,那么在继承中,是如何调用:
先去子类找,能找到就使用
如果找不到,去父类中找
如果父类也找不到,就去父类的父类找…。
如果继承树上都找不到,就会报错。

(1) 变量只能被隐藏(包括静态和非静态),不能被重写

(2) 可以用子类的静态变量隐藏父类的静态变量,也可以用子类的非静态变量隐藏父类的静态变量

(3) 静态方法(static)只能被隐藏,不能被重写;

(4) 非静态方法可以被重写;

(5) 不能用子类的静态方法隐藏父类中的非静态方法,否则编译会报错;

(6) 不能用子类的非静态方法覆盖父类的静态方法,否则编译会报错;

super

构造函数

class Sup{
    int a;
    Sup(int a) {
        this.a = a;
        System.out.println("Sup");
    }
}
class Sub extends Sup{
    int b;
    Sub(int a, int b) {//super的调用:
    					// 1.super的调用必须是在子类的构造方法之中,其余的方法编译报错
    					// 2.super的调用,必须出现在方法体的第一个语句。其余出错。
    					
    	super(a); // 
        System.out.println("Sub");
    }
}

调用被隐藏的成员

class Main{
    public static void main(String [] args) {
        IntelligentPhone phone = new IntelligentPhone();
        phone.printColor();
        System.out.println(phone.color);
    }
}
class Phone{
    String color="黑色";String  name="小米";
    String  getName()  {return  name;}
}
class IntelligentPhone extends Phone{
    String color="绿色";String  name="NOTE8";
    String  getName()   {return  name;}
    void printColor()
    { System.out.println(super.color);}
    void printName()
    { System.out.println(super.getName());}
}
/*
黑色
绿色
*/

this
this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。

super

super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。

java 派生类调用基类函数 java类的派生_java_05


this还可以在当前类的一种构造方法中调用当前类的另一种构造方法

class Person {
    String name;
    int age;
    Person() {   }
    Person(String name)
    {  this.name=name;  }
    Person(String name,int age)
    { this("王二"); // 这里调用this的时候,必须保证在构造方法的第一行。
        this.age=age  ;}
}

final

final变量就是常量,而且通常常量名要大写

private final int COUNT = 10;

final描述成员变量

表示该属性一旦被初始化便不可改变(常量),这里不可改变的意思对基本类型来说是其值不可变,而对对象属性来说其引用不可再变,引用空间的值是可以改变的。
fianl修饰属性必须初始化,初始化
只能在初始化可以在两个地方:
**一是其定义处、二是在构造函数中。**两者只能选择其一,因为无法进行两次的初始化。

class A{
    final int a = 0;
    int b;
    void change() {
        a = 15; // 编译报错,因为a是被final修饰的变量
    }
}
class Main{
    public static void main(String [] args) {
        final A a = new A();
        A b = new A();
        a = b; // 编译报错,a被final修饰,无法改变a对象的引用。
    }
}
class A{
    final int a = 0;
    int b;
    void change() {
        //a = 15;
    }
}
class Main{
    public static void main(String [] args) {
        final A a = new A();
        A b = new A();
        a.b = 15; // 编译通过,因为a即使被final修饰,但是a中的变量的值仍然可以改变。
    }
}
class A{
    final int a = 0;
    int b;
    void change() {
        //a = 15;
    }
}

final描述成员方法

final 修饰类中的方法说明这种方法提供的功能已经满足当前要求,不需要进行扩展,并且也不允许任何从此类继承的类来重写这种方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。在声明类中,一个 final 方法只被实现一次。

class A {
    public final void f1() {
    }
}
public class B extends A {
    public void f1() { // 编译出错,因为f1不可以被重写
    }
    public final void f1() {
    }
    public void f2() {
        f1(); // 编译通过,final会被继承给子类,子类可以直接调用。
    }
}

final描述类

final 表示类是无法被任何其他类继承的,意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。通常叫做最终类。

final class A {
    public final void f1() {
    }
}
class B extends A { // 编译报错,因为A被final修饰,不可以成为任何类的父类。
    
}

多态

Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定。
运行时类型由实际赋给该变量的对象决定。就是new的对象。
如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

上转型

因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型,向上转型由系统自动完成。
例如:父类 对象名称=new 子类()
这种情况下,编译的时候当作父类对象看待,运行时实际是对子类对象的操作。

class Main{
    public static void main(String [] args) {
        A B1 = new B();// 该对象在编译的时候按照的是A来进行编译。在运行时实际上按照的时B
        B1.f2(); // 在A中没有方法f2,所以会在编译的时候报错。
        B1.f1();
    }
}
class A{
    int a=10;
    static int b=10;
    public void f1(){System.out.println("我是父类的f1方法");}
}
class B extends A{
    int a=100;
    static int b=100;
    public void f1()    {System.out.println("我是子类的f1方法");}
    public void f2()    {System.out.println("我是子类的f2方法");}
}

这种多态的性质只是针对方法的,对于属性并没有多态一说。

class Main{
    public static void main(String [] args) {
        A B1 = new B();
        System.out.println(B1.a);

    }
}
class A{
    int a=10;
    static int b=10;
    public void f1(){System.out.println("我是父类的f1方法");}
}
class B extends A{
    int a=100;
    static int b=100;
    public void f1()    {System.out.println("我是子类的f1方法");}
    public void f2()    {System.out.println("我是子类的f2方法");}
}

所谓的多态就是,相同类型的变量进行调用同一个方法的时候呈现出了不同的行为特征。

下转型

试图把一个父类实例转换成子类类型,则这个对象对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException(强制类型转换错误)异常。

class Main{
    public static void main(String [] args) {
        B A1 = (B)new A(); // 编译出错,因为强制转换的并不是一个实例。
        A1.f1();
        A1.f2();
    }
}
class A{
    int a=10;
    static int b=10;
    public void f1(){System.out.println("我是父类的f1方法");}
}
class B extends A{
    int a=100;
    static int b=100;
    public void f1()    {System.out.println("我是子类的f1方法");}
    public void f2()    {System.out.println("我是子类的f2方法");}
    public static void f3() {System.out.println("我是子类的f2方法");}
}
class Main{
    public static void main(String [] args) {
        A A1=new B();
        B B1 = (B)A1; // 这里强制转换的就是子类的实例。
        B1.f2();
    }
}
class A{
    int a=10;
    static int b=10;
    public void f1(){System.out.println("我是父类的f1方法");}
}
class B extends A{
    int a=100;
    static int b=100;
    public void f1()    {System.out.println("我是子类的f1方法");}
    public void f2()    {System.out.println("我是子类的f2方法");}
    public static void f3() {System.out.println("我是子类的f2方法");}
}

当你用父类引用指向子类对象的时候,
1、成员变量不变,调用结果为父类的成员变量的值
2、成员方法改变,调用结果为子类的成员方法的结果
3、静态成员方法不变,调用的结果为父类的静态成员方法
引用成员之间的转换:
向上转型:子类转换成父类 由小到大 基本数据类型的自动类型转换
向下转型:父类转换成子类 由大到小 基本数据类型的强制类型转换