相关参考:
《Java核心技术精讲》
https://kb.cnblogs.com/page/104265/
泛型
为什么要使用泛型?
设想我们现在需要创建一个基类描述一个平面坐标系。而现实中的这种坐标系有很多种类,可以用整数表示(11,22),也可以用小数表示(11.22,22.11),亦或者可以用字符串表示(“东经112°”,“北纬23.5°”)。这些数据,都需要两个属性组合表示,我们可以分别用数学上常用的x,y代替,但是x,y可以接收多种类型的数据,那么使用Object类来接收是比较合适的:
public class Temp {
public static void main(String[] args){
Point point1 = new Point();
point1.setX(11);
point1.setY(22);
System.out.println("x:"+((Integer)point1.getX()));// 为了类型统一,强转
System.out.println("y:"+((Integer)point1.getY()));// 为了类型统一,强转
Point point2 = new Point();
point2.setX(11.22);
point2.setY(22.11);
System.out.println("x:"+((Double)point2.getX()));// 为了类型统一,强转
System.out.println("y:"+((Double)point2.getY()));// 为了类型统一,强转
Point point3 = new Point();
point3.setX("东经112°");
point3.setY("北纬23.5°");
System.out.println("x:"+((String)point3.getX()));// 为了类型统一,强转
System.out.println("y:"+((String)point3.getY()));// 为了类型统一,强转
}
}
class Point {
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
但是:
public class Temp {
public static void main(String[] args){
Point point1 = new Point();
point1.setX("东经112°");
point1.setY(22);
System.out.println("x:"+((Integer)point1.getX()));// 为了类型统一,强转
System.out.println("y:"+((Integer)point1.getY()));// 为了类型统一,强转
}
}
class Point {
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
此时就会发生ClassCastException异常。以上的代码是有安全隐患的,并且编译器并没有在编译的时候发现。所以需要泛型来解决这种问题。泛型相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误:
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();// 泛型不能传入int等基本数据类型
point1.setX(11);
point1.setY(22);
System.out.println("x:"+(point1.getX()));// 不需要强制转型了
System.out.println("y:"+(point1.getY()));// 不需要强制转型了
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();// 泛型不能传入int等基本数据类型
point1.setX("东经112°");// 此时编译器报错,会提示不能转换
point1.setY(22);
System.out.println("x:"+(point1.getX()));// 不需要强制转型了
System.out.println("y:"+(point1.getY()));// 不需要强制转型了
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class Temp {
public static void main(String[] args){
// 如果不设置泛型,统一按Object接收
Point point1 = new Point();
point1.setX("东经112°");// 此时编译器出现警告信息
point1.setY(22);
System.out.println("x:"+(point1.getX()));
System.out.println("y:"+(point1.getY()));
// 输出:
// x:东经112°
// y:22
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
另外泛型只在编译阶段有效:
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();// 泛型不能传入int等基本数据类型
point1.setX(11);
point1.setY(22);
Point<String> point2 = new Point();// 泛型不能传入int等基本数据类型
point2.setX("东经112°");
point2.setY("北纬23.5°");
Class<? extends Point> point1Class = point1.getClass();
Class<? extends Point> point2Class = point2.getClass();
System.out.println(point1Class == point2Class);// true
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
在编译过程中,正确检验泛型后,会将泛型的相关信息擦除
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();// 泛型不能传入int等基本数据类型
point1.setX(11);
point1.setY(22);
Point<String> point2 = new Point();// 泛型不能传入int等基本数据类型
point2.setX("东经112°");
point2.setY("北纬23.5°");
boolean b1 = point1 instanceof Point<Integer>;// 不能对确切的泛型类型使用instanceof操作,编译器会报错
System.out.println(b1);
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
通配符
先看下面的代码:
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
}
public static void method(Point<Integer> point){// 只接受Point<Integer>类型
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
如果此时把泛型为String的对象传入method(),编译器会报错(类型不匹配),那如果将method()重载呢?
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
}
public static void method(Point<Integer> point){// 编译器报错:提示该方法已定义
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
public static void method(Point<String> point){// 只接受Point<Integer>类型
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
因为泛型擦除后,两个方法是一模一样的,所以方法的重载观察的不是泛型类型,而是类的名称,或者是数据的类型
那如果方法的形参不设置泛型呢,是可以解决问题的:
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
Point<String> point2 = new Point();
point2.setX("balaba");
point2.setY("balala");
method(point2);
}
public static void method(Point point){// 编译器报错:提示该方法已定义
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
但是存在新的问题:
package com.example.wudongjiang.eventbusdemo;
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
// 输出:
// x = heihei
// y = 22
}
public static void method(Point point){
point.setX("heihei");
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
这就失去了泛型存在的意义!好比你今天买了瓶可乐喝,但是你喝完后,想整蛊下你的朋友,于是把墨汁和酱油参和进去了,明显属于造假!所以用户使用一个产品时,应该只能使用,不能修改。
要想解决上面的问题,就必须找到一种方法:此方法可以接收任意类型的泛型,但是不能修改,只能够输出,这就需要引用通配符"?"
package com.example.wudongjiang.eventbusdemo;
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
// 输出:
// x = heihei
// y = 22
}
public static void method(Point<?> point){// 编译器报错:提示该方法已定义
// point.setX("heihei");// 编译器报错,不能修改
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
对于通配符"?"表示任意类型,可以当成Object理解。但是要注意:
package com.example.wudongjiang.eventbusdemo;
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
Point<Number> point2 = point1;//报错,这样是无法接收的,
// 输出:
// x = heihei
// y = 22
}
public static void method(Point<?> point){
// point.setX("heihei");// 编译器报错,不能修改
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
因为Number要比Integer范围大,可以这样理解:Point point1 = new Point();表示我拥有了一筐的苹果,但是Point point2 = point1不代表我这个箩筐装的是各种各样的水果
设置泛型的上限
? extends 父类
:如? extends Number
,代表只能是Number或者Number的子类。
public class Temp {
public static void main(String[] args){
Point<Integer> point1 = new Point();
point1.setX(11);
point1.setY(22);
method(point1);
// 输出:
// x = 11
// y = 22
}
public static void method(Point<?> point){//
// point.setX("heihei");// 编译器报错,不能修改
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T extends Number> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
设置泛型的下限
? super 子类
:如 ? super String
,代表只能是String或者是String的父类。
public class Temp {
public static void main(String[] args){
Point<String> point1 = new Point();
point1.setX("东经112°");
point1.setY("北纬23.5°");
method(point1);
}
public static void method(Point<? super String> point){
System.out.println("x = "+point.getX());
System.out.println("y = "+point.getY());
}
}
/**
* T可以是任意标示,但是命名应该有意义
* @param <T>
*/
class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
泛型接口
方式一:在子类上继续定义泛型,同时泛型在此接口上继续使用
public class Temp {
public static void main(String[] args){
new MessageImpl<String>().showText("hello");
}
}
interface Message<T>{
void showText(T msg);
}
/**
* 在子类上继续定义泛型,同时泛型在此接口上继续使用
*
* 如果不在子类上定义泛型(class MessageImpl implements Message<T>),编译器会报错:不能解析符号T
* @param <T>
*/
class MessageImpl<T> implements Message<T>{
@Override
public void showText(T msg) {
System.out.println("showText() "+msg);
}
}
方式二:在子类设置具体类型
public class Temp {
public static void main(String[] args){
new MessageImpl().showText("hello");
}
}
interface Message<T>{
void showText(T msg);
}
/**
* 在子类设置具体类型
*/
class MessageImpl implements Message<String>{
@Override
public void showText(String msg) {
System.out.println("showText() "+msg);
}
}
泛型方法
public class Temp {
public static void main(String[] args){
String[] arrs = new String[]{"1","11","111"};
print(arrs);
}
public static<T> void print(T...arr){
for (int i = 0; i < arr.length;i++){
System.out.print(arr[i]+" , ");
}
}
}
定义泛型方法时,必须在返回值前边加一个,泛型类中的使用了泛型的成员方法并不是泛型方法。
静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 :
class Point<T>{
public static<C> void print(C...arr){// 不能使用T
......
}
}
在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加:
public static<T> void method(Point<T extends String> point){// 编译器报错
}
public static<T extends String> void method(Point<T> point){// 正确做法
}
在日后的编码中,应该尽量使用泛型方法:不应该为使用泛型方法,而将整个类泛型化
泛型类型的子类型
如果类型Father是类型Son的子类型,那ClassName 和 ClassName之间是什么关系?答案就是没有任何关系:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers;// 编译器报错
如果上面的操作是成立的话,就有可能出现下面这种情况:
List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers;// 编译器报错
numbers.add(new Double(11.3));
相当于一箱苹果是一箱水果,但是后面又往一箱水果里面参杂了一些橘子~这显然是不行的
数组和泛型类型上用法的不一致
对于泛型类型,它们和类型的子类型之间是没什么关系的。而对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:
Integer[] integers = new Integer[10];
Number[] numbers = integers;
Integer[] integers = new Integer[10];
Number[] numbers = integers;
numbers[0] = new Double(11.1);// java.lang.ArrayStoreException: java.lang.Double
这样写真的可以编译,但是在运行时抛出ArrayStoreException异常。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。
泛型出现后,数组的这个个性已经不再有使用上的必要了,实际上是应该避免使用。
如果你想从一个数据类型里获取数据(get),使用 ? extends 通配符
原因非常的简单,你可以这样想:这个? extends T
通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:
如果你想把对象写入一个数据结构里(add),使用 ? super 通配符
? super T
我们不知道究竟是什么超类,但我们知道T和任何T的子类都跟它的类型兼容。既然这个未知的类型即是T,也是T子类的超类,我们就可以写入。但是如果我们想往里面加入T的超类,编译器就会警告你。因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。