泛型学习
Posted 巴拉巴拉顺
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了泛型学习相关的知识,希望对你有一定的参考价值。
1.泛型定义:就是允许定义类、接口、方法时使用类型参数。这个类型参数将在声明变量、创建对象、调用方法时动态指定(实际类型)。
public class Apple<T>{ private T info; //T 可以拿来当一个类型使用,如String,int等。可以在真实使用Aplle类的时候,再指定。 public Apple(){} // 为带泛型的类,定义构造器时,不用增加泛型类型,但是在调用构造器时,可以使用Apple<T>形式。因为在JAVA7提供了菱形4语法,允许省略<>中的实际类型 public void setInfo(T info){ this.info = info; } }
public interface Apple<T>{ // 定义泛型接口 }
2.从泛型派生子类
当创建了带泛型的接口或类后,可以为该接口创建实现类或从该父类派生子类。但是,使用这些父类及接口时,不能再包含类型参数。如下面的代码就是错误的:
1 public class A extends Apple<T>{} // 错误,此时T必须制定实际的类型参数或者省略,默认的Object
也就是说,在定义类、接口、方法时可以声明类型参数,但在使用这些带类型参数的接口、类、方法时,必须为该类型参数传入实际的类型。
所以上面的代码应该改成:
public class A extends Apple<String>{ // 但是注意,重写父类方法时,这里的方法参数类型也要变成String了 public void setInfo(String info){ this.info = info; } }
但是,使用带泛型的父类和接口时,参数类型可以省略,如下也是正确的:
// 此时没有传入实际的,JAVA编译器可能会告警:使用了不检查或不安全的操作。此时,系统默认把省略的T当成Object处理 public class A extends Apple{ private T info; // 这里就是Object了 public void setInfo(Object info){ this.info = info; } }
3.并不存在的泛型类型
有人可能会把ArrayList<String> 当成ArrayList的子类。实际上,JVM并没有为ArrayList<String>生成任何新的class文件。也就是说ArrayList<String>和ArrayList它们在JVM中运行的都是同一个class文件。在内存中也都占用一块内存。因此在静态方法,静态初始化块或静态变量的声明中是不允许使用类型参数。
4.类型通配符
假如我们有一段逻辑,需要遍历List中的元素,但是List中对象类型,我们未知。
public void test(List c){ for(int i = o; i< c.size(); i++){ System.out.println(c.get(i)); } }
List是一个带泛型的对象,使用时,必须指定类型,否则会引起警告.假如,我们改成如下:
Public void test(List<Object> c){ for(int i = o; i< c.size(); i++){ System.out.println(c.get(i)); } }
程序改成这样后,代码虽然不会引入警告。但是假如我们传入一个List<String>类型的参数,程序会报错。因为List<String> 并不是List<Object>的子类。
在这种情况下,我就可以是类型通配符(?):
Public void test(List<?> c){ // 此时,List<?>,可以理解为,未知类型元素的List。 那么就可以传入任意类型的List对象了 for(int i = o; i< c.size(); i++){ System.out.println(c.get(i)); } }
需要注意的是,这种写法仅仅标识它是各种泛型List的父类,不能往这个List中添加元素,因为不知道List<?>的实际的元素类型。就好此,你声明了一个List<E>,往这个List中添加的元素,必定是E或者是它的子类,其元素的类型是确定的。但"?"不行,“?”代表的就是一个位置类型,往集合增加元素时,必须明确其类型。
另一方面,List<?>使用get()方法时,取出来的其实也是一个未知类型,但它总是Object对象或其子类。因此,从这种角度来说 ,get出来的元素类型也是确定的。
类型通配符的上限:
使用List<?>标识,标识任何泛型LIst的父类。有时候,我们想要限定这个范围,如我们想要这个List只放动物类,如:
public abstract class Animal{ // 定义一个抽象父类 动物 } public class Dog extends Animal{} // 定义一个子类 狗 public class Dat extends Animal{} // 定义一个子类 猫 public class Apple {} // 定义一个水果类,苹果
此时我们可以声明一个指允许添加animal的List:
public void test(List<? extends Animal> animals){ // 此时,传入一个List<Apple> 就会报错 for(int i=0; i < animals.size(); i++ ){ animals.get(i); } }
这里需要说明的时,List<? extends animal>是受限制通配符的例子,此处的?代表的一个未知的类型,因此也不能往这个List中添加元素。 但是此时的?这个未知类型,我们知道肯定是animal或它的子类,因此我们把animal称为这个通配符的上线。
我们也可以在定义类型参数时,使用通配符设定上限(就是创建带泛型的类和接口时,设定上线)。如下:
public class Animal<T extends Number>{} // 在使用T时,传入的元素类型,就必须是Number的子类或其本身了
在极端情况,程序可以为类型形参设定多个上线(之多一个父类上限(放第一个),可以有多个接口),表明该类型是其父类的子类(或本身),同时要实现多个上限接口,如:
public class Animal<T extends Number && java.io.Serializable>{} // 这里就要求,T类型必须是Number的子类且实现了Serializable接口
5. 泛型方法:
我们需要实现一个功能,将一个集合的元素,复制到一个集合中去:
public void test(Object[] a,Collection<Object> c){ for(Object o:a){ c.add(o); } }
上述这个可以正常运行,但是方法,限制很大,Collection<Object>形参只是能是Collection<Object>,因为Collection<String>不是其子类。如果使用Collection<?>,显然也是不行的,?代表一个未知类型,不能把对象放进一个未知类型的集合中。为了解决这个问题,java5中引入了泛型方法,语法格式:
修饰符 <T,S> 返回值类型 方法名(形参列表){ //。。。 }
将上述的方法可以改成:
public <T> void test(T[] a,Collection<T> c){ for(T o:a){ c.add(o); } }
此时,假如我们调用下面的程序来调用test方法:
public static void main(Stirng[] agrs){ String[] a = {"1","2"}; List<Object> c = new ArrayList<>(); test(a,c); // 会报错,要求连个参数的必须是相同的,但是实际传入的一个是String,一个是Object }
这里我们可以使用通配符来修改程序:
public <T> void test(<? extends T>[] a,Collection<T> c){ // 此时,第一个参数只要是T的子类就可以了 for(T o:a){ c.add(o); } }
通配符的下限:
<? extends T> ? 通配符为T的子类,T称为通配符的上限。反之,当我们标识一个泛型的父类,可以使用 <? super T>来标识。?通配符表示T的父类类型(或其本身类型)。此时,T称为通配符的下限
public <T> void test(<? super T>[] a,Collection<T> c){ // 此时,只要是T的父类即可 for(T o:a){ c.add(o); } }
以上是关于泛型学习的主要内容,如果未能解决你的问题,请参考以下文章