java_泛型结构模型整理

official tutorial:

https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html

从特殊情况:

在这里插入图片描述

抽象出较为一般的情况:

class(superclass) variable=class(subclass) instance
在这里插入图片描述

类型通配符

1、类型通配符一般是使用?代替具体的类型参数。

例如 List<?>:

在逻辑上是List<String>,List<Integer> 等所有List<具体类型实参>的父类

2、类型通配符上限通过形如class<? extends type>来定义
例如List<? extends Number>
如此定义就是通配符泛型值接受Number及其下层子类类型。

3、类型通配符下限通过形如 Class<? super type>来定义
例如List<? super Number>
表示类型只能接受Number及其父类类型,如 Object 类型的实例。

<?>通配

在这里插入图片描述

?extends 通配

Bounded Wildcards
Consider a simple drawing application that can draw shapes such as rectangles and circles. To represent these shapes within the program, you could define a class hierarchy such as this:

public abstract class Shape {
public abstract void draw(Canvas c);
}

public class Circle extends Shape {
private int x, y, radius;
public void draw(Canvas c) {

}
}

public class Rectangle extends Shape {
private int x, y, width, height;
public void draw(Canvas c) {

}
}
These classes can be drawn on a canvas:

public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:

public void drawAll(List shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List . That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List . What we really want is for the method to accept a list of any kind of shape:

public void drawAll(List<? extends Shape> shapes) {

}
There is a small but very important difference here: we have replaced the type List with List<? extends Shape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List if we want.

List<? extends Shape> is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. However, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.

There is, as usual, a price to be paid for the flexibility of using wildcards. That price is that it is now illegal to write into shapes in the body of the method. For instance, this is not allowed:

public void addRectangle(List<? extends Shape> shapes) {
// Compile-time error!
shapes.add(0, new Rectangle());
}
You should be able to figure out why the code above is disallowed. The type of the second parameter to shapes.add() is ? extends Shape-- an unknown subtype of Shape. Since we don’t know what type it is, we don’t know if it is a supertype of Rectangle; it might or might not be such a supertype, so it isn’t safe to pass a Rectangle there.

Bounded wildcards are just what one needs to handle the example of the DMV passing its data to the census bureau. Our example assumes that the data is represented by mapping from names (represented as strings) to people (represented by reference types such as Person or its subtypes, such as Driver). Map<K,V> is an example of a generic type that takes two type arguments, representing the keys and values of the map.

Again, note the naming convention for formal type parameters–K for keys and V for values.

public class Census {
public static void addRegistry(Map<String, ? extends Person> registry) {
}

Map<String, Driver> allDrivers = … ;
Census.addRegistry(allDrivers);

colored version(long lines):

Bounded Wildcards
Consider a simple drawing application that can draw shapes such as rectangles and circles. To represent these shapes within the program, you could define a class hierarchy such as this:
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class Circle extends Shape {
private int x, y, radius;
public void draw(Canvas c) {
...
}
}
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw(Canvas c) {
...
}
}
These classes can be drawn on a canvas:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List<Circle>. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List<Circle>. What we really want is for the method to accept a list of any kind of shape:
public void drawAll(List<? extends Shape> shapes) {
...
}
There is a small but very important difference here: we have replaced the type List<Shape> with List<? extends Shape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want.
List<? extends Shape> is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. However, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.
There is, as usual, a price to be paid for the flexibility of using wildcards. That price is that it is now illegal to write into shapes in the body of the method. For instance, this is not allowed:
public void addRectangle(List<? extends Shape> shapes) {
// Compile-time error!
shapes.add(0, new Rectangle());
}
You should be able to figure out why the code above is disallowed. The type of the second parameter to shapes.add() is ? extends Shape-- an unknown subtype of Shape. Since we don't know what type it is, we don't know if it is a supertype of Rectangle; it might or might not be such a supertype, so it isn't safe to pass a Rectangle there.
Bounded wildcards are just what one needs to handle the example of the DMV passing its data to the census bureau. Our example assumes that the data is represented by mapping from names (represented as strings) to people (represented by reference types such as Person or its subtypes, such as Driver). Map<K,V> is an example of a generic type that takes two type arguments, representing the keys and values of the map.
Again, note the naming convention for formal type parameters--K for keys and V for values.
public class Census {
public static void addRegistry(Map<String, ? extends Person> registry) {
}
...
Map<String, Driver> allDrivers = ... ;
Census.addRegistry(allDrivers);

上下界通配符的两个实例

package generic;
/**
* @Author xuchaoxin
* @Date 2021/1/29 23:20
* @Version 1.0
* the detail and improved version(if I update it ) to see:
* the github repository (search the article):https://github.com/xuchaoxin1375/LearnJava
*/
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
/*为泛型类box<T>指定具体的类型T,使得Box<T>实例化出来的对象也是类型具体的.而且调用该类中的方法传入的参数也是类型确定的*/
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
/* 调用泛型方法*/
/*generic.Box<T> public void add(T t)*/
/*由于integerBox和stringBox都是类型具体的,故而对它们调用的方法所传入的参数也是类型确定而且于被调用对象的类型相对应.*/
integerBox.add(10);
stringBox.add("test for generic.");
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
package generic;
import java.util.ArrayList;
import java.util.List;
/**
* @Author xuchaoxin
* @Date 2021/1/29 23:08
* @Version 1.0
* the detail and improved version(if I update it ) to see:
* the github repository (search the article):https://github.com/xuchaoxin1375/LearnJava
*/
public class Judge {
public static void main(String[] args) {
/*实例化出三个不同类型(没有继承关系的三个类型的)容器对象(类型具体的)
* 这些类型虽然没有继承关系,但是有共同点,是兄弟类型(可以抽象成Class<T>)
*/
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getFirstElement(name);
getFirstElement(age);
getFirstElement(number);
}
/**
* 打印传入的容器List<T>容器对象中的第一个元素.</T>
* 参数类型为List<?> 也就是所有List<T>的父类(可以接受任意具体类型T对应的具体类型List<T>类型的实参)
* @param listT
*/
public static void getFirstElement(List<?> listT) {
System.out.println("firstElement of "+listT.getClass().getName()+" : " + listT.get(0));
}
/*test for <T>*/
// public static void getData(<T> data) {
// System.out.println("data :" + data.get(0));
// }
// public static void getData(List<T> data) {
// System.out.println("data :" + data.get(0));
// }
}

类型通配符在使用方面的限制特性:

例子:

在这里插入图片描述

使用类似<? super Integer>通配符作为方法参数时表示:
方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;
方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst()
无限定通配符<?>很少使用,可以用<T>替换,同时它是所有<T>类型的超类。

代码例子

class Pair

package generic;
/**
* @Author xuchaoxin
* @Date 2021/1/30 19:31
* @Version 1.0
* the detail and improved version(if I update it ) to see:
* the github repository (search the article):https://github.com/xuchaoxin1375/LearnJava
*/
class Pair<T> {
/*T可以是Integer,Double,Decimal,Number,...等具体类型*/
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;
}
}

class TestExtends

package generic;
/**
* 泛型有界通配符的限制(举例说明)
* 主要是指,操作/访问泛型类实例化出来的对象的方法,这些方法中包含extends或super通配符.
* 对比extends和super通配符
* 作为(泛型类之外的)方法(记为Method())的参数,<? extends T>类型和<? super T>类型的区别在于:
*
* <? extends T>允许Method()调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
*
* <? super T>允许Method()调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
*/
import org.jetbrains.annotations.NotNull;
/**
* @Author xuchaoxin
* @Date 2021/1/30 19:31
* @Version 1.0
* the detail and improved version(if I update it ) to see:
* the github repository (search the article):https://github.com/xuchaoxin1375/LearnJava
*/
public class TestExtends {
public static void main(String[] args) {
/*从泛型类Pair<T>实例化出具体的类型Pair<Integer>类型的对象p(确定而且具体)*/
// Pair<Integer> p = new Pair<>(123, 456);
Pair<Number> p=new Pair<>(132,34);
/*尝试调用泛型方法 static int add(Pair<? extends Number> p)*/
// int n = add(p);
}
/**
*
* @param p 传入的参数p可以是Pair<Number>/Pair<Integer>/Pair<double>/..等类型的对象
* 到底是哪一个类型只有再调用该方法的时候才能够知道(确定下来)
* 这就意味着在赋值等写操作上面会收到限制:即编写该方法的实现的时候,左值的类型是不确定的
* 而赋值操作需要保证左值的类型是右值(表达式值)类型的本类或父类
* 如此以来,你为了赋值就必须保证满足以下条件中的至少一点:
* 传入的参数总是Pair<Number>(失去了上界通配符泛型的意义,不如直接确定类型,而且实际上还是报错)
* 右值总是左值(实参p的类型)的子类类型(一般无法实现)
* 其他的写操作也类似(增加元素/删除元素)
* @return
*/
static int add(Pair<? extends Number> p) {
/*上界通配泛型类型参数对象作为右值,(获取其引用)是方便的/可行的(因为我们知道了参数的上界父类型,那么就可以用其上界类型作为左值来接受从上界通配类型参数中读出对象的引用)*/
Number first = p.getFirst();
Number last = p.getLast();
/*尝试分别修改类型为Number(或其子类,即与实参p同类型的)p.first和p.last成员的值:
* 这里由于p的类型由传入的实参具体确定,该方法是无法独立确定p的类型,这就导致了
* 对p.first,p.last的赋值表达式的类型也不能够有方法独立确定的,而应该与p的类型一样(依赖与实参类型)
* 从反面来看(理解),如果传入的参数p是Double类型的,
* 而传给setFirst的表达式如果是Integer类型的值,
*那么就出现类型不符,导致赋值(修改)失败.
* 比如:
* p.setFirst(first.intValue() + 100);
* */
return p.getFirst().intValue() + p.getFirst().intValue();
}
/*对比:*/
static void add2(@NotNull Pair<?> p){
/*如果参数是Pair<?>,返回类型将难以确定
* generic.Pair<T> public T getFirst()
* 这里T=?,则:
* ? first=p.getFirst();*/
Object first=p.getFirst();
Object last=p.getLast();
}
}

class TestSuper

package generic;
/**
* @Author xuchaoxin
* @Date 2021/1/30 20:48
* @Version 1.0
* the detail and improved version(if I update it ) to see:
* the github repository (search the article):https://github.com/xuchaoxin1375/LearnJava
*/
public class TestSuper {
public static void main(String[] args) {
/*实例化两个不同的泛型类的具体对象对象*/
Pair<Number> p1 = new Pair<>(12.3, 4.56);
Pair<Integer> p2 = new Pair<>(123, 456);
/*尝试执行写的方法*/
setTwoMemberSame(p1, 100);
setTwoMemberSame(p2, 200);
/*执行读的方法*/
System.out.println(p1.getFirst() + ", " + p1.getLast());
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
static void setTwoMemberSame(Pair<? super Integer> p, Integer n) {
/*下界通配泛型参数对象作为左值(或者说被修改对象)是方便/可行的
(只要被写入的对象是通配类型或其子类)*/
p.setFirst(n);
p.setLast(n);
}
static void printGetValues(Pair<? super Integer> p){
/*可以获得p的成员值(或者说可以打印出来)
* 但却无法确定其成员的引用
* 比如,被作为右值(被读取的值(表达式)p.getFirst()的类型是Integer或其父类),
* 那么左值(也就是接收右值的引用变量)的类型该如何声明呢?
* 这就要求保证左值的类型右值类型的本类或父类
* 而右值的类型无法独立由独立于对象(Pair<? super Integer> 类型的对象)之外的第三方方法来确定
* 也就难以保证左值是右值的父类.
* 可见,第三方方法中,对于以下界通配符修饰的泛型实例化出来的对象为参数的方法,可以对参数进行修改(写操作),
* 但是难以获得参数对象的成员的引用(Object 类型的引用变量除外)只能够读到值(打印),但难做他用,因为不知到类型)*/
/*例外:p.getFirst()的类型可能是Integer/Number/Object*/
Object obj=p.getFirst();
}
}
posted @   xuchaoxin1375  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2023-04-12 整型变量保存浮点型数的效果
2022-04-12 js_reduce for javascript
2022-04-12 linux_shell_命令行查单词/shell英文词典工具包(sdcv/dict/trans/wd)
2022-04-12 linux_利用宝塔控制面版添加第一个站点(helloWorld)
点击右上角即可分享
微信分享提示