自定义泛型
| class MyArrayList<E> { |
| private Object[] elementData; |
| private int size = 0; |
| |
| public MyArrayList(int initialCapacity) { |
| this.elementData = new Object[initialCapacity]; |
| } |
| |
| public boolean add(E e) { |
| elementData[size++] = e; |
| return true; |
| } |
| |
| E elementData(int index) { |
| return (E) elementData[index]; |
| } |
| |
| |
| |
| |
| public <T> T[] toArray(T[] a) { |
| return (T[]) Arrays.copyOf(elementData, size, a.getClass()); |
| } |
| } |
| 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; |
| } |
| |
| public void setFirst(T first) { |
| this.first = first; |
| } |
| |
| public void setLast(T last) { |
| this.last = last; |
| } |
| } |
泛型擦除
| 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; |
| } |
| } |
| |
| Pair<String> p = new Pair<>("Hello", "world"); |
| String first = p.getFirst(); |
| String last = p.getLast(); |
| public class Pair { |
| private Object first; |
| private Object last; |
| public Pair(Object first, Object last) { |
| this.first = first; |
| this.last = last; |
| } |
| public Object getFirst() { |
| return first; |
| } |
| public Object getLast() { |
| return last; |
| } |
| } |
| |
| Pair p = new Pair("Hello", "world"); |
| String first = (String) p.getFirst(); |
| String last = (String) p.getLast(); |
- 泛型擦除导致编译器把类型
<T>
视为Object
;编译器根据<T>
实现安全的强制转型
java泛型局限性
<T>
不能是基本类型,例如int
,因为实际类型是Object
,Object
类型无法持有基本类型
| Pair<int> p = new Pair<>(1, 2); |
- 无法取得带泛型的
Class
,例如:Pair<String>.class
;
| public class Test2 { |
| public static void main(String[] args) { |
| Pair<String> p1 = new Pair<>("Hello", "world"); |
| Pair<Integer> p2 = new Pair<>(123, 456); |
| |
| Class c1 = p1.getClass(); |
| Class c2 = p2.getClass(); |
| |
| System.out.println(c1); |
| System.out.println(c1 == c2); |
| System.out.println(c1 == Pair.class); |
| |
| } |
| } |
- 无法判断带泛型的类型,例如:
x instanceof Pair<String>
;
| Pair<Integer> p = new Pair<>(123, 456); |
| |
| if (p instanceof Pair<String>) { |
| } |
- 并不存在
Pair<String>.class
,而是只有唯一的Pair.class
- 不能实例化
T
类型,例如:new T()
。
| public class Pair<T> { |
| private T first; |
| private T last; |
| |
| public Pair() { |
| |
| first = new T(); |
| last = new T(); |
| |
| |
| |
| |
| } |
| } |
| class Pair<T> { |
| private T first; |
| private T last; |
| |
| |
| |
| public Pair(Class<T> clazz) throws InstantiationException, IllegalAccessException { |
| first = clazz.newInstance(); |
| last = clazz.newInstance(); |
| } |
| } |
| public static void main(String[] args) throws Exception { |
| ArrayList<Integer> list = new ArrayList<>(); |
| list.add(6); |
| |
| |
| Class<? extends ArrayList> clazz = list.getClass(); |
| Method add = clazz.getDeclaredMethod("add", Object.class); |
| add.invoke(list, "哈哈哈"); |
| System.out.println("list = " + list); |
| } |
泛型继承
- 无法获取
Pair<T>
的T
类型,即给定一个变量Pair<Integer> p
,无法从p
中获取到Integer
类型。
- 但是,在父类是泛型类型的情况下,编译器就必须把类型
T
(对IntPair
来说,也就是Integer
类型)保存到子类的class文件中,不然编译器就不知道IntPair
只能存取Integer
这种类型。在继承了泛型类型的情况下,子类可以获取父类的泛型类型。
| public class Test2 { |
| 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); |
| } |
| |
| } |
| } |
| |
| class IntPair extends Pair<Integer> { |
| public IntPair(Integer first, Integer last) { |
| super(first, last); |
| } |
| } |
泛型通配符
常用通配符的含义
- T (Type) 具体的Java类
- E (Element)在集合中使用,因为集合中存放的是元素
- K V (key value) 分别代表java键值中的Key Value
- N (Number)数值类型
- ? 表示不确定的 Java 类型
上界通配符< ? extends E>
| public class Test2 { |
| public static void main(String[] args) { |
| |
| int sum = add(new Pair<Number>(1, 2)); |
| |
| |
| Pair<Integer> p = new Pair<>(123, 456); |
| |
| sum = add(p); |
| |
| |
| } |
| |
| static int add(Pair<Number> p) { |
| |
| |
| Number first = p.getFirst(); |
| Number last = p.getLast(); |
| return first.intValue() + last.intValue(); |
| } |
| } |
| |
| |
| static int add(Pair<? extends Number> p) { |
| |
| |
| Number first = p.getFirst(); |
| Number last = p.getLast(); |
| return first.intValue() + last.intValue(); |
| } |
- 之后就不能预测实际类型就是
Integer
,因为实际的返回类型可能是Integer
,也可能是Double
或者其他类型,编译器只能确定类型一定是Number
的子类(包括Number
类型本身),但具体类型无法确定。
| |
| Integer x = p.getFirst(); |
| public class Test2 { |
| public static void main(String[] args) { |
| Pair<Integer> p = new Pair<>(123, 456); |
| int n = add(p); |
| System.out.println(n); |
| } |
| |
| static int add(Pair<? extends Number> p) { |
| Number first = p.getFirst(); |
| Number last = p.getLast(); |
| |
| |
| p.setFirst(new Integer(first.intValue() + 100)); |
| p.setLast(new Integer(last.intValue() + 100)); |
| |
| return p.getFirst().intValue() + p.getFirst().intValue(); |
| } |
| |
| } |
| public interface List<T> { |
| int size(); |
| T get(int index); |
| void add(T t); |
| void remove(T t); |
| } |
| |
| int sumOfList(List<? extends Integer> list) { |
| int sum = 0; |
| for (int i=0; i<list.size(); i++) { |
| Integer n = list.get(i); |
| sum = sum + n; |
| } |
| return sum; |
| } |
List<? extends Integer>
的限制
- 允许调用
get()
方法获取Integer
的引用;
- 不允许调用
set(? extends Integer)
方法并传入任何Integer
的引用(null
除外)。
- 因此,方法参数类型
List<? extends Integer>
表明了该方法内部只会读取List
的元素,不会修改List
的元素(因为无法调用add(? extends Integer)
、remove(? extends Integer)
这些方法。换句话说,这是一个对参数List<? extends Integer>
进行只读的方法(恶意调用set(null)
除外)。
下界通配符< ? super E>
| |
| void set(Pair<Integer> p, Integer first, Integer last) { |
| p.setFirst(first); |
| p.setLast(last); |
| } |
- 这次,我们希望接受
Pair<Integer>
类型,以及Pair<Number>
、Pair<Object>
,因为Number
和Object
是Integer
的父类,setFirst(Number)
和setFirst(Object)
实际上允许接受Integer
类型。
| |
| |
| void set(Pair<? super Integer> p, Integer first, Integer last) { |
| p.setFirst(first); |
| p.setLast(last); |
| } |
- 考察
Pair<? super Integer>
的setFirst()
方法,它的方法签名实际上是:
| void setFirst(? super Integer); |
- 考察
Pair<? super Integer>
的getFirst()
方法,它的方法签名实际上是:
| ? super Integer getFirst(); |
| |
| Integer x = p.getFirst(); |
| |
| Object obj = p.getFirst(); |
使用<? super Integer>
通配符作为方法参数,表示方法内部代码对于参数只能写,不能读
无限定通配符
| static boolean isNull(Pair<?> p) { |
| return p.getFirst() == null || p.getLast() == null; |
| } |
PECS原则
- 如果需要返回
T
,它是生产者(Producer),要使用extends
通配符;如果需要写入T
,它是消费者(Consumer),要使用super
通配符。
| |
| class PESC { |
| ArrayList<? extends Animal> exdentAnimal; |
| ArrayList<? super Animal> superAnimal; |
| Dog dog = new Dog("小黑", "黑色"); |
| |
| private void test() { |
| |
| Animal a1 = exdentAnimal.get(0); |
| |
| |
| |
| |
| |
| |
| superAnimal.add(dog); |
| } |
| } |
| public static <T> void copy(List<? super T> dest, List<? extends T> src) { |
| int srcSize = src.size(); |
| if (srcSize > dest.size()) |
| throw new IndexOutOfBoundsException("Source does not fit in dest"); |
| |
| if (srcSize < COPY_THRESHOLD || |
| (src instanceof RandomAccess && dest instanceof RandomAccess)) { |
| |
| for (int i = 0; i < srcSize; i++) |
| dest.set(i, src.get(i)); |
| } else { |
| ListIterator<? super T> di = dest.listIterator(); |
| ListIterator<? extends T> si = src.listIterator(); |
| for (int i = 0; i < srcSize; i++) { |
| di.next(); |
| di.set(si.next()); |
| } |
| } |
| } |
copy()
方法内部不会读取dest
,因为不能调用dest.get()
来获取T
的引用;
copy()
方法内部也不会修改src
,因为不能调用src.add(T)
。
- 如果在方法代码中意外修改了
src
,或者意外读取了dest
,就会导致一个编译错误
总结
-
使用类似<? extends Number>
通配符作为方法参数时表示:
-
使用类似<T extends Number>
定义泛型类时表示:
- 泛型类型限定为
Number
以及Number
的子类。
-
使用类似<? super Integer>
通配符作为方法参数时表示:
- 只用于读功能时,泛型结构使用<? extends T>
- 只用于写功能时,泛型结构使用<? super T>
- 如果既用于写,又用于读操作,那么直接使用
- 如果操作与泛型类型无关,那么使用<?>
- ?类型不确定和Object作用差不多,好多场景下可以通用,但?可以缩小泛型的范围,如:List<? extends Animal>,指定了范围只能是Animal的子类,但是用
List<Object>
,没法做到缩小范围
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步