zoukankan      html  css  js  c++  java
  • 泛型

    二、泛型
    Java5开始提供的新特性,表示不确定的类型。
    注意:泛型是提供的javac编译器使用的,它用于限定集合的输入类型,让
    编译器在源代码级别上,挡住向集合中插入的非法数据。但编译器编译完之后,生产
    的.class字节码文件中不再代用泛型的信息,依此不影响程序的运行效率,这个
    过程被称为“擦除”。
    另外泛型还被用在方法或类上。


    -------------------------------------
    public class Demo2 {
    public static List method1(){
    //.....
    return new ArrayList();
    }
    public static void method2(List list){
    //...
    }
    public static void main(String[] args) {
    List list1 = new ArrayList();//正确的
    List<String> list2=new ArrayList<String>();//正确的
    List<String> list3 = new ArrayList();//正确的
    List<String> list3x = method1();//兼容当前情况
    List list4 = new ArrayList<String>();//正确的
    List<String> list4x = new ArrayList<String>();
    method2(list4x);//兼容当前情况。
    List<Object> list5 = new ArrayList<String>();//错误的
    List<String> list6 = new ArrayList<Object>();//错误的
    }
    }
    ---------------------------
    总结:可以两边都没有;可以一边有一边没有;
    也可以两边都用,一旦两边都有的话,两边必须保持相同(不考虑边界)。
    2.2自定义泛型
    分两类:方法上的泛型和类上的泛型。
    2.2.1方法上的泛型。
    -------------------------
    class Person{}
    class Dog{}
    public class God {
    public Object kill(Object obj){
    System.out.println("神弄死了:"+obj);
    return obj;
    }
    public Person kill(Person p){
    System.out.println("神弄死了的人是:"+p);
    return p;
    }
    public Dog kill(Dog d){
    System.out.println("神弄死了的狗是:"+d);
    return d;
    }
    public static void main(String[] args) {
    God god = new God();
    Person p = god.kill(new Person());
    Dog d = god.kill(new Dog());
    }

    }
    ------------------------------
    神可以kill各种类型的对象,我们不可能为每一个类型都编写一个方法,所以
    需要使用定义在方法上的泛型。
    注意:
    (1).泛型需要先定义,再使用,方法上泛型需要定义在方法返回值的前面,通常使用
    一个大写的英文字母(sun公司推荐使用T),也可以是任意的字母,但是不要使用
    java中的关键字和常用的类(String)。
    (2).定义在方法上的泛型的作用范围是当前方法的内部。
    -----------------
    public <T> T kill(T t){
    System.out.println("上帝弄死了"+t);
    return t;
    }
    -------------------------
    public <T> T save(T t){
    System.out.println("上帝救活了:"+t);
    return t;
    }
    如果把save方法上的<T>去掉,发现报错,再次证明定义在方法
    上的泛型只用在当前方法的内部有效。
    两个方法上"T"不是同一个"T"。
    -----------------------
    God god = new God();
    Person p = god.kill(new Person());
    Dog d = god.save(new Dog());
    -----------------------
    如果想让两个方法上的T表示同一个,怎么办?
    2.2.2.类上的泛型
    定义在类上的泛型作用范围是当前类的内部;
    需要先定义再使用,类上的泛型定义在类名的后面。通常使用大写的英文字母。
    创建具有泛型的类(God)的对象时,通常需要指定泛型的具体类型。
    如果在创建对象时,不明确指定泛型的具体类型,则默认为泛型的“上边界”。
    在类上定义的泛型不能用在静态方法上,如果想在静态方法上使用泛型,需要当该静态
    方法上重新定义。

    另外查看List接口源码发现,它其实就是使用了定义在类上的泛型
    public interface List<E> extends Collection<E>{
    boolean add(E e);
    void add(int index, E element);
    E set(int index, E element);
    <T> T[] toArray(T[] a);
    }
    -----------------------
    想让所有的神的对象之间公用泛型T:
    public static <T> T sleep(T t){
    System.out.println("神催眠了:"+t);
    return t;
    }
    注意:在sleep方法上的T,和该方法所在类God<T>上的T不是同一个。

    补充:在泛型使用中可以同时定义多个。
    public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
    {
    public V put(K key, V value){...}
    ...
    }
    神马是“上边界”?


    一、泛型进阶
    !!通配符 ?
    因为泛型没有继承的概念,所以当需要用一个“泛型引用”引用不同的泛型实现时,泛型写
    他们的共同父类是不行的,这时该怎么做?引入一个新的概念,叫做泛型的通配符“?”,注意
    通配符只能用在声明处,不能用在实现实例的过程中。
    void print(Collection<?> c){//Collection of unknown
    for(Object obj:c){
    System.out.pritnln(obj);
    }
    }
    new ArrayList<Stdudent>();
    new ArrayList<Person>();
    注意:由于参数c类型为Collection<?>,表示一种不确定的类型,因此在方法内部
    不能调用与类型相关的方法。

    如果没有指定泛型默认类型,可以接收任意类型,有时希望进一步限制范围,需要使用泛型的边界:
    !!上边界:限定通配符的上边界
    extends-用来指定泛型的上边界,和泛型通配符配合使用(List<? extends Person> list),指定具体的泛型实现必须是指定类或它子孙类。
    List<? extends Number> list1 = new Vector<Number>();//正确的
    List<? extends Number> list2 = new Vector<Integer>();//正确的
    List<? extends Number> list3 = new Vector<String>();//错误的

    List<? extends Person> list1 = null;
    list1 = new Vector<Teacher>();
    Teacher thr = new Teahcer();
    list1.add(thr);
    SomeOne<T extends Person>
    演示和相关总结见:cn.tedu.v1.Demo2
    !下边界:限定通配符的下边界
    super:用来指定泛型的下边界,和通配符一起使用。指定具体的泛型实现必须是指定类或指定的“超类”
    ? super Person

    这个原因也是很简单的, 因为我们所传入的类都是Integer的类或其父类, 所传入的数据类型可能是Integer到Object之间的任何类型, 这是无法预料的, 也就无法接收. 唯一能确定的就是Object, 因为所有类型都是其子类型.
    使用? super E还有个常见的场景就是TreeSet有这么一个构造方法:

    TreeSet(Comparator<? super E> comparator)

    就是使用Comparator来创建TreeSet, 那么请看下面的代码:
    public class Person {
    private String name;
    private int age;
    public Person(){}
    public Person(String name,int age){
    this.name = name;
    this.age = age;
    }//省略getters和setters
    }
    public class Student extends Person {
    public Student() {}
    public Student(String name,int age) {
    super(name,age);
    }
    }
    class comparatorTest implements Comparator<Person>{
    @Override
    public int compare(Person p1, Person p2) {
    int num = p1.getAge() - p2.getAge();
    return num == 0 ? p1.getName().compareTo(p2.getName()):num;
    }
    }
    public class Demo {
    public static void main(String[] args) {
    TreeSet<? super Person> ts1 = new TreeSet<Person>(new comparatorTest());
    ts1.add(new Person("Tom", 20));
    ts1.add(new Person("Jack", 25));
    ts1.add(new Person("John", 22));
    System.out.println(ts1);

    TreeSet<? super Student> ts2 = new TreeSet<Student>(new comparatorTest());
    ts2.add(new Student("Susan", 23));
    ts2.add(new Student("Rose", 27));
    ts2.add(new Student("Jane", 19));
    System.out.println(ts2);
    }
    }

    三、总结:
    "in out"原则, 总的来说就是:
    in就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固
    定上边界的通配符. 你可以将该对象当做一个只读对象;
    out就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下
    边界的通配符. 你可以将该对象当做一个只能写入的对象;
    当你希望in的数据能够使用Object类中的方法访问时, 使用无边界通配符;List<?>
    当你需要一个既能读又能写的对象时, 就不要使用通配符了.

  • 相关阅读:
    C++ allocator
    C++操作符重载
    Theron (C++ concurrency library) 读后感
    第五章 [BX]和loop指令
    第四章 第一个程序
    第三章 寄存器(内存访问)
    第二章 寄存器
    第一章
    jquery下ajax异步执行操作笔记
    CSSFlex布局
  • 原文地址:https://www.cnblogs.com/Mike_Chang/p/12911270.html
Copyright © 2011-2022 走看看