Java基础知识查漏补缺(Java核心技术)----类 超类和子类

知识点1 子类访问父类字段。

  • 尽管子类拥有父类的所有字段和方法,但是子类中的方法不能直接访问父类中的私有字段,只有父类的方法才能访问私有字段。如果一定要访问父类的私有字段,需要借助于公有接口(即为私有字段提供一个访问器)。

知识点2 子类构造器

在这里插入图片描述

这里是类Manager的构造方法,其继承了父类Employee类

这里的关键字super具有不同的含义。语句

super(name,salary,year,month,day)

是“调用超类Employee中含有name、salary、year、month和day参数的构造器”的简写形式。
由于Manager类的构造器不能访问Employee类的私有域,所以必须利用Employee类 的构造器对这部分私有域进行初始化,我们可以通过super实现对超类构造器的调 用。使用super调用构造器的语句必须是子类构造器的第一条语句。

如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有 参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有 显式地调用超类的其他构造器,则Java编译器将报告错误。

注释:回忆一下,关键字this有两个用途:一是引用隐式参数,二是调用该类 其他的构造器。同样,super关键字也有两个用途:一是调用超类的方法,二是调用 超类的构造器。在调用构造器的时候,这两个关键字的使用方式很相似。调用构造
器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类 (this)的其他构造器,也可以传递给超类(super)的构造器

知识点3 理解方法调用(调用过程的详细描述)

弄清楚如何在对象上应用方法调用非常重要。下面假设要调用x.f(args),隐式 参数x声明为类C的一个对象。下面是调用过程的详细描述:

  • 1.编译器查看对象的声明类型和方法名。假设调用x.f(param),且隐式参数x声 明为C类的对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方 法。例如,可能存在方法f(int)和方法f(String)。编译器将会一一列举所有C 类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不 可访问)。
    至此,编译器已获得所有可能被调用的候选方法。

  • 2.接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中 存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析 (overloading resolution)。例如,对于调用x.f(“Hello”)来说,编译器 将会挑选f(String),而不是f(int)。由于允许类型转换(int可以转换成 double,Manager可以转换成Employee,等等),所以这个过程可能很复杂。如果 编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之 匹配,就会报告一个错误。
    至此,编译器已获得需要调用的方法名字和参数类型。
    注释:
    方法的名字和参数列表称为方法的签名。例如, f(int)和f(String)是两个具有相同名字,不同签名的方法。如果在子类中定 义了一个与超类签名相同的方法,那么子类中的这个方法就覆盖了超类中的这个相 同签名的方法。
    不过,返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的 兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。例如假设Employee类有下面的这个方法
    public Employee getBuddy(){...}
    经理不会想找这种地位低下的员工。为了反映这一点,在后面的子类Manager中, 可以按照如下所示的方式覆盖这个方法
    public Manager getBuddy(){...}
    我们说,这两个getBuddy方法具有可协变的返回类型。

  • 3.如果是private方法、static方法、final方法(有关final修饰符的含义将在 下一节讲述)或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们 将这种调用方式称为静态绑定(static binding)。与此对应的是,调用的方法依 赖于隐式参数的实际类型,并且在运行时实现动态绑定。在我们列举的示例中,编 译器采用动态绑定的方式生成一条调用f(String)的指令。

  • 4.当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的 实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D类 定义了方法f(String),就直接调用它;否则,将在D类的超类中寻找 f(String),以此类推。
    每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了 一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这 样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。在前面的例子中, 虚拟机搜索D类的方法表,以便寻找与调用f(Sting)相匹配的方法。这个方法既 有可能是D.f(String),也有可能是X.f(String),这里的X是D的超类。这里 需要提醒一点,如果调用super.f(param),编译器将对隐式参数超类的方法表进 行搜索。

下面我们来看一段程序:
ManagerTest.java

public class ManagetTest {
    
    
    public static void main(String[] args) {
    
    
        Manager boss=new Manager("Carl Cracker",80000,1987,12,15);
        boss.setBonus(5000);
        Employee[] staff=new Employee[3];
        staff[0]=boss;
        staff[1]=new Employee("Harry Hacker",50000,1989,10,1);
        staff[2]=new Employee("Tommy Tester",40000,1990,3,15);

        for(Employee e:staff){
    
    
            System.out.println("name="+e.getName()+",salary="+e.getSalary());
        }
    }
}

Employee.java

import java.time.LocalDate;

public class Employee {
    
    
    private String name;
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day){
    
    
        this.name=name;
        this.salary=salary;
        this.hireDay=LocalDate.of(year,month,day);
    }

    public String getName() {
    
    
        return name;
    }

    public double getSalary() {
    
    
        return salary;
    }

    public LocalDate getHireDay() {
    
    
        return hireDay;
    }

    public void raiseSalary(double byPercent){
    
    
        double raise=salary*byPercent/100;
        salary+=raise;
    }
}

Manager.java

public class Manager extends Employee {
    
    
    private double bonus;

