一. 泛型的使用
1. 泛型类的概述及使用
A: 泛型类概述: 把泛型定义在类上
B: 定义格式: public class 类名<泛型类型 1,...>
C: 注意事项: 泛型类型必须是引用类型
2. 泛型方法的概述和使用
A: 泛型方法概述: 把泛型定义在方法上
B: 定义格式: public <泛型类型> 返回类型 方法名(泛型类型 变量名)
- public <T> void show(T t) {
- }
所谓泛型方法, 就是在声明方法时定义一个或多个类型形参. 泛型方法的用法格式如下:
修饰符 < T, S> 返回值类型 方法名(形参列表){
方法体
}
注意要点:
方法声明中定义的形参只能在该方法里使用, 而接口, 类声明中定义的类型形参则可以在整个接口, 类中使用. 当调用 fun()方法时, 根据传入的实际对象, 编译器就会判断出类型形参 T 所代表的实际类型.
- class Demo{
- public <T> T fun(T t){ // 可以接收任意类型的数据
- return t ; // 直接把参数返回
- }
- };
- public class GenericsDemo26{
- public static void main(String args[]){
- Demo d = new Demo() ; // 实例化 Demo 对象
- String str = d.fun("汤姆") ; // 传递字符串
- int i = d.fun(30) ; // 传递数字, 自动装箱
- System.out.println(str) ; // 输出内容
- System.out.println(i) ; // 输出内容
- }
- };
3. 泛型接口的概述和使用
先来看一个案例
A: 泛型接口概述: 把泛型定义在接口上
B: 定义格式: public interface 接口名<泛型类型>
- /**
- * 泛型接口的定义格式: 修饰符 interface 接口名<数据类型> {}
- */
- public interface Inter<T> {
- public abstract void show(T t) ;
- }
- /**
- * 子类是泛型类
- */
- public class InterImpl<E> implements Inter<E> {
- @Override
- public void show(E t) {
- System.out.println(t);
- }
- }
- Inter<String> inter = new InterImpl<String>() ;
- inter.show("hello") ;
然后看看源码中泛型的使用, 下面是 JDK 1.5 以后, List 接口, 以及 ArrayList 类的代码片段.
- // 定义接口时指定了一个类型形参, 该形参名为 E
- public interface List<E> extends Collection<E> {
- // 在该接口里, E 可以作为类型使用
- public E get(int index) {}
- public void add(E e) {}
- }
- // 定义类时指定了一个类型形参, 该形参名为 E
- public class ArrayList<E> extends AbstractList<E> implements List<E> {
- // 在该类里, E 可以作为类型使用
- public void set(E e) {
- .......................
- }
- }
这就是泛型的实质: 允许在定义接口, 类时声明类型形参, 类型形参在整个接口, 类体内可当成类型使用, 几乎所有可使用普通类型的地方都可以使用这种类型形参.
泛型类派生子类
当创建了带泛型声明的接口, 父类之后, 可以为该接口创建实现类, 或者从该父类派生子类, 需要注意: 使用这些接口, 父类派生子类时不能再包含类型形参, 需要传入具体的类型.
错误的方式:
public class A extends Container<K, V>{}
正确的方式:
public class A extends Container<Integer, String>{}
也可以不指定具体的类型, 此时系统会把 K,V 形参当成 Object 类型处理. 如下:
public class A extends Container{}
4. 泛型类的概述和使用
定义一个容器类, 存放键值对 key-value, 键值对的类型不确定, 可以使用泛型来定义, 分别指定为 K 和 V.
- public class Container<K, V> {
- private K key;
- private V value;
- public Container(K k, V v) {
- key = k;
- value = v;
- }
- public K getkey() {
- return key;
- }
- public V getValue() {
- return value;
- }
- public void setKey() {
- this.key = key;
- }
- public void setValue() {
- this.value = value;
- }
- }
在使用 Container 类时, 只需要指定 K,V 的具体类型即可, 从而创建出逻辑上不同的 Container 实例, 用来存放不同的数据类型.
- public static void main(String[] args) {
- Container<String,String> c1=new Container<String ,String>("name","hello");
- Container<String,Integer> c2=new Container<String,Integer>("age",22);
- Container<Double,Double> c3=new Container<Double,Double>(1.1,1.3);
- System.out.println(c1.getKey() + ":" + c1.getValue());
- System.out.println(c2.getKey() + ":" + c2.getValue());
- System.out.println(c3.getKey() + ":" + c3.getValue());
- }
在 JDK 1.7 增加了泛型的 "菱形" 语法: Java 允许在构造器后不需要带完成的泛型信息, 只要给出一对尖括号 (<>) 即可, Java 可以推断尖括号里应该是什么泛型信息. 如下所示:
- Container<String,String> c1=new Container<>("name","hello");
- Container<String,Integer> c2=new Container<>("age",22);
5. 泛型构造器的概述
正如泛型方法允许在方法签名中声明类型形参一样, Java 也允许在构造器签名中声明类型形参, 这样就产生了所谓的泛型构造器.
和使用普通泛型方法一样没区别, 一种是显式指定泛型参数, 另一种是隐式推断, 如果是显式指定则以显式指定的类型参数为准, 如果传入的参数的类型和指定的类型实参不符, 将会编译报错.
- public class Person {
- public <T> Person(T t) {
- System.out.println(t);
- }
- }
如何使用
- public static void main(String[] args){
- // 隐式
- new Person(22);
- // 显示
- new<String> Person("hello");
- }
这里唯一需要特殊注明的就是:
如果构造器是泛型构造器, 同时该类也是一个泛型类的情况下应该如何使用泛型构造器: 因为泛型构造器可以显式指定自己的类型参数(需要用到菱形, 放在构造器之前), 而泛型类自己的类型实参也需要指定(菱形放在构造器之后), 这就同时出现了两个菱形了, 这就会有一些小问题, 具体用法再这里总结一下.
以下面这个例子为代表
- public class Person<E> {
- public <T> Person(T t) {
- System.out.println(t);
- }
- }
这种用法: Person<String> a = new <Integer>Person<>(15); 这种语法不允许, 会直接编译报错!
二. 泛型高级之通配符
1. 为什么要使用通配符
通配符的设计存在一定的场景, 例如在使用泛型后, 首先声明了一个 Animal 的类, 而后声明了一个继承 Animal 类的 Cat 类, 显然 Cat 类是 Animal 类的子类, 但是 List 却不是 List 的子类型, 而在程序中往往需要表达这样的逻辑关系. 为了解决这种类似的场景, 在泛型的参数类型的基础上新增了通配符的用法.
2. <? extends T> 上界通配符
上界通配符顾名思义,<? extends T > 表示的是类型的上界[包含自身] , 因此通配的参数化类型可能是 T 或 T 的子类.
正因为无法确定具体的类型是什么, add 方法受限 (可以添加 null, 因为 null 表示任何类型), 但可以从列表中获取元素后赋值给父类型. 如上图中的第一个例子, 第三个 add() 操作会受限, 原因在于 List 和 List 是 List<? extends Animal > 的子类型.
它表示集合中的所有元素都是 Animal 类型或者其子类
List<? extends Animal>
这就是所谓的上限通配符, 使用关键字 extends 来实现, 实例化时, 指定类型实参只能是 extends 后类型的子类或其本身.
例如:
这样就确定集合中元素的类型, 虽然不确定具体的类型, 但最起码知道其父类. 然后进行其他操作.
- //Cat 是其子类
- List<? extends Animal> list = new ArrayList<Cat>();
3. <? super T> 下界通配符
下界通配符<? super T > 表示的是参数化类型是 T 的超类型(包含自身), 层层至上, 直至 Object
编译器无从判断 get()返回的对象的类型是什么, 因此 get()方法受限. 但是可以进行 add()方法, add()方法可以添加 T 类型和 T 类型的子类型, 如第二个例子中首先添加了一个 Cat 类型对象, 然后添加了两个 Cat 子类类型的对象, 这种方法是可行的, 但是如果添加一个 Animal 类型的对象, 显然将继承的关系弄反了, 是不可行的.
它表示集合中的所有元素都是 Cat 类型或者其父类
List <? super Cat>
这就是所谓的下限通配符, 使用关键字 super 来实现, 实例化时, 指定类型实参只能是 extends 后类型的子类或其本身.
例如:
- //Shape 是其父类
- List<? super Cat> list = new ArrayList<Animal>();
4. <?> *** 通配符
任意类型, 如果没有明确, 那么就是 Object 以及任意的 Java 类了
*** 通配符用 <?> 表示,? 代表了任何的一种类型, 能代表任何一种类型的只有 null(Object 本身也算是一种类型, 但却不能代表任何一种类型, 所以 List 和 List 的含义是不同的, 前者类型是 Object, 也就是继承树的最上层, 而后者的类型完全是未知的).
三. 泛型只能使用引用类型
当声明泛型类的实例时, 传递的类型参数必须是引用类型, 不能使用基本类型
例如, 对于 User 泛型类来说, 以下声明是非法的
User<int,double> user=new User<>(1,10,0);
如何解决
可以使用类型的包装类来解决该问题
来源: http://www.bubuko.com/infodetail-3343595.html