笔者做android开发也有几年的时间,对java的高级特性一直以来都处于一种浅尝则止的状态,最近恰好处于一段摸鱼期,所以就想深入学习一下java里的高级特性来充实下自己的武器库。
这是第一篇,研究一下java泛型,相信项目中很多人都用过,笔者也是在项目中经常用到,但是要是让大家具体说说泛型相信大家可能不知道该如何说起。那我就从下面几个点来学习泛型。
a.为什么需要泛型?
b.泛型类、泛型接口的定义。
c.泛型方法辨析
d.限定类型变量
e.泛型中的约束性和局限性
f.泛型类型的继承规则
g.通配符
h.虚拟机如何实现泛型?
1.首先我们来说第一个:为什么需要泛型?
这里举这么一个例子:计算两个数字之和;相信大家都觉得这个太简单了,代码如下:
public class NonGeneric {
public int addInt(int x,int y){
return x+y;
}}
调用:
NonGeneric nonGeneric = new NonGeneric();
System.out.println("计算1+2="+nonGeneric.addInt(1,2));
so easy! 那么问题来了,上面例子中我们传入的数据类型是int,如果我们要计算两个float数据之和呢,两个double数据呢?
再来看第二个例子:
private void nonGeneric2() {
List list = new ArrayList();
list.add("nice");
list.add("ok");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i);
System.out.println("name:"+name);
}
}
运行上面一段代码时:我们发现这么一个错误:
Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
而如果在创建list的时候直接使用泛型指定数据类型List,就可以在编写代码的时候给我们错误提示,避免错误,这就是使用泛型的好处之一。这里只是一个简单的例子,相信大家在编写代码中也是这么写的。
2.泛型类,泛型接口,泛型方法的定义:
泛型类:
public class NormalGeneric<T> {
private T data;
public NormalGeneric(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}}
泛型类的定义方式如上,这里看到,在泛型类中,同样可以使用set,get方法,也可以使用构造方法。(注:这里的T不是强制规定的,可以用其他字母代替)
泛型接口:
public interface Genertor<T> {
public T next();
}
泛型接口实现方式1(泛型类实现泛型接口):
public class ImplGenertor<T> implements Genertor<T> {
@Override
public T next() {
return null;
}
}
泛型接口实现方式2(指定数据类型):
public class ImplGenentor2 implements Genertor<String> {
@Override
public String next() {
return null;
}}
泛型方法辨析1:
public class GenericMethod{
public <T> T GenericMethod(T...a){
return a[a.length/2];
}
}
这里定义的是正常类里的一个泛型方法,T...a是一种可变参数的表示,这种可变参数底层其实会转化为T[] x的形式,所以可以接受多个T类型的传参。如:
GenericMethod genericMethod = new GenericMethod();
System.out.println(genericMethod.GenericMethod("12","56","333"));
泛型方法辨析2:
public class GenericMethod2 {
public class Generic<T>{
private T key;
public Generic(T key){
this.key = key;
}
public T getKey(){
return key;
}
public void show(Generic<Number> object){
}
}
}
虽然在方法getKey()中使用了泛型,但是这并不是一个泛型方法。这只是类中的一个普通成员方法,只不过它的返回值是在声明泛型类已经声明过的泛型;所 以在这个方法中才可以继续使用T这个泛型。show(Generic object)方法也不是一个泛型方法,只是使用Generic作为行参而已。
泛型方法辨析3:
public class GenericMethod3 {
static class Fruit{
@NonNull
@Override
public String toString() {
return "fruit";
}
}
static class Apple extends Fruit{
@NonNull
@Override
public String toString() {
return "apple";
}
}
static class Person{
@NonNull
@Override
public String toString() {
return "person";
}
}
static class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
//在泛型类中声明了一种泛型方法,使用泛型E,这种泛型E可以为任意类型。
//可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>因此即使泛型类中并未声明泛型,
//编译器也能够正确的识别泛型方法中识别的泛型;
public <E> void show3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,
//注意这个T是一种全新的泛型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<>();
generateTest.show_1(apple);
//无法传入person
// generateTest.show_1(person);
generateTest.show_2(apple);
generateTest.show_2(person);
generateTest.show3(apple);
generateTest.show3(person);
}
}
在如上所示的 GenericMethod3中,定义了一个四个静态内部类,Fruit、Apple、Person、GenerateTest。其中Apple是Fruit的子类; 通过上面的调用实例可以得出结论:在泛型类里定义泛型方法,参数类型以泛型方法里的为准。
3.限定类型变量
例:取a,b,两个变量最小值:
public class ArrayAlg {
public static <T> T min(T a,T b){
if (a.comapareTo(b)>0)return a;else return b;
}
static class Test{
}
public static void main(String[] args) {
}}
首先我们可能想到的写法min();这里我们无法保证a可以实现compareTo()方法,那么如何确保一定可以用compareTo()去比较a,b变量,这里我们就用到限定类型变量,让T 派生自 Comparable,这样就可以使传入变量一定可以使用compareTo();
优化后:
public static <T extends Comparable> T min(T a,T b){
return a.compareTo(b)>0 ?a:b;
}
注:这里可实现多个接口,也可以派生自某个类,但是如果需要实现类,同时实现接口时,必须将类置于首位,如:<T extends ArrayList & Comparable>,在泛型类中使用泛型是同样需要遵循此规则。
4.泛型中的约束性和局限性
(1)不能实例化类型变量
public class Restrict<T> {
private T data;
//不能实例化类型变量
// public Restrict(){
// this.data = new T();
// }
}
这里的T作为泛型,是不能进行实例化的;
(2)静态域或者方法里不能引用类型变量
private static T instance;//这里同样不能引用
注:在创建对象时虚拟机会先执行static 方法,再执行类的构造方法,此时虚拟机无法识别泛型T;但是静态方法本身泛型方法就行;
private static <T> T getInstance(){}
(3)基础类型不能作为泛型实例化具体参数
RestrictM<double> restrict;//不能使用;
Restrict<Double> restrict; //可以使用;
(4)泛型中不允许使用instanceof关键字
if (restrict instanceof Restrict<Double>) //不允许使用;
if( restrict instanceof Restrict<T>) // 不允许使用;
(5)泛型类的类型与传入的泛型参数无关
例:
Restrict<Double> restrict = new Restrict<>();
Restrict<String> restrictString = new Restrict<>();
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());
结果:
com.terry.genericpractice.restrict.Restrict
com.terry.genericpractice.restrict.Restrict
(6)泛型类不能extends Exception/Throwable;
private class Problem<T> extends Exception; //不能使用;
//不能捕获异常
/*public <T extends Throwable> void doWork(T t){
try {
}catch (T t){
}
}*/
//可以抛出异常
public <T extends Throwable> void doWork(T t)throws T{
try {
}catch (Throwable e){
throw t;
}
}
5.泛型类型的继承规则
(1)泛型类的派生关系与其传入的参数无关;
例: 有普通类Worker 派生于 Employee;泛型类Pair;
//Pair<Employee> 和 Pair<Worker> 没有任何的继承关系;
Pair<Employee> employeePair = new Pair<>();
Pair<Worker> workerPair = new Pair<>();
//可以定义
Employee employee = new Worker();
//不可以定义
Pair<Employee> employeePair1 = new Pair<Worker>();
(2)泛型类可以继承或者扩展其他内部类如List与ArrayList;
6.通配符(适用于泛型方法,不适用泛型类) 例:现有基类Fruit,apple派生于Fruit、orange派生于Fruit,HongFuShi派生于apple;泛型类 GenericType;
public class WildChar {
public static void print(GenericType<Fruit> p){
System.out.println(p.getData().getColor());
}
public static void use(){
GenericType<Fruit> a = new GenericType<>();
print(a);
GenericType<Orange> b = new GenericType<>();
print(b); //编译报错
}
}
如上 GenericType<Fruit) 和 GenericType<Orange)并没有继承关系,所以这里传入orange去调用print方法时会报错,这里我们就可以使用通配符:
public static void print(GenericType<? extends Fruit> p){
System.out.println(p.getData().getColor());
}
注:这里表示传入的类是Fruit子类及其本身;
同样可以用super public static void print(GenericType<? super Fruit> p){ System.out.println(p.getData().getColor()); } 注:这里表示传入的类是Fruit父类及其本身;
- 虚拟机如何实现泛型?
在Java中实现泛型中是用类型擦除;
例:
public class GenericRaw<T extends Comparable & Serializable> {
public T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
}
}
注:当 T extends Comparable & Serializable 时,它会以Comparable 作为原生类型,如果在某个位置使用到Serializable,编译器会在合适的位置插入一个强制转型的代码;
例:
public class Conflict {
public static void method (List<String> stringList){
}
public static void method(List<Integer > integerList){
}
}
注:虽然这里List<String> 和List<Integer> 不同,但是编译器会报错,这里就是因为类型的擦除。