JAVA继承和Object

继承

类、超类和子类

子类将拥有父类/超类中的全部方法和域(private不算)。同时也可以在这基础上加入新的方法和域。子类能否访问并继承父类方法和域详见下表。
值得注意的是,访问protected方法和变量可以通过导入包的方式实现访问

访问包位置\类修饰符privateprotectedpublic
本类可见可见可见
同包其他类或子类不可见可见可见
其他包的类或子类不可见不可见可见

子类的定义

格式如下

public class ChildClass extends ParentClass
{

}

重写(override)

有些书里会翻译成覆盖,其实就是重写。常常与重载相比较:

  • 重载:方法名相同,参数不同的多个重名函数
  • 重写:子类中要求方法名和参数与父类完全相同的函数。要求返回值和异常比父类小或相同, 访问修饰符比父类大或相同。并且子类的静态方法不能重写,也能不能重写父类的静态方法。但是可以子类静态方法重写父类静态方法。

特殊注意:

  • final修饰的方法不能被重写,可以被重载
  • final修饰的类不能被继承
  • private 方法隐式添加了final
  • 若父类构造函数非空,则子类必须也有一个与父类同参的构造函数
super方法

由于在子类中,重写过的父类方法是隐藏的,所以调用时无法直接调用,否则会直接调用子类的方法,因此,我们需要使用super关键字来调用父类方法。
格式为:super.method();

super并非一种对象的引用,也无法赋值给其他对象变量,他只是个单纯的关键字,类似C++中的:ParentClass::method();

若父类构造函数含显式参数,则子类必须重写一个构造函数
并且需要用super();方法进行初始化。

好了,现在你已经基本掌握了JAVA中继承的基础知识了,让我们来编写一个范例吧

例:
创建一个Employee类,定义name、salary、hireDay等属性
创建一个Manager类,使其继承Employee类并额外加一个奖金属性
在main函数中测试这两个类

class Employee
{
    private String name;
    private double salary;
    private LocalDate hireDay;
    public Employee(String name,double salary,LocalDate hireDay)
    {
        this.name=name;
        this.salary=salary;
        this.hireDay=hireDay;
    }

    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;
    }
}
class Manager extends Employee
{
    double bonus;
    public Manager(String name,double salary,double bonus,LocalDate hireDay)
    {
        super(name,salary,hireDay);
        this.bonus=bonus;
    }
    public double getSaraly()
    {
        double baseSaraly=super.getSalary();
        return (baseSaraly+bonus);
    }
}

其中,Employee类显然是含有一个带三个参数的构造函数,那么子类Manger就需要一个至少带三个以上的参数构造函数。
而且,这个构造方法需要用super方法来调用父类完成参数的继承。

即,类继承时,构造函数也要相应继承

除此之外,super还可以用于帮助子类调用父类私有变量

理解方法调用

假设要调用x.f(INTgs)。x为类C的一个对象,f为C中一个方法,INT为一个int型参数(同理STRING、DOUBLE等)

  1. 编译器查看对象的声明类型和方法名。假设调用x.f(INT)。需要注意的是:可能存在多个名字为f但是参数不同的方法。例如:可能同时存在f(int)和f(String)。编译器会一一列举所有C中的f方法和其超类中能访问到的名为f的方法。

  2. 编译器获得了所有待选方法名单后,就会查看方法的参数类型。若有完全匹配的参数类型就会立即调用。例如:类C中同时有f(int) and f(double),而调用语句为x.f(INT),则会立即调用x.f(int)。这个过程被称为重载解析

  3. 若是private、static、final方法或构造器,那么编译器会准确获知需要调用哪个方法。这种调用方式被称为静态绑定。

  4. 若每次调用方法都需要进行搜索,则会造成巨大的资源和时间的开销。因此,虚拟机为每个类预先创建了一个方法表,需要调用方法时,虚拟机直接查表即可。但仅仅在动态绑定时需要,静态方法由于在加载时直接和类信息一起加载到方法区,无需创建对象,因此也无须通过方法表调用

额外理清一下动态绑定与静态绑定

  • 动态绑定:对大多数方法和类来说都需要通过构造器创建或绑定对象来创建。为了减少频繁搜索带来的巨额开销,虚拟机会为每个类创建一个方法表,每次调用时只需要查表即可获取需要调用的方法。这个过程即为动态绑定

-静态绑定:以private、static、final修饰的静态方法,在每次加载类时就会直接被加载到方法区,系统直接调用即可,无需构造器方法表等。此过程即为静态绑定

final类和方法——阻止继承

类中的final无法继承到子类中,子类也无法读取,除非使用super方法或关键字。除此之外fianl类也无法被其他类继承(final类中的方法也会自动成为fianl方法)

fianl可以让我们阻止某些重要的类或方法被错误重写,final
也可以避免动态绑定带来的额外系统开销

强制类型转换

double x=3.1415926;
int nx=(int)x;

基本数据类型转换没啥好说的,转换时可能会丢失部份信息量
重点是,有时很可能需要将某个类对象引用转换成其他类对象引用。
例如,此前我们的员工管理系统中, staff[] 数组中存储了Employee对象,但是我们如果要将 staff[0] 变为Manager对象呢

Manager boss =(Manager) staff[0];

但是staff[0]未必能满足转换为Manager对象的条件

  • 若是将一个子类引用赋给超类变量,编译器是允许的(即可以向上)
  • 若是将一个超类引用赋给子类变量,则会抛出一个ClassCastException的异常(不能向下)。

