Android:写了这么多代码,你真的理解泛型吗?
子木_lsy | 作者
承香墨影 | 校对
https://h.lishaoy.net/generics.html | 原文
在我们的实际工作中 泛型(Generics) 是无处不在的,我们也写过不少,看到的更多,如,源码、开源框架... 随处可见。但是,我们真正理解泛型吗?理解多少呢?
例如:Box
、Box<Object>
、Box<?>
、Box<T>
、Box<? extends T>
、Box<? super T>
之间的区别是什么?
本篇文章将会对 泛型(Generics) 进行全面的解析,让我们对泛型有更深入的理解。
本篇文章涉及知识点的脑图如下:
Lucy喜欢吃????(泛型的意义)
首先,通过一个盘子装水果小故事,来打开我们的泛型探索之旅(我们为什么要使用泛型),故事场景如下:
Lucy 到 James 家做客,James 需要招待客人,且知道 Lucy 喜欢吃橘子????,于是使用水果盘装满了????来招待客人
这个场景怎么用代码表现呢?我们来新建几个类,如下:
Fruit:水果类。
public class Fruit {@Overridepublic String toString() {return "This is Fruit";}
}
Apple:苹果类,继承水果类。
public class Apple extends Fruit {@Overridepublic String toString() {return " Apple ????";}
}
Orange:橘子类,继承水果类。
public class Orange extends Fruit {@Overridepublic String toString() {return " Orange ????";}
}
Plate:水果盘接口。
public interface Plate<T> {public void set(T t);public T get();
}
FruitPlate:水果盘类,实现水果盘接口。
public class FruitPlate implements Plate {private List items = new ArrayList(6);@Overridepublic void set(Object o) {items.add(o);}@Overridepublic Fruit get() {int index = items.size() - 1;if(index >= 0) return (Fruit) items.get(index);return null;}
}
AiFruitPlate:智能水果盘,实现水果盘接口。
/*** 使用泛型类定义* @param <T>*/
public class AiFruitPlate<T> implements Plate<T> {private List<T> fruits = new ArrayList<T>(6);@Overridepublic void set(T t) {fruits.add(t);}@Overridepublic T get() {int index = fruits.size() - 1;if(index >= 0) return fruits.get(index);return null;}
}
Person:人类。
public class Person {
}
Lucy:Lucy类,继承 Person 类,她拥有吃橘子的能力 eat()
。
public class Lucy extends Person {public void eat(Orange orange) {System.out.println("Lucy like eat" + orange);}
}
James:James类,继承 Person 类,他拥有获取水果盘的能力 getAiFruitPlate()
。
public class James extends Person {public FruitPlate getPlate() {return new FruitPlate();}public AiFruitPlate getAiFruitPlate() {return new AiFruitPlate();}public void addFruit(FruitPlate fruitPlate, Fruit fruit) {fruitPlate.set(fruit);}public void add(AiFruitPlate<Orange> aiFruitPlate, Orange orange) {aiFruitPlate.set(orange);}
}
Scenario:测试类。
public class Scenario {public static void main(String[] args) {scenario1();scenario2();}//没有使用泛型private static void scenario1() {James james = new James();Lucy lucy = new Lucy();// James 拿出水果盘FruitPlate fruitPlate = james.getPlate(); // James 往水果盘里装橘子james.addFruit(fruitPlate,new Orange()); // 需要转型为 Orangelucy.eat((Orange) fruitPlate.get()); }//使用了泛型private static void scenario2() {James james = new James();Lucy lucy = new Lucy();// James 拿出智能水果盘(知道你需要装橘子)AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate(); // James 往水果盘里装橘子(如果,装的不是橘子会提醒)james.add(aiFruitPlate, new Orange()); // 不需要转型lucy.eat(aiFruitPlate.get());}
}
运行结果,如下:
Lucy like eat Orange ????
Lucy like eat Orange ????Process finished with exit code 0
我们可以很明显的看出,使用了泛型之后,不需要类型转换.如果我们把 scenario1()
方法,稍微改下,如下:
private static void scenario1() {James james = new James();Lucy lucy = new Lucy();FruitPlate fruitPlate = james.getPlate();james.addFruit(fruitPlate,new Apple()); //new Orange() 改成 new Apple()lucy.eat((Orange) fruitPlate.get());
}
编译器不会提示有问题,但是运行时,会报错,如下:
Exception in thread "main" java.lang.ClassCastException: entity.Apple cannot be cast to entity.Orangeat Scenario.scenario1(Scenario.java:21)at Scenario.main(Scenario.java:7)Process finished with exit code 1
而我们把 scenario2()
(使用了泛型)做出同样的修改,如下:
private static void scenario2() {James james = new James();Lucy lucy = new Lucy();AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate();james.add(aiFruitPlate, new Apple());lucy.eat(aiFruitPlate.get());
}
编译器,会提示我们有错误。
通过以上案例,很清晰的知道,我们为什么要使用泛型。
消除类型转换;
在编译时进行更强的类型检查;
增加代码的复用性;
泛型类
泛型类是通过类型进行参数化的类,这样说可能不好理解,之后我们用代码演示。
普通类
首先,我们来定义一个普通的类,如下:
public class SimpleClass {private Object object;public Object getObject() {return object;}public void setObject(Object object) {this.object = object;}
}
它的 get
、set
方法接受和返回一个 Object
,所以我们可以随意的传递任何类型。在编译时,无法检查类型的使用,我们可以传入 Integer
且取出 Integer
,也可以传入 String
,这种无约束的设计,很容易导致运行时错误。
泛型类
泛型类的定义格式如下:
class name<T1,T2,...,Tn>{...
}
在类名之后的 <>
尖括号,称之为类型参数(类型变量),定义一个泛型类就是使用 <>
给它定义类型参数:T1、T2 ... Tn。
然后,我们把 SimpleClass
改成泛型类,如下:
public class GenericClass<T> {private T t;public T getT() {return t;}public void setT(T t) {this.t = t;}
}
所以的 object
都替换成为 T
,类型参数可以定义为任何的非基本类型,如:class类型、interface,类型、数组类型、甚至是另一个类型参数。
调用和实例化泛型类型
要想使用泛型类,必须执行泛型类调用,如:
GenericClass<String> genericClass;
泛型类的调用类似于方法的调用(传递了一个参数),但是,我们没有将参数传递给方法,而是,将类型参数(String)传递给了 GenericClass
类本身。
此代码不会创建新的 GenericClass
对象,它只是声明了 genericClass
将保存对 String
的引用。
要实例化此类,要使用 new
关键字,如:
GenericClass<String> genericClass = new GenericClass<String>();
或者
GenericClass<String> genericClass = new GenericClass<>();
在 Java SE 7 及更高的版本中,编译器可以从上下文推断出类型参数,因此,可以使用 <>
替换泛型类的构造函数所需的类型参数。
类型参数命名规范
我们的类型参数是否一定要写成 T
呢,按照规范,类型参数名称是单个大写字母。
常用的类型参数名称有,如:
多类型参数
泛型类可以有多个类型参数,如:
public interface MultipleGeneric<K,V> {public K getKey();public V getValue();
}public class ImplMultipleGeneric<K, V> implements MultipleGeneric<K, V> {private K key;private V value;public ImplMultipleGeneric(K key, V value) {this.key = key;this.value = value;}@Overridepublic K getKey() {return key;}@Overridepublic V getValue() {return value;}public static void main(String[] args) {MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<String, Integer>("per",6);System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<String, String>("per","lsy");System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());}
}
输出结果:
key:per, value:6
key:per, value:lsyProcess finished with exit code 0
如上代码,new ImplMultipleGeneric
将 K
实例化为 String
,将 V
实例化为 Integer
,因此, ImplMultipleGeneric
构造函数参数类型分别为 String
和 Integer
,在编写 new ImplMultipleGeneric
代码时,编辑器会自动填写 <>
的值。
由于 Java 编译器会从声明 ImplMultipleGeneric
推断出 K
和 V
的类型,因此我们可以简写为,如下:
MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<>("per",6);
System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<>("per","lsy");
System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());
泛型接口
定义泛型接口和定义泛型类相似(泛型类的技术可同用于泛型接口),如下:
interface name<T1,T2,...,Tn>{...
}
我们来定义一个泛型接口,如下:
public interface Genertor<T> {public T next();
}
那么,如何实现一个泛型接口呢,我们使用两种方式来实现泛型接口。
使用泛型类,实现泛型接口,且不指定确切的类型参数,所以,实现的 next()
返回值自动变成 T
public class ImplGenertor<T> implements Genertor<T> {@Overridepublic T next() {return null;}
}
使用普通类,实现泛型接口,且指定确切的类型参数为 String
,所以,实现的 next()
返回值自动变成 String
public class ImplGenertor2 implements Genertor<String> {@Overridepublic String next() {return null;}
}
泛型方法
泛型方法使用了类型参数的方法,泛型方法比较独立,可以声明在 普通类、泛型类、普通接口、泛型接口中。
泛型方法定义格式,如下:
public <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2)
泛型方法的类型参数列表,在 <>
内,该列表必须在方法返回类型之前;对于静态的泛型方法,类型参数必须在 static
之后,方法返回类型之前。
普通类里定义泛型方法
我们在普通类中定义泛型方法,如下:
public class MethodGeneric {//定义一个泛型方法public <T> T genericMethod(T...t) {return t[t.length/2];}public static void main(String[] args) {MethodGeneric methodGeneric = new MethodGeneric();System.out.println(methodGeneric.<String>genericMethod("java","dart","kotlin"));}
}
methodGeneric.<String>genericMethod("java","dart","kotlin")
通常可以省略掉 <>
的内容,编译器将推断出所需的类型,和调用普通方法一样,如:
methodGeneric.genericMethod("java","dart","kotlin")
泛型类里定义泛型方法
我们在泛型类中定义泛型方法,如下:
public class MethodGeneric2 {static class Fruit{@Overridepublic String toString() {return "fruit";}}static class Apple extends Fruit {@Overridepublic String toString() {return "Apple";}}static class Person{@Overridepublic String toString() {return "person";}}//定义了泛型类static class ShowClass<T> {//定义了普通方法public void show1(T t){System.out.println(t.toString());}//定义了泛型方法public <E> void show2(E e) {System.out.println(e.toString());}//定义了泛型方法public <T> void show3(T t) {System.out.println(t.toString());}}public static void main(String[] args) {Apple apple = new Apple();Person person = new Person();ShowClass<Fruit> showClass = new ShowClass<>();showClass.show1(apple); //可以放入 apple,因为 apple 是 fruit 的子类showClass.show1(person); //此时,编译器会报错,因为 ShowClass<Fruit> 已经限定类型showClass.show2(apple); //可以放入,泛型方法 <E> 可以是任何非基本类型showClass.show2(person);//可以放入,泛型方法 <E> 可以是任何非基本类型showClass.show3(apple); //可以放入,泛型方法 <T> 和泛型类中的 <T> 不是同一条 T,可以是任何非基本类型showClass.show3(person); //可以放入,泛型方法 <T> 和泛型类中的 <T> 不是同一条 T,可以是任何非基本类型}
}
在泛型类中定义泛型方法时,需要注意,泛型类里的泛型参数 <T>
和泛型方法里的泛型参数 <T>
不是同一个。
限定类型参数
我们经常看到类似 public <U extends Number> void inspect(U u)
的代码,<U extends Number>
就是限制类型参数,只对数字进行操作且只接受 Number
或其子类。
要声明一个限定的类型参数,需要在参数类型后加上 extends
关键字,然后是其上限类型(类或接口)。
限定类型参数的泛型类
泛型类也可以使用限定类型参数,如下:
public class BoundedClass<T extends Comparable> {private T t;public void setT(T t) {this.t = t;}public T min(T outter){if (this.t.compareTo(outter) > 0)return outter;elsereturn this.t;}public static void main(String[] args) {//只能传入实现了 Comparable 接口的类型BoundedClass<String> boundedClass = new BoundedClass<>(); boundedClass.setT("iOS");System.out.println(boundedClass.min("android"));}
}
限定类型参数的泛型方法
泛型方法也可以使用限定类型参数,如下:
public class BoundedGeneric {public static <T extends Comparable> T min(T a, T b) {if (a.compareTo(b) < 0)return a;elsereturn b;}public static void main(String[] args) {System.out.println(BoundedGeneric.min(66,666));}
}
多重限定
限定类型参数,也可以为多个限定,如:
<T extends B1 & B2 & B3>
多个限定参数,如果其中有类,类必须放在第一个位置,例如:
interface A { ... }
interface B { ... }
class C { ... }class D <T extends C & A & B>
泛型,继承和子类型
在前面的盘子装水果小故事里我们已经创建好了一些水果类,如下:
public class Fruit {@Overridepublic String toString() {return "This is Fruit";}
}public class Apple extends Fruit {@Overridepublic String toString() {return " Apple ????";}
}public class Orange extends Fruit {@Overridepublic String toString() {return " Orange ????";}
}public class QIOrange extends Orange {@Overridepublic String toString() {return "qi Orange ????";}
}
他们的继承关系,如图:
众所周知,我们可以把子类赋值给父类,例如:
Apple apple = new Apple();
Fruit fruit = new Fruit();
fruit = apple;
泛型也是如此,我们定义一个水果盘子的泛型类,如下:
public class FruitPlateGen<Fruit> implements Plate<Fruit> {private List<Fruit> fruits = new ArrayList<>(6);@Overridepublic void set(Fruit fruit) {fruits.add(fruit);}@Overridepublic Fruit get() {int index = fruits.size() - 1;if(index >= 0) return fruits.get(index);return null;}
}
所以,是 Fruit
的子类都可以放入水果盘里,如下:
FruitPlateGen<Fruit> fruitPlate = new FruitPlateGen<Fruit>();
fruitPlate.set(new Apple());
fruitPlate.set(new Orange());
现在,James 可以获取盘子,如下:
public class James extends Person {public FruitPlateGen getAiFruitPlateGen(FruitPlateGen<Fruit> plate) {return new FruitPlateGen();}
}
如是,James 想获取放橘子的盘子,如下:
James james = new James();
james.getAiFruitPlateGen(new FruitPlateGen<Fruit>()); //获取成功
james.getAiFruitPlateGen(new FruitPlateGen<Orange>()); //编译器报错
虽然,Orange
是 Fruit
的子类,但是,FruitPlateGen<Orange>
不是 FruitPlateGen<Fruit>
的子类,所以,不能传递产生继承关系。
泛型类和子类型
我们可以通过继承(extends)或实现(implements)泛型类或接口,例如:
private static class ExtendFruitPlate<Orange> extends FruitPlateGen<Fruit> {
}
此时,ExtendFruitPlate<Orange>
就是 FruitPlateGen<Fruit>
的子类,James 再去拿盘子,就不会有错误提示:
james.getAiFruitPlateGen(new ExtendFruitPlate<Orange>());
通配符
我们经常看到类似 List<? extends Number>
的代码,?
就是通配符,表示未知类型。
上限通配符
我们可以使用上限通配符来放宽对变量的限制,例如,上文提到的 FruitPlateGen<Fruit>
和 FruitPlateGen<Orange>()
就可以使用上限通配符。
我们来改写一下 getAiFruitPlateGen
方法,如下:
public FruitPlateGen getAiFruitPlateGen2(FruitPlateGen<? extends Fruit> plate) {return new FruitPlateGen();
}
这时候,James 想获取放橘子的盘子,如下:
James james = new James();
james.getAiFruitPlateGen2(new FruitPlateGen<Fruit>()); //获取成功
james.getAiFruitPlateGen2(new FruitPlateGen<Orange>()); //获取成功
上限通配符 FruitPlateGen<? extends Fruit>
匹配 Fruit
和 Fruit
的任何子类型,所以,我们可以传入 Apple
、Orange
都没有问题。
下限通配符
上限通配符将未知类型限定为该类型或其子类型,使用 extends
关键字,而下限通配符将未知类型限定为该类型或其父类型,使用 super
关键字。
我们再来宽展一下 getAiFruitPlateGen
方法,如下:
public FruitPlateGen getAiFruitPlateGen3(FruitPlateGen<? super Apple> plate) {return new FruitPlateGen();
}
这时候,James 只能获取 FruitPlateGen<Fruit>
和 FruitPlateGen<Apple>
的盘子,如下:
James james = new James();
james.getAiFruitPlateGen3(new FruitPlateGen<Apple>());
james.getAiFruitPlateGen3(new FruitPlateGen<Fruit>());
下限通配符 FruitPlateGen<? super Apple>
匹配 Apple
和 Apple
的任何父类型,所以,我们可以传入 Apple
、Fruit
。
通配符和子类型
在 泛型,继承和子类型 章节有讲到,虽然,Orange
是 Fruit
的子类,但是,FruitPlateGen<Orange>
不是 FruitPlateGen<Fruit>
的子类。但是,你可以使用通配符在泛型类或接口之间创建关系。
我们再来回顾下 Fruit
的继承关系,如图:
代码,如下:
Apple apple = new Apple();
Fruit fruit = apple;
这个代码是没有问题的,Fruit
是 Apple
的父类,所以,可以把子类赋值给父类。
代码如下:
List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = apples; // 编辑器报错
因为,List<Apple>
不是 List<Fruit>
的子类,实际上这两者无关,那么,它们的关系是什么?如图:
List<Apple>
和 List<Fruit>
的公共父级是 List<?>
。
我们可以使用上下限通配符,在这些类之间创建关系,如下:
List<Apple> apples = new ArrayList<>();
List<? extends Fruit> fruits1 = apples; // OK
List<? super Apple> fruits2 = apples; // OK
下图展示了上下限通配符声明的几个类的关系,如图:
PECS原则
在上文中有 FruitPlateGen
水果盘子的类,我们尝试使用上下限通配符来实例化水果盘,代码如下:
Apple apple = new Apple();
Orange orange = new Orange();
Fruit fruit = new Fruit();FruitPlateGen<? extends Fruit> fruitPlateGen = new FruitPlateGen<>();
fruitPlateGen.set(apple); // error
fruitPlateGen.set(orange); // error
fruitPlateGen.set(fruit); // error
Fruit fruit1 = fruitPlateGen.get(); // OK
Orange orange1 = fruitPlateGen.get(); // error
Apple apple1 = fruitPlateGen.get(); // error
上限通配符无法 set
数据,但是可以 get
数据且只能 get
到其上限 Fruit
,所以,上限通配符可以安全的访问数据。
在来看一下代码,如下:
FruitPlateGen<? super Apple> fruitPlateGen1 = new FruitPlateGen<>();
fruitPlateGen1.set(apple); // OK
fruitPlateGen1.set(orange); // error
fruitPlateGen1.set(fruit); // error
Object object = fruitPlateGen1.get(); // OK
Fruit fruit2 = fruitPlateGen1.get(); // error
Apple apple2 = fruitPlateGen1.get(); // error
Orange orange2 = fruitPlateGen1.get(); // error
下限通配符可以且只能 set
其下限 Apple
,也可以 get
数据,但只能用 Object
接收(因为Object是所有类型的父类,这是一个特例),所以,下限通配符可以安全的写入数据。
所以,在使用上下限通配符时,可以遵循以下准则:
如果你只需要从集合中获得类型T , 使用<? extends T>通配符;
如果你只需要将类型T放到集合中, 使用<? super T>通配符;
如果你既要获取又要放置元素,则不使用任何通配符;
类型擦除
Java 语言使用类型擦除机制实现了泛型,类型擦除机制,如下:
编译器会把所有的类型参数替换为其边界(上下限)或 Object,因此,编译出的字节码中只包含普通类、接口和方法;
在必要时插入类型转换,已保持类型安全;
生成桥接方法以在扩展泛型类时保持多态性;
泛型类型的擦除
Java 编译器在擦除过程中,会擦除所有类型参数。
如果类型参数是有界的,则替换为第一个边界,如果是无界的,则替换为 Object。
我们定义了一个泛型类,代码如下:
public class Node<T> {private T data;private Node<T> next;public Node(T data, Node<T> next) { this.data = data;this.next = next;
}public T getData() { return data; }...
}
由于类型参数 T
是无界的,因此,Java 编译器将其替换为 Object,如下:
public class Node {private Object data;private Node next;public Node(Object data, Node next) { this.data = data;this.next = next;
}public Object getData() { return data; }...
}
我们再来定义一个有界的泛型类,代码如下:
public class Node<T extends Comparable<T>> {private T data;private Node<T> next;public Node(T data, Node<T> next) { this.data = data;this.next = next;
}public T getData() { return data; }...
}
Java 编译器其替换为第一个边界 Comparable
,如下:
public class Node {private Comparable data;private Node next;public Node(Comparable data, Node next) { this.data = data;this.next = next;}public Comparable getData() { return data; }...
}
泛型方法的擦除
Java 编译器同样会擦除泛型方法中的类型参数,例如:
public static <T> int count(T[] anArray, T elem) {int cnt = 0;for (T e : anArray) { ... }
}
由于 T
是无界的,因此,Java 编译器将其替换为 Object,如下:
public static int count(Object[] anArray, Object elem) {int cnt = 0;for (Object e : anArray) if (e.equals(elem))
}
如下代码:
class Shape { ... }
class Circle extends Shape { ... }
class Rectangle extends Shape { ... }
有一个泛型方法,如下:
public static<T extends Shape> void draw(T shape){...
}
Java 编译器将用第一个边界 Shape
替换 T
,如下:
public static void draw(Shape shape){...
}
桥接方法
有时类型擦除会导致无法预料的情况,如下:
public class Node<T> {public T data;public Node(T data) { this.data = data; }public void setData(T data) { System.out.println("Node.setData"); this.data = data;}
}
public class MyNode extends Node<Integer> {public MyNode(Integer data) { super(data); }public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data);}
}
类型擦除后,代码如下:
public class Node {public Object data;public Node(Object data) { this.data = data; }public void setData(Object data) { System.out.println("Node.setData"); this.data = data;}
}
public class MyNode extends Node {public MyNode(Integer data) { super(data); }public void setData(Integer data) { System.out.println("MyNode.setData");super.setData(data);}
}
此时,Node 的方法变为 setData(Object data)
和 MyNode 的 setData(Integer data)
不会覆盖。
为了解决此问题并保留泛型类型的多态性,Java 编译器会生成一个桥接方法,如下:
class MyNode extends Node {// 生成的桥接方法public void setData(Object data) {setData((Integer) data);}public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data);}...
}
这样 Node 的方法 setData(Object data)
和 MyNode 生成的桥接方法 setData(Object data)
可以完成方法的覆盖。
泛型的限制
为了有效的使用泛型,需要考虑以下限制:
无法实例化具有基本类型的泛型类型
无法创建类型参数的实例
无法声明类型为类型参数的静态字段
无法将Casts或instanceof与参数化类型一起使用
无法创建参数化类型的数组
无法创建,捕获或抛出参数化类型的对象
无法重载每个重载的形式参数类型都擦除为相同原始类型的方法
无法实例化具有基本类型的泛型类型
代码如下:
class Pair<K, V> {private K key;private V value;public Pair(K key, V value) { this.key = key;this.value = value; }...
}
创建对象时,不能使用基本类型替换参数类型:
Pair<int, char> p = new Pair<>(8, 'a'); // error
无法创建类型参数的实例
代码如下:
public static <E> void append(List<E> list) {E elem = new E(); // error list.add(elem);
}
无法声明类型为类型参数的静态字段
代码如下:
public class MobileDevice<T> {private static T os; // error...
}
类的静态字段是所有非静态对象共享的变量,因此,不允许使用类型参数的静态字段。
无法将Casts或instanceof与参数化类型一起使用
代码如下:
public static <E> void rtti(List<E> list) {if (list instanceof ArrayList<Integer>) { // error...}
}
Java 编译器会擦除所有类型参数,所有,无法验证在运行时使用的参数化类型。
无法创建参数化类型的数组
代码如下:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // error
无法创建,捕获或抛出参数化类型的对象
代码如下:
class MathException<T> extends Exception { ... } // error
class QueueFullException<T> extends Throwable{ ... } // error
无法重载每个重载的形式参数类型都 擦除为相同原始类型的方法
代码如下:
public class Example {public void print(Set<String> strSet) { }public void print(Set<Integer> intSet) { }
}
print(Set<String> strSet)
和 print(Set<Integer> intSet)
在类型擦除后是完全相同的类型,所以,无法重载。
本文结合大量示例代码,演示了 Java 泛型的使用和技术细节,希望对大家有所帮助。觉得不错欢迎收藏、转发、在看,谢谢支持!
热文推荐:
JVM 通过「逃逸分析」就能让对象在「栈上分配」?没那么简单!
学不动也要学!探究Fragment延迟加载的前世今生
面试官:"Handler的runWithScissors()了解吗?为什么Google不让开发者用?"
Android:写了这么多代码,你真的理解泛型吗?相关推荐
- Android Binder通信一次拷贝你真的理解了吗?
Android Binder通信一次拷贝你真的理解了吗? Android Binder框架实现目录: Android Binder框架实现之Binder的设计思想 Android Binder ...
- Android:写了这么多代码,你真的理解泛型吗
查看全文 http://www.taodudu.cc/news/show-5955185.html 相关文章: android textView添加不同颜色的边框 Android 信号量signal使 ...
- 这么糟糕的代码,真的是我以前写的吗?
GitChat 作者:Zm 原文:日常开发与设计模式的那点事 关注微信公众号:「GitChat 技术杂谈」 一本正经的讲技术 前言 "很多程序员不知道怎么组织代码.怎么提升效率.怎么提高代码 ...
- Linux系统学习方法——写给小白 每个人都有心中向往的一些事情,而我,刚进实验室,看到师兄师姐在电脑前面噼里啪啦的敲代码,真的好羡慕,看着他们实现一个又一个的功能满心欢喜,我也很想尝试。后来了解到师
Linux系统学习方法--写给小白 每个人都有心中向往的一些事情,而我,刚进实验室,看到师兄师姐在电脑前面噼里啪啦的敲代码,真的好羡慕,看着他们实现一个又一个的功能满心欢喜,我也很想尝试.后来了解到师 ...
- [Android] 开心消消乐代码(写的比较简单)
突然想要在android上写一个消消乐的代码,在此之前没有系统地学过java的面向对象,也没有任何android相关知识,不过还是会一点C++.8月初开始搭建环境,在这上面花了相当多的时间,然后看了一 ...
- Android开发如何写出优雅的代码
很多时候我们去面试,人家总会问一个问题,你们公司开发一个app是如何进行技术选择的,app中涉及到了哪些开发模式,谈谈你对mvc.mvp和mvvm的区别.或许在这些问题上每个人有每个人的看法,在我看来 ...
- 不是我吓唬你,写不出这种代码,那就等着被leader开除吧
前言 在我们平时刷题的时候,你可能会写过很多诸如 int a,b,c int [] arrays=new int arrays[10]; if((numbers > 10 && ...
- 写了100万行代码的程序员?
今天在社群上闲逛,突然发现一个十分有趣的帖子,<写了100W行的代码是啥感觉?>看完之后就头皮一阵发麻,让我写一万行的代码?!are you kidding me? 我估计写到20万的时候 ...
- Android逆向之调试smali代码基础
点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 介绍Android逆向中调试smali代码的方法." 最近在重整Android逆向分析环境,一切都在从零开始,做下记录,给大家分享 ...
最新文章
- 如何屏蔽ctrl + v 粘贴事件,鼠标右键粘贴事件
- java 判断日期是同一天_如何检查Java中的两个日期是否在同一天
- stm32 can bus 总结
- notepad正则表达式替换_正则表达式装逼(实用)指南
- [Swift实际操作]八、实用进阶-(7)使用通知的方法进行对象间的消息传递
- 【转载】贝叶斯决策论
- Unity时钟定时器插件
- 分子动力学软件LAMMPS学习--常见错误运行
- 特征选择mRMR算法实现全解
- Asp.net 2.0在Windows 2003 Server 上配置Microsoft Excel、Microsoft Word应用程序权限时 error: 8000401a 的解决方法!...
- 《嵌入式 – GD32开发实战指南》第1章 开发环境搭建
- Java 枚举 ordinal 使用以及介绍
- python增加一列数据_使用Python向DataFrame中指定位置添加一列或多列的方法
- [转载]图论500题
- 11.9 至 11.17 四道典型题记录: Counter 弹出 | map函数 | 子集求取 | 有序字符桶分装
- 初学C语言(时间2022.11.7)
- JAVA中如何实现代码优化(技巧讲解)
- android手机 无电池开机,手机无法开机的6种解决方法
- 操作系统---计算机系统概述
- ABAP GIT 使用教程