jdk5.0新增的特性。
我们可以将中药柜作比喻,每一种中药是一个类,这个类里面有不同品种的中药。比如,大黄是一个类,但是大黄的品种有很多。
我们要找到中药这个类,就要在中药柜上面贴上标签,里面的品种就如同一个类下的各种类型属性。
中药柜上的标签,就是我们的泛型,以此推出:
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
正如:List,这表明该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,
从而可以在声明集合变量、创建集合对象时传入类型实参。
前一章讲到集合的时候,我们就说过什么是数组,我们了解了数组有哪些优点,
其中,它的优点之一就是可以限制数组的类型,这加强的数据的严密性,安全性更高。
而我们在我们集中当中,数据是无序的,且可以放置多种类型,这就使得安全性减低,
但我们也必须用到集合,所以便引出了泛型,其它规范也是如此。
/*问题引入*/
@Test
public void test1(){
List<java.io.Serializable> list=new ArrayList<java.io.Serializable>();
list.add(1);
list.add(2);
list.add(3);
for (Object obj:list){
// 多态
int i=(Integer)obj;
System.out.println(i);
}
/*以上输出就不用多说了,但是在这里若是我们执行下面的操作*/
list.add("a");
for (Object obj:list){
int i=(Integer) obj;
System.out.println(i);
}
// 输出:java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
// 我们可以看见它报错,也就是说我们存进去了一个字符型,造成了原来的整型数据不安全,但是我们也要执行这样的操作,这个时候泛型就起作用了。
}
/*List接口泛型例子*/
@Test
public void test2(){
List<Integer> list= new ArrayList<Integer>();//指定泛型为Integer
list.add(1);
list.add(2);
list.add(3);
for (Integer o:list){
// 因为泛型,不用强转。
int i= o;
System.out.println(i);
}
// list.add("tom");因为泛型,我们再放String类型的数据就会报错,保证了数据了安全性,这就是泛型的最大优点之处。
Iterator<Integer> itr=list.iterator();
while (itr.hasNext()){
int i=itr.next();
System.out.println(i);
}
}
/*HashMap泛型举例*/
@Test
public void test3(){
Map<String,Integer> map=new HashMap<String,Integer>();
map.put("hyb",20);
map.put("zyl",20);
// Set entrySet():返回所有key-value对构成的Set集合
// Set set = map.entrySet();
// Iterator iterator1 = set.iterator();
// while (iterator1.hasNext()){
// Object obj=iterator1.next();
map里面的静态Entry接口
// Map.Entry entry= (Map.Entry) obj;
// System.out.println(entry.getKey()+"->"+entry.getValue());
// }
// 以上是我们未用泛型之前遍历key-value的方法,那我们使用了泛型之后又怎么遍历呢?
// 我们可以查看源代码,你会发现,不仅Map里使用了泛型,Set里面也使用了泛型,甚至Entry也使用了泛型,
// 所以这里便会引用泛型嵌套。
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> itr = entry.iterator();
while (itr.hasNext()){
Map.Entry<String, Integer> i=itr.next();
System.out.println(i);
System.out.println(i.getKey() + "->" + i.getValue());
}
}
package com.hyb.Generic;
/**
* @program: Generic
* @description:泛型类
* @author: Huang Yubin
* @create: 2021-06-13 23:39
**/
public class Generic <T>{//要缔造多个泛型变量,只需要用逗号隔开就可以了
private String name;
private int age;
T t;//泛型变量,不知道要输入什么类型的变量
public Generic() {
}
public Generic(String name, int age, T t) {
this.name = name;
this.age = age;
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "Generic{" +
"name='" + name + '\'' +
", age=" + age +
", t=" + t +
'}';
}
}
/*实现泛型类Generic*/
@Test
public void AchieveGeneric(){
// 以下方式不建议,通常要指定
// Generic generic = new Generic("hyb",20,"zyl");
Generic<String> generic = new Generic<>("hyb",20,"zyl");
System.out.println(generic);
// Generic{name='hyb', age=20, t=zyl}
}
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father
}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
泛型类的构造器如下: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,而非泛型参数类型数组。
public <E> List<E> toList(E[] a){
// List list = new ArrayList<>();
// for (E e:a){
// list.add(e);
// }
// return list;
return new ArrayList<>(Arrays.asList(a));
}
//public E get(){
//return E;
//}
public void OrderToList(){
Generic<String> gni = new Generic<>();
Integer[] i = new Integer[]{1,2,3};
List<Integer> integers = gni.toList(i);
System.out.println(integers);
}
以上便是我们定义泛型方法的规则,那么我们还来思考一个问题,泛型方法可以用static修饰吗?
答案是可以的。
泛型类加载(此刻还未new对象,不知道泛型是什么)=static加载,那么使用了类的泛型的静态方法,因为不知道泛型是什么,报错。
同样的,泛型类加载(条件如上)=泛型static方法加载(泛型方法还未说明传入什么类型),不会报错,若是此刻泛型方法里使用了类的泛型,报错。
?则是通配符,在泛型继承关系里,我们知道A是不能被A赋值,但它们有一个共同父类,A>。
所以在传参的时候可以让参数为它们的共同父类,这样,就可以实现不能赋值但又不想写多个方法体的问题。
但这里又引出了一个问题,我们将他们赋值给了共同父类,那么共同父类是否可以操作它们呢?
理论是可以的。但有条件限制。
我们容易得知A>的范围是负无穷到正无穷。这个范围代表了Object的范围,而像String等类都是Object的子类,所以当我们用它来读取数据的时候,都是可以的,只不过返回了一个Object类型而已,要想得到原来的类型,直接多态转型就可以了。但是值得注意的是,既然它是Object类型的,也就是我们具体类型是不知道的,所以我们是无法读入数据的(null例外,因为任何类型都可以是null),若果可以读入,又违背使用泛型的初衷。
但是通配符是一个泛型,可以继承别人,也可以被继承,也即是A extend …>和A super …>是允许的。但是我们要理解它们的范围,很容易知道,extend是<=后者…,super是>=后者…。这么说来,只要我们在这个范围内读入数据不就行了?答案自然是肯定的。下面给出具体例子。
** extends Number> (无穷小 **, Number]只允许泛型为Number及Number子类的引用调用
super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
** extends Comparable>**只允许泛型为实现Comparable接口的实现类的引用调用
子类可以赋值给父类,但是父类不能赋值给子类,必须强转。
public void testFather(){
Object i=1;
int a=2;
a= (int) i;
System.out.println(a);
}