我们可以使用instanceof运算符来判断能否转换
boolean result = obj instanceof Class
其中obj表示一个对象,Class为一个类。若obj是Class的一个对象或是其直接或间接子类、接口实现对象等,result都返回true,否则false
那么我们就可以用下面这个办法来判断staff[]能否转换

if(staff[0] instanceof Manager)
{
    boss =(Manager) staff[0];
}

若转换失败则会自动跳过而不会使得整个程序停止运行

总结:

  • 只能在继承层次内进行转换,而且一般最好从下向上转换

  • 超类转换为子类时最好使用 instanceof进行检查

  • 尽量少使用类型转换和instanceof运算符,只要类设计合理,其实往往类型转换都是可以避免的

抽象类

通常来说,位于上层的类往往有着较高的通用性,甚至可能更抽象。比如说,Person类就可以作为Employee类和Student类的超类,因为无论雇员还是学生都是“人”。他们可以具有共有的属性,比如姓名、年龄等就可以直接放在高层次的Person类中。

但是雇员和学生显然存在着诸多不同的属性。如果我们需要对不同种类的人都有相应的“描述”,应该在Person类中提供何种内容呢?

  • 一种办法是:在Person类中创建getDescription方法 ,并使得该方法返回空值,到了子类中再对这个方法重写

  • 最好的办法还是:使用abstract关键字创建一个抽象类

特别提醒:拥有一或多个抽象方法的类本身也必须被定义为抽象类,但是抽象类中可以有非抽象方法

抽象类通常起到占位作用,相当于一个接口,他的具体实现在子类中。一般可以将所有的抽象方法在抽象超类全部定义出来。这样就不需要再定义抽象子类了。

定义一个Person抽象类,并在子类中实现相关功能

public  abstract class Person
{
    private String name;
    private int age;
    //private Date date;
    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
        //this.date=date;
    }
    public abstract String getDescription();
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
}
public class text
{
    public static void main(String[] args)
    {
        //Student student=new Student("San",17);
        //System.out.println(student.getDescription());
        /*Employee employee=new Employee("San",22,200000, LocalDate.of(2022,2,20));
        System.out.println(employee.getDescription());
        Manager manager=new Manager("San",29,100000000,10000000, LocalDate.of(2022,2,20));
        System.out.println(manager.getDescription());
        Person person=new Student("San",17);
        System.out.println(person.getDescription());*/
        Person [] people=new Person[3];
        people[0]=new Student("San",17);
        people[1]=new Employee("San",22,200000, LocalDate.of(2022,2,20));
        people[2]=new Manager("San",29,100000000,10000000, LocalDate.of(2022,2,20));
        for (Person p:people)
        {
            System.out.println(p.getDescription());
        }
    }
}
class Student extends Person
{
    private String name;
    private String major= this.getClass().getName();
    public Student(String name, int age)
    {
        super(name, age);
    }

    public String getDescription()
    {
        return String.format("A %d yeas old "+major+" named "+getName(),getAge());
    }
}

class Employee extends Person
{
    private String name;
    private double salary;
    private LocalDate hireDay;
    private String major=this.getClass().getName();
    public Employee(String name,int age,double salary,LocalDate hireDay)
    {
        super(name, age);
        this.name=name;
        this.salary=salary;
        this.hireDay=hireDay;
    }
    @Override
    public String getDescription()
    {
        return String.format("A %d years %s with a salary of $ %.2f named "+name+" hired on "+hireDay,getAge(),major,getSalary());
    }

    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;
    }
}
class Manager extends Employee
{
    double bonus;
    private String major=this.getClass().getName();
    public Manager(String name,int age,double salary,double bonus,LocalDate hireDay)
    {
        super(name,age,salary,hireDay);
        this.bonus=bonus;
    }
    public String getDescription()
    {
        return String.format("A %d years %s with a salary of $ %.2f named "+getName()+" hired on "+getHireDay(),getAge(),major,getSalary()+bonus);
    }
    public double getSaraly()
    {
        double baseSaraly=super.getSalary();
        return (baseSaraly+bonus);
    }
}

抽象类不可被实例化,但是可以定义对象,但是他只能引用非抽象子类对象。例如:
Person person=new Student("San",17);
这是上面的例子的引用代码。最终实现和直接创建子类Student类对象是一样的:
A 17 yeas old Student named San
同理,p.getDescription()也是调用的子类的方法

受保护访问

让我们再复习一下访问修饰符权限范围

域\修饰符privateprotectedpublic
同类可以可以可以
同包不同类不可以可以可以
不同包不可以不可以可以

一般来说最好将类中的域标记为private,将方法标记为public。
private私有内容对其他类是不可见的,包括子类也是如此。
但是protected是对子类可见的,即便不是同包也可见

Object:所有类的超类

Object是java所有类的始祖,但是不需要写明extends Object

可以用Object类型变量引用任何类型的对象:
Object obj = new Student("San",17)

但是Object数据类型只能作为各种值的“容器”,我们无法直接对Object类型的数据进行修改,而必须转换为其原始类型(一般Object向下转换是被允许的):
Student student=(Student) obj;

除此之外,当你不确定数组中的对象的类型时,也可以先都统一定为Object,后面根据需求可以对数组中进行变换。

后续还有150余种equals,hashCode和toString
未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值