Java中泛型的学习及使用总结
学会使用泛型,有助于看懂源码
什么是泛型
Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型的好处
在JDK1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
Java语言引入泛型的好处是安全简单,在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型中常见的符号
E - Element (在集合中使用,因为集合中存放的是元素),E是对各方法中的泛型类型进行限制,以保证同一个对象调用不同的方法时,操作的类型必定是相同的。
T - Type(Java 类) T代表在调用时的指定类型。会进行类型推断
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型,是类型通配符,代表所有类型。
其实这些符号都是可以自己任意定义,个人认为是为了一种约定俗成的原因,才会使用具有一定含义的单词首字母作为对应符号。
这里注意?和T的区别
1、T代表一种类型,?代表任意类型,使用?后,有什么好处可以看文章后面
2、使用的了?后是不能添加元素的,原因可以看文章后面
泛型类
基本语法:[修饰符] class 类名称
泛型类的主要作用在于类被实例化后,传入具体的类型参数,对类的成员属性的类型和成员方法的参数类型和返回值类型进行替换。
如果定义的一个类或接口有一个或多个类型变量,则可以使用泛型。泛型类型变量由尖括号界定,放在类或接口名的后面,尖括号中的T称为类型变量。意味着一个变量将被一个类型替代替代类型变量的值将被当作参数或返回类型。
public class Demo<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
//对于T我们可以指定任意类型
//比如String类型
Demo<String> stringDemo = new Demo<>();
stringDemo.setT("string类型的demo");
//如果指定其他类型的会报错,如int类型
// stringDemo.setT(123);
System.out.println(stringDemo.getT());
//再比如User类型
User user = new User("李黑",999);
Demo<User> userDemo = new Demo<>();
userDemo.setT(user);
System.out.println(userDemo.getT());
}
}
泛型接口
基本语法:[修饰符] interface 接口名
泛型接口
public interface Demo<T> {
public T getDemo();
}
实现类
这里的例子传入的是String,当然也可以实体类等各种类型
public class DemoImpl implements Demo<String> {
@Override
public String getDemo() {
return "example";
}
}
泛型方法
基本语法:[修饰符] [static] 返回值类型 方法名(T 参数列表)
public static <T> T getT(T t){
return t;
}
public static void main(String[] args) {
String str = "泛型方法示例";
String t = getT(str);
}
通配符
泛型中的通配符分为<?>、<? extends T>、<? super T>。其中
<?> 代表无界通配符
<? extends T> 代表上界通配符,表示T以及T的子类,即T是“最大的”
<? super T> 代表下界通配符,表示T以及T的父类,T是“最小的”。
注意这里应该将尖括号当做一个整体,即<? extends T>可以代表T以及T的子类,<? super T>代表T以及T的父类
下面看个例子:
public class Animal {
public void eat(){
System.out.println("动物在吃");
}
}
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗在吃");
}
}
public class Cat {
public void eat(){
System.out.println("猫在吃" );
}
}
public class Demo2<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
//通配符作为参数
public void getResult1(Demo2<? extends String> result){
System.out.println("例子1");
}
//通配符作为参数
public void getResult2(Demo2<?> result){
System.out.println("例子2");
}
}
public static void main(String[] args) {
//正确
Demo2<? extends Animal> demo21 = new Demo2<Dog>();
//报错
// Demo2<? extends Animal> demo22 = new Demo2<Cat>();
}
因为Cat没有继承Animal,所以如果将其类型指定为Cat就会报错。
下面再看个例子
public static void main(String[] args) {
List<? extends Object> list = new ArrayList<>();
list.add(null);
//报错
//list.add("");
}
使用了通配符的泛型不能添加元素,但是可以添加null。
为什么不能添加元素呢?
首先泛来型是用来约束的(或者说是规范化),就如上面的例子,使用了<? extends Object>,这个上界通配符代表的是Object以及Object的子类,Object的子类包括String等Java对象,如果可以添加元素,就会和不使用泛型一样,即不能确定向list中添加元素类型的唯一性。因此使用通配符后是不能添加元素的
使用?的好处
SuperClass<?> sup = null;
sup = new SuperClass<String>("lisi");
sup = new SuperClass<Cat>(new Cat());
sup = new SuperClass<Dog>(new Dog());
如果不使用?,使用T的话
SuperClass<String> sup = new SuperClass<String>("lisi");
SuperClass<Cat> sup = new SuperClass<Cat>(new Cat());
SuperClass<Dog> sup = new SuperClass<Dog>(new Dog());
上面介绍了泛型的基本使用,泛型还有更多灵活可变的用法,有兴趣的可以深入学习。