
本文共 7863 字,大约阅读时间需要 26 分钟。
一,前期基础知识储备
1)Java泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
2)泛型使用
泛型使用方式,分别为:泛型类、泛型接口、泛型方法。
①泛型类,泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
格式为:public class 类名<泛型类型1,…>
public class Box{ private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box integerBox = new Box (); Box stringBox = new Box (); integerBox.add(new Integer(10)); stringBox.add(new String("Java泛型")); System.out.printf("整型值为 :%d\n\n", integerBox.get()); System.out.printf("字符串为 :%s\n", stringBox.get()); }}
②泛型方法,所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
格式为:public <泛型类型> 返回类型 方法名(泛型类型 .)
public class ObjectPrint { publicvoid show(T t) { System.out.println(t); }}public class ObjectToolDemo { public static void main(String[] args) { ObjectPrint op = new ObjectPrint(); op.show("java"); op.show(1100); op.show(true); }}
③泛型通配符,类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List<String>,List<Integer> 等所有List<具体类型实参>的父类。
public class GenericTest { public static void main(String[] args) { Listnames = new ArrayList (); List ages = new ArrayList (); List numbers = new ArrayList (); names.add("icon"); ages.add(18); numbers.add(314); getData(names); getData(ages); getData(numbers); } public static void getData(List data) { System.out.println("data :" + data.get(0)); }}
因为getData()方法的参数是List类型的,所以names,ages,numbers都可以作为这个方法的实参,这就是通配符的作用。
④泛型上下边界,可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界限。
public class GenericTest { public static void main(String[] args) { Listname = new ArrayList (); List age = new ArrayList (); List number = new ArrayList (); name.add("icon"); age.add(18); number.add(314); //getUperNumber(name);//1 getUperNumber(age);//2 getUperNumber(number);//3 } public static void getData(List data) { System.out.println("data :" + data.get(0)); } public static void getUperNumber(List data) { System.out.println("data :" + data.get(0)); }}
上图中的extends不是类继承里的那个extends,两个根本没有任何关联。图中的extends后的BoundingType可以是类,也可以是接口,意思是说,T是在BoundingType基础上创建的,具有BoundingType的功能,可能是JAVA开发人员不想再引入一个关键字,所以用已有的extends来代替而已。
类型通配符下界限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如 Object 类型的实例。
? extends Number
? super Number 这两者有什么区别呢? 1."? extends T" 表示类型的上界,表示参数化类型可能是T或者是T的子类,只能取,不能写; 2."? super T" 表示类型的下界,Java core中叫超类型限定,表示参数化类型是此类型的超类型(父类型),甚至是Object。只能写,不能取。
3)泛型的优势
①运行时不确定类型;
②类型安全; ③消除强制转换; ④提高虚拟机性能。在没有泛型的情况的下,通过对类型Object
的引用来实现参数的“任意化”,缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。一个错误的示范如下:
public static void main(String[] args) { List list = new ArrayList(); list.add(1); list.add("String"); int isInt = (int) list.get(1); //ClassCastException }
本例中对于强制类型转换错误的情况,编译器在编译时并不提示错误,在运行的时候才出现ClassCastException
异常,这样便存在着安全隐患。
利用泛型类可以选择具体的类型对类进行复用相对比较容易理解,具体的说明如下:
public class Box{ private T t; public void set(T t) { this.t = t; } public T get() { return t; }}
这样我们的Box类便可以得到复用,我们可以将T替换成任何我们想要的类型:
BoxintegerBox = new Box ();Box doubleBox = new Box ();Box stringBox = new Box ();
二,上代码,具体实现
在android开发中经常需要从接口服务器获取数据,然后展示在手机界面上。其中手机端和接口服务器之间通常使用json数据来进行通信。
常用的解析场景如下:
public class TypetokenActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String json = "{\"Success\":true,\"ErrorMsg\":\"\",\"ErrorNo\":\"\"}"; Gson gson = new Gson(); FamilyMember member = gson.fromJson(json, FamilyMember.class); Log.e("TypetokenActivity", "bean name: " + member .name); Log.e("TypetokenActivity", "bean jsonStr: " + gson.toJson(member)); } class FamilyMember { public boolean Success; public String ErrorMsg; public String ErrorNo; }}
但是上述代码在遇到泛型时就会遇到问题。示例如下:
public class TypetokenActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String json = "{\"Success\":true,\"ErrorMsg\":\"\",\"ErrorNo\":\"\",\"Result\":" + "{\"FmMobileNumber\":\"15555215554\",\"FmId\":3,\"FlId\":5,\"FmUser\":\"15555215554\"}}"; Gson gson = new Gson(); ResponseEntityentity = gson.fromJson(json, new ResponseEntity ().getClass()); Log.d(TAG, "onCreate entity: " + entity); /*com.example.javatast.typetoken.ResponseEntity@22dae67*/ FamilyMember result = entity.getResult(); /*com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.javatast.typetoken.FamilyMember*/ } class FamilyMember { public long FmId; ... } class ResponseEntity { public T Result; public boolean Success; public String ErrorMsg; public String ErrorNo; ......}
通过Log信息,entity得到的是一个ResponseEntity对象,但是在接着调用getResult()方法时,出现了错误:
“com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.javatast.typetoken.FamilyMember”。
导致上述问题的原因是Java在运行时,泛型参数的类型会在运行时被擦除,导致在运行期间所有的泛型类型都是Object类型。
解释一下类型擦除
不同的语言在实现泛型时采用的方式不同,C++的模板会在编译时根据参数类型的不同生成不同的代码,而Java的泛型是一种伪泛型,编译为字节码时参数类型会在代码中被擦除,单独记录在Class文件的attributes
域,而在使用泛型处做类型检查与类型转换。
List<String>
容器的add()
方法,绕过泛型检查,成功插入Integer
类型的变量。 //代码ArrayListstringList = Lists.newArrayList();ArrayList intList = Lists.newArrayList();System.out.println("intList type is " + intList.getClass());System.out.println("stringList type is " + stringList.getClass());System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));//运行结果intList type is class java.util.ArrayListstringList type is class java.util.ArrayListtrue
上述代码中两个泛型类型的ArrayList,一个参数是String,另一个是Integer,当输出getClass方法的类型时均为java.util.ArrayList。而泛型类型擦除导致第三行输出为true,即运行时认为stringList和intList类型一致。
(编译是将你写的代码弄成Java虚拟机可以执行的字节码。 运行是Java虚拟机运行你写的代码(编译后的字节码文件),然后显示运行结果。)
假设参数类型的占位符为T,擦除规则如下:<T>擦除后变为Obecjt
<? extends A>擦除后变为A
*<? super A>擦除后变为Object
上述擦除规则叫做保留上界。泛型擦除之后保留原始类型。原始类型raw type
就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除crased
,并使用其限定类型(无限定的变量用Object
)替换。
正确的解决方法——TypeToken
对于上面的类ResponseEntity<T>,由于在运行期间无法得知T的具体类型,对这个类的对象进行序列化和反序列化都不能正常进行。Gson通过借助TypeToken类来解决这个问题。
String json = "{\"Success\":true,\"ErrorMsg\":\"\",\"ErrorNo\":\"\",\"Result\":" + "{\"FmMobileNumber\":\"15555215554\",\"FmId\":3,\"FlId\":5,\"FmUser\":\"15555215554\"}}"; try { Gson gson_1 = new Gson(); java.lang.reflect.Type type = new TypeToken>() { }.getType(); ResponseEntity entity_1 = gson_1.fromJson(json, type); entity_1.getResult(); Log.d(TAG, "onCreate: " + entity_1 + ",,,," + entity_1.getResult()); } catch (Exception e) { Log.e(TAG, "Error parsing data " + e.toString()); }
TypeToken的使用非常简单,如上面的代码,只要将需要获取类型的泛型类作为TypeToken的泛型参数构造一个匿名的子类,就可以通过getType()方法获取到我们使用的泛型类的泛型参数类型。
参考文章:
《》
《》
《》
《》
发表评论
最新留言
关于作者