    public Manager(String name,double salary,int year,int month,int day){
    
    
        super(name,salary,year,month,day);
        bonus=0;
    }

    public double getSalary(){
    
    
        double baseSalary=super.getSalary();
        return baseSalary+bonus;
    }

    public void setBonus(double bonus) {
    
    
        this.bonus = bonus;
    }
}

上述中调用e.getSalary()的详细过程。e声明为 Employee类型。Employee类只有一个名叫getSalary的方法,这个方法没有参数。 因此,在这里不必担心重载解析的问题。
由于getSalary不是private方法、static方法或final方法,所以将采用动态绑定。虚拟机为Employee和Manager两个类生成方法表。在Employee的方法表中,列出了这个类定义的所有方法:
在这里插入图片描述
实际上,上面列出的方法并不完整,稍后会看到Employee类有一个超类Object, Employee类从这个超类中还继承了许多方法,在此,我们略去了Object方法。

Manager方法表稍微有些不同。其中有三个方法是继承而来的,一个方法是重新定 义的,还有一个方法是新增加的。
在这里插入图片描述
在运行时,调用e.getSalary()的解析过程为:
1)首先,虚拟机提取e的实际类型的方法表。既可能是Employee、Manager的方法 表,也可能是Employee类的其他子类的方法表。
2)接下来,虚拟机搜索定义getSalary签名的类。此时,虚拟机已经知道应该调用 哪个方法。
3)最后,虚拟机调用方法。

注意::在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别 是,如果超类方法是public,子类方法一定要声明为public。经常会发生这类错 误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器将会把它解释为 试图提供更严格的访问权限。

知识点4 阻止继承final类和方法

==不允许扩展的类被称为final类。 ==

public final Executive extends Employee{
   ...
}

不允许重写的方法被称为final方法

public class Employee{
      ...
      public final String getName{
         ...
      }
      ...
}

final类中的所有方法自动地成为final方法
注意:
前面曾经说过,域(即字段)也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过,如果将一个类声明为final,只有其中的方法自动地成为final,而不包括域。

知识点5: 类之间的强制转换和instanceof关键字

将一个子类的引用赋给一个 超类变量,编译器是允许的。
比如上述的程序

staff[0]=boss;

boss是Manager类的对象的引用。staff[0]被声明为Employee类的变量。

但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。
比如:

Manager designBoss=(Manager)staff[0]

为了使上述对象强制转化成功,就必须确保staff[0]引用的是子类Manager的一个实例,否则转化将不成功,例如下面的例子将转化不成功

Manager designBoss=(Manager)staff[1]

这是因为staff[1]引用的不是Manager对象,而是Employee对象。

instanceof关键字就是确保在强制转化之前确认该对象是不是要转化对象的实例而设计的关键字
例如下面句话

if(staff[1] instanceof Manager){
     Manager designBoss=(Manager)staff[1]
}

这时候if的条件判断为false,所以不会执行大括号里面的话。

注:

x=null
x instanceof C

若x为null。则会返回false。因为null没有引用任何对象, 当然也不会引用C类型的对象。

知识点6 抽象类

本文主要参考: https://blog.csdn.net/zyj497863419/article/details/88994651

定义:

  • 抽象类:在普通类的基础上扩充了一些抽象方法(0~n)的类(抽象类是普通类的超集),使用abstract关键字定义。抽象类中可以没有抽象方法

  • 抽象类不能直接产生实例化对象,因为抽象类是“半成品”,无法直接使用。不能直接new

  • 抽象方法:** 使用abstract 关键字定义并且没有方法体的方法。抽象方法所在类一定是抽象类。

abstract class Person{
    
               //抽象类
    public abstract void print();//抽象方法
}

抽象类的特征

  • 1.所有抽象类必须有子类 :(abstract与final不能同时出现,编译出错)

  • 2.如果子类不是抽象类 ,子类必须覆写抽象类(父类)的所有抽象方法。(子类是抽象类则可以不要求全部覆写)

  • 3.抽象类可以使用子类向上转型对其实现实例化,抽象类一定不能直接实例化对象(无论是否有抽象方法)

abstract class Person{
    
    
   public abstract void print();
  
}
class Student extends Person{
    
    
    public void print(){
    
    
        System.out.println("hello i am student");
    }
}
public class Test19{
    
    
    public static void main(String[] args){
    
    
        //Person per = new Student();
        Person per = new Person();//此处报错
        per.print(); 
    }
}

正确写法是

//正确写法:
public class Test19{
    public static void main(String[] args){
        Person per = new Student();//正确写法  由子类向上转型实现实例化
        per.print(); 
    }
}
  • 4.由于抽象类强制要求子类覆写父类方法,所以private 与 abstract 不能同时使用。(private 修饰私有属性,被private修饰的属性和方法不能被外部使用)
