我是灼灼,一只初学Java的大一金渐层。
向往余秀华和狄兰·托马斯的疯狂,时常沉溺于将情感以诗相寄;追逐过王尔德、王小波的文字,后陷于毛姆和斯蒂芬·金不可自拔;热爱文学的浪潮,白日梦到底却总在现实里清醒;艳羡平静又极度渴盼奔跑的力量。
欢迎与我交流鸭· QQ:1517526827;
个人博客:https://blog.csdn.net/weixin_52777510?spm=1001.2101.3001.5343
Java笔记——泛型
Java笔记正在连载中,欢迎来其他内容逛逛哟~
相关内容如下:
【连载1】Java笔记——基本结构与流程控制
【连载2】Java笔记——数组操作
【连载3】Java笔记——面向对象编程
【连载4】Java笔记——Java核心类
【连载5】Java笔记——异常处理
【连载6】Java笔记——反射和注解
【连载7】Java笔记——泛型
【连载8】Java笔记——集合
【连载9】MySQL学习笔记
【连载10】JDBC学习笔记
【连载11】Git和GitHub的使用笔记
笔记内容来源于廖雪峰官方网站~
什么是泛型
思维导图如下:
假设ArrayList<Integer>
可以向上转型为ArrayList<Number>
,观察一下代码:
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
泛型的使用
思维导图如下:
关键字implements
泛型接口写法举例:
public interface Comparable<T> {
/**
* 返回负数: 当前实例比参数o小
* 返回0: 当前实例与参数o相等
* 返回正数: 当前实例比参数o大
*/
int compareTo(T o);
}
泛型的编写
思维导图如下:
按照以下步骤来编写一个泛型类:
首先,按照某种类型,例如:String
,来编写类:
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
然后,标记所有的特定类型,这里是String
:
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
最后,把特定类型String
替换为T
,并申明<T>
:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
熟练后即可直接从T
开始编写。
多个泛型类型的实现栗子–>
当希望Pair
不总是存储两个类型一样的对象,就可以使用类型<T, K>
:
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}
使用的时候,需要指出两种类型:
Pair<String, Integer> p = new Pair<>("test", 123);
擦拭法
思维导图如下:
Java擦拭法的局限四:不能实例化T类型–>
代码无法通过编译,构造方法的两行语句:
first = new T();
last = new T();
擦拭后实际上变成了:
first = new Object();
last = new Object();
创建new Pair<String>()
和创建new Pair<Integer>()
全部成了Object
,编译器会阻止这种类型不对的代码!
正确地实例化T
类型,必须借助额外的Class<T>
参数:
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();//反射用句
last = clazz.newInstance();
}
}
上述代码借助Class<T>
参数并通过反射来实例化T
类型,使用的时候,也必须传入Class<T>
。例如:
Pair<String> pair = new Pair<>(String.class);//实例化语句
因为传入了Class<String>
的实例,所以借助String.class
就可以实例化String
类型;
泛型方法定义不能写成覆写:
一个看似正确定义的方法会无法通过编译。例如:
public class Pair<T> {
public boolean equals(T t) {
return this == t;
}
}
这是因为,定义的equals(T t)
方法实际上会被擦拭成equals(Object t)
,而这个方法是继承自Object
的,编译器会阻止一个实际上会变成覆写的泛型方法定义。//关键字冲突
换个方法名,避开与Object.equals(Object)
的冲突就可以成功编译:
public class Pair<T> {
public boolean same(T t) {
return this == t;
}
}
获取父类的泛型类型的复杂代码:
import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public class Main { public static void main(String[] args) {
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
Type firstType = types[0]; // 取第一个泛型类型
Class<?> typeClass = (Class<?>) firstType;
System.out.println(typeClass); // Integer
}
}
```
class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}
```
Java的系统结构:
extends通配符
思维导图如下:
super通配符
思维导图如下:
泛型与反射
思维导图如下:
如何安全的使用泛型数组?
使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps
,因为它的类型是泛型数组。但是,编译器不会检查变量arr
,因为它不是泛型数组。这两个变量实际上指向同一个数组,所以,操作arr
可能导致从ps
获取元素时报错,例如,以下代码演示了不安全地使用带泛型的数组:
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
ps[0] = new Pair<String>("a", "b");
arr[1] = new Pair<Integer>(1, 2);
// ClassCastException:
Pair<String> p = ps[1];
String s = p.getFirst();
要安全地使用泛型数组,必须扔掉arr
的引用:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
上面的代码中,由于拿不到原始数组的引用,就只能对泛型数组ps
进行操作,这种操作就是安全的。
代码来自《Effective Java》的示例:
public static void main(String[] args) {
String[] arr = asArray("one", "two", "three");
System.out.println(Arrays.toString(arr));
// ClassCastException:
String[] firstTwo = pickTwo("one", "two", "three");
System.out.println(Arrays.toString(firstTwo));
}
static <K> K[] pickTwo(K k1, K k2, K k3) {
return asArray(k1, k2);
}
static <T> T[] asArray(T... objs) {
return objs;
}
直接调用asArray(T...)
似乎没有问题,但是在另一个方法中,我们返回一个泛型数组就会产生ClassCastException
,原因还是因为擦拭法,在pickTwo()
方法内部,编译器无法检测K[]
的正确类型,因此返回了Object[]
;
不知道为什么和可变参数方法没关系,反而是和另一个方法有关系了…不懂-_-
泛型的笔记到这里就结束啦~
如果对你有帮助的话不要忘记一键三连噢~
谢谢鸭~
初次编写于2021//15日;
一改于2021/1/25日。