1.基础概念
- 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
- 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List< String>,这表明该List只能保存字符串类型的对象。
- JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
2使用泛型的原因
2.1当不使用泛型时
- 任何类型都可以添加到集合中:类型不安全
- 读取出来的对象需要强转:繁琐可能有ClassCastException
@Test
public void text1()
{
ArrayList arrayList = new ArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
//类型不安全
arrayList.add("abd");
for(Object o:arrayList)
{
//强转时可能报ClassCastException
int i=(Integer)o;
System.out.println(o);
}
}
2.2当使用泛型时
- 只有指定类型才可以添加到集合中:类型安全
- 读取出来的对象不需要强转:便捷
@Test
public void text2()
{
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
//编译时会进行类型检查,保证数据的安全
// arrayList.add("abd");
for(Integer i:arrayList)
{
//避免墙转操作
int x=i;
System.out.println(i);
}
System.out.println("=======================");
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext())
{
System.out.println(iterator.next());
}
}
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
总结:
- 集合接口或集合类在jdk5.0时都修改为带泛型的结构
- 在实例化时,可以具体指明泛型的类型
- 指明完以后,在集合类或接口中凡是的定义类或接口时,内部结构使用到类的泛型位置时,都指定为实例化时的泛型
- 主要:泛型类型必须是类,不是基本数据类型,凡是用到基本数据类型的位置,都要用包装类来代替
- 如果实例化时没有指明泛型类型,默认类型为java.lang.Object类型
2.3 如何自定义泛型结构
2.3.1自定义泛型类、接口
自定义范例类Order
public class Order<T> {
private int id;
private String name;
//内部结构就可以使用类的泛型
private T orderT;
public Order() {
}
public Order(int id, String name, T orderT) {
this.id = id;
this.name = name;
this.orderT = orderT;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", name='" + name + '\'' +
", orderT=" + orderT +
'}';
}
}
测试
@Test
public void text1()
{
//如果定义泛型,实例化没有指明类的泛型,则默认类的泛型为Object
Order order = new Order();
order.setOrderT(1);
System.out.println(order.getOrderT());
//建议实例化是指明类的泛型
Order<String> order1 = new Order<>();
order1.setOrderT("1");
System.out.println(order1.getOrderT());
}
泛型类的继承
public class Order1<T> extends Order<T>{//Order1任然是泛型类
}
public class Order2 extends Order<Integer> {//Order不在是泛型类
}
测试
@Test
public void text2()
{
Order2 order2 = new Order2();//由于子类在继承带泛型的父类时,指明了泛型的类型,则在实例化子类对象时,不在需要指明泛型
order2.setOrderT(1);
Order1<Integer> order1 = new Order1<>();
order1.setOrderT(1);
}
注意事项
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
- 泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。
- 尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型(泛型是在创建是确定的)。
- 异常类不能是泛型的
- 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型:按需实现
- 没有类型 擦除
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
2.3.2 泛型方法
- 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
text
public class Text extends Order {
public <E> List<E> copy(E[] arr)
{
ArrayList<E> list = new ArrayList<>();
for(E e:arr)
{
list.add(e);
}
return list;
}
}
测试
@Test
public void text3()
{
Text text = new Text();
Integer[] a=new Integer[]{1,2,3,4};
List<Integer> copy = text.copy(a);
System.out.println(copy);
}
说明
- 在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系,换句话说,泛型方法所属的类是不是泛型类都没有关系
- 泛型方法可以声明为静态,原因:泛型参数是的调用方法时确定的,并非在实例化时确定的
2.3.4
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G< B>并不是G< A>的子类型,二者不存在父子关系,二者为并列关系
类A是类B的父类,A< G> 是B< G>的父类
3. 通配符的使用
通配符: ?
- 如果B是A的一个子类型,G< B>和G< A>是没有关系的,二者的共同父类时G< ?>
举例
@Test
public void text4()
{
ArrayList<String> arrayList1 = new ArrayList<>();
ArrayList<Integer> arrayList2 = new ArrayList<>();
ArrayList<?> arrayList3 = new ArrayList<>();
arrayList3=arrayList1;
arrayList3=arrayList2;
}
- 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
- .写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。( 唯一的例外是null,它是所有类型的成员。)
注意点
//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
3.1有限制的通配符
- <?> 允许所有泛型的引用调用
- 通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
可以读,不能写 - 通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=
测试
People.java
package com.blb;
class People {
private String name;
private String age;
public People() {
}
public People(String name, String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
student.class
public class Student extends People{
private int id;
public Student(int id) {
this.id = id;
}
public Student(String name, String age, int id) {
super(name, age);
this.id = id;
}
public Student()
{
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
测试
@Test
public void text5()
{
List<? extends People> students=null;
List<? super People> students1=null;
ArrayList<People> list1 = new ArrayList<>();
ArrayList<Student> list2 = new ArrayList<>();
ArrayList<Object> list3 = new ArrayList<>();
students=list1;
students=list2;
//编译不通过
// students=list3;
students1=list1;
//编译不通过
// students1=list2;
students1=list3;
//读数据
students=list2;
People people = students.get(0);
//编译不通过
// Student s = students.get(0);
//写数据
//编译不通过
// students.add(new People());
students1=list1;
//读数据
Object object = students1.get(0);
//编译不通过
// People object1 = students1.get(0);
//写数据
boolean add = students1.add(new People());
boolean add1 = students1.add(new Student());
}