Java泛型类
1. 泛型的概念引入
Java为什么会出现泛型这个规范类。我们就需要知道,为什么Java要引入泛型这个概念。 在JDK1.5中。Java引入了泛型。目的就是解决类型不明确这个问题。我们举个最通俗移动的方式来说明。
我们拿List集合来举例。
我们可以很清楚的看到,List接口后面的 <E> 就属于泛型标识。
我们现在并不知道什么是泛型。我们也不需要关注<E> 他本身是个什么东西。我们在学泛型之前总应该学习了接口吧,学习过集合吧。 呢么我们就可以创建一个ArrayList集合,并且要求传入集合的内容为String类型。很简单对吧
List<String > arrayList = new ArrayList<>();
可能会有人有疑惑,为什么 List <String > 需要带上泛型类型,但是后面的ArrayList<>(); 直接就不需要写了。 针对于这个问题,这是在JDK1.8还是多少来着,记不大清楚了。大约这个版本附近往后, 所有的泛型只需要写前面的呢一个,后面的会自动识别。
回到我们之前的呢个问题。
现在可以很明确的告诉你,在创建集合的时候,泛型可以不必要的书写。也就是可以省略不写。比如
List list = new ArrayList();
在我们不去写泛型类型的时候,它默认,传进去的类型。就是Object类型。
Object的数据类型范围可是很大的。
list.add(11);
list.add("张三");
list.add(true);
list.add(1.3);
我们在写完这些内容后,发现编译器并没有报错, 也就是说我们在编译的时候并没有什么问题。他是可以成功添加各种类型的。运行得到结果。
[11, 张三, true, 1.3]
但是一个集合里面存在各种数据类型,我们如何知道我们在使用的时候我们取出来的数据类型是什么样子的?
显然我们没办法知道,在实际开发中。很容易出现运行时错误,这个问题可是 很致命的,也就是说,在我们编译的时候,并没有任何问题。在实际上线的项目里,会产生异常,导致项目异常错误。 很大的原因就是 数据类型不明确导致的错误。
小知识
当然,像我们这些比较少的内容,还是有方法知道他是什么数据类型的。可以通过反射。 getClass().getSimpleName()
List list = new ArrayList();
list.add(11);
list.add("张三");
list.add(true);
list.add(1.3);
for (Object o : list) {
System.out.println(o.getClass().getSimpleName());
}
/*
可以得到结果
Integer
String
Boolean
Double
*/
2. 泛型类
我们直接使用代码带入。 新建一个类。名称随意,在类里存在一个属性,我们就暂时称为 param。 我们在并不知道 param使用什么数据类型的时候,我们就可以使用到泛型类。
// 在这里我随便创建了一个类,类名称为 Use
public class Use<T> {
private T param;
// 空参构造
public Use() {}
// 有参构造
public Use(T param) {
this.param = param;
}
// Get方法
public T getParam() {
return param;
}
// Set方法
public void setParam(T param) {
this.param = param;
}
}
然后我们需要创建一个测试类来使用这个类。在这里我使用了Index.class
public class Index {
public static void main(String[] args) {
Use<String > use = new Use<>();
use.setParam("我是String类型的");
System.out.println(use.getParam());
System.out.println("------------------------------------------");
Use<Integer > use1 = new Use<>();
use1.setParam(1002);
System.out.println(use1.getParam());
}
}
// 运行之后得到如下结果。
/*
我是String类型的
------------------------------------------
1002
*/
综上,这就是我们泛型类的创建, 到这里就可以解释一下 T, E这些东西,到底代表了什么。
T - Type | Java 类 |
E - Element | 在集合中使用,因为集合中存放的是元素 |
K - Key | 键值对中的键 |
V - Value | 键值对中的值 |
N - Number | Number类型 |
? | 不确定的Java类型 |
上述是我们常用的一些代表。当然,一定要注意。他只是通俗来说,我们这些人自己规范的一个声明,泛型的标识符,并不一定是这些内容。它可以随便定义, A-Z , 我们通常采用大写,你想写什么都可以。 都是没问题的
现在我们来解释一下我们为什么要这样写。
首先我们并不知道我们这个参数需要什么数据类型,呢么我们就可以通过类的泛型,来决定这个参数的数据类型。 也就是我们上面使用的 <T>。
我们拆分来看。我们抛去构造方法,抛去Getter,Setter方法。我们只看类和属性。
public class Use <T> {
private T param;
}
我们在看我们调用的时候的使用
Use<String > use = new Use<>();
---------------------------------
Use<Integer > use1 = new Use<>();
相信看到这里,就已经有朋友可以看出一些端倪了。是的,我们其实就可以把他看成方法里的形参和实参。呢么我们在创建对象实例的时候,使用 < > 括起来的,里面的内容,就看成我们方法内的实参参数。 呢么我们类里面的,采用泛型标识符来使用的,我们就可以看成为,形参参数。
这样是不是更好理解。 我画一幅较为直观的图,来进行举例。
3. 泛型的 ?
我们直接借用上面创建的Use<A>类。
public class Use <A> {
private A param;
public Use() {}
public Use(A param) {
this.param = param;
}
public A getParam() {
return param;
}
public void setParam(A param) {
this.param = param;
}
}
请你不要对 A 标识符有疑惑,上面有说过,泛型的标识并不是特定的。它可以随便去使用。
首先我们先看几行代码。任然借用上面的Use类,并且不变
public class Test {
public static void showUse(Use<Integer> integerUse) {
System.out.println(integerUse.getParam());
}
public static void main(String[] args) {
Use<Integer > use = new Use<>(100);
showUse(use);
}
}
呢么在运行结束后,我们的showUse(use); 这个方法,应该输出一个什么样的答案。 应该输出一个 100. 毋庸置疑对吧。 但是我要突然要使用一个String类型的 Use ,怎么办?
如果我们直接传递String类型的数据,呢么毋庸置疑。我们可能会出现类型错误的提示。
呢么有的人就可能说,呢我们可以使用重载呀。我们试试看。
可以很清楚的看到报错信息。他并不构成重载。这是为什么。我们可以通过反射来查看Use类。
public static void main(String[] args) {
Use<Integer > integerUse = new Use<>();
Use<String > stringUse = new Use<>();
System.out.println("integer: " + integerUse.getClass());
System.out.println("string: " + stringUse.getClass());
}
呢么我们会得到运行结果如下
integer: class test.Use
string: class test.Use
可以看到,是一个类里面出来的。我们可以进行判断。判断他俩是否相等。
System.out.println(integerUse.getClass() == stringUse.getClass());
得到结果为True。
呢么我们明明传入的是两个不同的泛型类型,结果却是true。这代表了什么?这是不是可以说明,我们传入的类类型。并不能改变他的本身。这个泛型类依然是string: class test.Use。呢么说回到重载问题上。两个完全相同的,能构成重载吗?显然是不能的。
呢我们怎么解决?
这个时候就可以引入我们的类型通配符。 也就是 ?
我们稍作修改我们的代码。
public static void showUse(Use<? > integerUse) {
System.out.println(integerUse.getParam());
}
public static void main(String[] args) {
}
可以比较清楚的看见,我们把方法里原来指定的泛型类型,变成了 ? 通配符来使用。先剧透一下,通配符就是可以传入任意的类型。我们来写代码进行尝试。
public class Test {
public static void showUse(Use<? > integerUse) {
System.out.println(integerUse.getParam());
}
public static void main(String[] args) {
Use<String > use = new Use<>("张三");
showUse(use);
System.out.println(use.getParam().getClass().getSimpleName());
System.out.println("---------------------------------");
Use<Integer > use1 = new Use<>(1122);
showUse(use1);
System.out.println(use1.getParam().getClass().getSimpleName());
System.out.println("---------------------------------");
Use<Boolean > use2 = new Use<>(true);
showUse(use2);
System.out.println(use2.getParam().getClass().getSimpleName());
}
}
这里可以很清楚的看到,我们分别传入了三个不同的类型,分别是String, Integer, Boolean 并没有产生我们上面所述的错误提示,说我们类型错误,也就是说,这个方法是可行的。我们运行得到如下结果。
String
---------------------------------
1122
Integer
---------------------------------
true
Boolean
Process finished with exit code 0
呢么这个时候可能有人发现。这不是绕了一大圈子绕回来了吗。什么类型都可以传递,怎么保证数据类型啊?
所以这个时候就要引入我们的新概念。 泛型的上限,泛型的下限。
4. 泛型的上限
首先我们规定好代码。泛型类依然不变
public class Use <A> {
private A param;
public Use() {}
public Use(A param) {
this.param = param;
}
public A getParam() {
return param;
}
public void setParam(A param) {
this.param = param;
}
}
我们的Java测试代码
public class Test {
public static void showUse(Use<? > integerUse) {
System.out.println(integerUse.getParam());
}
public static void main(String[] args) {
}
}