概念:
java泛型是j2se 1.5引入的新特性,本质是参数化类型,将用到的数据类型指定为任意一种,可用在类、接口、方法中。可以用任意字母来表示,但一般使用大写字母T(Type的简写)来表示。
为什么要有泛型:
在j2se 1.5之前,java允许我们构建一个元素类型为Object的集合,我们可以对类型Object的引用来实现参数的“任意化”,这时编译是没有问题的,
但是在运行期间会出现问题,这是一个安全隐患,为了解决此问题,java 1.5 引入了泛型。
当我们不使用泛型的时候:
public class TestArrayList {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();//没有声明泛型的集合
list1.add("abc");//添加String类型
list1.add(123);//添加Integer类型,编译阶段不会报错
String str1 = (String) list1.get(0);
String str2 = (String) list1.get(1);//运行期间会报错
}
}
上面的例子表示在不声明泛型的时候,这个集合里面可以放任意类型的数据,并且在编译期间不显示错误,却出现了安全隐患。一个集合里面放多种类型的数据,没有做到解耦和单一职责,这样也是不推荐的。
当我们使用泛型的时候:
public class TestArrayList {
public static void main(String[] args) {
ArrayList <String>list1 = new ArrayList<String>();
list1.add("abc");
list1.add(123);//在编译阶段就会提示错误,避免隐患。
}
}
泛型的使用
一、泛型类的使用
当我们不知道未来属性类型的时候,这种情况可以使用类型占位符进行占位
单个泛型的使用
下面声明一个泛型类
public class Person<T> {
private T name;
private T address;
public T getAddress() {
return address;
}
public void setAddress(T address) {
this.address = address;
}
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
}
我们在使用的时候,设定好属性的类型之后,就只能传入指定的泛型类型。
public class TestArrayList {
public static void main(String[] args) {
Person<String> person = new Person<String>();
person.setName(123);//编译期间会报错;提示类型不匹配
person.setAddress("China");
}
}
多个泛型的使用
上面的Person类中声明了两个属性,但是如果我想让这两个属性的类型是不一样的怎么办?这个时候可以声明多个泛型。
声明一个泛型类:
public class Animal<K,V> {
private K name;
private V age;
public K getName() {
return name;
}
public void setName(K name) {
this.name = name;
}
public V getAge() {
return age;
}
public void setAge(V age) {
this.age = age;
}
}
我们在调用的时候,只能按照泛型类型声明的顺序给属性赋值。
下面String 和Integer的声明顺序分别对应泛型类中name 和age的声明顺序。
public class TestArrayList {
public static void main(String[] args) {
Animal<String, Integer> animal = new Animal<String, Integer>();
animal.setName("Tom");
animal.setAge(18);
}
}
二、泛型方法的声明
普通的泛型方法
泛型类以及方法的声明:
public class Person1<T,K> {
public void showName(T name) {
System.out.println(name);
}
public void showAge(K age){
System.out.println(age);
}
}
方法的调用:
public class TestArrayList {
public static void main(String[] args) {
Person1<String,Integer> person1 = new Person1<String,Integer>();
person1.showName("Jack");
person1.showAge(19);
}
}
静态的泛型方法
静态泛型方法中的类型占位符和类中的占位符是没有关系的。
这是因为不用static修饰的方法和所在的类不是同一级别,运行时只会加载类,不会加载非static修饰的方法和变量;而用static修饰的方法是和类同一级别的,都是在类加载的时候进行初始化,所以类上面的泛型类型无法应用于static修饰的方法里面。它俩各论各的。
public class Person1<T,K> {
public void showName(T name) {
System.out.println(name);
}
public void showAge(K age){
System.out.println(age);
}
//静态方法中的泛型和类中的泛型没有关系,所以此行代码会报错
public static void show2( T t){
System.out.println(t);
}
如果想要在static修饰的方法里面使用泛型,应该使用如下写法:
在static后面先声明泛型,然后在参数中声明泛型变量。
调用方法:
public class TestArrayList {
public static void main(String[] args) {
Person1.show2("Jerry");
}
}
另外,如果想让方法只依赖于自己的泛型,直接在方法的public后面声明即可。
public class Person1<T,K> {
public <S> void show3(S name) {
System.out.println(name);
}
}
调用:
public class TestArrayList {
public static void main(String[] args) {
Person1<String,Integer> person1 = new Person1<String,Integer>();
person1.show3(111);
}
}
三、泛型接口
首先在接口中声明泛型
public interface IPlant<T> {
public void eat(T t);
}
实现类A实现该接口
public class Flower implements IPlant<Integer> {
public void eat(Integer integer) {
}
}
实现类B实现该接口
public class Tree implements IPlant<String >{
public void eat(String string ) {
}
}
由此可见,不同类型的实现类可以实现相同的接口,但是如果泛型接口的实现类没有指定具体的泛型类型,必须要在这个实现类中声明一个泛型类型占位符。如下:
public class Flower<T> implements IPlant<T> {
public void eat(T integer) {
}
}
四、泛型擦除模式
java中的泛型只存在于代码编译阶段,在运行期间泛型类型是会被去除掉的,这个就叫做擦除模式。
如下代码输出的结果显示为true
如上两个集合的类型不同,但是比较他们的class,返回的却是true,说明在运行阶段,java消除了泛型的类型。
其实在运行阶段它默认的形式如下:
Person p1= new Person();
Person p2= new Person();
泛型擦除的原因有两种说法:
1、为了兼容jdk老版本的编码,之前jdk有一个list,现在也有一个,如果不擦除会有冲突。
2、为了防止创建泛型时造成过度资源消耗。