由于有时候调用父类向下转型为子类时,若其对象本身创建时属于父类,则会抛出一个运行时的异常。
public class Person {
private String name;
// 省略构造方法和getter和setter
}
public class Student extends Person {
private String classroom;
// 省略构造方法和getter和setter
}
public class Main {
public static void main(String[] args){
//第一部分
Person aPerson = new Student("小王", "高三一班");
Student student = (Student) aPerson;
System.out.println(student.getClassroom());
// 第二部分
Person bPerson = new Person("小李");
Student bStudent = (Student) bPerson;
System.out.println(bPerson.getName());
}
}
在上述运行中,第一部分由于本来就是子类的对象,向下转型没问题,但是第二部分向下转型就会有问题。
第一部分即使为子类对象,但是我们知道,子类对象在实例化时也会调用父类的构造方法,所以子类对象的堆内存其实会存有父类的属性。所以即使向下转型也没有问题
第二部分中父类实例化不会去调用子类的构造方法,强制类型转换的话,父类的堆内存的对没有子类属性,子类在父类的对象内找不到其属性。所以会出问题。(个人理解,如有误请斧正,谢谢)
下图来自魔乐科技
个人案例分析:
案例一
Person [] people = new Person[5];
Teacher teacher = new Teacher("李老师", 5000.0);
Student student = new Student("小王", "高三一班");
people[0] = teacher;
people[1] = student;
for (Person temp:
people) {
System.out.println(((Teacher)temp).getSalary());
}
此时看出teacher转换为student时,肯定会出错。这就是使用父类类型接收后需要向下转型的问题。
加入泛型
-
为了慢慢解决这种对象的向下转型的安全隐患问题,JDK1.5后加入了泛型,使类中的属性、方法或者参数可以通过运行时来决定。
-
泛型定义完成后可以在对象实例化时进行泛型类型的设置。
-
注意:
- 泛型在使用时只允许设置引用类
-
对于只提供访问,不提供修改的方法,可以使用通配符
?
来接收所有类型。
使用? extends 类
来设置泛型的上限(只允许是指定的类
或其子类)
使用? super 类
来设置泛型的下限(只允许是指定的类
其父类)
参考ArrayList源码后,个人做了一个简易版的MyArray
public class MyArray<T> {
private final Object[] array;
public int index = 0;
public int size;
public MyArray(int size) {
array = new Object[size];
this.size = size;
}
public MyArray() {
this.array = new Object[5];
this.size = 5;
}
public boolean add(T t) {
if (this.index >= this.size) {
return false;
} else {
this.array[index] = t;
index++;
return true;
}
}
public T get(int index) {
return (T) this.array[index];
}
public Object[] getArray() {
return Arrays.copyOf(array, index);
}
}
重点来了,在于get
方法,为何此时我们可以直接向下转型呢?因为添加对象调用的add
方法添加的对象是确定的T
类型,传入的对象原本一定是T
类型,所以可以向下转型。
此时上述案例代码可以改为
MyArray<Teacher> myArray = new MyArray<>();
Teacher teacher = new Teacher("李老师", 123.5);
// youngTeacher 是teacher的一个子类
Teacher youngTeacher = new YoungTeacher("xxx");
Student student = new Student("小王", "高三一班");
/* 下面这注释掉的一句,如果取消注释会发现编译时候就不能通过了,所以保证
了数组存放内容的唯一确定。
*/
// myArray.add(student);
myArray.add(teacher);
myArray.add(youngTeacher);
Object[] result = myArray.getArray();
for (Object temp:
result) {
Teacher tempTeacher = (Teacher) temp;
System.out.println(tempTeacher.getSalary());
}
案例二
个人参考第一个案例的感悟写出了第二个案例。可能与现实生活有很大区别,但是原理应该是没问题的。
card类是抽象类,InterCard是只能进行整存争取的卡片,Double是可以进行浮点存取的卡。
public abstract class Card<T extends Number> {
private T balance;
public Card(T balance) {
this.balance = balance;
}
public boolean addCheck(T t) {
if (this.check(t)) {
return this.add(t);
}
return false;
}
protected abstract boolean add(T t);
private boolean check(T t) {
// 此处小偷懒,直接使用了其double值
return t.doubleValue() > 0;
}
public T getBalance() {
return balance;
}
protected void setBalance(T balance) {
this.balance = balance;
}
}
public class DoubleCard extends Card<Double> {
public DoubleCard(Double balance) {
super(balance);
}
public DoubleCard() {
/*
一定要调用父类有参构造,否则会有空指针异常,个人认为原因应该是balance未初始化,
因为是泛型类型,所以父类无法提供默认赋值。
*/
super(0.0);
}
@Override
protected boolean add(Double aDouble) {
this.setBalance(this.getBalance() + aDouble);
return true;
}
}
public class InterCard extends Card<Integer> {
public InterCard(Integer balance) {
super(balance);
}
public InterCard() {
/*
一定要调用父类有参构造,否则会有空指针异常,个人认为原因应该是balance未初始化,
因为是泛型类型,所以父类无法提供默认赋值。
*/
super(0);
}
@Override
public boolean add(Integer integer) {
this.setBalance(this.getBalance() + integer);
return true;
}
}
public class Main {
public static void main(String[] args){
Card<Integer> card = new InterCard();
card.addCheck(100);
Card<Double> doubleCard = new DoubleCard();
doubleCard.addCheck(2000.52);
System.out.println(card.getBalance() + "\n" + doubleCard.getBalance());
}
}
总结
因为对象向下转型可能会抛出异常,所以个人认为泛型只是可以限定容器存放的内容的类型,其实也就是以父类为类型的对象中保存的子类的类型是其子类或子类的子类,转换为子类就不会出现运行时错误了。