泛型-Java泛型基础

面向对象相对于面向过程而言,是软件领域的一个重大进步。面向对象的多态特性,使得系统具有了较好的扩展性,通常我们使用父类来代替具体的类型,在实际运行时,却可以使用子类的对象。Java又更进了一步,提倡面向接口的编程,我们指定接口而不是具体的实现类。这样的约束有时候还是太强,我们希望编写更通用的代码,使代码能够用运行于“某种不具体的类型”,而不是具体的接口或类。

泛型的概念

假设我们要实现一个类,这个类持有一个属性,为了灵活,我们希望这个属性可以是任何类型,如果这样的话,这个属性就应该定义为Object类型的,其实现代码如下:

public class Holder {
    private Object obj;

    public void setObj(Object obj) {
        this.obj = obj;
    }

    public Object getObj() {
        return obj;
    }
}
在实际使用的时候,不管传递进来的参数是什么类型,在Holder类里都被向上转型为Object类型了;在希望获得持有的属性时,也仅仅能得到Object类型,必须要使用类型转换
Holder holder = new Holder();
holder.setObj("cxy");
//显式的类型转换
String name = (String)holder.getObj();
我们可以看到,get()返回的是Object类型,但其实我们希望得到更具体的类型,这个时候就要使用类型转换。问题是,类型转换的任务需要有程序员来完成,这意味着程序要需要知道Holder实际持有的是什么类型的。如果在类型转换时发生了错误,必须要等到运行期才能发现。
holder.set(new Integer(1));
//错误的类型转换
String name = (String)holder.get();
我们希望错误发现的越早越好。泛型就是用来保证类型的正确性的,在定义类时,使用一个占位符,代表类型信息,通常使用T来表示。上面的代码如果使用泛型来写:
public class Holder<T> {
    private T obj;
    public void set(T t) {
        obj = t;
    }

    public T get() {
        return obj;
    }
}
在使用的时候,只需要用实际的类型来代替T,编译器会确保传递和获得时的类型正确性。如下面的代码所示:
//只能持有String类型
Holder<String> holder = new Holder<String>();
holder.set("cxy");
//编译器会确保返回的是String类型
String name = holder.get();
//如果试图传入非String类型的参数,编译无法通过
//holder.set(new Integer(1));
就是这么简单,类型的正确性由编译器来处理,但其实Java中的泛型远比想象中的要复杂,上面只是泛型最简单的用法,泛型出了可以和普通类配合使用之外,还可以和接口、内部类一起使用,可以写一个没有实际意义的代码来演示一下:
//泛型接口
public interface GenericIn<T> {
    public void f();
}

//泛型匿名内部类
public class Outer<T> {
    public GenericIn<T> getGeneric() {
        return new GenericInt<T>() {
               public void f() {
                   do something
               }
        };
    }
}
泛型方法

泛型类在定义时使用一个占位符来表示类型,这个类型只有在具体构造该类的实例时才会知道,所以static方法和static字段都不能使用泛型类的类型参数,如果static想要拥有泛型能力,必须要借助于其他的机制。还有,有时候我们仅仅希望某个方法拥有泛型能力,而不是整个类,这样更加方便灵活,不用为了局部的泛型需要,而必须要在所有使用该的地方都使用泛型。出于以上两个原因,泛型方法就被提出来了。泛型方法的定义很简单,就是把类型参数放到方法的返回值之前:

public <T> void f();
public <T> T f(T t);
这样在方法f内部就可以使用类型T了。泛型方法是独立于泛型类而产生变化的,不是泛型类也可以拥有泛型方法;泛型方法可以使用额外的类型信息:
public class Generic<T> {
   //泛型方法使用额外的类型信息
   public <K> K f(K k) {
       return k; 
   }
}
在创建泛型类时必须要显式的指定类型信息,而在使用泛型方法时则不用,编译器会自动找出具体的类型,这称为 类型推断(type argument inference)。比如
//编译器会自动推断出类型是String
String value = xxx.f("value");
但是类型推断只有在赋值时有效,在其他的时候,类型参数并不起作用,有如下代码:
public static <K,V> Map<K,V> map() {
	return new HashMap<K,V>();
}

static void f(Map<String, String> map){}
如果这样调用是通不过的:
f(map());
因为不是赋值操作,所以编译器会认为map()返回的是Map<Object,Object>。这个时候可以使用显式的类型说明来解决此问题,显式的类型说明就是在方法调用者和方法之间指定类型信息,如果是在类内部调用实例方法,需要使用this,如果是类方法,需要使用类名,其形式如下:
f(ClassName.<Map<String,String>>map());

以上只是泛型的基本用法,Java的泛型远比想象的要复杂,Java的泛型并不是真正的泛型,而是使用擦除来实现的,泛型所有的动作都是由编译器来完成的,在实际运行时,虚拟机根本不知道泛型的存在,具体内容可以参考《泛型-擦除实现的Java泛型》

转载请注明:喻红叶《泛型-Java泛型基础》

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,泛型是一种强类型机制,它可以让你在编译时检查类型错误,从而提高代码的安全性和可读性。在使用泛型时,我们经常会遇到父类和子类的泛型转换问题。 首先,我们需要明确一点:子类泛型不能转换成父类泛型。这是因为Java中的泛型是不协变的。例如,如果有一个类A和它的子类B,那么List<A>和List<B>之间是不存在继承关系的。 下面我们来看一个例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 } } ``` 在这个例子中,我们定义了Animal类和它的子类Dog。然后我们定义了两个List,分别是List<Animal>和List<Dog>。如果将List<Dog>赋值给List<Animal>,会出现编译错误。这是因为List<Animal>和List<Dog>之间不存在继承关系。 那么,如果我们想要让子类泛型转换成父类泛型,应该怎么办呢?这时我们可以使用通配符来解决问题。通配符可以表示任意类型,包括父类和子类。例如,我们可以将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 下面我们来看一个使用通配符的例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 List<? extends Animal> list3 = new ArrayList<>(); list3 = list2; // 正确 } } ``` 在这个例子中,我们定义了List<? extends Animal>来表示任意继承自Animal的类型。然后我们将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 总结一下,Java中的泛型是不协变的,子类泛型不能转换成父类泛型。如果需要实现子类泛型转换成父类泛型,可以使用通配符来解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值