泛型(Generics),是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型,不用到虚拟机运行期(检测),避免报ClassCastException(类型转换异常)泛型的本质是为参数化类型,即把类型当参数一样传递,该参数类型可用在类,接口和方法中,即泛型类,泛型方法,泛型接口;
特性: 泛型只在 编译阶段 有效,示例如下
List
List
boolean isSame = classStringList.getClass() == classIntegerList.getClass();
Log.d(TAG,isSame ?"类型相同为"+classIntegerList.getClass():"类型不同");
//打印结果 类型相同为class java.util.ArrayList
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施,也就是说Java中的泛型只在编译阶段有效;
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并在对象进入和离开方法的边界处添加类型检查
和类型转换的方法,即泛型信息不会进入到运行时阶段。这也是Java的泛型被称为“伪泛型”的原因。
小结: 泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
1)增强代码的健壮性,泛型将类型检查由运行时提前到了编译时,不用进行强转,增强错误检测,避免因类型问题引发的运行时异常;
2)适用于多种类型执行相同的代码,从而增加代码复用性;
泛型有三种使用方式:分别是泛型类、泛型接口、泛型方法;
泛型类型用于类的定义中,被称为泛型类。
通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
注意事项:
1.泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型;
2.传入的实参类型需要与泛型的类型参数类型相同,Generic
3.不能对确切的泛型类型使用 instanceof 操作,编译时会报非法操作;
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
//泛型类
public class Generic<T>{...}
//继承类
public class Generic2<T> extends Generic<T>{}
泛型接口与泛型类的定义及使用基本相同,泛型接口常被用于在各种类的生产器中
public interface Generator<T>{
T next();
}
/**
* 当时实现泛型接口的类,未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator implements Generator{}
* 如果不声明泛型,如:class FruitGenerator implements Generator编译器会报错:"Unknown class"
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
//泛型接口
interface Generic<T> {fun next():T}
//接口实现类1
class ImplGeneric1<T> : Generic<T>{
override fun next(): T {
TODO("Not yet implemented")
}
}
//接口实现类1
class ImplGeneric2 : Generic<String>{
override fun next(): String { //返回类型跟随接口泛型类型
TODO("Not yet implemented")
}
}
/**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public K showKeyName(Generic container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test = container.getKey();
return test;
}
//泛型方法
public <K,V> V showKeyName(Generic<K> container){
...
}
public <T> test(Generic<T> container){
...
}
//泛型方法与可变参数
public void test() {
printMsg("111",222,"abc",555);
}
public <T> void printMsg(T...args) {
for (T t : args) Log.d(TAG, "t is " + t);
}
在类中的静态方法使用泛型:
静态方法无法访问类上定义的泛型,因为静态方法是类属性,而泛型初始化在创建对象时;故静态方法要将泛型定义在方法上;
即:如果静态方法要使用泛型的话,必须将静态方法页定义成泛型方法:
public class StaticGenerator<T>{
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}
E:元素(在Java集合框架中有广泛的应用)
K:键,
V:值,
N:数字,
T:类型,
S,U,V等: 第二,第三,第四个类型
//砖石运算符
JDK7以下版本
Box integerBox=new Box ();
JDK7及以上版本
Box integerBox2=new Box<>();//少写类型
Foo
Foo
泛型方法的辨析
public class Generic {
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
//虽然在方法中使用了泛型,但这并不是一个泛型方法
//这只是类中一个普通成员方法,只不过它的返回值是在声明泛型类已经声明过的泛型;
//所以在这个方法中才可以继续使用T 这个泛型
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
public class Generic {
static class Fruit{}
static class Apple extends Fruit{}
public void show_1(T t) {
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型类T,这种泛型T可以为任意类型:
//注意这个T是一种全新的类型,可以类型与T相同,也可以不同
//由于泛型方法在声明时会声明泛型,因此即使在泛型类中未声明泛型,
//编译器也能够正确的第识别泛型方法中的泛型
public void show_2(T t) {
System.out.println(t.toString());
}
public static void main(String[] args) {
Person person = new Person();
Apple apple = new Apple();
Generic generic = new Generic<>();
generic.show_1(apple);
//generic.show_1(person);//show_1不是泛型方法只是方法参数为泛型,所以参数必须跟类中声明的泛型一致;
generic.show_2(apple); //show_2为泛型方法,在声明时会声明泛型,因此参数中的泛型类型随方法走
generic.show_2(person);
}
}
缺少实际类型变量的泛型就是一个原始类型
例如:class Box
1>类约束
/**
* 类说明:约束
*/
public class Restrict {
private T data;
//不能实例化类型变量
public Restrict(T data) {
this.data = data;
// this.data= new T();//不能new T
}
public Restrict() {
}
//静态域或方法不能引用类型变量
// private static T instance;
//静态方法,本身是泛型方法就行
private static T getT(T t){
return t;
}
public static void main(String[] args) {
// Restrict data1;//泛型不能为基本数据类型
Restrict data2=new Restrict () ;//泛型只能为引用数据类型
Restrict data3 = new Restrict ();
System.out.println(data2.getClass().getSimpleName());// Restrict
System.out.println(data3.getClass().getSimpleName());// Restrict
System.out.println(data2.getClass()==data3.getClass()); //true 泛型类的类型永远为原生类
}
}
2>异常约束
/**泛型类不能 extends Exception/Throwable */
//private class Problem extends Exception
// 不能捕获泛型类对象,只能抛出去
public void doWork(T x) throws T {
// try {} catch (T x) {//do sth}
try {//do sth
} catch (Throwable e) { throw x;}
}
功能:对泛型变量的范围作出限制
格式:
单一限制:
多种限制:
extends表达的意义:这里指的是广义上的"扩展",兼有"类继承"和"接口实现"之意;
具有多个限定的类型变量是范围中列出的所有类型的子类型,若上限类型是一个类,必须第一位标出,否则编译错误;
关键:利用受限类型参数;
知道泛型的受限类型,可以调用受限类型的方法 B和C都是接口,单继承 可以实现多个接口,但只能继承一个类;
/**
* 类型变量的限定--方法上
* @author NorthStar
*/
public class ArrayAlg {
//确保泛型T 有 compareTo方法, 需要用
//T extends 类(唯一在前) &(连接符) 接口(多个)
public static T min(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
static class Test extends ArrayList implements Comparable,Serializable{
@Override
public int compareTo(@NonNull Object o) {
return 0;
}
}
public static void main(String[] args) {
ArrayAlg.min(new Test(), new Test());
}
}
给定两种具体的类型A和B(例如Number和Integer)
1) 无论A和B是否相关,MyClass与MyClass没有关系
2) MyClass和MyClass的公共父对象是Object; //和擦除机制相关;
我们知道Integer是Number的一个子类,同时在特性章节中我们也验证过Generic
实际上是相同的一种基本类型。那么问题来了,在使用Generic
的实例传入呢?在逻辑上类似于Generic
Generic
Generic
//showKeyValue(gInteger);不是泛型通配符不能用使用多态
showKeyValue1(gInteger);
public void showKeyValue(Generic
public void showKeyValue1(Generic> obj){Log.d("泛型测试","key value is " + obj.getKey());}
public void showKeyValue(Generic> obj){}
类型通配符一般是使用?代替具体的类型实参,此处'?'是类型实参,而不是类型形参,再直白点的意思是
此处'?' 和 String,Integer都一样是一种实际的类型,可以把?看成所有类型的父类,是一种真实类型;
当操作类型时,不需要使用类型的具体功能,只使用Object类中的功能,那么可以用 ? 通配符来表未知类;
通配符使用的原因: 泛型所传出的具体类如果不用通配符, 传入的泛型类不能使用继承关系以及多态关系,
通配符的适用范围:参数类型,字段类型,局部变量类型,返回值类型(但返回一个具体类型值更好)
受上下限控制的通配符和不受限的通配符;
open class Food{}
open class Fruit:Food() {}
class Apple : Fruit() {}
class Wildcard {
fun printFruit(p: Generic<Fruit>) {
print(p.toString())
}
fun printFruit2(p: Generic<out Fruit?>) {
println(p.toString())
}
fun printFruit3(p: Generic<in Fruit?>) {
println(p.toString())
}
fun use(){
val apple:Apple= Apple()
// printFruit(apple)// 报错 不能用泛型的派生类, 所以出现了通配符
}
}
通配符(Wildcards)和 边界(Bounds)的概念。传入泛型类型实参进行上下界的限制,如:类型实参只准传入指定类型的父类或子类;
extends T>和 super T>是Java泛型通配符,
extends T> /
称为生产者,上限只读(保证安全访问数据),可获取数据 get(),作为返回值,返回值类型为指定类型T或其子类;
频繁往外读取内容的,适合用上界extends /out。
super T> /
称为消费者,下限只能写不能读,可获取数据 set(item:T)可以作为参数传递,传入的实参是指定类型T或其父类;
经常往里插入的,适合用下界super / in
open class Food{}
open class Fruit:Food() {}
open class Apple : Fruit() {}
data class Basket<out T>(val item: T)
class Wildcard {
fun printFruit(p: Generic<Fruit>) {
print(p.toString())
}
//上界通配符,传入的参数类型是Apple或其子类 extends Fruit>
private fun printFruit2(p:Generic<out Fruit>): Generic<out Fruit>{
return p
}
//下界通配符,传入的参数类型是Apple或其父类 super Fruit>
private fun printFruit3(p: Generic<in Fruit>) {
println(p.toString())
}
fun use(){
val food:Generic<Food> = Generic()
val fruit:Generic<Fruit> = Generic()
val apple:Generic<Apple> = Generic()
printFruit2(apple)
printFruit3(food)
//无法通过 因为food不是Fruit或其子类
// printFruit2(food) //报错 extends 或 in 上界通配符, 指传入的参数不能高于形参范围(本身或子类)
//无法通过 因为apple不是Fruit或其父类
// printFruit3(apple)
}
}
适用通配符有上限就是只能有只读属性, 下限只有写属性 PESC原则
//PE原则 Product extends 生产者原则 extends T> 只读 可获取数据
public static double sum(List extends Integer> list) {
list.add(1);//不能写 报错
Integer i= list.get(1);//上限只能读,不能写
}
//SC Consumer super 消费者原则 super T> 只写, 可以添加数据
public static double sum(List super Integer> list) {
list.add(0);//可写
Integer i=list.get(0);//下限只能写,不能读 报错
}
//不受限,会退化不能使用List中任何依赖类型参数[T]的方法
public static double sum(List> list) {
list.add(1);//报错
list.get(1);
}
public void get() {
//如果 class Apple extends Fruit;
Plate fruitPlate= new Plate ();//可以
Plate extends Fruit> p = new Plate ();//使用通配符可以
Plate p= new Plate ();//不行
使用 ? extends 要符合PE原则 导致 只能取,不能放
使用 ? super 要符合CS原则 导致 只能放,不能取
//但通过反射还能放:
Method set=fruitPlate.getClass().getMethod("set",Object.class);
set.invoke(fruitPlate,new Banana);
}
1)定于:
保证了泛型在不运行时出现,java中的泛型为 伪泛型,编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,
如果没有对类型参数做出限制,那么就替换为Object类型,因此编译出的字节码仅仅包含原始类型而不需要产生新的类型
到字节码,在必要时插入类型转换以保持类型安全;生成桥方法以扩展泛型时保持多态性;
2)擦除流程
*1.检测泛型类型,获取目标类型 ╭如果泛型类型的类型变量没有限定(
*2.擦除类型变量,并替换为限定类型< 如果有限定(T extends XClass),则使用XClass1作为原始类;
╰如果有多个限定(T extends X1 & XInterface),则使用第一个边界作为原始类;
*3.在必要时插入类型转换以保持类型安全;
*4.生成桥方法以在扩展时保持多态性;
Bridge Method 桥方法
当编译一个扩展参数化类的类,或一个实现了参数化接口的接口时,编辑器可能因此要创建一个合成方法,名为桥方法,
它是类型擦除过程中的一部分; 擦除还是会写到常量池中;
3)没有泛型数组: 因为数组是协变,擦除后就没法满足数组协变的原则; 协变: A extends B 则 A[] extends B[];
1)无法利用原始类型,只能使用包装类型
Pair p=new Pair<>(Integer.valueOf(8),new Character('a'));
Char-->Character, int->Integer
2)无法创建类型参数的实例
public static void append(Class a) throws Exception{
//E elem0 = new E();//不能new 创建
E elem = a.newInstance();//可以通过反射创建
}
3)无法创建参数化类型的静态变量,因为static是类方法
public class Mobile {
public static T t;//不能声明静态变量
public static void get(T,t){...}//不能用于静态方法
}
4)无法使用instanceOf关键字
public static void test(List> list) {
//用通配符?可以 用T不行
boolean isaArray=list instanceof ArrayList>;
}
5)无法创建参数化类型的数组
List [] array=new List [2];//报错,因为擦除机制;
6)一个类中是无法存在类型擦除后具有相同签名的两个方法重载
public void test(List list) {}
public void test(L)
api返回的json数据
{
"code":200,
"msg":"成功",
"data":{
"name":"Jay",
"email":"10086"
}
}
baseResponse.java
//接口数据接收基类
public class BaseResponse {
private int code;
private String msg;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
/**
* Description: 用户信息接口实体类
*/
public class UserResponse<T> extends BaseResponse {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
1)Array中不支持泛型
2)ArrayList
3)Set与Set>到底区别在哪? Set>是受类型检查的
4>List> 和 List
List
//举例
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型测试","item = " + item);
}
//毫无疑问,程序的运行结果会以崩溃结束:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以放任意类型,例子中添加了一个String类型,添加了一个Integer类型,但都以String方式使用因此程序崩溃了,
为了解决此类问题(使其在编译阶段就可以解决),泛型应运而生;
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List
//arrayList.add(100); 在编译阶段就会报错,
这样泛型通过类型参数使得我们的程序具有更好的可读性和安全性;
1>Java中的泛型是什么 ? 使用泛型的好处是什么?
泛型是一种参数化类型机制,他可以使java中的类,方法以及接口适用于各种类型,从而编写更加通用的代码,例如集合框架
泛型一种编译时类型确认机制,它提供了编译期的类型安全,确保在泛型类型上只能使用正确类型的对形象,避免了在运行时
出现ClassCastException(类型转换异常);
2>java的泛型是如何工作的,什么是类型擦除
泛型的正常工作是依赖编译源码的时候,先进行类型检查,然后进行类型擦除并在类型参数出现的地方插入
强制转换的相关指令实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
这是为了避免类型膨胀;
3>什么是泛型中的限定通配符和非限定通配符?
限定通配符对类型进行了限制,有两种限定通配符:一种是 extends T>它通过确保类型必须是T的子类来设定类型的上界
另一种是 super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,
否则会导致编译错误。另一方面>表示了非限定通配符,因为>可以用任意类型来替代。
4>Array中可以用泛型吗?
这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,
建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能
5>List
带参数类型是类型安全的,而且其类型安全是由编译器保证的,但原始类型List却不是类型安全的。
你不能把String之外的任何其它类型的Object存入String类型的List
6>Java中List>和List
List> 是一个未知类型的List,而List
你可以把List
List> isOfAnyType = new ArrayList<>();
List
List
isOfAnyType = stringLs;//list>表示是未知类型的List,可以赋予String类型;
//isOfObjectType = stringLs; List