Java基础——泛型
一、为什么需要泛型?
1.集合元素存储时候的安全性
2.集合元素取出时候的强转问题
主要内容:
泛型在集合中的使用
自定义泛型类、泛型接口、泛型方法
泛型与继承的关系
通配符
二、泛型(Generic)的几个术语:
对于List<User> 和List<T>
整个List<T> 泛型类型
List<T>中的T 类型参数
整个List<User> 参数化类型
List<User>中的User 实际类型参数
<> 为 typeof
注意的地方:
参数化类型没有实际类型参数的继承关系!
List<Integer> list = new List<Object>(); //报错,反之亦然
其中,JDK7之后的新特性:后一个泛型可以省略,JDK可以自动推断!
Map<String, String> map = new HashMap<>();
参数化类型与原始类型的兼容性:
- 参数化类型可以引用一个原始类型的对象,编译时编译器会报警告,例如:Collection<String> c = new Vector();
- 原始类型可以引用一个参数化类型的对象,编译时编译器会报警告,例如:Collection c = new Vector<String>();
三、自定义泛型类、接口、方法
1.自定义泛型类
集合中的泛型就不再赘述了,我们可以尝试着做自定义泛型类,最简单的入门办法,就是先看看集合类它是怎么做的:
public interface List<E> extends Collection<E> {
基本上就是在类名后进行泛型的声明,之后涉及的类型参数地方都用类型参数(当然类型参数T是自定义的字母,T E自定义即可)代替,那么实际使用中类型参数就会是我们传入的泛型;
定义泛型类:
package com.zw.test.generic; /** * Person的泛型类 * 作者: Administrator * 日期: 2017/9/22 **/ public class Person<T> { private String name; private Integer age; private T t; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public T getT() { return t; } public void setT(T t) { this.t = t; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", t=" + t + '}'; } }
如何使用:
当然泛型不是强制需要传入的,如果不传入,那么就是Object类型(不推荐)
// Person p = new Person(); // Object t = p.getT();
正常应该是要指定的:——这样泛型类中的类型参数就会被实际类型参数代替
Person<Boolean> p = new Person<>(); p.setT(true); Boolean b = p.getT();
当然,可以通过子类继承的时候指定泛型,这样子类继承自父类的方法属性都会自动确定相关的泛型:
class subClass extends Person<Integer> {
自定义接口基本类同,就不赘述了。
2.自定义泛型方法
我们还是来看看集合类中怎么写的:
public class ArrayList<E> extends AbstractList<E>
注意,以下这个不是泛型方法,不过返回值是类在声明时候的泛型,仅此而已:
public E get(int index) { rangeCheck(index); return elementData(index); }
这点可以参见泛型总结的一篇随笔:http://blog.csdn.net/s10461/article/details/53941091
但下面这个ArrayList中的方法是真正的泛型方法:
@SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
//为了和类声明时候的泛型区分,这里使用的是T(与类的E区分,当然,即使这里使用的是E也没关系,它依然是一个全新的类型,不会受限于类的泛型声明E),这也是泛型方法的标准格式:
修饰符 [static] <T> 返回值 方法名(参数列表)
其他的泛型方法相关的详细细节(例如T可以在任意位置,而且T也可以是多个 <T,E>等)可以参考上面的链接
四、泛型的通配符
泛型引用和创建两端泛型变量必须相同
重载时只有泛型变量无法完成,因为泛型擦除后参数是相同的
此问题主要是由于兼容老版本引起的,使用的是假的泛型
于是,通配符就出现了 List<? extends Object> list,这里的问号?就是通配符
通配符就是解决泛型的继承方面的问题
通配符只能出现在引用一端,而new那端不能用,只能用在左边,而不能用在右边
?表示一个不确定的值,只会在调用时进行确定,当然可以简写成List<?>
通配符的局限就出来了:
当使用通配符时,对泛型类中参数为泛型的方法起了副作用:无法使用此方法(如无法使用add()方法等)
返回值为泛型类型时也无法使用
好处就是形参带泛型时可以更加通用
?通配符还有边界的概念(上下边界,父类子类边界)
如:List<? extends Number> list 只能传递Number及其子类型
List<? super Number> list2 只能传递Number及其父类型
也就是分为三种:无界通配、子类通配、父类通配
上界(父类)通配:add()受限——但是可以get(),数据可以通过赋值得到,这也就是我们说的通配符实现泛型继承的意义
下界(子类)通配:get()受限
更多泛型通配符的概念,请参见:http://blog.csdn.net/claram/article/details/51943742
五、泛型的继承关系
(注意,list<String>不是list<Object>的子类,也就说类型参数的 父子关系不能引申到泛型类型的父子关系,关于这点可以参见之前泛型出现的意义(类型安全)结合例子进行推断,泛型的继承关系需要通过通配符)
定义一个父类:
package cn.test; public class Father<T> { T text;//成员变量 //get方法 public T getText() { return text; } //无参构造器 public Father() { } //有参构造器 public Father(T text) { super(); this.text = text; } }
1.最正常的继承,子类的泛型参数和父类的参数是一致的
public class Child<T> extends Father<T>{
2.子类增加了一个泛型参数,父类的泛型参数不能遗漏,所以仍然要定义
public class Child<T,E> extends Father<T>{
3.继承时不指定父类的泛型参数,会有警告信息:
Father is a raw type. References to generic type Father<T> should be parameterized
public class Child extends Father{
4.继承时指定父类的泛型参数,子类就不用再写泛型参数,如果写了,那就是子类自己新增加的
public class Child extends Father<String>{ public Child(){ super("我是子类"); } public void print(){ System.out.println(text); } }
调用如上print方法正常输出 我是子类
5. 父类指定了类型,子类又增加了,这时子类的只是新增加的泛型参数,跟父类没有关系
public class Child<E> extends Father<String>{
posted on 2018-11-23 21:58 LeviZhuang 阅读(128) 评论(0) 编辑 收藏 举报