安卓中的Java语言高级特性:1.泛型
为什么我们需要泛型
泛型,就是用一个大写字母(通常是T,E,K,V)来并且用<>括起来表示一个参数的类型,在使用的时候可以指定类型,具有泛化的效果。泛型可以定义在接口,类,方法中。
对于为什么使用泛型,可以总结为一下两点:
- 泛型的泛化效果使我们减少逻辑相同但是所需参数类型不同的业务的代码量,在架构开发阶段十分常见
- 在使用的时候如果指定了类型,IDE会在编译阶段就发现一些错误
就拿我们常用的list举例而言
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add(111);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i);
System.out.println(name);
}
}
这段代码我们发现编译器不会报错,但是运行下来还是会抛出异常,第三个元素是Integer类型不能强转为String
而如果在使用list的时候指定了类型,插入111的语句在编译阶段就会被IDE识别并且报错,从而保证的下面语句的正确执行
List<String> list = new ArrayList();
泛型类的定义
一个简单的泛型类
public class MyGeneric<T> {
private T t;
public MyGeneric(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public <T> T genericMethod (T value) {
return value;
}
}
泛型的使用
MyGeneric<String> myGeneric = new MyGeneric<>("hello");
MyGeneric<Boolean> myGeneric2 = new MyGeneric<>(true);
System.out.println(myGeneric.getT());
System.out.println(myGeneric.getT());
泛型接口
public interface GenericImpl <T>{
T getT(T value);
}
泛型接口的使用
- 在定义类的时候就指定类型
public class GenericImplClass implements GenericImpl<String>{
@Override
public String getT(String value) {
return value;
}
}
- 定义类的时候也指定一个T作为参数传给接口
public class GenericImplClass2<T> implements GenericImpl<T> {
@Override
public T getT(T value) {
return value;
}
}
泛型方法辨析
泛型方法没有限定范围,在哪里都可以定义,以下是一个简单的泛型方法
public <T> T genericMethod (T value) {
return value;
}
在类中我们要注意区分泛型方法和普通方法
public class MyGeneric2<T> {
public T normalMethod(T value) {
return value;
}
public <T> T genericMethod (T value) {
return value;
}
public <E> E genericMethod2 (E value) {
return value;
}
}
类中的普通方法没有<>括起来的泛指类型,而泛型方法要在返回类型前面声明这个泛型
要注意一下normalMethod和genericMethod方法,虽然都用了泛型T,但是在类中的泛型方法T与类声明的T是没有关系的
我们可以用以下代码来证明
MyGeneric2<String> myGeneric2 = new MyGeneric2<>();
System.out.println(myGeneric2.normalMethod("hello"));
System.out.println(myGeneric2.<Boolean>genericMethod(true));
System.out.println(myGeneric2.<Double>genericMethod2(3.14D));
以下是运行结果
在上述代码中我们创建了一个对象,并且在创建的时候我们指定类的T是String类型,调用普通方法的时候参数T就是String类型,但是我们在调用两个泛型方法的我们分别指定的两个泛型方法的T和E为Boolean和Double类型,由此可以证明泛型方法的T和E与定义类的T无关
限定类型变量
用一个具体例子来说
static <T extends Comparable>T fun4(T a,T b) {
if (a.compareTo(b) > 0) return a;else return b;
}
我在代码中用到的Comparable接口的方法,如何确保我们方法里面的代码执行正确呢,这时候我们就可以限定泛型T他必须要派生于Comparable接口,以确保传进来的参数有compareTo方法
此时我们如果传入一个没有派生于Comparable的类,则编译器会报错
System.out.println(fun4(new MyGeneric<>(), new MyGeneric<>()));//报错
我们查看源码可以知道String是派生于Comparable接口的
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc
因此我们可以执行以下代码而不会报错
System.out.println(fun4("hello", "world"));
不光可以派生接口也可以派生类,但是泛型T只能派生一个类,可以派生于多个接口,并且类只能放在第一位,下面是一个示例
static <T extends ArrayList&Comparable& Serializable>T fun5(T a,T b) {
if (a.compareTo(b) > 0) return a;else return b;
}
泛型中的约束和局限性
- 在一个泛型类中我们不能new 一个泛型
public class MyGeneric3<T> {
void fun1(){
T t = new T();//报错
}
}
因为我们不能保证这个T是一定可以创建对象的,有些类例如抽象类等是不能new出来的
- 在类中我们不能用泛型声明静态变量,静态方法
private static T t;//报错
private static void fun(T t){//报错
System.out.println(t);
}
因为对象在创建的时候才能知道泛型T的具体类型,而静态域的代码会在对象创建(执行构造方法)前就会被执行,此时我们还不知道T的具体类型
注意:在静态域中还是能定义泛型方法,上述方法是类中的普通方法
- 泛型不能是如int,double的基础类型,可以使用其包装类Integer,Double
- 我们在进行泛型对象类型比对的时候是不能检测出泛型具体类型的不同
static void fun6() {
MyGeneric<String> myGeneric = new MyGeneric<>();
MyGeneric<Double> myGeneric1 = new MyGeneric<>();
System.out.println(myGeneric1.getClass() == myGeneric.getClass());
}
上述代码运行结果为true,但是实际上我们两个对象所指定的泛型类型是不一样的,所以不能够判断泛型类型不同
- 我们可以声明一个泛型数组,但是不能创建对象
static void fun7() {
MyGeneric<String>[] myGenerics;
MyGeneric<String>[] myGenerics1 = new MyGeneric<String>[10];//报错
}
- 泛型类是不能派生于Exception类的
class GenericException<T> extends Exception { }//报错
- 泛型方法如果泛型派生于Throwable,这个泛型是不能被捕获的,但是可以在捕获异常后抛出
<T extends Throwable> void fun1(T t) {
try {
} catch (T t) {//报错
throw t;
}
}
<T extends Throwable> void fun2(T t) throws T {
try {
} catch (Throwable e) {
throw t;
}
}
泛型类型的继承规则
也是先举一个例子
下面的Woker是派生于Employee的
static void fun8() {
MyGeneric<Employee> GenericEmployee = new MyGeneric<>();
MyGeneric<Worker> GenericWorker = new MyGeneric<>();
Employee employee = new Worker();
MyGeneric<Employee> GenericEmployee2 = new MyGeneric<Worker>();//报错
}
在java中我们可以new一个子类赋值给父类变量,但是泛型T不可以,说明MyGeneric和MyGeneric是没有关系的,完全是两个类型,类似的方法参数也不能混淆
static void print(MyGeneric<Employee> e){
System.out.println(e);
}
static void fun9() {
MyGeneric<Employee> a = new MyGeneric<>();
MyGeneric<Worker> b = new MyGeneric<>();
print(a);
print(b);//报错
}
但是泛型类派生于另外一个类就可以用类似的用法
下面是一个例子
public class MyGeneric5<T>extends MyGeneric<T> { }
static void fun10() {
MyGeneric<Employee> a = new MyGeneric5<>();
}
通配符类型
那么类能够使用这种多态的方法创建对象,泛型凭什么就不能,为了解决这个问题,于是就引入了通配符的概念
我们可以将通配符分为两类
- 限制上届的通配符:? extends
下面继承关系是:Food子类是Fruit,Fruit的两个子类是Apple和Orange,Apple子类是HongFuShi每个类都有个方法是fun返回className
当我们用以下方法定义方法参数就不会在受上面问题的困扰,他规定了上届则这个上届和他的的子类作为泛型都可以传参
static void print1(MyGeneric<? extends Fruit> myGeneric){
System.out.println(myGeneric.getT().fun());
}
static void fun12() {
MyGeneric<Food> food = new MyGeneric<>();
MyGeneric<Fruit> fruit = new MyGeneric<>();
MyGeneric<Apple> apple = new MyGeneric<>();
MyGeneric<Orange> orange = new MyGeneric<>();
MyGeneric<HongFuShi> fuShi = new MyGeneric<>();
print1(food);//报错
print1(fruit);
print1(apple);
print1(orange);
print1(fuShi);
}
但同时也会带来一点小小的问题,我们之前定义的MyGeneric是由setT和getT方法的,如果用通配符创建对象的话set和get方法会不会出现一点问题呢
static void fun14() {
MyGeneric<? extends Fruit> myGeneric = new MyGeneric<>();
Food food = new Food();
Fruit fruit = new Fruit();
Apple apple = new Apple();
Orange orange = new Orange();
HongFuShi fuShi = new HongFuShi();
myGeneric.setT(food);//报错
myGeneric.setT(fruit);//报错
myGeneric.setT(apple);//报错
myGeneric.setT(orange);//报错
myGeneric.setT(fuShi);//报错
Fruit a = myGeneric.getT();
Food b = myGeneric.getT();
Apple c = myGeneric.getT();//报错
Orange d = myGeneric.getT();//报错
HongFuShi e = myGeneric.getT();//报错
}
我们发现无论是子类还是父类都是不能用set方法的,因为虽然限制了上届,但是编译器是不会知道你这个类具体是哪个子类的(其实我也不太理解,如有错误欢迎指正)
而get方法可以用上届及其父类可以赋值,但子类不行,是因为返回的类型最多已经到了Fruit类,而子类是可以赋值给父类的,所以get方法可以赋值给上届和他的父类
? extends 是用来安全访问数据的
- 限制下届的通配符:? super
这个与上面用法恰恰相反,下届和他的的父类作为泛型传参
static void print2(MyGeneric<? super Fruit> myGeneric) {
System.out.println(myGeneric.getT());
}
static void fun13() {
MyGeneric<Food> food = new MyGeneric<>();
MyGeneric<Fruit> fruit = new MyGeneric<>();
MyGeneric<Apple> apple = new MyGeneric<>();
MyGeneric<Orange> orange = new MyGeneric<>();
MyGeneric<HongFuShi> fuShi = new MyGeneric<>();
print2(food);
print2(fruit);
print2(apple);//报错
print2(orange);//报错
print2(fuShi);//报错
}
同样,下届也会和上届出现set,get方法的问题
static void fun15() {
MyGeneric<? super Fruit> myGeneric = new MyGeneric<>();
Food food = new Food();
Fruit fruit = new Fruit();
Apple apple = new Apple();
Orange orange = new Orange();
HongFuShi fuShi = new HongFuShi();
myGeneric.setT(food);//报错
myGeneric.setT(fruit);
myGeneric.setT(apple);
myGeneric.setT(orange);
myGeneric.setT(fuShi);
Fruit a = myGeneric.getT();//报错
Food b = myGeneric.getT();//报错
Apple c = myGeneric.getT();//报错
Orange d = myGeneric.getT();//报错
HongFuShi e = myGeneric.getT();//报错
Object object =myGeneric.getT();
}
我们发现set方法可以传入Fruit和他的子类,因为规定了下届,子类对象可以赋值给父类,而T类型最低就是Fruit,所以可以将Fruit的子类赋值给T
而get方法都不能使用,但是可以用Object接受,是因为规定了下届是Fruit,getT返回的类型一定是Fruit类和他的父类,但是不知道具体是哪一个父类,但是在java中所有对象都有一个最基本的父Object,所以可以用Object来接收
? super是用来安全写入数据的
虚拟机是如何实现泛型的
在java虚拟机里面实现泛型实际上是进行了类型擦除,也就是用原生类型来替代泛型T,如果T吧、派生于其他类和接口,会将T替换为派生于的第一个类或者接口的类型,在需要用到其他类或接口的方法的时候进行强制转化
还是举个例子
public class MyGeneric7<T extends ArrayList&Comparable> {
private T t;
public MyGeneric7(T t) {
this.t = t;
}
public void test(){
t.compareTo(new Object());
}
}
在代码进行编译过后会变成以下代码
public class MyGeneric7<T extends ArrayList & Comparable> {
private ArrayList t;
public MyGeneric7(ArrayList t) {
this.t = t;
}
public void test() {
((Comparable)this.t).compareTo(new Object());
}
}
会直接将T转化为为ArrayList类型,并且在test方法中要调用Comparable中compareTo方法的时候进行了强制转化
如果我们制定了泛型的类型,则也会在编译后进行强制转换
static void fun11() {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
for (String s : list) {
System.out.println(s);
}
}
编译后
static void fun11() {
List<String> list = new ArrayList();
list.add("hello");
list.add("world");
Iterator var1 = list.iterator();
while(var1.hasNext()) {
String s = (String)var1.next();//重点看这行
System.out.println(s);
}
}
我们发现编译后确实将list里面的item强制转化我一开始指定的String类型