Chapter 5 继承


作者:Denis

版本:1.0

编写时间:2022/10/16

编写地点:中国山西省

5.1 类、超类和子类


如果一个类继承自另一个类,那么这个类被称为子类,被继承的类被称为超类。

继承的关系实际上是特殊与一般的关系。例如,老师和学生都属于人,但他们的具体角色是不同的。

在Java中,使用extends关键字表示继承关系,所有的继承都是公共继承。

具体来说,使用继承可以使子类获得比父类更强的功能,同时也具有父类的某些行为。

子类可以增加方法、字段或者覆盖超类的方法来达到增强超类的功能。

子类覆盖超类的方法

子类中的某些方法与超类的方法签名相同,就称为方法覆盖。需要注意,子类方法的返回类型需要与超类方法返回类型一致或者兼容

例如超类方法返回类型double,子类方法返回类型为double或者int都行。

同时,子类方法不能低于父类方法的可见性,例如超类方法访问修饰符protected,子类方法访问修饰符为public或者protected都行。

关键字super

super的功能与this非常类似,super可以调用父类的构造器,也可以调用父类暴露出的方法与字段。

区别是,super不是一个对象引用,可以将this赋值给一个对象变量,但这对super来说是错误的操作。

子类构造器

子类构造器的代码必须显著地调用超类构造器,语句是super(parameter...)。倘若存在超类的无参构造器,才可以不声明,但实际上还是调用了(可以通过javap命令查看)。总之,调用子类构造器必定会调用一次超类构造器。而且,这条语句必须位于子类构造器的第一句。

多态

超类的对象变量既能够接收超类对象,也可以接收子类对象。反之,子类对象变量无法接收超类对象。当子类存在对超类方法覆盖时,超类对象变量会自动地执行对应类的方法。

多态的原理是可以判断对象运行时所属类,并生成每个类的可使用的方法表(可用的方法既有自己定义的,也有继承超类的方法)。如果使用超类的对象变量接收子类对象,例如Person p = new Student(...),这个对象编译类型是Person,而运行时类型为Student。在调用p的方法时,会查看Student类的方法表,如果这个方法恰好子类重写了,那么执行其子类的该方法。而对于调用p的字段,则调用其超类字段,而非子类字段。

下面时是对多态行为的演示。

import java.time.*;
public class ManagerTest
{
   public static void main(String[] args)
   {
      // construct a Manager object
      var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);

      var staff = new Employee[3];

      // fill the staff array with Manager and Employee objects

      staff[0] = boss;
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

class Manager extends Employee
{
   private double bonus;

   /**
    * @param name the employee's name
    * @param salary the salary
    * @param year the hire year
    * @param month the hire month
    * @param day the hire day
    */
   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 b)
   {
      bonus = b;
   }
}

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;
      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;
   }
}

阻止继承:final类和方法

如果给类、方法定义添加final关键字,则代表该类无法被继承、重载

将一个类添加final关键字后,所有的方法都自动隐式添加final,但字段不会被隐式添加final

字段添加final意味着创建对象后该字段的值(对于基本类型)无法修改,字段指向的对象(对于引用)无法重新指向新的对象。

向上转型和向下转型

向上转型,例如Person p = new Student()

向下转型,例如Student s = (Student)p;

向下转型需要确保该对象的实际类型确实是接收变量的所属类型,否则会报类型转换错误。

为此,在进行向下转型时,需要判断该对象的实际类型,使用instanceof关键字可以进行判断

注意:null不是任何类的对象,使用 null instanceof Object永远返回false

5.2 抽象类


在类的声明中添加abstract修饰符,即可让该类成为抽象类,抽象类不能产生任何抽象类实例对象。

抽象类对象变量可以接收一个子类的对象

在方法的声明中添加abstract修饰符,可以只给出方法签名,具体的实现依靠子类

包含一个或多个抽象方法的类必须被声明为abstract类型的,如果没有抽象方法,也可以被声明为抽象类

抽象类可以包含字段和具体的方法以及构造器,下面是一个抽象类的代码

public abstract class Person
{
   public abstract String getDescription();
   private String name;

   public Person(String name)
   {
      this.name = name;
   }

   public String getName()
   {
      return name;
   }
}

5.3 Object类


Object类是所有类的超类,有必要研究Object类的方法

equals方法

用于判断当前对象和接收的参数是否为同一个对象。但对于基于状态比较两个对象的相等性,需要在类中编写自己的equals方法来覆盖Object类的equals方法。下面给出一个equals方法的具体实现。

public boolean equals(Object otherObject)
{
    // a quick test to see if the objects are identical
    if (this == otherObject) return true;

    // must return false if the explicit parameter is null
    if (otherObject == null) return false;

    // if the classes don't match, they can't be equal
    if (getClass() != otherObject.getClass()) return false;

    // now we know otherObject is a non-null Employee
    var other = (Employee) otherObject;

    // test whether the fields have identical values
    return Objects.equals(name, other.name) 
    && salary == other.salary && Objects.equals(hireDay, other.hireDay);
}

