1.泛型类的定义
1.1 为什么需要泛型
假设我们自定义了一个简单的数组类,如下:
public class MyArray {
private int[] array = null;
private int size;
private int capacity;
public MyArray(int capacity){
array = new int[capacity];
size = 0;
this.capacity = capacity;
}
public void add(int index, int data){
if(size < capacity)
array[size++] = data;
}
public int get(int index){
return array[index];
}
public int size(){
return size;
}
public static void main(String[] args) {
// 创建一个MyArray对象,里面存储int类型的数据
MyArray m1=new MyArray(10);
m1.add(0,1);
m1.add(1,2);
m1.add(2,3);
for(int i=0;i<m1.size();++i){
System.out.println(m1.get(i)+"");
}
System.out.println();
//但是,如果创建一个MyArray对象,里面要想存doulble类型的数据
//代码就不能通过编译
MyArray m2=new MyArray(10);
m2.add(0,1.0)
m2.add(1,2.0)
m2.add(2,3.0)
}
}
通过上述示例发现,MyArray类中实际只能保存int类型的数据,对于其他类型的数据比如:double、String或者自定义类型的对象,根本无法存储。
想要解决上述问题,最简单的方式就是:对于不同的类型,分别实现各自的MyArray类即可,但是估计你可能不愿意。业界有大佬是按照如下方式改进的:将上述代码中存储数据的类型全部有int改为Object,因为在Java中Object是所有类的基类。
class Person {
public String name;
public Person(String name) {
this.name = name;
}
}
public class MyArray {
private Object[] array = null;
private int size;
private int capacity;
public MyArray(int capacity){
array = new Object[capacity];
size = 0;
this.capacity = capacity;
}
public void add( Object data){
if(size < capacity)
array[size++] = data;
}
public Object get(int index){
return array[index];
}
public int size(){
return size;
}
public static void main(String[] args) {
// 创建一个MyArray对象,里面存储int类型的数据
MyArray m1=new MyArray(10);
m1.add(0);
m1.add(1);
m1.add(2);
System.out.println("===============");
// 创建一个MyArray对象,里面存储浮点类型的数据
MyArray m2=new MyArray(10);
m2.add(1.0);
m2.add(2.0);
m2.add(3.0);
System.out.println("===============");
// 创建一个MyArray对象,里面存储Person类型的数据
MyArray m3 = new MyArray(10);
m3.add(new Person("Peter"));
m3.add(new Person("Jim"));
m3.add(new Person("David"));
}
}
经改过之后的MyArray终于任意类型都可以存储了,最后:代码只需实现一份,但是任意类型都可以存储,貌似一切都比较美好。但是大家使用之后,纷纷吐槽:因为Object是所有类的基类,那就意味着可以在一个MyArray中存储不同种类的数据类型喽:
public static void main(String[] args) {
MyArray m = new MyArray(10);
m.add(1);
m.add(2.0);
m.add("Peter");
m.add(new Person("David"));
for(int i = 0; i < m.size(); ++i){
String s = (String)m.get(i);
System.out.print(s + " ");
}
}
虽然代码可以通过编译,但是如果想要遍历MyArray中的数据,怎么遍历啊~~~
上述代码再运行期间报错: Exception in thread “main” java.lang.ClassCastException:
java.lang.Integer cannot be cast to java.lang.String;运行时出错的原因非常简单:上述代码中,由于MyArray存储数据不都全是String类型的,那如果强转成String类型之后,肯定会发生类型转换异常。以上就是JDK1.5之前的解决方式,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要作显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以在预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患,为了解决该问题,JDK1.5中引入了泛型。
1.2 泛型的概念
泛型是java1.5中增加的一个新特性,通过泛型可以写与类型无关的代码,即编写的代码可以被很多不同类型的对象所重用,经常用在一些通用的代码实现中,比如:java集合框架中的类几乎都是用泛型实现的。
泛型的本质是:类型参数化。类似函数传参一样,传递不同的实参,函数运行完将会产生不同的结果。
1.3 泛型的分类
泛型主要包含:泛型类、泛型方法和泛型接口。
2. 泛型类
2.1 泛型类的定义
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
// 类实现体
}
类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型。
2.2 泛型类的例子
public class MyArray<T> {
private T[] array=null;
private int size;
private int capacity;
public MyArray(int capacity) {
//由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
array=(T[])new Object[capacity];
size=0;
this.capacity=capacity;
}
public void add(T data){
if(size<capacity){
array[size++]=data;
}
}
public T get(int index){
return array[index];
}
public int size(){
return this.size;
}
}
2.3 泛型类的实例化
2.3.1 实例化语法
泛型类<类型实参> 变量名; 定义一个泛型类引用。 new 泛型类<类型实参>(构造方法实参); 实例化一个
泛型类对象。
class Person{
public String name;
public Person(String name) {
this.name = name;
}
}
public class MyArray<T> {
private T[] array=null;
private int size;
private int capacity;
public MyArray(int capacity) {
//由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
array=(T[])new Object[capacity];
size=0;
this.capacity=capacity;
}
public void add(T data){
if(size<capacity){
array[size++]=data;
}
}
public T get(int index){
return array[index];
}
public int size(){
return this.size;
}
public static void main(String[] args) {
// 将泛型类使用String类型来实例化,表明m中只能存放String类型的对象
MyArray<String> m = new MyArray<>(10);
m.add("Peter");
m.add("David");
m.add("Jim");
// 编译失败:因为在实例化时,已经明确其内部只能存储String类型的对象
// Person对象和String对象之间不能转换
// m.add(new Person("Lily"));
for(int i = 0; i < m.size(); ++i){
// 此处从m中获取到的成员,再不需要进行强制类型转换了
String s = m.get(i);
System.out.print(s +"");
}
}
}
注意:
右侧的<>中的类型可以省略不写;左侧的<>中的类型不能省略不写。
2.4 泛型类的定义-类型边界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名称<E extends U> {
...
}
在实例化时,E只能是U或者U的子类。
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
public class MyArray<T extends Animal> {
private T[] array=null;
private int size;
private int capacity;
public MyArray(int capacity) {
//由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
array=(T[])new Object[capacity];
size=0;
this.capacity=capacity;
}
public void add(T data){
if(size<capacity){
array[size++]=data;
}
}
public T get(int index){
return array[index];
}
public int size(){
return this.size;
}
public static void main(String[] args) {
MyArray<Animal> a=new MyArray<>(10);
a.add(new Animal("老虎"));
MyArray<Cat> c=new MyArray<>(10);
c.add(new Cat("小猫"));
// 编译失败,因为String不是Animal的子类
// MyArray<String> m3 = new MyArray<>(10);
}
}
2.5 泛型类的使用-通配符(Wildcards)
通配符上界(语法格式):
<? extends 上界>
代码示例:
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
public class MyArray<T> {
private T[] array=null;
private int size;
private int capacity;
public MyArray(int capacity) {
//由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
array=(T[])new Object[capacity];
size=0;
this.capacity=capacity;
}
public void add(T data){
if(size<capacity){
array[size++]=data;
}
}
public T get(int index){
return array[index];
}
public int size(){
return this.size;
}
public static void main(String[] args) {
//传入Animal及其子类都是可以的
printall(new MyArray<Cat>(10));
printall(new MyArray<Animal>(10));
//传入其他类型则不可以
// printall(new MyArray<String>(10)); //编译报错
}
public static void printall(MyArray<?extends Animal> c){
System.out.println(c);
}
}
通配符下界:
<? super 下界>
代码示例:
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
}
public class MyArray<T> {
private T[] array=null;
private int size;
private int capacity;
public MyArray(int capacity) {
//由于T是啥类型,不确定,所以不能直接new,而是通过强制类型转换
array=(T[])new Object[capacity];
size=0;
this.capacity=capacity;
}
public void add(T data){
if(size<capacity){
array[size++]=data;
}
}
public T get(int index){
return array[index];
}
public int size(){
return this.size;
}
public static void main(String[] args) {
//传入Animal及其Animal的父类都是可以的
printall(new MyArray<Object>(10));
printall(new MyArray<Animal>(10));
//传入其他类型则不可以
// printall(new MyArray<Cat>(10)); //Cat是Animal的子类,是会报错的
}
public static void printall(MyArray<? super Animal> c){
System.out.println(c.get(0));
}
}
2.6 泛型中的父子类型
public class MyArray<E> { ... }
// MyArray<Object> 不是 MyArray<Animal> 的父类型
// MyArray<Animal> 也不是 MyArray<Cat> 的父类型
// 需要使用通配符来确定父子类型
// MyArray<?> 是 MyArray<? extends Animal> 的父类型
// MyArray<? extends Animal> 是 MyArrayList<Dog> 的父类型
3.泛型方法
1 语法格式
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
2.代码示例
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
public Animal() {
}
public void eat(){
System.out.println("吃吃吃,大吃货!");
}
}
public class Test0525_1 {
//<T>可以理解位泛型方法的标志,T就是返回值
public static <T> T myprint(Class<T> c) throws IllegalAccessException, InstantiationException {
return c.newInstance(); //本质调用无参构造器,实例化对象
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Animal a1 = myprint(Animal.class);
a1.eat();
}
}
4. 泛型接口
代码示例:
import java.util.Arrays;
class Peson implements Comparable<Peson> {
String name;
int age;
public Peson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Peson o) {
//要利用Intager类重写好的CompareTo来排序
return new Integer(age).compareTo(o.age);
}
@Override
public String toString() {
return "Peson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test0525 {
public static void main(String[] args) {
Peson[] p={new Peson("达摩",20),
new Peson("三藏",25),
new Peson("宫本",22)};
Arrays.sort(p);
System.out.println(Arrays.toString(p));
}
}
5. 类型擦除
5.1什么是类型擦除:
Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。 即泛型类和普通类在 java 虚拟机内是没有什么特别的地方。
原理图如下:
5.2 类型擦除的规则
1:消除类型参数声明,即删除<>及其包围的部分。
2:根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
3:为了保证类型安全,必要时插入强制类型转换代码。
4:自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性“。
5:代码示例:
// 1. 无限制类型擦除---<E>和<?>类型参数都被替换为Object
class MyArray<E> {
// E 会被擦除为 Object
}
// 2. 有限制类型擦除---<T extends Animal>和<? extends Animal>的类型参数被替换为Animal
// <? super Animal>被替换为Object
class MyArray<E extends Animal> {
// E 被擦除为 Animal
}
// 3. 擦除方法中的类型参数
public class Util {
public static <E> void swap(E[] array, int i, int j) {
// ...
// <E>删除掉 E被擦除为Object
}
}
总结: 即类型擦除主要看其类型边界而定