1. 为什么使用泛型程序设计?
使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。 泛型对于集合类尤其有用。
泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所 重用。
2. 泛型定义
声明类的泛型版本与声明类的一般版本的语法格式很类似,只有稍微的不同。
声明一般类的语法格式为:
[修饰符] class类名 [extends父类名] [implements接口1名,接口2名,…]{
//类体
}而声明泛型类型时,只需要在类名后面跟上一个类型参数即可,其语法格式为:
- 单类型参数泛型类型声明
[修饰符] class类名<类型参数> [extends父类名] [implements接口1名,接口2名,。。。]{
//类体
}- 多类型参数泛型类型声明
angelscript
[修饰符] class类名<类型参数1,类型参数2,类型参数3> [extends父类名] [implements接口1名,接口2名,。。。]{
//类体
}每个类型参数必须唯一。
代码示例:
- box类
public class Box {
private Object object;
public void add(Object object){
this.object = object;
}
public Object get(){
return object;
}
}- box 泛型类
// Box类的泛型版本.
public class Box<T> {
private T t; // T代表"类型"
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}- T:”代表类型变量” Type
3. 泛型使用
一个泛型类型调用通常被看做是“参数化类型”。要实例化这个类,照常使用new关键字,但是将<Integer>放在类名和圆括号之间:
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
// 不需要类型转换
Integer someInteger = integerBox.get();
System.out.println(someInteger)4.泛型方法
4.1 定义泛型方法
类型参数还可以在方法和构造器签名中声明,用来创建“泛型方法”和“泛型构造器”。这与声明一个泛型类型相似,但是类型参数的作用域被限制在它被声明的方法或构造器中。
声明格式:
[修饰符] <类型变量> [返回类型] 方法名(参数列表){
// 方法体
}代码示例:
class ArrayAlg{
public static <T> T getMiddle(T... a){
return a[a.length / 2];
}
}这个方法是在普通类中定义的, 而不是在泛型类中定义的。 然而, 这是一个泛型方法, 可以从尖括号和类型变量看出这一点。注意:类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。
4.2 调用泛型方法
当调用一个泛型方法时’ 在方法名前的尖括号中放入具体的类型
调用ArrayAlg类中的泛型方法:
String middle = ArrayAlg.<String>getMiddle("]ohnM, "Q.n, "Public");在大多数情况下,方法调用中可以省略类型参数。编译器有足够的信息能够推断出所调用的方法。它用 names 的类型(即 String[]) 与泛型类型 T[ ] 进行匹配并推断出 T 一定是 String。也就是说上述代码也可以修改为:
String middle = ArrayAlg.getHiddle("]ohn", "Q.", "Public");5.类型变量
5.1 类型参数命名惯例
按惯例,类型参数命名为单个的大写字母。这与已经知道的变量命名规则不太相同。这样命名的原因是:如果不这样命名,将很难区分在一个类型变量和一个变通类或接口名称之间的不同。最普遍使用的类型参数名称如下:
- E——元素(被Java集合框架所广泛地使用 )。
- K——键。
- N——数字。
- T——类型。
- V——值。
- S,U,V等——第二、第三、第四个类型。
5.2 类型变量的限定
5.2.1 类型参数边界
默认可以使用任何类型来实例化一个泛型类对象,但Java中也对泛型类实例的类型作了限制。
语法如下:
<T exteds anyClass>- anyClass指某个接口或类
- anyClass指的是上限
- 限定上限的类型中,最多只能有1个类(或0个类),其余的必须均为接口。
- 限定上限的类型中,接口可以有多个,中间用&字符分割。
使用泛型限制后,泛型类的类型必须实现或者继承了anyClass 这个接口或类。无论anyClass是接口还是类,在进行泛型限制时都必须使用extends关键字。
- 泛型类限制使用
import java.util.List;
public class LimitClass<T extends List>{
// ....
}- 泛型方法限制使用
import java.util.List;
public class LimitClass{
public static <T extends List> void testMedthod(T t){
// ....
}
}对类型参数的限定,也可以包括指定额外的必须被实现的接口,使用&字符,例如指定类型变量U可接收的值为实现了MyInterface接口的Number类或其之类。 语法如下:
javascript
// 上限类型中有一个接口
<U extends Number & MyInterface>
// 上限类型中有n个接口
<U extends Number & MyInterface1 & MyInterface2 & ... &MyInterface...>5.2.2 使用通配符: ?
固定的泛型类型使用起来不灵活,java提供一种巧妙的方案:通配符类型。
定义格式
swift
//不设置上限:表示任何类型
AnyGeneric<?>
AnyGeneric<?,?>
// 设置上限
AnyGeneric<? extends MyClass>表示任何泛型AnyGeneric类型,它的类型参数是MyClass的子类
代码1:
public class Demo {
public static void main(String[] args){
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
printPoint(p1);
Point<String, String> p2 = new Point<String, String>();
p2.setX("东经180度");
p2.setY("北纬210度");
printPoint(p2);
}
// 使用通配符
public static void printPoint(Point<?, ?> p){
System.out.println("This point is: " + p.getX() + ", " + p.getY());
}
}
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
/*
运行结果:
This point is: 10, 20
This point is: 东经180度, 北纬210度
*/代码2:
java
public class Demo {
public static void main(String[] args){
Point<Integer, Integer> p1 = new Point<Integer, Integer>();
p1.setX(10);
p1.setY(20);
printNumPoint(p1);
Point<String, String> p2 = new Point<String, String>();
p2.setX("东经180度");
p2.setY("北纬210度");
printStrPoint(p2);
}
// 借助通配符限制泛型的范围
public static void printNumPoint(Point<? extends Number, ? extends Number> p){
System.out.println("x: " + p.getX() + ", y: " + p.getY());
}
public static void printStrPoint(Point<? extends String, ? extends String> p){
System.out.println("GPS: " + p.getX() + "," + p.getY());
}
}
class Point<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
/*
运行结果:
x: 10, y: 20
GPS: 东经180度,北纬210度
*/6.类型擦除
当一个泛型被实例化时,编译器通过称为“类型擦除”的技术来编译这些类型。所谓类型擦除,指的是编译器移除一个类或方法中所有与类型参数相关的信息。类型擦除能够使使用泛型的Java应用程序与Java类库和在泛型出现之前创建的应用程序保持二进制上的兼容性。
例如:将泛型类Pair 进行类型擦除 擦除前:
java
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;
}
}擦除后:
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;
}
}因此,Java使用擦拭法实现泛型,导致了:
- 编译器把类型视为Object;
- 编译器根据实现安全的强制转型。
7.泛型的局限
Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。了解了Java泛型的实现方式——擦拭法,我们就知道了Java泛型的局限:
7.1 不能用类型参数代替基本类型
不能用类型参数代替基本类型,因此没有 Pair、Pair…
arduino
Pair<int> p = new Pair<int>(1, 2); // compile error!7.2 无法判断带泛型的Class
lasso
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>.class) {
}原因和前面一样,并不存在Pair.class,而是只有唯一的Pair.class。
7.3 无法实例化T类型
java
public class Pair<T> {
private T first;
private T last;
public Pair() {
// Compile error:
first = new T();
last = new T();
}
}上述代码无法通过编译,因为构造方法的两行语句:
java
first = new T();
last = new T();
//擦拭后实际上变成了:
first = new Object();
last = new Object();7.4 无法创建参数化类型的数组
haxe
Pair<String>[] table = new Pair<String>[10]; //error需要说明的是,只是不允许创建这些数组,而声明类型为Pair[]的变量仍是合法的,不过不能用new Pair[10]初始化这个变量。
7.5 总结
Java的泛型是采用擦拭法实现的;擦拭法决定了泛型<T>:
- 不能是基本类型,例如:int;
- 不能获取带泛型类型的Class,例如:Pair.class;
- 不能判断带泛型类型的类型,例如:x instanceof Pair;
- 不能实例化T类型,例如:new T()。
https://liuqh.icu/tags/SpringBoot/
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
static int add(Pair<Number> p) {
//读取操作
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
问题1:为什么需要 extends?
```java
public class Main {
public static void main(String[] args) {
//Pair<Number> p = new Pair<Number>(1, 2)
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}
//incompatible types: Pair<Integer> cannot be converted to Pair<Number>
//原因很明显,因为Pair<Integer>不是Pair<Number>的子类,因此,add(Pair<Number>)不接受参数类型Pair<Integer>。
//但是从add()方法的代码可知,传入Pair<Integer>是完全符合内部代码的类型规范,因为语句:Number first = p.getFirst();
//实际类型是Integer,引用类型是Number,没有问题。问题在于方法参数类型定死了只能传入Pair<Number>。
//有没有办法使得方法参数接受Pair<Integer>?办法是有的,这就是使用Pair<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型。我们把代码改写如下:
static int add(Pair<Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
//这样一来,给方法传入Pair<Integer>类型时,它符合参数Pair<? extends Number>类型。这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。
//除了可以传入Pair<Integer>类型,我们还可以传入Pair<Double>类型,Pair<BigDecimal>类型等等,因为Double和BigDecimal都是Number的子类。
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
//如果我们考察对Pair<? extends Number>类型调用getFirst()方法,实际的方法签名变成了:
//<? extends Number> getFirst();
//即返回值是Number或Number的子类,因此,可以安全赋值给Number类型的变量:
//编译器只能【确定】类型一定是Number的子类(包括Number类型本身),但具体类型无法确定。
return first.intValue() + last.intValue();
}
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
p.setFirst(new Integer(first.intValue() + 100));
//public void setFirst(Pair<? extends Number> p)
//Pair<Double> p2=new Pair<>(123.1, 456.23);
//此时 P2是Doule类型,如果我们写的方法扩大的画,可以写入Inter类型,这样是不对的
//Pair<Double>的setFirst(Pair<? extends Number> p)显然无法接受Integer类型。
//读的时候我们可以放大界限,但是写的时候,是什么就得是什么。
//这就是<? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)。
//
// 总结
// 使用类似<? extends Number>通配符作为方法参数时表示:
// 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;
// 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。
// 即一句话总结:使用extends通配符表示可以读,不能写。
// 使用类似<T extends Number>定义泛型类时表示:
// 泛型类型限定为Number以及Number的子类。
p.setLast(new Integer(last.intValue() + 100));
return p.getFirst().intValue() + p.getLast().intValue();
}
}
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 setFirst(T first) {
this.first = first;
}
public void setLast(T last) {
this.last = last;
}
}
```
extends 为什么无法写数据,为什么能读数据public class Main {
public static void main(String[] args) {
Pair<Number> p1 = new Pair<>(12.3, 4.56);
Pair<Integer> p2 = new Pair<>(123, 456);
setSame(p1, 100);
setSame(p2, 200);
System.out.println(p1.getFirst() + ", " + p1.getLast());
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
//extends通配符相反,这次,我们希望接受Pair<Integer>类型,以及Pair<Number>、Pair<Object>,
//因为Number和Object是Integer的父类,setFirst(Number)和setFirst(Object)
//实际上允许接受Integer类型。
//Integer 职员(能写), 领导(肯定也能写)(最小权限
static void setSame(Pair<? super Integer> p, Integer n) {
p.setFirst(n);
p.setLast(n);
}
//? super Integer getFirst();
//这里注意到我们无法使用Integer类型来接收getFirst()的返回值,即下面的语句将无法通过编译:
// 因为我们现在是职员,但是容器里面有领导的权限,我们不能拿出来
// Integer x = p.getFirst();
//因为如果传入的实际类型是Pair<Number>,编译器无法将Number类型转型为Integer
// extends
//这里是Number 是领导,肯定能读取到职权的权限
//但是写入的时候,领导能写入的,只能不一定能写(最大权限)
static int king(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.getLast().intValue();
}
}
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;
}
}