欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  Java

Java--泛型

程序员文章站 2022-05-13 17:59:31
...

类型参数

在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。

命名类型参数

推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:

K —— 键,比如映射的键。
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型

方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。

1.为什么要用泛型

1.对放进去的元素没有限制,放进两种不同的对象,可能会引起异常。

2.把对象丢进集合,集合丢失了对象的状态信息,集合只知道它盛放的是Object、因此取出集合元素后通常还需要进行强制转换

2.什么是泛型

Java的参数化类型被称为泛型,允许程序在创建集合的时候指定集合元素的类型

3.泛型的菱形语法

Java--泛型

后面只需要带一对菱形括号,不需要再带泛型

4.创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。

5.从泛型类派生子类,继承的时候必须为父类传入实际的参数

public class A extends Apple<String>{}

里面所有重写父类的方法变成相应的类型

也可以不传入实际的参数

public class A extends Apple{}

当成Obejct类型处理

6.并不存在泛型类

不管泛型的实际类型参数是什么,他们在运行时总有同样的类。不管泛型的类型形参传入哪一种类型实参,(在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。)对于Java来说,他们依然被当成同一个类处理,在内存中也占一块内存空间9,因此在静态方法,静态初始化块或者静态变量的声明和初始化中不允许使用类型形参

解释:

静态变量是被泛型类所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。假设允许类型参数作为静态变量的类型。那么考虑下面一种情况:

MyClass<String> class1 = new MyClass<String>();

MyClass<Integer> class2 = new MyClass<Integer>();

class1.myStaticVar = "hello";

class2.myStaticVar = 5;

由于泛型系统的类型擦除(type erasure)。myStaticVar被还原成Object类型,然后当调用class1.myStaticVar= "hello"; 编译器进行强制类型转换,即myStaticVar = (String)"hello";接着调用class2.myStaticVar语句时,编译器继续进行强制类型转换,myStaticVar = (Integer)Integer.valueOf(5); 此时myStaticVar是String类型的,当然该语句会在运行时抛出ClassCastException异常,这样一来存在类型安全问题。因此泛型系统不允许类的静态变量用类型参数作为变量类型。

系统中不会真正生成泛型类,因此instanceod运算符后不能使用泛型类,因为根本不存在!

7.类型通配符

注意的是,如果Foo是Bar的一个子类型,而G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型。

假设List<Object>在逻辑上可以视为List<String>的父类,那么a.test(list)将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Object?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上List<Object>不能视为List<String>的父类

Java--泛型

为了表示各种泛型集合的父类,可以使用类型通配符,类型通配符是一个问号,他可以匹配任何类型:

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且List<?>在逻辑上是List<Integer>、List<Number>...等所有List<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

Java--泛型

不管list的真实类型是什么,包含的都是Object

注意:带通配符的仅仅表示它是各种泛型集合的父类,并不能把元素加入其中

Java--泛型

并不知道c集合中元素的类型,不能向其中添加对象

7.1 设置通配符上限

Java--泛型

因为List<Circle> 并不是List<Shape> 的子类型,所以编译出错

这回收可以用被限制的泛型通配符

Java--泛型

7.2 设置类型形参的上限

Java--泛型

8.泛型方法

定义类、接口时没有使用类型形参,定义方法时想自己定义

独立的泛型静态方法,在不考虑多线程的情况下,同一时间点,只会被初始化并调用一次,不会出现重叠初始化并错误调用,不会出现类似数据库中读脏数据的情况,所以不会出现强制类型转换的代码错误。

Java--泛型

方法声明中定义的形参只能在该方法里使用。

与类、接口不同,方法中的泛型无需显示传入实际类型参数

一定不能让编译器迷惑你传入的到底是什么类型

比如test<Collection<T> a, Collection<T> b>

你传进去一个String类型的,一个Object类型的,编译器不知道你这T到底是啥类型的

可以改成<Collection<? extends T> a, Collection<T> b>

泛型方法

(在 类型参数 一节中)您已经看到,通过在类的定义中添加一个形式类型参数列表,可以将类泛型化。方法也可以被泛型化,不管它们定义在其中的类是不是泛型化的。

