JAVA类的继承与多态

JAVA类的继承与多态

包含的内容不全面,前面一些简单的继承语法就跳过了,主要记录了一些不懂的地方,参考书籍:《JAVA核心技术 卷Ⅰ》

1. 正确理解多态

一个对象变量可以指示多种实际类型的现象称为多态(polymorphism),在运行时能够自动地选择适当的方法,称为动态绑定(dynamic binding)。

多态有三要素:

  1. 要有继承关系
  2. 要有子类重写父类成员方法
  3. 要有父类数据类型引用子类

多态后父类数据类型不能使用子类特有的属性和方法

举个栗子,Manager 类是 employee 类的一个子类,重写了 getSalary 方法,于是作为 employee 类型的 staff[0] 调用 getSalary 方法就是多态的一个表现,但是 staff[0] 不能调用 Manager 类特有的方法。

public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            employee[] staff = new employee[3];
            staff[0] = new Manager("Jack", 5000, 2015, 10, 25, 2000);
            staff[1] = new employee("Bob", 3000, 2017, 8, 7);
            staff[2] = new employee("Alice", 2000, 2018, 11, 25);
            for(employee e:staff){
    
    
                System.out.println(e.getName() + "'s salary:"+e.getSalary());
            }
    }
}

关于**覆盖(重写)**值得注意的是,子类方法不能低于超类方法的可见性!

比如超类方法是public,子类方法必须也要声明为public。

2. 强制类型转换

每一个对象变量都有一个类型,当一个值存入变量时,编译器将检查你是否承诺过多。

如果将一个子类的引用赋值给一个超类变量,编译器时允许的(比如上文多态的栗子),但是将一个超类的引用赋值给一个子类变量时,就承诺过多了,必须进行强制类型转换。

有两点需要注意:

  • 只能在继承层次内进行强制类型转换
  • 强制类型转换可能会失败,因此在进行强制类型转换前需要使用 instanceof 操作符进行检测
public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            employee staff = new employee("Bob", 3000, 2017, 8, 7);;
            if(staff instanceof Manager){
    
       // 判断是否能够转换
                Manager boss = (Manager) staff; // 如果可以就进行转换
                boss.show();
            }
    }
}

3. final类和方法

有时候,我们不希望某个类定义子类,这种不允许扩展的类称为final类,我们只需要在定义类时使用final修饰符即可。

同样的,如果不希望类中的方法被重写,我们也可以在定义方法时使用final修饰符,但是这样的话,就会破坏多态性(当然许多程序员觉得用不到多态性)。

4. 抽象类

一般来说,位于上层的类更具有一般性,可能更加抽象,从某种角度看,祖先类更具有一般性,人们一般只将它作为派生其他类的基类,而不是用来构造想要的特定实例,这就是抽象类!

比如说我们为人建立一个抽象类 Person ,我们将一些共有属性就放在高层次抽象类中,比如姓名 name ;然后我们再增加一个 getDescription 方法,用于获得一个人的描述,但是考虑到具体的子类需要采用不同的描述形式,我们可以不用定义这个方法,让子类重写该方法,这时就需要使用 abstract 关键词。

public abstract class Person {
    
    
    private final String name;
    public Person(String name){
    
    
        this.name = name;
    }
    public abstract void getDescription();
    public String getName(){
    
    
        return this.name;
    }
}

并且,包含一个即以上的抽象方法的类本身必须被声明为 abstract

然后我们就能继承该抽象类定义子类了(注意,必须重载所有的 abstract 方法,否则子类仍是抽象的!)

public class Employee extends Person {
    
          // 员工类
    private double salary;
    public Employee(String name, double salary){
    
    
        super(name);
        this.salary = salary;
    }
    public void getDescription(){
    
       // 重写抽象方法
        System.out.println(super.getName() + " is an employee with a salary of " + this.salary + "$");
    }
}
public class Student extends Person{
    
            // 学生类
    private String major;
    public Student(String name, String major){
    
    
        super(name);
        this.major = major;
    }
    public void getDescription(){
    
       // 重写抽象方法
        System.out.println(super.getName() + " is a student majoring in " + this.major);
    }
}

这样我们就基于抽象类定义了两个具体的子类!

抽象类不能创建对象实例,但是我们可以定义抽象类变量引用非抽象子类的对象,比如这样:

public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            // 抽象类变量引用非抽象子类对象
            Person a = new Student("Alice", "compute science");
            a.getDescription();
    }
}

5. 所有类的超类:Object

Object类是Java所有类的超类

5.1 equals方法

Object类中的equals方法用于检测一个对象与是否等于另一个对象,其实现的方法是确定两个对象的引用是否相等。但是我们经常需要基于状态检测对象的相等性,如果两个对象状态相同,就认为他们是相等的。

