目录
一、泛型的概念
(一)什么是泛型
(二)为何引入泛型
(三)泛型在集合中的使用
二、泛型的使用(以下用代码进行讲述)
(一)泛型类
(二)泛型方法
(1)普通泛型方法
(2)静态泛型方法
(三)泛型接口
(四)泛型上界及其擦除机制
(1)泛型上界
(2)java泛型擦除机制
三、通配符
(一)通配符的上界
(二)通配符的下界
四、泛型总结
public class MyArrayList {
private final int capacity = 10;
private int usedSize;
//Object[]数组能存储任何类型的元素
private Object[] elem;
public MyArrayList() {
elem = new Object[capacity];
}
//增添元素
public void add(Object val) {
if(usedSize != capacity) {
elem[usedSize++] = val;
} else {
System.out.println("容量溢出");
}
}
//获取元素
public Object get(int pos) {
return elem[pos];
}
public static void main(String[] args) {
//在自定义的集合中能够存储任何类型的元素,显然不是程序员想要的结果
MyArrayList myArrayList = new MyArrayList();
myArrayList.add("a");//字符串型
myArrayList.add(1);//整型
myArrayList.add(2.0);//double型
//每次读取元素的时候都要强制类型转换
String s = (String) myArrayList.get(0);
Integer a = (Integer) myArrayList.get(1);
}
}
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName {}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName extends ParentClass {
// 可以只使用部分类型参数
}
public class MyArrayList1 {
private final int capacity = 10;
private int usedSize;
private T[] elem;
public MyArrayList1() {
//报错,不能创建一个泛型数组
//elem = new T[capacity];
//不报错,但是存在问题,此问题留到下文讲擦除机制时讲
elem = (T[]) new Object[capacity];
}
//真正正确的创建泛型数组的方式--运用反射
public MyArrayList1(Class clazz,int capacity) {
elem = (T[]) Array.newInstance(clazz,capacity);
}
//增添数据
public void add(T val) {
if(usedSize != capacity) {
elem[usedSize++] = val;
} else {
System.out.println("容量溢出");
}
}
//读取数据
public T get(int pos) {
return elem[pos];
}
public static void main(String[] args) {
//使用泛型后自动进行编译检查和类型转换
MyArrayList1 myArrayList = new MyArrayList1();
myArrayList.add("a");
//报错
//myArrayList.add(1);
//myArrayList.add(2.0);
//读取元素时不需要强制类型转换
String s = myArrayList.get(0);
}
}
泛型实现数据类型参数化,传入的数据类型必须是基本数据类型的包装类。
泛型类 < 类型实参 > 变量名 ; // 定义一个泛型类引用new 泛型类 < 类型实参 > ( 构造方法实参 ); // 实例化一个泛型类对象
//指定一个泛型类引用泛型类的对象 ArrayList
list = new ArrayList ();//后面的String可以省略 //自定义一个交换数据类 class Swap {} public static void main(String[] args) { Swap swap = new Swap<>(); }
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
//定义交换两个数据值的类
class Swap {
//普通泛型方法
//(形式一)
public void swap(T[] array, int i, int j) {
T tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
//(形式二)
public void swap(T[] array, int i, int j) {
T tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
方法限定符 static <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
//泛型的静态方法
class Swap {
public static void swap(T[] array, int i, int j) {
T tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
注:泛型静态方法不依赖于对象,所以不在class类后面添加泛型
interface 泛型接口名<类型形参列表> {....}
//在此用Comparable接口来举例
//此接口适用于任何类型的比较
interface Comparable {
public int compareTo(T o);
}
class 泛型类名称<类型形参 extends 类型边界> { ... }
//泛型上界为Number
class MyArray {
public static void main(String[] args) {
//创建对象时只能是Number或Number的子类
MyArray myArray1 = new MyArray();
MyArray myArray2 = new MyArray();
//报错,String不是Number的子类
MyArray myArray = new MyArray();
}
}
//自定义一个比较类
//泛型方法
class Alg1> {
public T findMax(T[] array) {
T max = array[0];
for (T elem: array) {
if(elem.compareTo(max) > 0)
max = elem;
}
return max;
}
}
该代码块的上界是Comparable接口,当构造该类对象时,必须是实现了Comparable接口,基本数据类型的包装类都是实现了Comparable接口。
擦除机制是Java5用来实现泛型的技术。一般来说,在运行时阶段,Java编译器先执行类型检查,然后执行擦除或删除泛型信息。而具体化的泛型,与此正好相反。基于具体化泛型系统的类实现在运行时作为顶级实体,它在运行时保留了类型参数,而这给基于类型和反射性的语言提供了确定的操作。
不能创建泛型数组,会存在隐患,以下用代码进行演示。
class MyArray {
private final int capacity = 10;
private int usedSize;
private T[] elem;
public MyArrayList1() {
//报错,不能创建一个泛型数组
//elem = new T[capacity];
//不报错,但是存在问题,编译的时候,替换为Object[]
elem = (T[]) new Object[capacity];
}
//真正正确的创建泛型数组的方式--运用反射
public MyArrayList1(Class clazz,int capacity) {
elem = (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;
}
//编译时返回的类型是Object[]
public T[] getArray() {
return array;
}
public static void main(String[] args) {
MyArray myArray1 = new MyArray<>();
//使用getArray()方法,返回的是bject[]类型的数组,不是不能直接用Integer[]来接受的
//数组和泛型的区别是数组是在运行时检查错误,而泛型是在编译时检查错误,为此该语句段未报错
Integer[] strings = myArray1.getArray();
}
}
数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息,泛型在编译时检查类型错误。
返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Double等等类型,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。
另外,即使对返回的数组进行强制类型转换为(Integer[])也不能改变其内部元素是其他的数据类型,运行时程序任然会报错。
public class MyArrayList1 {
private final int capacity = 10;
private int usedSize;
private T[] elem;
//运用反射创建泛型数组
public MyArrayList1(Class clazz,int capacity) {
elem = (T[]) Array.newInstance(clazz,capacity);
}
}
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Cat 是Animal 的子类,那么 List
也应 该是 List 的子类。但是经过泛型的擦除机制,最终两个都擦除到object,所以泛型是不支持这样的父子类关系的。 ? 用于在泛型的使用,即为通配符。
extends 上界 >extends Animal > // 可以传入的实参类型是Animal或者Animal的子类
extends Animal> 是
及 的父类 > 是 extends Animal>的父类
//构成父子类关系
class Animal {
}
class Dog extends Animal {
}
class Dubianquan extends Dog {
}
②父类引用子类对象:
public class Zoon {
public static void main(String[] args) {
// extends Animalr> 是 及的父类
ArrayList extends Animal> arrayList1 = new ArrayList();
ArrayList extends Animal> arrayList2 = new ArrayList();
ArrayList extends Animal> arrayList3 = new ArrayList();
//> 是 extends Animalr>的父类
ArrayList> arrayList = arrayList1;
}
}
通配符的上界只适合于读取数据,不适用于写入数据。因为通配符上界引用的是
的对象,但是不能确定到底是哪个子类,不能存储确定的子类类型,java在编译时会自动查错。
class Animal {
public String toString() {
return "Animal :>";
}
}
class Cat extends Animal {
@Override
public String toString() {
return "Cat :>";
}
}
class Dog extends Animal {
@Override
public String toString() {
return "Dog :>";
}
}
public class Zoon {
//通配符的上界是不适用于写入的,只适合于读取
public static void main0(String[] args) {
ArrayList extends Animal> arrayList = new ArrayList<>();
//报错
//arrayList.add(new Animal());
//arrayList.add(new Dog());
//但是可以读取数据,用泛型的上界来读取子类的数据,属于向上转型
Animal animal = arrayList.get(0);
//也可以用Object,因为他是所有类的父类
Object o = arrayList.get(0);
}
}
super 下界 >super Animal > // 代表 可以传入的实参的类型是 Animal 或者 Animal 的父类类型
super Animal> 是
及 的父类 > 是 super Animal>的父类
//构成父子类关系
class Animal {
}
class Dog extends Animal {
}
class Dubianquan extends Dog {
}
public class Zoon {
public static void main(String[] args) {
// super Dog> 下界是Dog,引用的是的对象
//因为 super Dog>的上界没有限制,所以一直可以到Object。
//因此 super Dog>是<任一Dog及其父类>的父类
//符合父类引用子类对象的规则
ArrayList super Dog> arrayList1 = new ArrayList();
ArrayList super Dog> arrayList2 = new ArrayList();
//报错,下界是Dog,不能引用下界以下的子类
// ArrayList super Dog> arrayList3 = new ArrayList();
//> 是 super Dog> 的父类
ArrayList> arrayList = arrayList1;
}
}
通配符的下界只适合于写入数据,不适用于读取数据。因为通配符下界引用的是
的对象,但是不能确定读取到的是哪个父类类型,所以引用的类型不能确定,也就不能读取,但是Object是所有类的父类,非要读取的话可以用Object来接受。因为下界存储的内容都是下界以下的数据类型,所以适合写入数据。
class Animal {
@Override
public String toString() {
return "Animal{}";
}
}
class Dog extends Animal {
@Override
public String toString() {
return "Dog{}";
}
}
class Dubianquan extends Dog {
@Override
public String toString() {
return "Dubianquan{}";
}
}
public class Zoon {
public static void main(String[] args) {
ArrayList super Dog> arrayList1 = new ArrayList();
ArrayList super Dog> arrayList2 = new ArrayList();
//报错,存储的数据是Dog及其子类的数据类型
// arrayList2.add(new Animal());
//添加的元素 是Dog或者Dog的子类
arrayList2.add(new Dog());
arrayList2.add(new Dubianquan());
//ArrayList super Dog> arrayList2引用的是Dog及其父类对象
//编译器会考虑到ArrayList super Dog> arrayList2引用的对象如果是 new ArrayList();
//那么它存储的数据可能会有Animal类型的数据,那么就不能用Dog来接受Animal类型的数据。
// Dog dog = arrayList2.get(0);//报错
// Animal animal = arrayList2.get(0);//报错 原理同上
//但是Object是所有类的父类,可以使用其来读取数据
Object o = arrayList2.get(0);
}
}
class Animal {
@Override
public String toString() {
return "Animal{}";
}
}
class Dog extends Animal {
@Override
public String toString() {
return "Dog{}";
}
}
class Dubianquan extends Dog {
@Override
public String toString() {
return "Dubianquan{}";
}
}
public class Zoon {
public static void main(String[] args) {
//通配符下界存储数据
ArrayList super Dog> arrayList1 = new ArrayList();
arrayList1.add(new Dog());
arrayList1.add(new Dubianquan());
//要用通配符下界读取数据,就要用Object来接受
for (Object O: arrayList1) {
System.out.println(O);
}
//利用通配符上界来读取数据
ArrayList extends Animal> arrayList = (ArrayList extends Animal>) arrayList1;
//因为上述存储的数据类型都是Animal的子类,所以可以用Animal来接受
for (Animal a: arrayList) {
System.out.println(a);
}
}
}