什么是泛型
所谓泛型是指在定义类、接口、方法时使用类型参数,这个类型参数将在变量声明、对象创建、方法调用时动态指定。
泛型的意义
泛型的概念是在JDK1.5提出的,在JDK1.7和1.8有不同程度的优化。泛型最大的贡献就是让程序中的错误在编译期间被发现,从而增加了代码的正确性和稳定性。在泛型没有提出之前,Java集合中的元素都是以Object类型存储的,这就有可能导致同一个集合中可能存储有不同数据类型的数据,同时也会导致具体使用集合中的数据时出现类型转换异常。
不使用泛型的List集合使用
package com.icypt.generics;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
/**
*运行结果
*A
*B
*C
*D
*Exception in thread "main" java.lang.ClassCastException:
*java.lang.Integer cannot be cast to java.lang.String
*at com.icypt.generics.Test1.main(Test1.java:25)
*/
public class Test1 {
//取消编译时报的警告
@SuppressWarnings("unchecked")
public static void main(String[] rags) {
//不使用泛型,创建ArrayList对象
List list = new ArrayList();
//我们的初衷是定义一个String类型的数组
list.add("A");
list.add("B");
list.add("C");
list.add("D");
//不要小心存入一个Integer类型的数据
list.add(1);
//以上代码在编译时没有任何问题
//处理集合中的数据
Iterator iter = list.iterator();
while(iter.hasNext()) {
//向下转型,强转,这里会抛出异常
String str = (String)iter.next();
System.out.println(str);
}
}
}
以上栗子是在没有定义泛型的情况下使用List集合,发现这种编码方式特别容易出错误,而且这种错误在编译期间又不能被及时的发现。虽然我们也可以使用 instance of 表达式进行优化,但是我们一句关于业务处理的代码都没有写,就写一堆判断,这样的代码是不是有点太臃肿了?
泛型的定义
常用类型参数名称
E:element,表示元素,在Java类集框架中大量的使用了其参数名称。
K:key,表示键值。
V:value,表示值。
T:type,表示类型。
N:number,表示数字。
当然我们还可以定义符合自己编程风格的类型参数名称,只要符合Java标识符命名规则即可,建议以大写字母命名。
接口及类中使用泛型
定义语法:修饰符 class|interface 类名称<数据类型1,数据类型1,...> {}
package com.icypt.generics;
/**
*运行结果
*1-执行结果:泛型就是如此的简单
*2-执行结果:123.45
*3-执行结果:换个String类型试一哈
*/
public class Test2{
public static void main(String[] args) {
//JDK1.7之前语法,接口实现类中泛型使用
MessageImpl<String> msgImpl = new MessageImpl<String>();
msgImpl.printMsg("1-执行结果:泛型就是如此的简单");
//JDK1.7语法,普通类中泛型使用
MyBox<Double> mb = new MyBox<>();
mb.set(123.45);
System.out.println("2-执行结果:" + mb.get());
//JDK1.8及之后语法
MyBox<String> mb1 = new MyBox();
mb1.set("换个String类型试一哈");
System.out.println("3-执行结果:" + mb1.get());
}
}
//在接口中定义泛型
interface Message<T> {
public void printMsg(T t);
}
//接口的实现类
class MessageImpl<T> implements Message<T> {
public void printMsg(T t) {
System.out.println(t);
}
}
//在普通类中定义泛型
class MyBox<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return this.t;
}
}
以上栗子简单的演示了泛型在接口以及普通类中的使用,以及使用不同的方式指定类型参数。为了能够更好的理解泛型,可以将它视为一种特殊的占位符,而指定占位符的时机就是我们创建对象、调用方法以及定义变量的时候,一旦指定了占位符的值也就是类型参数,则源码中所有的占位符都将被替换成指定的类型。需要注意的是当一个接口定义了泛型后,其实现类要么定义和接口相同的泛型,要么在实现接口的时候指定类型参数,否则编译无法通过。
泛型方法
定义语法:修饰符 <T,S,...> 返回值类型 方法名(形参列表) {}
package com.icypt.generics;
/**
*运行结果:
*1-执行结果:测试
*2-执行结果:123.0
*3-执行结果:1
*/
public class Test3{
public static void main(String[] args) {
System.out.println("1-执行结果:" + getInstance(new String("测试")) );
System.out.println("2-执行结果:" + getInstance(new Double(123)) );
System.out.println("3-执行结果:" + getInstance(new Integer(1)) );
}
//泛型方法
public static <T> T getInstance(T t) {
return t;
}
}
由栗子可知泛型方法允许类型参数被用来表示方法的一个或多个参数之前的类型依赖关系,或者返回值与类型参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。
通配符
通配符是指当我们无法确定要实际指定的泛型是哪一个类型时可用通配符(?)代替,它只能在类成员中使用。我们还可以使用 <? extends T> 为通配符指定上限,表示其泛型必须是T或者T的子类;当然我们也可以使用<? super T>为通配符制定下限,表示其泛型必须是T或者T的父类。
public class Main{
public static void main(String[] args) {
Generics<C> g1 = new Generics<>();
//传入的类型参数是A的子类
g1.testUpperLimit(g1);
//通配符无限制
g1.testNotLimit(g1);
//不满足下限,编译不通过,传入的类型参数不是A以及A的父类
//g1.testLowerLimit(g1);
Generics<E> g2 = new Generics<>();
//不满足上限,编译不通过,传入的类型参数不是A以及A的子类
//g1.testUpperLimit(g2);
//传入的类型参数A的父类
g1.testLowerLimit(g2);
//通配符无限制
g1.testNotLimit(g2);
}
}
/**
* 定义四个普通类,他们的继承关系如下
*/
class E{}
class A extends E {}
class B extends A{}
class C extends B{}
class D extends B{}
/**
* 定一个泛型类
*/
class Generics<T> {
//测试通配符上限
public void testUpperLimit(Generics<? extends A> g1) {
System.out.println(g1);
}
//测试通配符下限
public void testLowerLimit(Generics<? super A> g2) {
System.out.println(g2);
}
//测试通配符无限制
public void testNotLimit(Generics<?> g3) {
System.out.println(g3);
}
}
通过栗子可以看出通配符的使用将使我们定义泛型更加的灵活。
泛型的擦除
泛型的擦除其实就是一句话:使用泛型的原生类代替泛型。
package com.icypt.generics;
import java.util.List;
import java.util.ArrayList;
/**
*运行结果
*A
*/
public class Test4{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
List originList = new ArrayList();
//擦除泛型
originList = list;
//编译错误,泛型已经被擦除
//String str = originList.get(0);
//向下转型
String str = (String) originList.get(0);
System.out.println(str);
}
}
对于泛型的检查只存在于编译过程中,编译结束后泛型就会被擦除掉,所以Java中的泛型其实是一种伪泛型,对于JVM来讲操作的还是原生类型。
至此关于泛型的使用就讨论完了,下篇我们来讨论Java中异常的使用。
java培训:http://www.baizhiedu.com/java2019