文章目录
- 第五章 继承
- 类、超类和子类
- 定义子类
- 覆盖方法
- 子类构造器
- 多态
- 阻止继承: `final`类和方法
- 强制类型转换
- 抽象类
- 受保护访问
- 访问控制修饰符小结
- `Object`: 所有类的超类
- `Object`类型的变量
- `equals`方法
- 相等测试与继承
- `hashCode`方法
- `toString`方法
- ==泛型数组列表==
- 声明数组列表
- 访问数组列表元素
- 类型化与原始数组列表的兼容性
- 对象包装器与自动装箱
- 参数数量可变的方法
- 枚举类
- 反射
- `Class`类
- 声明异常入门
- 利用反射分析类的能力
- 继承的设计技巧
第五章 继承
类、超类和子类
定义子类
extend
: 该关键字表示继承
public class Manager extends Employee // java中所有的继承都是公共继承
{added methods and fields
}
extend
: 表示正在构造的类派生于一个已存在的类.这个已存在的类称为超类
(基类,父类). 新类称为子类
(派生类, 孩子类)- 子类比超类拥有更多的功能
- 设计类: 最一般的类放在超类中, 更特殊的方法放入子类
覆盖方法
-
super
: 调用超类方法public class Manager extends Employee {public double getSalary(){double baseSalary = super.getSalary();return baseSalary + bonus;} }
子类构造器
-
由于
Manager
构造器不能访问Emplyee
类的私有字段, 所以通过super
来调用初始化这些私有字段public Manager(String name, double salary, int year, int month, int day) {super(name, salary, year, month, day);//必须放在第一句bonus = 0; }
-
this
: 1. 隐式参数的引用 2. 调用该类的其他构造器super
: 1. 调用其他超类的方法 2. 调用超类的构造器
多态
多态
: 一个变量能够指示多种实际类型的现象
Manager boss = new Manager(...)
Employee[] staff = new Employee[3];
staff[0] = boss;
- 上面的例子中
boss, staff[0]
指示同一个对象, 但是编译器只将staff[0]
看成一个Emplyee
对象 - 子类引用的数组可以转换成超类引用的数组
Manager[] managers = new Manager[10];
Employee[] staff = managers;
- 重载解析: 选择所有名字相同的方法中存在一个与提供参数完全相匹配的方法
签名
: 方法的名字和参数列表. 返回类型不是签名的一部分- 在覆盖一个方法时,子类方法不能低于超类方法的可见性.e.g.: 超类为public, 子类也必须为public
阻止继承: final
类和方法
final
类: 不允许扩展的类, 其中的方法自动设置成为fianl
方法, 不包括字段final
方法: 子类不能覆盖的方法fianl
字段: 构造对象后就不允许改变其值
强制类型转换
-
Manager boss = (Manager) staff[0];
-
Manager boss = (Manager) staff[1]; // ERROR 抛出异常: ClassCastException
-
if (staff[1] instanceof Manager){boss = (Manager) staff[1];.... }//查看能否强制类型转换成功
-
继承层次
: 由一个公共类派生出来所有类的集合 -
- 只能在继承层次类进行强制类型转换
- 在将超类强制类型转换为子类之前, 应该用
instanceof
进行检查
抽象类
-
位于上层的类更具有一般性,更加抽象
-
包含一个及以上抽象方法的类必须声明为抽象类
-
public abstract class Person {...public abstract String getDescription(); }
-
抽象类也可以包含字段和具体方法
-
抽象类不能实例化.
new Person("dvsj"); //ERROR
-
可以定义一个抽象类的对象变量, 引用非抽象子类的对象
Person p = new Student("Alice");
package chapter5_inheritance.abstruct_classes;public abstract class Person {//抽象类public abstract String getDescription();//抽象方法private String name;public Person(String name) {this.name = name;}public String getName() {//抽象类中也可以由方法return name;}
}package chapter5_inheritance.abstruct_classes;public class Student extends Person {private String major;/*** @param name the student's name* @param major the student's major*/public Student(String name, String major) {super(name); // 继承Personthis.major = major;//隐式参数的引用}public String getDescription() { // 对应抽象类中的抽象方法return "A student major in " + major;}
}package chapter5_inheritance.abstruct_classes;import java.time.LocalDate;public class Employee extends Person
{private double salary;private LocalDate hireDay;public Employee(String name, double salary, int year, int month, int day) {super(name);this.salary = salary;hireDay = LocalDate.of(year, month, day);}public double getSalary() {return salary;}public LocalDate getHireDay() {return hireDay;}public void raiseSalary(double byPercent) {double delta = salary * byPercent / 100;salary += delta;}public String getDescription() {return String.format("An employee with a salary of %.2f", salary);}
}package chapter5_inheritance.abstruct_classes;public class Person_test {public static void main(String[] args) {/* 声明为Person类型 */var people = new Person[2];/* 每一个Person类型的people[i] 声明为对应子类类型 */people[0] = new Student("oukunnan", "Software Engineering");people[1] = new Employee("vfjh", 1500.0, 1259, 5, 12);/* 注意点 类型 参数: 实参数组 */for (Person p : people) {System.out.println(p.getName() + ", " + p.getDescription());}}
}
受保护访问
protected
: 限制超类中某个方法只允许子类访问, 希望允许子类的方法访问超类的某个字段- 保护字段只能由同一个包中的类访问
访问控制修饰符小结
private
: 仅对本类可见public
: 对外部完全可见protected
: 对本包和所有子类可见- 无: 对本包可见, 不需要修饰符
Object
: 所有类的超类
Object
类型的变量
- 可以使用
object
类型的变量引用任何类型的对象
Object obj = new Employee("sbjd", 2000);
- 只能作为各种值的一个泛型容器, 进行具体操作需要强制类型转换
Employee e = (Employee)obj;
- 只有基本类型不是对象: 数值, 字符, 布尔类型的值
equals
方法
- 该方法确定两个对象的引用是否相等
public boolean equals(Object otherObject) {if (this == otherObject) {return true;}//判引用if (otherObject == null) {return false;}//判空if (getClass() != otherObject.getClass()) {return false;}//判对象所属类是否相等Employee other = (Employee) otherObject;//调用Object.equals()而不是name.equals(other.name)为了防备name, hireDay为null情况return Objects.equals(name, other.name) && salary == other.salary&& Objects.equals(hireDay, other.hireDay);
}
- 子类中:
if(!super.equals(otherObject)) return false; ...
相等测试与继承
equals
方法要求: 自反性, 对称性, 传递性, 一致性- 比较数组元素是否相等, 使用静态方法
Arrays.equals
@Override
标记要覆盖超类的那些子类方法
hashCode
方法
- 散列码: 值由对象的储存地址得出
- 字符串的散列码时由内容导出的
Object.hasCode
:null
安全的方法Object.hash():
组合多个散列值, 这个方法对于各个参数调用Object.hasCode()
Arrays.hashCode
:数组类型字段计算散列码hasCode, equals
方法必须兼容
toString
方法
x.toString() 等价于 "" + x
System.out.println(x)
会自动调用x.toString()
- 对于数组:
Arrays.toString, Arrays.deepToString
泛型数组列表
ArrayList
类: 类似于数组, 在添加或者删除数组元素时可以自动调整数组容量- 它是一个有类型参数的泛型类
声明数组列表
- 以下三种声明方式等价
ArrayList<Employee> staff = new ArrayList<Employee>();
ArrayList<Employee> staff = new ArrayList<>(); // 菱形语法
var staff = new ArrayList<Employee>();
add
: 将元素添加到列表中ensureCapacity
: 确保数组列表在不重新分配内部储存数组的情况下有足够容量储存给定数量的元素trimToSize
: 将数组列表的存储容量消减到当前大小size
:staff.size()
等价于数组的a.length
, 表示实际包含元素个数
访问数组列表元素
get
: 得到指定索引位置的值set
: 重新设置索引位置已经存在的值add(int index, E obj):
后移元素从而插入obj到指定位置remov(int index):
删除指定索引位置的元素, 后面的元素前移toArray
: 将数组元素拷贝到一个数组中
list.toArray(a); //将数组列表list中的元素拷贝到数组a中
类型化与原始数组列表的兼容性
-
public class EmployeeDB {public void update(ArrayList list){...}public ArrayList find(String query) {...}
-
可以将类型化的数组列表传递给
update
, 不需要强制类型转换 -
但是原始
ArrayList
赋给类型化ArrayList
会有警告, 强制类型转换也不可以消除警告,会引发类型转换有误的另一个警告 -
@SuppressWarning("unchecked") ArrayList<Employee> result = (ArrayList<Employee>) emplyeeDB.find(x);
-
用以上注解标记问题不太严重的强制类型转换的变量
对象包装器与自动装箱
-
包装器
: 所有的基本类型对应的类(Integer, Double, Long, Float, Short, Byte, Character, Boolean
) -
包装器类是
final
, 不能派生子类 -
尖括号中的类型参数不允许是基本类型, 可以这样
var list = new ArrayList<Integer>();
-
自动装箱
:list.add(3) --> list.add(Integer.valueOf(3))
-
自动拆箱
:
int n = list.get(i); --> list.get(i).intValue();
-
Integer a = 1000; Integer b = 1000; if (a == b) ..//通常失败
==
运算符比较的是对象是否占有相同的内存位置 -
-128~127之间的
short, int
被包装到固定的对象中, 上面的比较就会成功
参数数量可变的方法
-
...
: 是Java
代码的一部分, 表示这个方法可以接受任意数量的对象public class PrintStream {public PrintStream printf(String fmt, Object... args){return format(fmt, args);} }
-
printf
方法接受两个参数, 一个是格式化字符串, 另一个是Object[]
数组, 其中保存着所有其他参数(整数或者其他基本类型的值会自动装箱为对象),Object...参数类型和Object[]完全一样
枚举类
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARG};
- 枚举构造器总是私有的
- 所有枚举类型都是
Enum
的子类 values
: 返回一个包含全部枚举值的数组
反射
反射
: 能够分析类能力的程序
Class
类
getClass
: 返回类的名字forName
: 获得类名对应的Class
对象T.class
: 代表匹配的类对象Class
实际上是泛型类,Employee.class-->Class<Employee>
声明异常入门
- 处理器处理抛出的异常
- 异常分为
- 非检查型异常
- 检查型异常
利用反射分析类的能力
java.lang.reflect
包中有三个类Field, Method, Constructor
分别用于描述类的字段,方法和构造器, 这三个类都有一个gteName
方法, 返回其名称Class
类中的getFields, getMethods, getConstructors
方法返回这个类支持的公共字段,方法和构造器的数组, 其中包括超类的公共成员Class
类中的getDeclaredFields, getDeclareMethods, getDeclaredConstructors
方法返回这个类声明的全部字段,方法和构造器,包括私有成员,包成员,受保护成员,不包括超类成员
package chapter5_inheritance;import java.util.*;
import java.lang.reflect.*;public class reflection {public static void main(String[] args)throws ReflectiveOperationException{// read class name from command line args or user inputString name;if (args.length > 0) name = args[0];else{var in = new Scanner(System.in);System.out.println("Enter class name (e.g. java.util.Date): ");name = in.next();}// print class name and superclass name (if != Object)Class cl = Class.forName(name);Class supercl = cl.getSuperclass();String modifiers = Modifier.toString(cl.getModifiers());if (modifiers.length() > 0) System.out.print(modifiers + " ");System.out.print("class " + name);if (supercl != null && supercl != Object.class) System.out.print(" extends "+ supercl.getName());System.out.print("\n{\n");printConstructors(cl);System.out.println();printMethods(cl);System.out.println();printFields(cl);System.out.println("}");}/*** Prints all constructors of a class* @param cl a class*/public static void printConstructors(Class cl){Constructor[] constructors = cl.getDeclaredConstructors();for (Constructor c : constructors){String name = c.getName();System.out.print(" ");String modifiers = Modifier.toString(c.getModifiers());if (modifiers.length() > 0) System.out.print(modifiers + " ");System.out.print(name + "(");// print parameter typesClass[] paramTypes = c.getParameterTypes();for (int j = 0; j < paramTypes.length; j++){if (j > 0) System.out.print(", ");System.out.print(paramTypes[j].getName());}System.out.println(");");}}/*** Prints all methods of a class* @param cl a class*/public static void printMethods(Class cl){Method[] methods = cl.getDeclaredMethods();for (Method m : methods){Class retType = m.getReturnType();String name = m.getName();System.out.print(" ");// print modifiers, return type and method nameString modifiers = Modifier.toString(m.getModifiers());if (modifiers.length() > 0) System.out.print(modifiers + " ");System.out.print(retType.getName() + " " + name + "(");// print parameter typesClass[] paramTypes = m.getParameterTypes();for (int j = 0; j < paramTypes.length; j++){if (j > 0) System.out.print(", ");System.out.print(paramTypes[j].getName());}System.out.println(");");}}/*** Prints all fields of a class* @param cl a class*/public static void printFields(Class cl){Field[] fields = cl.getDeclaredFields();for (Field f : fields){Class type = f.getType();String name = f.getName();System.out.print(" ");String modifiers = Modifier.toString(f.getModifiers());if (modifiers.length() > 0) System.out.print(modifiers + " ");System.out.println(type.getName() + " " + name + ";");}}
}/*
Enter class name (e.g. java.util.Date):
java.lang.Double
public final class java.lang.Double extends java.lang.Number
{public java.lang.Double(double);public java.lang.Double(java.lang.String);public boolean equals(java.lang.Object);public static java.lang.String toString(double);public java.lang.String toString();public int hashCode();public static int hashCode(double);public static double min(double, double);public static double max(double, double);public static native long doubleToRawLongBits(double);public static long doubleToLongBits(double);public static native double longBitsToDouble(long);public volatile int compareTo(java.lang.Object);public int compareTo(java.lang.Double);public byte byteValue();public short shortValue();public int intValue();public long longValue();public float floatValue();public double doubleValue();public static java.lang.Double valueOf(java.lang.String);public static java.lang.Double valueOf(double);public static java.lang.String toHexString(double);public static int compare(double, double);public static boolean isNaN(double);public boolean isNaN();public static boolean isInfinite(double);public boolean isInfinite();public static boolean isFinite(double);public static double sum(double, double);public static double parseDouble(java.lang.String);public static final double POSITIVE_INFINITY;public static final double NEGATIVE_INFINITY;public static final double NaN;public static final double MAX_VALUE;public static final double MIN_NORMAL;public static final double MIN_VALUE;public static final int MAX_EXPONENT;public static final int MIN_EXPONENT;public static final int SIZE;public static final int BYTES;public static final java.lang.Class TYPE;private final double value;private static final long serialVersionUID;
}
*/
继承的设计技巧
- 将公共操作和字段放在超类中
- 不要使用受保护的字段
- 使用继承实现"is-a"关系
- 除非所有继承方法都有意义, 否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态, 而不要使用类型信息
- 不要滥用反射