在这个例子中,最核心的比较语句是最后一句,为了防止空指针异常(对象中的某些值可能为null),使用了更安全的Objects.equals(a, b)方法。这个方法如果两个参数都为null返回true,只有一个参数为null返回false,否则就调用a.equals(b)

注意,覆盖Object类的equals方法最好添加注解@Override,注意形参为Object类型的参数

如果超类有编写好的equals方法,子类无需从头编写,可以先调用超类的equals方法,然后再比较其独有的字段即可。

Java语言对equals方法要求如下特性:

  • 对称性,a.equals(b) == b.equals(a)
  • 自反性,a.equals(a) == true
  • 传递性,if(a.equals(b) && b.equals(c)) a.equals(c)
  • a.equals(null) == false

对于数组的比较,可以使用Arrays.equals方法

hashCode方法

hashCode是由对象导出的一个整型值(可以为负数),如果是两个不同对象,那么它们的散列码一般不相同,反之,如果两个对象通过equals测试,那么它们地hashCode应该相同。

通过该方法产生的散列码值一般用于插入到散列表中。

Object类的hashCode方法由对象的存储地址导出

如果重新定义equals方法,那么就需要重新定义hashCode方法。应该合理组合散列码,以便产生地更加均匀。

String类的hashCode方法设计如下。

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

自己编写的类的hashCode可以像这样

public int hashCode(){
    return 7 * name.hashCode() + 11 * Double.hashCode(salary) + 13 * hireDay.hashCode();
}

最好使用null安全的方法(因为对象中某些字段的值为null,对此调用hashCode()会产生空指针异常),Objects类提供了这个方法。

Objects.hashCode会判断参数是否为null,如果为null,就返回0,否则对参数调用其类的hashCode方法

需要组合多个散列值时,可以用Objects.hash(...)。它会对每个参数调用Objects.hashCode方法。

对于数组类型的字段,可以使用Arrays.hashCode,这个方法得到的散列值由数组元素的散列码组成

5.4 ArrayList类


java中,数组可以再运行时确定数组大小,但是确定大小后就不能修改

ArrayList类可以在空间满的时候进行扩容

ArrayList是一个有类型参数的泛型类,类型参数放在一对尖括号中,如ArrayList<Person>

下面的程序演示了ArrayList类的使用

import java.util.*;

public class ArrayListTest
{
   public static void main(String[] args)
   {
      // fill the staff array list with three Employee objects
      var staff = new ArrayList<Employee>();

      staff.add(new Employee("Carl Cracker", 75000, 1987, 12, 15));
      staff.add(new Employee("Harry Hacker", 50000, 1989, 10, 1));
      staff.add(new Employee("Tony Tester", 40000, 1990, 3, 15));

      // raise everyone's salary by 5%
      for (Employee e : staff)
         e.raiseSalary(5);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" 
            + e.getHireDay());
   }
}

ArrayList类的用法

  • 添加元素,使用add方法
  • 移除元素,使用remove方法
  • 如果事先能够估计出存储的元素数量,在填充数组前使用ensureCapacity方法,或者让构造器接收一个正数参数。
  • 返回实际元素的个数,使用size方法
  • 如果确定当前ArrayList空间将不会发生变化,可以使用trimToSize方法,调用后会把多余的未利用空间回收。
  • 改变元素,使用set方法,并提供索引值与新值【set不能代替add来添加元素】
  • 获得元素,使用get方法,并提供索引值
  • 将ArrayList对象元素拷贝到普通数组中,使用ToArray方法

ArrayList类在添加和移除元素后,这个元素后面的元素都需要往前或者往后移动,如果需要经常在中间添加、删除元素,而且ArrayList中元素很多的情况下,使用ArrayList效率并不高,这时应该使用链表。

5.5 自动装箱与自动拆箱


基本类型都有其对应的包装器类型,所有的包装器都是final,包装器可以接收基本类型的赋值。

Integer i = 277;

实际上,这条语句编译后被自动替换为

Integer i = Integer.valueOf(277);

这被称为自动装箱

相反,如果把一个Integer值赋值给基本类型int,称为自动拆箱

Integer i = 277;
int age = i;
//实际上编译后被自动替换为 int age = i.intValue();

如果一个条件表达式混用了Integer和Double,那么Integer会拆箱为int,转为double,再装箱为Double

包装器类型的比较

两个值相同的包装器对象,如果使用==进行判断,结果可能为true,也可能为false

自动装箱规范要求介于-128和127之间的short和int装在固定的对象中,在这个范围内的Integer对象进行==比较一定相等

最好使用equals方法来消除这些不确定的因素。

将字符串转为整型

使用Integer.parseInt(String s)即可,对于其他包装器也是这个办法。但前提是String的含义真的是一个数字。

Integer类的其他方法

  • toString(int i, int radix),返回一个指定进制的i值的字符串
  • parseInt(String s, int radix),返回一个指定进制的i值
  • valueOf(String s, int radix),返回一个指定进制的i值的Integer对象

原文地址:http://www.cnblogs.com/run-bit/p/16797438.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性