一、什么是泛型
(1)、 解释:
一般的类和方法,只能使用具体类型,要么是基本类型,要么是自定义类型,如果我们要编写可以应用多种类型的的代码,就能使用到泛型。
(2)、语法:
代码示例:
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 class Test { public static void main(String[] args) { MyArray<Integer> myArray = new MyArray<>(); myArray.setVal(0, 10); myArray.setVal(1, 20); int ret = myArray.getPos(0); System.out.println(ret);//输出10 } }
代码解释:类名后的 <T> 代表占位符,表示当前类是一个泛型类
二、泛型类的使用
语法:
注意:泛型的使用只能接受类,所以基本数据类型必须使用包装类
如下图:
我们也可以写成如下形式:
编译器可以根据上下文推导出类型的实参,可以省略类型的实参。
三、擦除机制
Java的泛型机制是在编译级别实现的,将所有T替换成Object这种机制我们称为擦除机制。
(代码是上面的代码)通过命令:javap -c 查看字节码文件,所有的T都是Object
注意:
泛型类不能实例化,演示代码如下:
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 this.array; } } public static void main(String[] args) { MyArray<Integer> myArray = new MyArray<>(); Integer[] array = myArray.getArray(); }
运行代码时会报错,报错结果如下:
原因:
因为擦除机制,在编译时将所有的T类型都转换为Object类型,所以调用getArray方法是拿到的是Object类型,而不是Integer类型,将Object[]分配给Integer[]引用,是不安全的,程序会报错;
通俗的来讲,因为Object默认是任何类型的父类,可以存放任何类型,可能是String,可能是Person,运行时直接把Object传给Integer类型的数组,编译器认为是不安全的。
四、泛型的上界
(1)、语法
(2)、示例
而调用MyArray,E的类型,我们可以传入Number,也可以传入Number的子类。
(3)、复杂示例
E必须是实现了Comparable接口的类型。
五、泛型的方法
(1)、语法
(2)、示例
代码如下:
public class Test { public static <E> void swap(E[] array, int i, int j) { E t = array[i]; array[i] = array[j]; array[j] = t; } public static void main(String[] args) { Integer[] array = {1,2,3,4,5}; System.out.println(Arrays.toString(array)); //不使用类推到 Test.<Integer>swap(array, 0, 1); System.out.println(Arrays.toString(array)); System.out.println("=============="); //使用使用类推到 swap(array, 0, 1); System.out.println(Arrays.toString(array)); } }
执行代码结果如下:
六、通配符
(1)、通配符解决什么问题
代码示例:
class Message<T> { private T message ; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class TestDemo { public static void main(String[] args) { Message<String> message = new Message<>() ; message.setMessage("hello world"); fun(message); } public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); }
上述程序带来了新的问题,如果泛型设置的不是String类,而是Integer类呢
我们想让fun能接受所有泛型类型,但又不能更改fun的形参类型,这时候就可以用到通配符<?>
通配符的使用如下:
(2)、通配符的上界
语法:
代码示例:
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test2 { public static void main(String[] args) { Message<Fruit> message1 = new Message<>(); message1.setMessage(new Fruit()); fun(message1); Message<Banana> message2 = new Message<>(); message2.setMessage(new Banana()); fun(message2); Message<Food> message3 = new Message<>(); message3.setMessage(new Food()); //fun(message3);//err } public static void fun(Message<? extends Fruit> tmp) { System.out.println(tmp.getMessage()); } }
由上述例子我们可以看出,我们想调用fun方法,传入的参数的类型必须是Fruit或者是Fruit的子类,不然会报错
此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错!但是可以获取元素。
(3)、通配符的下届
语法:
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test2 { public static void main(String[] args) { Message<Fruit> message1 = new Message<>(); message1.setMessage(new Fruit()); fun2(message1); Message<Food> message2 = new Message<>(); message2.setMessage(new Food()); fun2(message2); Message<Apple> message3 = new Message<>(); message3.setMessage(new Apple()); //fun2(message3);//err } public static void fun2(Message<? super Fruit> tmp) { System.out.println(tmp.getMessage()); } }
调用fun2方法时,我们传入的类型只能是Fruit类或者Fruit的父类,不然会报错
通配符的下界,不能进行读取数据,只能写入数据