因此我们就需要重写由Object继承下来的equals方法。不过在此之前,我要需要区分 ==euquals 的区别。

  • 对于基本类型(char,boolean,byte,short,int,long,float,double)来说,它不是对象,不能使用 equals 方法,其存储在常量池里,使用 == 进行判断时时根据其值进行判断的。
  • 对于一般对象而言,其存储在堆中,当使用 == 进行判断时,是判断其在内存堆地址是否相等,而当使用 equals 进行判断时,将调用类中定义的 equals 方法来判断,如果类中没有重写 equals 方法,将会调用其超类的 equals 方法,对于所有类型的超类:Object,其 equals 方法就是 == ,下面是其源码。(一些预定义类,比如String,已经实现好了基于状态的 equals 方法)

08cjsA.png

  • 对于基本类型的包装类,它可能存储在堆中,也可能存储在常量池中,是由其大小决定的,以 Integer 为例,其在常量池的范围是[-128,127],超出这个范围就存储在堆中,我们看下面这个栗子就非常的清楚。
public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            Integer a = 127;    // 存储在常量池中,使用==比较的是其值
            Integer b = 127;
            System.out.println(a == b);     // 输出true

            Integer c = 128;    // 存储在堆中,使用==比较的是其堆地址
            Integer d = 128;
            System.out.println(c == d);     // 输出false

            // 使用equals方法,已预定义好,是比较其状态值(即其int值)
            System.out.println(c.equals(d));     // 输出true
    }
}

理解了 ==equals 的区别之后,我们就可以给我们的类重写 equals 方法了,主要是下面这样一个思路(已一个Employee类为例)。

    ...
    public boolean equals(Object otherObject){
    
    
        // 1.在开始比较之前,首先比较地址值,如果地址值相等就无需后续比较了
        if(this == otherObject)
            return true;
        // 2.判断是否为空,非常重要!否则容易引发异常
        if(otherObject == null)
            return false;
        // 3.如果由子类决定相等性概念,我们就使用getClass来进行判断
        if(this.getClass() != otherObject.getClass())
            return false;
        //3*.如果由超类决定相等性概念,我们就使用instanceof来进行判断
        //if(!(otherObject instanceof Employee))
        //    return false;

        // 类型转换
        Employee other = (Employee) otherObject;
        // 4.进行状态比较
        return Objects.equals(other.getName(), this.getName()) && other.salary == this.salary;
    }
    ...

我们简单测试一下:

public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            Employee a = new Employee("Bob", 2000);
            Employee b = new Employee("Bob", 2000);
            System.out.println(a.equals(b));    // true
            System.out.println(a == b);         // false
    }
}

大功告成~

5.2 hashCode方法

获取散列码的方法

hashCode方法定义在Object中,其值是由其存储地址得出的,而我们通常需要的散列值是希望基于其内容导出的,所有我们在创建类时需要重写它。

在一些预定义类中已经重写好了hashCode方法,比如String类:

int hash = 0;
for(int hash = 0; i < length(); i++)
    hash = 31 * hash + charAt(i);

那对于我们自己定义的类呢?

我们的自定义的类一般都是由基本数据类型以及一些预定义类组成的,于是我们只需要将他们的hashCode组合起来即可。

直接看如下代码,还是以Employee类为例:

    ...
	public int hashCode(){
    
    
        // 使用预定义类Double和String的hashCode函数生成新的hashCode
        return 7 * Double.hashCode(this.salary) + 9 * Objects.hashCode(this.getName());
    }
	...

然后试试我们实现的hashCode

public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            Employee a = new Employee("Bob", 2000);
            Employee b = new Employee("Bob", 2000);
            System.out.println(a.hashCode());   // 查看a的hashCode
            System.out.println(a.hashCode() == b.hashCode());   // true 成功实现基于内容的hashCode
    }
}

great!

5.3 toString方法

Object中还有一个非常重要的方法:toString ,如下是其默认方法:

    public String toString() {
    
    
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

当我们想打印一个LocalDate类时,我们很容易就写出如下代码:

LocalDate now = LocalDate.now();
System.out.println("现在时间是" + now);

我们知道只有字符串String在能简单的进行 “+” 操作,但为什么LocalDate也可以?

因为只要一个对象通过操作符 “+” 和字符串连接起来时,Java编译器就会自动的调用其 toString 方法,使用起来就非常的方便。

对于这些预定义类,已经重写好了 toString 方法,对于我们自定的类就需要我们自己重写了,绝大多数的 toString 方法都遵循着这样的格式:类的名字,随后时一对方括号括起来的字段值。

上代码,还是以Employee类为例:

    ...
	public String toString(){
    
    
        return this.getClass()+"[name="+this.getName()+",salary="+this.salary+"]";
    }
	...

试试看~

public class helloworld {
    
    
        public static void main(String[] args) {
    
    
            Employee a = new Employee("Bob", 2000);
            System.out.println(a.toString());
    }
}

输出结果:

class com.panfeng.Employee[name=Bob,salary=2000.0]

不错不错~

猜你喜欢

转载自blog.csdn.net/weixin_44338712/article/details/108917536