从此泛型不是事儿

1.泛型

Java5开始提供的新特性,表示不确定的类型

注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。泛型也可以用在方法上或者是类上。

1.1、集合泛型
class Person{}
class Student extends Person{}
public class Demo1 {
    public static void main(String[] args) {
    List<String> list1= new ArrayList<String>();
    List list2 = new ArrayList();
    List list3 = new ArrayList<String>();//正确的
    method1( new ArrayList<String>());
    List<String> list4 = new ArrayList();//正确的
    List<String> list41 = method2();
    List<Person> list5 = new ArrayList<Student>();//错误的
    List<Student> list6 = new ArrayList<Person>();//错误的
    }

关于集合泛型总结:要么两边都没有;或者一边有,一边没有;如果两边都有时,那么要保存“一致”

1.2自定义的泛型
  • 分为:方法上的泛型和类上的泛型

1.2.2、方法上的泛型

我们来考虑这样一个例子:
上帝(神)能够杀死任何生物,我们设计方法的需要为每一个不同的生物重载相似的方法,这样的话需要我们编写非常多的类。思考是否可以设计一个通用的方法:

public Object kill(Object obj){
    System.out.println("上帝杀死了"+obj);
    return obj;
}

该方法的优点是所有的生物一个方法搞定。
缺点:每次调用该方法还需要强制类型转换。使用不方便。

可以使用泛型:

public <T> T kill(T t){
    System.out.println("上帝杀死了"+t);
    return t;
}

调用使用方向的方法,不要再强制类型转换了。

*泛型定义: 一个大写的英文字母,一般使用使用T,集合泛型一般使用E、K、V
在修饰符和返回值类型之间定义泛型: <一个大写的英文字母>
定义在方法上的泛型只能在方法签名和方法的内容有效;出了该方法将无效。在方法上使用泛型要注意:需要先定义,再使用。*
例如:

public <T> T kill(T t){
    System.out.println("上帝杀死了"+t);
return t;
}
public <T>  T  save(T t){
    System.out.println("上帝救活了"+t);
    return t;
}
public static void main(String[] args) {
    God god = new God();
    Person person = god.kill(new Person());
    god.save(person);
    god.save(new Dog());
}

1.2.2、类上的泛型

定义在类型的泛型需要先定义在使用。
类上的泛型的定义在类名后面。
定义泛型时通常一个大写的英文字母(sun公司推荐使用T)。
定义在类上泛型有效范围是在当前类的内部有效。
定义在类上的泛型不能在静态方法上使用,所以静态方法上要想使用泛型,需要自己定义。

public class God<T> {
    public  T kill(T t){
        System.out.println("上帝杀死了"+t);
        return t;
    }
    public  T  save(T t){
        System.out.println("上帝救活了"+t);
        return t;
    }
    public static void main(String[] args) {
        God<Person> god1 = new God<Person>();
        Person person = god1.kill(new Person());
        Person person2 = god1.save(person);
    }
}
1.3、泛型通配符

因为泛型没有继承关系,所以当需要用一个泛型引用引用不同的泛型实现时,泛型中写他们共同的父类是不行的,这时该怎么做呢?引入一个新的概念,叫做泛型通配符?,注意泛型通配符只能用在泛型引用中,用来引用不同的泛型实现,不能出现在实现中.
问题:该方法只能打印保存了String对象的集合,不能打印其它集合。通配符用于解决此类问题,方法的定义可改写为如下形式:

void print (Collection<?> c){   
//Collection<?>(发音为:"collection of unknown") 
    for (Object e : c) {
        System.out.println(e);
    }
}

总结:使用?通配符主要用于引用对象,使用了?通配符,就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。

*泛型的边界:
如果没有指定泛型默认可以接受任意的类型,有时希望进一步限制,此时可以使用泛型的边界:

限定通配符的上边界:
extends - 用来指定泛型的上边界,使用在泛型的通配符中和泛型定义中,指定具体的泛型实现必须是指定的类或其子类.
坏处是,在传入对象时,只能传入null
好处是,获取到泛型的对象时,可以调用上边界的方法.

正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
class Ani{
    public void sayx(){
    }
    public void say(){
    }
}
class Person extends Ani{
    public void say(){
    }
}
class Teacher extends Person{
    public void say() {
    }
    public void teach(){
    }
    }
class SomeC<T extends Person>{
    public void method1(T t){
        t.say();
    }
}
public class Demo {
    public static void print(List<? extends Person> list){
        for (Person person : list) {
            person.say();
        }
    }
    public static void main(String[] args) {
        Person p = new Person();
        /*List<?> list0 = new ArrayList<Teacher>();
        list0.get(0);*/
        //--上边界:指定泛型必须是某个类或其子孙类
        List<? extends Person>list1 = null;
        list1 = new ArrayList<Teacher>();
        //好处是,获取到泛型类型后,可以直接调用上边界的方法,
        //坏处是,当进行传入操作时,只能传入null,其他的值不能传入
        //list1.get(0).say();
        /*list1.add(new Object());
        list1.add(new Ani());
        list1.add(new Person());
        list1.add(new Teacher());*/
        //以上几种添加都不行,只能添加null
        list1.add(null);
        //Object和Animal不可以添加很好理解,为什么不能添加Person和Teacher类
        //?可以是Person、Teacher、Student、Doctor...所以没有办法确定具体是哪个,
        //那么上边界还有什么用处?常见用处:1、方法形参
        List<Teacher> tealist = new ArrayList<Teacher>();
        tealist.add(new Teacher());
        tealist.add(new Teacher());
        print(tealist);
        List<Person> psonList = new ArrayList<Person>();
        psonList.add(new Person());
        psonList.add(new Person());
        print(psonList);
        List<Ani> aniList = new ArrayList<Ani>();
        //print(aniList);//无法使用
        //上边界的使用场景2  class SomeC<T extends Person>{}
        SomeC<Person> sc1 = new SomeC<Person>();
        SomeC<Teacher> sc2 = new SomeC<Teacher>();

super - 用来指定泛型的下边界,使用在泛型的通配符中,指定具体的泛型实现必须是指定类或其超类.
好处是,可以传入对象时,可以传入下边界的子孙类对象
坏处是,获取到泛型对象时,只能调用Object身上的方法

//-泛型的下边界,用来指定泛型是某个类型或其祖先类型(超类),
//好处是,可以传入指定类的子孙对象,坏处是获取出来后只能当作Object使用
List<? super Person>list2 = null;
list2 = new ArrayList<Ani>();
list2.add(new Person());
list2.add(new Teacher());
public class DemoSuper {
    public static void addNumbers(List<? super Integer> list) {
      for (int i = 1; i <= 10; i++) {
          list.add(i);
      }
    }
    public static void main(String[] args) {
      List<Object> list1 = new ArrayList<Object>();
      addNumbers(list1);
      System.out.println(list1);
      List<Number> list2 = new ArrayList<Number>();
      addNumbers(list2);
      System.out.println(list2);
      List<Double> list3 = new ArrayList<Double>();
      addNumbers(list3); // 编译报错
    }
}

我们看到,List<? super E> 是能够调用add方法的, 因为我们在addNumbers所add的元素就是Integer类型的, 而传入的list不管是什么, 都一定是Integer或其父类泛型的List, 这时add一个Integer元素是没有任何疑问的. 但是, 我们不能使用get方法, 请看如下代码:

public static void getTest2(List<? super Integer> list) {
     // Integer i = list.get(0); //编译报错
     Object o = list.get(1);
}

这个原因也是很简单的, 因为我们所传入的类都是Integer的类或其父类, 所传入的数据类型可能是Integer到Object之间的任何类型, 这是无法预料的, 也就无法接收. 唯一能确定的就是Object, 因为所有类型都是其子类型.

总结:

“in out”原则, 总的来说就是:

in就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固定上边界的通配符. 你可以将该对象当做一个只读对象;
out就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下边界的通配符. 你可以将该对象当做一个只能写入的对象;
当你希望in的数据能够使用Object类中的方法访问时, 使用无边界通配符;

当你需要一个既能读又能写的对象时, 就不要使用通配符了.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值