在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了
一个包装类型。
那么,泛型到底是怎么编译的?这个问题,也是曾经的一个面试问题。泛型本质是一个非常难的语法,要理解好他
还是需要一定的时间打磨。
通过命令:javap -c 查看字节码文件,所有的T都是Object。
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
有关泛型擦除机制的文章截介绍:https://zhuanlan.zhihu.com/p/51452375
提出问题:
1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new
Object[5]吗?
2、类型擦除,一定是把T变成Object吗?
小结: 基类数组不能直接强制类型转换赋值给子类数组,因为基类数组可能存在其他类型的子类
讲解步骤:
代码1:
class MyArray<T> {
public T[] array = (T[])new Object[10];//泛型类型数组
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
public static void main(String[] args) {
MyArray<Integer> myArray1 = new MyArray<>();
Integer[] strings = myArray1.getArray();//erro
}
/*
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at TestDemo.main(TestDemo.java:31)
*/
原因:擦除替换后, 将Object[]分配给Integer[]引用,程序报错。
// 替换前
public T[] getArray() {
return array;
}
// 替换后
public Object[] getArray() {
return array;
}
通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时
候,直接转给Integer类型的数组,编译器认为是不安全的。
如下两点详细解释:
1.如下代码,在java中,new必须指定确定类型
class MyArray<T> {
//public T[] array = (T[])new Object[10];
public T[] array = new T[2];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
2.在Java中,不能将Object类型的数组分配给子类的数组,因为这会违反Java的类型安全性规则。如果可以这样做,那么子类数组可能包含其它类型的元素,从而导致类型转换异常或者其他运行时错误。要将一个Object类型的数组分配给子类的数组,需要使用强制类型转换并确保所有元素都是正确的子类类型。
例如:
ojbecj对象是所有类的基类,下面是基类与子类的例子,假设有一个父类Animal和两个子类Cat和Dog,如果想要将一个Object类型的Animal数组分配给一个Cat类型的数组,需要进行以下强制类型转换:
public static void main(String[] args) {
// erro1:
Animal[] animalArray = new Animal[2];
Cat[] catArray = (Cat[]) animalArray;//erro
}
// 报错:
//Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
//at genericArray.main(genericArray.java:68)
但是这样做会导致运行时错误,因为animalArray实际上并不是一个Cat数组,其中可能包含其他类型的Animal对象。
正确的做法是,使用for循环逐一将Object类型的元素转换为Cat类型,并添加到Cat类型的新数组中,例如:
public static void main(String[] args) {
// success:
Animal[] animalArray = new Animal[2];
Cat[] catArray = new Cat[2];
for (int i = 0; i < animalArray.length; i++) {
catArray[i] = (Cat) animalArray[i]; // 进行强制类型转换
}
}
这样可以确保每个元素都被正确地转换为Cat类型。但是需要注意,如果animalArray中包含了除Animal以外的其它类型的对象,则会在转换时抛出ClassCastException异常。
public static void main(String[] args) {
// erro2:
Animal[] animalArray = new Animal[2];
animalArray[1] = new Dog(); // Animal[]数组包含了其他Animal子类
Cat[] catArray = new Cat[2];
for (int i = 0; i < animalArray.length; i++) {
catArray[i] = (Cat) animalArray[i]; // 进行强制类型转换
//erro i==1时,Dog错误类型转换
}
// 报错:
//Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
//at genericArray.main(genericArray.java:68)
解决办法: 【了解即可,开发中不会用到】
class MyArray1<T> {
public T[] array;
public MyArray1() {
}
public MyArray1(Class<T> clazz, int capacity) {
array = (T[]) Array.newInstance(clazz, capacity);
}
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public T[] getArray() {
return array;
}
}
实际开发是这样的:
class MyArray3<T> {
//public T[] array = (T[])new Object[10];
public Object[] array = new Object[2];
public T getPos(int pos) {
return (T)array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public Object[] getArray() {
return array;
}
}
public class genericArray {
public static void main(String[] args) {
MyArray<Integer> myArray = new MyArray<>();
myArray.setVal(0,1);
myArray.setVal(1,0);
Object a = myArray.getPos(0);
System.out.println(a);
// 不用转换类型也可以的,因为返回的就是object
Object b = (Object)myArray.getPos(1);
System.out.println(b);
//Integer[] int_array = myArray.getArray(); erro 基类数组不能给子类
// 获取数组后,访问数组元素需要强制转换.
Object[] int_array = myArray.getArray();
System.out.println((Integer)int_array[0]);
}
}
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名<泛型新参 extend 类型边界>{
}
具体意思通过例子说明。
public class MyArray<E extends Number> {
........
}
接受 Number 的子类型作为 E 的类型实参
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
error: type argument String is not within bounds of type-variable E
MyArrayList<String> l2;
^
where E is a type-variable:
E extends Number declared in class MyArrayList
了解: 没有指定类型边界 E,可以视为 E extends Object
// #2 语法规定只允许有使用Comparable接口的类型
class Alg<T extends Comparable<T>>{
public T findMax(T[] array){
T max = array[0];
for(int i=1;i<array.length;i++){
// if( max < array[i]) #1 erro : 引用类型无法比较
// 只能实现接口compareTO()
if(max.compareTo(array[i])<0)
{
max = array[i];
}
}
return max;
}
}
public class Main {
public static void main(String[] args) {
Alg<Integer> alg = new Alg();
Integer[] array = {1,2,3,4,5};
// 根据array自动类型推导
System.out.println(alg.findMax(array));
// 新语法 <类型> 指定类型
System.out.println(alg.<Integer>findMax(array));
// Alg alg2 = new Alg(); // erro
//#3
// Alg alg2 = new Alg(); // erro
}
}
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
静态的泛型方法 需要在static后用<>
声明泛型类型参数
class Alg1{
public static<T extends Comparable<T>> T findMax(T[] array){
T max = array[0];
for(int i=1;i<array.length;i++){
// if( max < array[i]) #1 erro : 引用类型无法比较
// 只能实现接口compareTO()
if(max.compareTo(array[i])<0)
{
max = array[i];
}
}
return max;
}
}
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,4,5};
// 根据array自动类型推导
System.out.println(Alg1.findMax(array));
// 新语法 <类型> 指定类型
System.out.println(Alg1.<Integer>findMax(array));
}
}
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,4,5};
// 根据array自动类型推导
System.out.println(Alg1.findMax(array));
}
}
public class Main {
public static void main(String[] args) {
Integer[] array = {1,2,3,4,5};
// 新语法 <类型> 指定类型
System.out.println(Alg1.<Integer>findMax(array));
}
}