【0】README
1)本文主要对 java 继承的一些 重点知识进行复习;
2)for source code, please visit java_basic_review(5)源代码
3) proj dir tree
【1】super 和 this 的比较
1)this的用途:一是引用隐式参数+二是调用该类其它的构造器;
2)super的用途:一是调用超类 的方法 + 二是调用超类构造器;(super只是一个编译器调用超类方法的特殊关键字)
【2】多态
public class Employee {private int age;private String name;private double salary;
public class Manager extends Employee{private double bonus;public Manager(int age, String name, double salary) {super(age, name, salary);}@Overridepublic double getSalary() {return super.getSalary() + bonus;}public void setBonus(double bonus) {this.bonus = bonus;}
}
@Testpublic void testArrayConvert() {Manager[] mArray = new Manager[3];;Employee[] eArray;eArray = mArray; // 编译器允许数组对象转换.eArray[0] = new Employee(1, "zhangfei", 13000); // throws exception.mArray[0].setBonus(1000);System.out.println(mArray[0].getSalary());}
1)intro:多态指 一个对象变量可以指示多种实际类型的现象 被称为多态;(如上述代码中的 Employee e 既可以指向 Manager实例 也可以指向 Employee实例)
2)Manager m = eArray[i] 非法:原因很清楚, 不是所有的雇员都是经理;
3)编译器允许数组对象转换: 原因是 数组都是 Object的子类(Employee[] extends Object, Manager[] extends Object),不管是基本类型数组 还是 对象类型数组;
【3】 动态绑定
1)intro to 方法签名: 方法的名字和参数列表称为方法签名,不包括返回类型;
2)在覆盖一个方法的时候:子类方法不能低于超类方法的可见性;特别是, 如果超类方法是public, 子类方法一定要声明为public;
【4】阻止继承:final 类 和 方法
1)final 类:如果将一个类声明为final, 其中的方法自动地成为final, 而不包括域;
2)内联:如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理,这个过程就称为内联;如, 内联调用e.getName() 将被替换为 访问 e.name 域;
3)即时编译器对方法进行内联处理: 虚拟机中的即时编译器比传统编译器的处理能力强得多, 这个编译器可以准确谁知道类之间的继承关系,并能够检测出类中是否真正地存在覆盖给定的方法, 如果方法很短,被频繁调用且没有真正地被覆盖,那么即时编译器就会将这个方法进行内联处理;
【5】强制类型转换(instanceof 关键字)
1)intro:我们应该养成一个良好的程序设计习惯,在进行类型转换之前, 先查看一下是否能够成功地转换, 这个过程使用 instanceof 就可以实现;
Manager mTemp;Employee eTemp = new Employee();if(eTemp instanceof Manager) { // highlight.mTemp = (Manager)eTemp;}
2)instanceof 其实并不那么完美(不建议在equals方法中用到 instanceof 来检测):java 规范要求 equals() 方法满足对称性,即 a.equals(b) == b.equals(a);
@Overridepublic boolean equals(Object obj) { // defined in Person.javaif(obj instanceof Person) {return true;}return false;}
@Testpublic void testInstanceof() {Person p = new Person();Student s = new Student(); // Student extends PersonSystem.out.println(p.equals(s)); // true. 然而它们却属于不同的类.System.out.println(s.equals(p)); // true. 然而它们却属于不同的类.}
【6】抽象类
public abstract class Person {private String name;public Person(String n) {name = n;}public abstract String getDescription();public String getName() {return name;}
}
public class Student extends Person {private String major;public Student(String n, String m) {super(n);major = m;}public String getDescription() {return "a student majoring in " + major;}
}
1)由于不能构造抽象类 Person的对象, 所以变量p 永远不会引用 Person对象:而是引用诸如 Student这样的具体子类对象;
【7】Object:所有类的超类
1)在java中, 只有基本类型不是对象;而所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类;
2)equals 方法:该方法用于检测一个对象是否等于另外一个对象;(在Object类中, 这个方法比较的是内存地址,判断的是两个对象是否具有相同引用)
3)如何重写equals方法:下面给出编写一个完美的 equals 方法的建议
step1)检测this 与 otherObject 是否引用同一个对象:if(this == otherObject) return true; // 实际上, 这是一种经常采用的形式, 因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多;
step2)检测otherObject 是否为 null, 如果为 null ,则返回 false, 这项检测很有必要;if(otherObject == null) return false;
step3)比较this 与 otherObject 是否属于同一个类,如果equals 的语义在每个子类中有所改变,就是用 getClass 进行检测:if(getClass() != otherObject.getClass()) return false;
step4)将 otherObject 转换为 相应的类类型变量:ClassName other = (ClassName)otherObject;
step5)现在开始对所有需要比较的域进行比较了; 使用 == 比较基本类型域, 使用 equals比较对象域, 如果所有的域都匹配返回 true, 否则返回 false;return field1 == other.field1 && Object.equals(field2, other.field2) ;
Attention)如果在子类中重新定义 equals, 就要在其中包含 调用 super.equals(other);
@Override // defined in Manager.javapublic boolean equals(Object obj) {if(this == obj) { // step1: 检测this 与 obj 是否引用同一个对象return true;} else if(obj == null) { // step2: 检测 obj 是否为 nullreturn false;} else if(getClass() != obj.getClass()) { // step3: 比较this 与 obj 是否属于同一个类return false;} else { // step4: 将 obj 转换为 相应的类类型变量Manager m = (Manager)obj;return getName()==m.getName() && getAge()==m.getAge() && getSalary()==m.getSalary()&& bonus == m.getBonus();}}
【8】使用 @Override 对覆盖超类的方法进行标记
/*@Overridepublic boolean equals(Employee obj){}*/
1)这个方法声明的显式参数类型是 Employee, 其结果并没有 覆盖 Object类的 equals 方法,而是定义了一个完全无关的方法;2)为了避免发生类型错误: 可以使用 @Override 对覆盖超类的方法进行标记;
【9】hashCode 方法
1)intro: 散列码是由对象导出的一个整型值, 散列码没有规律的;
2)String类通过下列算法计算散列码:
int hash = 0;
for(int i=0;i<length(); i++)
hash = 31 * hash + charAt(i);
3)下面是 Employee类 的 hashCode方法:
/*@Overridepublic int hashCode() {return 7*name.hashCode()+ 11*String.valueOf(salary).hashCode()+ 13*String.valueOf(age).hashCode();}*/@Override // defined in Employee via jdk7.public int hashCode() {return Objects.hash(age, name, salary);}
@Overridepublic int hashCode() { // defined in Manager.(Manager extends Employee.)return super.hashCode() + Objects.hash(bonus);}
@Testpublic void testHashCode() {Manager m1 = new Manager(1, "1", 1);Employee m2 = new Employee(1, "1", 1);System.out.println(m1.equals(m2)); // true.System.out.println(m1.hashCode() == m2.hashCode()); // true.}
Attention)equals 方法与 hashCode 的定义必须一致, 如果x.equals(y) 返回 true, 那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值;
【10】toString方法
1)随处可见toString 方法的原因是: 只要对象与一个字符串通过操作符 + 连接起来,java编译器就会自动地调用 toString 方法, 以便获得这个对象的字符串描述;
2)在调用它 x.toString()的地方可以用 “”+x替代,这条语句将一个空串与 x 的字符串表示相连接, 这里的x就是 x.toString();
【11】泛型数组列表ArrayList
1)intro:把初始容量传给 ArrayList 构造器: ArrayList<Employee> staff= new ArrayList<>(100);
2)在填充数组之前调用 ensureCapacity方法: staff.ensureCapacity(100); 这个方法将分配一个包含100个对象的内部数组, 然后调用100 次add, 而不用重新分配空间了;
3)trimToSize方法:一旦能够确认数组列表的大小不再发生变化,就可调用 trimTSize 方法, 这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数组。垃圾回收器将回收多余的存储空间;
@Testpublic void testArrayList() {ArrayList<Double> list = new ArrayList<>();list.ensureCapacity(3);list.add(Double.valueOf(1));list.add(Double.valueOf(2));list.add(Double.valueOf(3));list.trimToSize();// 不会报错,只是告诉jvm 回收多余的内存.list.add(Double.valueOf(4));System.out.println(list.size()); // 4}
【11.1】访问数组列表元素
1)在中间插入和删除元素
@Testpublic void testArrayList() {ArrayList<Double> list = new ArrayList<>();list.ensureCapacity(3);list.add(Double.valueOf(1));list.add(Double.valueOf(2));list.add(Double.valueOf(3));list.trimToSize();// 不会报错,只是告诉jvm 回收多余的内存.list.add(Double.valueOf(4));System.out.println(list.size());Double[] dArray = new Double[list.size()]; list.toArray(dArray); // 访问数组元素for(double d : dArray) {System.out.print(d + ", "); // 1, 2, 3, 4 }int n = list.size() / 2;list.add(n, Double.valueOf(5)); // 在 数组列表中间插入元素.这种操作如果多的话,建议 LinkedListSystem.out.println("\n" + "new array: ");for (Double temp : list) {System.out.println(temp); // 1, 2, 5, 3, 4} }
【12】参数数量可变的方法
@Test // 参数数量可变的方法 ( Object... == Object[] )public void testVariableParameters() {System.out.printf("%d %s", new Object[]{new Integer(100), "widgets"});prettyPrint(100, "widgets");prettyPrint(new Object[]{new Integer(100), "widgets"});}public void prettyPrint(Object... args) {System.out.printf("\n%d %s", args);}
100 widgets
100 widgets
100 widgets
【13】枚举类
1)intro: 定义一个枚举类
public enum Size {SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");private String abbreviation;private Size(String abbreviation) {this.abbreviation = abbreviation;}public String getAbbreviation() {return abbreviation;}
}
2)所有的枚举类型都是 Enum类的子类: 它们继承了这个类的许多方法,包括toString方法, 它可以返回枚举常量名;如, Size.SMALL.toString() 将返回字符串 "SMALL";
3)toString 的逆方法是静态方法valueOf:如,Size s = Enum.valueOf(Size.class, "SMALL"); 将s 设置为 Size.SMALL;
4)每个枚举类型都有一个静态的values方法:它将返回一个包含全部枚举值的数组;
Size[] values = Size.values();
5)ordinal :该方法返回enum声明中枚举常量的位置, 从0开始计数;
如, Size.MEDIUM.ordinal() 返回 1;
Annotation)Enum类省略了一个类型参数, 例如,实际上, 应该将枚举类型 Size 扩展为Enum<Size> ;
6)测试用例
public enum Size {SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");private String abbreviation;private Size(String abbreviation) {this.abbreviation = abbreviation;}public String getAbbreviation() {return abbreviation;}
}
@Testpublic void testEnum() {Size[] values = Size.values();for(Size size : values) {System.out.println(size); // 调用枚举类中元素.}int ordinal = Size.MEDIUM.ordinal();System.out.println(ordinal); // 枚举常量的序数// 将s 设置为 Size.SMALL;Size s = Enum.valueOf(Size.class, "SMALL");System.out.println(s);}
SMALL // console info.
MEDIUM
LARGE
EXTRA_LARGE
1
SMALL