泛型类在多个方法签名间实施类型约束。在 List<V> 中,类型参数 V 出现在 get()、add()、contains() 等方法的签名中。当创建一个 Map<K, V> 类型的变量时,您就在方法之间宣称一个类型约束。您传递给 add() 的值将与 get() 返回的值的类型相同。

类似地,之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。例如,下面代码中的 ifThenElse() 方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:

public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}

注意,您可以调用 ifThenElse(),而不用显式地告诉编译器,您想要 T 的什么值。编译器不必显式地被告知 T 将具有什么值;它只知道这些值都必须相同。编译器允许您调用下面的代码,因为编译器可以使用类型推理来推断出,替代 T 的 String 满足所有的类型约束:

String s = ifThenElse(b, "a", "b");

类似地,您可以调用:

Integer i = ifThenElse(b, new Integer(1), new Integer(2));

但是,编译器不允许下面的代码,因为没有类型会满足所需的类型约束:

String s = ifThenElse(b, "pi", new Float(3.14));

为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:

当泛型方法是静态的时,这种情况下不能使用类类型参数。

当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。


有限制类型

在前一屏 泛型方法 的例子中,类型参数 V 是无约束的或无限制的 类型。有时在还没有完全指定类型参数时,需要对类型参数指定附加的约束。

考虑例子 Matrix 类,它使用类型参数 V,该参数由 Number 类来限制:

public class Matrix<V extends Number> { ... }

编译器允许您创建 Matrix<Integer> 或 Matrix<Float> 类型的变量,但是如果您试图定义 Matrix<String> 类型的变量,则会出现错误。类型参数 V 被判断为由 Number 限制 。在没有类型限制时,假设类型参数由 Object 限制。这就是为什么前一屏 泛型方法 中的例子,允许 List.get() 在 List<?> 上调用时返回 Object,即使编译器不知道类型参数 V 的类型。

9.泛型方法和类型通配符的区别

如果某个方法中的一个形参(a)的类型或返回值的类型依赖于另一个形参(b)的类型,则形参(b)的类型声明不应该使用通配符,只能考虑使用在方法签名中声明类型形参,也就是泛型方法。

我理解的是类型通配符是不需要添加、修改集合里面的元素,并且是依附于别人而不是别人依附于他,使用

类型通配符既可以在方法签名定义形参的类型,也可以用于定义变量的类型,泛型方法中的类型形参必须在对应方法中显示声明。

10.擦拭和转换

当把一个具有泛型信息的对象赋给另个一个没有泛型信息的变量时,所有在尖括号之间的类型信息都会被扔掉。

Java--泛型

将li赋给List的时候,编译器会擦拭掉前者的泛型信息,即丢失掉list集合里元素的类型信息。

Java又允许直接把list对象赋给一个List<Type>的变量,所以程序可以编译通过,只是发出“未经检查的转换”(将逻辑上的父类直接赋给子类),但对list变量实际上引用的是list<Integer>集合,所以当试图把该集合里的元素当成String类型的对象取出时,将会引发运行时异常

11.泛型和数组

java泛型有一个很重要的设计原则---如果一段代码在编译时没有提出“未经转换的异常”警告,程序不会引起ClassCastException异常,基于这个原因,所有数组元素的类型不能包含类型变量或者类型形参,除非是无上限的类型通配符,但可以声明元素类型包含类型变量或类型形参的数组

Java--泛型

假设能通过,将不会引起任何警告,但会引发异常

Java--泛型

改成如下格式:

Java--泛型

第一行会有“未经检查的转换”警告,最后一行也会引发异常

创建无上限的通配符泛型数组

Java--泛型

编译不会发出任何警告,但是运行时候会引发异常,因为程序需要将lsa的第一个数组元素的第一个集合元素强制转换成String类型,所以程序应该通过instanceof运算符来保证它的数据类型

Java--泛型

于此类似,创建元素类型是类型变量的数组对象也将导致编译错误

<T> T[] makeArray(Collection<T> coll)

{ return new T[cool.size()]

}

类型变量在运行时不存在,编译器无法确定实际类型是什么

以上就是Java--泛型的详细内容,更多请关注其它相关文章!

相关标签: 泛型