abstract class Person{
    
    
   private abstract  void print();//此时报错  private 与 abstract不能同时使用
}
class Student extends Person{
    
    
     public void print(){
    
    
     System.out.println("hello world");
   }
}
public class Test18{
    
    
    public static void main(String[] args){
    
    
        Person per = new Student();
    }
} 

  • 5.抽象类也存在构造方法,并且子类也一定按照构造实例化流程。先调用抽象类构造方法,再调用子类构造方法。
  • 6.特殊代码
abstract class A{
    
    
    public A(){
    
    //3.调用父类构造方法
        this.print();//4.调用父类方法,方法被子类覆写
    }
    public abstract void print();
}
class B extends A{
    
    
    private int num=100;
    public B(int num){
    
    //2.调用子类构造方法
        super();//3.隐含调用父类构造方法
        this.num=num;//为类中属性初始化
    }
    public void print(){
    
    //5.调用覆写后的方法。此时子类对象的属性没有被初始化(对象初始化操作在构造方法中执行)
        System.out.println(this.num);//6.打印数据为数据类型的默认值
    }
}
public class Test21{
    
    
    public static void main(String[] args){
    
    
        new B(30);//1.实例化子类对象
        new B(30).print();//修改代码后
    }
}

运行结果为
在这里插入图片描述
结果为 0 原因:此时子类对象的属性没有被初始化,即没有进入子类的构造方法中。

补充:
在父子类中,初始化子类对象,编译器的执行顺序是:

爷爷类的静态初始化块|静态属性初始化>
父类静态初始化块|静态属性初始化>
子类静态初始化块|静态属性初始化>
爷爷类普通初始化块|普通属性初始化>构造器>
父类普通初始化块|普通属性初始化>构造器>
子类普通初始化块|普通属性初始化>构造器

  • 7.抽象类也分内部抽象类和外部抽象类。内部抽象类的抽象方法与外部抽象类的抽象方法无关。当前直接继承哪个抽象类,就覆写其抽象方法。即:若直接继承外部抽象类,则只需覆写外部抽象类的所有抽象方法即可
abstract class A{
    
    //外部抽象类
    public abstract void fun();
    abstract class AInner{
    
    //内部抽象类
       abstract void fun1();
    }
}
class B extends A{
    
    //子类
    public void fun(){
    
    //只覆写外部类的抽象方法
        System.out.println("0000");
    } 
}
public class Test23{
    
    
    public static void main(String[] args)        
    }
}
//此时程序不报错

//修改程序
class B extends A{
    
    
    /*public void fun(){
        System.out.println("0000");
    } */
    public void fun1(){
    
    };//覆写内部抽象类的抽象方法 程序报错
}

//要覆写内部抽象类抽象方法 正确写法
abstract class A{
    
    
    public abstract void funA();
    abstract class AInner{
    
    
        public abstract void funB();
    }
}
class B extends A{
    
    
    public void funA(){
    
    };
    class BInner extends AInner{
    
    
        public void funB(){
    
    };
    }
}
public class Test24{
    
    
    public static void main(String[] args){
    
    

    }
}

  • 8.外部类抽象类不允许使用static ,内部类抽象类可以使用static
abstract class A{
    
    
    public abstract void print();
    static abstract class B{
    
    //内部抽象类
        public abstract void printB();
    }
}
class C extends A.B{
    
    
    public void printB(){
    
    };
}
public class Test25{
    
    
    public static void main(String[] args){
    
    
      C c = new C(); 
    }
}

知识点7 受保护访问

大家都知道,最好将类中的域标记为private,而方法标记为public。任何声明为 private的内容对其他类都是不可见的。前面已经看到,这对于子类来说也完全适 用,即子类也不能访问超类的私有域。

然而,在有些时候,人们希望超类中的某些方法允许被子类访问,或允许子类的方 法访问超类的某个域。为此,需要将这些方法或域声明为protected。例如,如果 将超类Employee中的hireDay声明为proteced,而不是私有的,Manager中的方法 就可以直接地访问它。

不过,Manager类中的方法只能够访问Manager对象中的hireDay域,而不能访问其 他Employee对象中的这个域。这种限制有助于避免滥用受保护机制,使得子类只能 获得访问受保护域的权利。

在实际应用中,要谨慎使用protected属性。假设需要将设计的类提供给其他程序 员使用,而在这个类中设置了一些受保护域,由于其他程序员可以由这个类再派生 出新类,并访问其中的受保护域。在这种情况下,如果需要对这个类的实现进行修 改,就必须通知所有使用这个类的程序员。这违背了OOP提倡的数据封装原则。

受保护的方法更具有实际意义。如果需要限制某个方法的使用,就可以将它声明为 protected。这表明子类(可能很熟悉祖先类)得到信任,可以正确地使用这个方 法,而其他类则不行。

下面归纳一下Java用于控制可见性的4个访问修饰符:
1)仅对本类可见——private。
2)对所有类可见——public。
3)对本包和所有子类可见——protected。
4)对本包可见——默认(很遗憾),不需要修饰符。

后续待补充

猜你喜欢

转载自blog.csdn.net/weixin_42684418/article/details/104226834