05-继承

继承关系

首先,复习一下访问控制权限

  • private:仅对本类可见

    子类不能直接访问private字段,但可以拥有

  • public:外部完全可见

  • protected:本包和所有子类可见

  • 缺省:本包可见

使用继承

使用extends关键字继承类

public Manager extends Employee{
    ...
}

在子类中调用超类的方法:super

子类构造器:调用子类构造器前会先调用父类构造器(必须

多态

静态绑定:private方法、static方法、final方法不会有二义性

父类变量可以指向子类变量,但只能调用父类中存在的方法和字段

如果覆写了,则调用的子类方法;否则调用父类方法

理解:一个对象变量可以引用该类型的变量,也可以引用任何一个子类的对象

方法的重写

签名:方法名+参数列表

子类中签名相同的会覆写父类的方法

返回类型不是签名的一部分,运行返回原返回类型的子类

可见性:子类方法可见性不能低于父类

final类

final关键字修饰的类不可继承

final方法:子类可以继承获得,但不能重写

final字段:一旦赋值就不能再被修改

强制类型转换

有两种情况:

  • 子类引用赋值给超类变量,这显然是可以的,属于多态的一部分

  • 超类引用赋值给子类变量呢?答案是不一定

    明确:超类引用不能凭空的强制转换为子类

    什么叫凭空?就是超类变量一开始就引用的超类引用,相比于子类引用,超类引用肯定缺少了一些内容,此时强制转换会失败。

    如果这个超类变量是一开始引用的子类引用,现如这个子类引用包含了所有内容,只不过超类变量只能访问超类中定义的部分;这时候可以强制转换为子类

    可以使用instanceof来检查是否能够进行强制转换

public class test02 {
    public static void main(String[] args){
        //这种强制转换会失败
        //Employee e1 = new Employee("sjs","1",1700);
        //Manager m1 = (Manager) e1;
        //System.out.println(m1.getMoney());
		
        //这种转换会成功
        Manager m1 = new Manager("sjs","1",1700,1899);
        Employee e1 = m1;
        //强制转换前可以用instanceof进行检查
        System.out.println(e1 instanceof Manager);
        Manager m2 = (Manager) e1;
        System.out.println(m2.getMoney());
    }
}

抽象类

使用abstract关键字定义抽象类

特点:和普通类一样有字段也有方法,但是方法不一定实现。

没有实现所有方法的子类也是抽象类

可以有抽象变量,但只能引用非抽象子类的实例,因此调用的方法必然是非抽象子类的完整方法

不实现的方法要用abstract标识,且abstract只能用在抽象类里

public abstract class AbstractEmploy {
    public abstract float getMoney();
}
public class test03 {
    public static void main(String[] args){
        AbstractEmploy e1 = new Employee("123","456",1700);
        AbstractEmploy m1 = new Manager("789","1011",899,200);

        System.out.println(e1.getMoney());  //得到1700
        System.out.println(m1.getMoney());  //得到1099
    }
}

Object类一些常用方法

Object的一个典型用法是可以用作容器引用任何类型,但具体操作其中内容时一般要强制转换

equals与==

==:如果是基本类型,则比较是值;如果是引用类型,比较的是地址

equals:可以自定义equals来比较

数组类型一般调用Arrays.equal

//这是Object的原生equals方法
public boolean equals(Object obj) {
    return (this == obj);
}

//这是String的equals方法
//可见这个equals方法比较的是字符串的值是不是相等
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
}

如何编写一个equlas方法

equals方法要满足自反性、对称性、传递性等性质

  1. 形参类型定义为Object
  2. 和this进行比较
  3. 进行类层面的比较,也就是Class对象
  4. 强转后进行字段的比较
@Override
public boolean equals(Object object){
    //1.equals(null)必须为false
    if(object == null){
        return false;
    }
    //2.如果是同一引用,直接返回true,就不用做后面的比较了
    if(this == object){
        return true;
    }
    //3.比较Class类
    if(this.getClass() != object.getClass()){
        return false;
    }
    //4.类结构一样,开始比较字段
    Manager objManager = (Manager) object;
    if(this.getName().equals(objManager.getName()) && this.getWork().equals(objManager.getWork()) &&
       this.getSalary() == objManager.getSalary() && this.bonus == objManager.bonus){
        return true;
    }
    return false;
}

值为null的引用不能调用equals方法,否则会报NullPointerException

如果一定要和null进行可能的比较,则可以调用Objects的静态方法equals

hashCode方法

默认的hashCode由对象的存储地址得到

如果重写了equlas,就必须重写hashCode

原因:如果a.equals(b)为true,那么a和b的hashCode也应该一样

简单来说,重新定义的equlas比较的是什么,就应该重新散列什么。

注意:hashCode相等,对象不一定相等

//接上文,比较的字段拿来计算hashCode
public int hashCode(){
    return 7*this.getName().hashCode()+5*this.getWork().hashCode()+3*Double.hashCode(this.getSalary())+
        6*Double.hashCode(this.bonus);
}

hashCode有什么作用呢?

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals() 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

toSrting方法

通常会把类中字段的所有值打印出来,方便调试

通过+连接字符串和对象时,会自动调用toString方法

注意:数组自带的toString有历史遗留问题,不得劲,建议使用Arrays.toString方法

对象包装器

基本类型都有对应的包装器

自动装箱:需要用到包装类型时基本类型变量会自动包装

自动拆箱:需要用到基本类型时包装类型变量会自动拆除

常量池

Java 基本类型的包装类的大部分都实现了常量池技术。Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据,Boolean 直接返回 True Or False

Integer i1 = 33;

Integer i2 = 33;

System.out.println(i1 == i2);// 输出 true

可变参数列表

使用...可以表示不限参数数量,相当于传入一个该类型的数组

本质就是传入该类型的数组

public float bePromoted(float ...values){
    float res = 0;
    for(float v : values){
        res += v;
    }
    return res;
}

枚举类

枚举类除了包含枚举常量,还可以像普通类一样有字段、方法、构造器

可以在枚举常量定义传入构造器的参数

所有枚举类型都是Enum的子类,其中的toString会返回枚举常量名

通过Enum的valueOf可以构造实体类

public enum Color {
    RED(""),BLUE(""),YELLOW("");

    private String desc;
    Color(String desc){
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

枚举类的构造器是private的:无法被new实例化,只能在类内部调用构造方法

如何在其他地方实例化枚举类型:调用Enum.valueOf方法

public class test06 {
    public static void main(String[] args){
        Color color = Enum.valueOf(Color.class,"YELLOW");	//实例化
        Color[] values = Color.values();	//获取全部枚举常量

        System.out.println(color);	//toString
        System.out.println(Arrays.toString(values));	
    }
}
上一篇:为什么重写equals方法必须重写hashCode方法


下一篇:Java基础(六)——集合