前言
在jdk5.0之前Java所使用的类,接口,泛型都是没有指定可以操作的数据类型的,这个问题在集合中体现得很突出,这样的话就会使得数据有可能不安全,在jdk5.0以后java引进了泛型,解决了这一问题,在操作数据的时候指明了类型,大大提高了数据的安全性。
为什么要有泛型
- 指明一个结构所能操作的数据类型保证了操作数据的安全性
- 在一些方面简化了代码,比如省去了部分强转操作
具体举例
public void test(){
//创建一个集合
ArrayList list = new ArrayList();
list.add(132);
list.add("aaa");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
//强制类型转换
Integer next1= (Integer) next;
}
}
在编写代码的时候没有报错,说明可以编译过去,但是在运行的时候就会出错
类型转换错误,应为在向集合里面添加数据的时候加入了String类型的数据,但是在下面强制类型转换的时候强制转换为了Integer,这是int类型的包装类,所以运行时就会出现类型转换错误
泛型的概念
泛型好比一个标签,在操作数据的时候提示我们只能操作的数据类型。在定义接口,定义类时通过这个标识(标签),表示某个属性的类型或者某个方法的返回值及参数类型,这个参数将在使用时确定
具体举例
public void test(){
ArrayList list = new ArrayList();
list.add(132);
list.add("aaa");
}
可以看出当我们创建了一个集合对象的时候,可以往这个集合里面添加任何类型的数据,因为我们没有指定可以添加的数据类型,默认就是object了,object可以存任何类型的数据
当我们通过泛型指定之后,泛型的定义:<操作对象>
可以发现这里就只能添加Integer类型的数据了,String的类型就会报错,这样就限制了所能操作的数据类型
集合中如何使用泛型
在集合中使用泛型和直接使用集合没有太大区别,只是加入了一个泛型而已
public void test1(){
//创建一个集合,利用泛型指明所能操作的数据类型为Integer
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(123);
list.add(456);
list.add(789);
System.out.println(list);
//方式一:通过增强for循环遍历集合中的内容
for(Integer in :list){
System.out.println(list);
}
//方式二:通过Iterator(迭代器),遍历集合中的内容
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
注意
- 在jdk5.0以后修改为泛型
- 在实例化集合时可以指明泛型的类型
- 指明完后,在集合使用时,内部结构都必须使用指定的类型
- 泛型的类型必须是一个类,在使用基本数据类型的时候可以用包装类替换
- 如果实例化的时候没有指明泛型的类型,默认为object类型
- 现在java提供了类型推断,在创建对象的时候可以不用写后面的那一个泛型直接写成
<>
就可以了
自定义泛型结构
在编写代码的时候我们自己也可以自定义泛型结构,可以自定义泛型类,泛型接口,泛型方法
自定义泛型类
自定义泛型类时我们可以参考Java本身提供的泛型类
可以发现通过<E>
来定义一个泛型类,其实在定义的时候可以使用<大写字母>
来表示一个泛型,我们通常使用T,E来定义泛型
格式:class 类名<声明自定义泛型>{}
举例
public class GenericClass<T>{
String name;
int age;
//类的内部结构就可以使用类的泛型,这里是一个条件,并不是一个类
//但是我们可以理解为一个类,因为这个条件在大多数的时候功能和类的相同
T orderT;
public GenericClass(){
}
public GenericClass(String name, int age,T orderT){
this.name = name;
this.age = age;
this.orderT = orderT;
}
public T getOrder(){
return orderT;
}
public void setOrder(T order){
this.orderT = order;
}
//重写orderT的toString方法
@Override
public String toString() {
return "GenericClass{" +
"orderT=" + orderT +
'}';
}
}
在自定义类完成后在之后的调用里面我们就可指定这个类操作的具体数据类型了
public void test(){
GenericClass<String> gc = new GenericClass<String>();
gc.name = "zjns";
gc.setOrder("abc");
gc.setOrder("bbb");
GenericClass<Integer> gc2 = new GenericClass<Integer>();
gc2.setOrder(123);
gc2.setOrder(789);
}
注意
- 泛型可以同时申明多个中间使用
,
隔开<T,E> - 不同泛型对象之间不能相互赋值
自定义泛型接口
格式:interface 接口名<声明自定义泛型>{}
interface Dao<T>{
public void add(T t);
}
public class Demo6 implements Dao<String> {
public void add(String t) {
}
}
注意:接口上自定义泛型的具体数据类型是在实现一个接口的时候指定的
自定义泛型方法
方法也可以自定义类型,并且和泛型类没有任何关系,只和自身定义的泛型类型有关系,即使在没有定义过泛型的类里面也可以定义泛型方法
格式:public <T> K<T> showKeyName(Generic<T> container){}
这里面的表示次方法是泛型方法,次结构必不可少,但是k表示次方法返回的数据类型为k类型,这个可以使用其他对象来替换,只要满足规则
public <E> List<E> Generic(E[] arr){
ArrayList<E> list = new ArrayList<E>();
for(E e : arr){
list.add(e);
}
return list;
}
在调用方法的时候才指明传入方法中数据的类型,静态能方法不能使用泛型方法
泛型继承
当我们声明了父类方法和子类方法的时候,可以通过继承的方式类来继承泛型
//父类
public class Generic2<T,E> {}
//子类继承的方式
//方式一:全继承
public class Generic3<T,E> extends Generic2<T,E>{}
//方式二:半继承
public class Generic3<T> extends Generic2<T,E>{}
//方式三:不继承
public class Generic3<A,B> extends Generic{}
如何使用通配符
从上面我们知道,List list = ArrayList 这样的语句是无法通过编译的,尽管 Integer 是 Number 的子类型。那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。
格式:<?>
,这样书写的就是通配符,他可以表示任何一种泛型,就好比是所有泛型的父类,有了泛型之后我们就可以进行以下操作了
可以发现使用泛型之后可以将list赋值给list2,而不能将list赋值给list3,这就能体现出list2是list的父类
通配符的上下界(有限制条件的通配符)
利用 <? extends Fruit>
形式的通配符,Fruit是类型参数的上界,可以实现泛型的向上转型,?
可以理解为<=
当通配符的另一个方向是 “超类型的通配符:<? super T>
,T 是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了,?
,可以理解为>=