zoukankan      html  css  js  c++  java
  • 泛型学习

    一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类,如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的限制就会很大。
    泛型的概念,泛型实现了参数化类型的概念,使代码可以应用于多种类型。“泛型"这个术语的意思是:适用于许多许多的类型。

    简单的泛型

    public class GenericMethods {
    	
    	public <T> void f(T t){
    		System.out.println(t.getClass().getName());
    	}
    	public <T> void power(T... ts){
    		for (T t : ts) {
    			System.out.println(t.getClass().getName());
    			
    		}
    	}
    	public static void main(String[] args) {
    		GenericMethods gm = new GenericMethods();
    		gm.f(1);//输出结果 Integer
    		gm.f(1.0);//Double
    		gm.f(1.0f);//Float
    		gm.f('1');// char
    		gm.f("1");//String
    		gm.f(gm);//GenericMethods
    		GenericMethods gmPower = new GenericMethods();
    		gmPower.power(1,1.0,1.0f,'1',"1",gmPower);
    	}
    
    }

    注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,以为编译器会为我们找出具体的类型。这称为类型推断。因此,我们可以像调用普通方法一样调用f(),而且好像是f()被无限次地重载过。他甚至可以接受GenericMethods作为其类型参数。如果传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。

    泛型擦除

    java编程思想中介绍:

    1.迁移的兼容性

    为了减少潜在的关于擦除的混淆,你必须清楚地认识到这不是一个语言特性。它是Java的 泛型实现中的一种折中,因为泛型不是Java语言出现时就有的组成部分,所以这种折中是必需 的。这种折中会使你痛苦,因此你需要习惯它并了解为什么它会是这样。
    如果泛型在Java 1.0中就已'经是其一部分了,那么这个特性将不会使用擦除来实现——它将 使用具体化,使类型参数保持为第一类实体,因此你就能够在类型参数上执行基于类型的语言 操作和反射操作。擦除减少了泛型的泛化性。泛型在Java中仍旧是有用 的,只是不如它们本来设想的那么有用,而原因就是擦除。在基于掠除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检査期间才出现,在此之后,程序中的所有泛型类型 都将被擦除,替换为它们的非泛型上界。例如,诸如List<T>这样的类型注解将被擦除为List, 而普通的类型变量在未指定边界的情况下将被擦除为Object.。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被 称为“迁移兼容性”。在理想情况下,当所有事物都可以同时被泛化时,我们就可以专注于此。 在现实中,即使程序员只编写泛型代码,他们也必须处理在Java SE5之前编写的非泛型类库。那 些类库的作者可能从没有想过要泛化它们的代码,或者可能刚刚开始接触泛型。
    因此Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持 其之前的含义,而且还要支持迁移兼容性,使得类库按照它们自己的步调变为泛型的,并且当 某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。在决定这就是目标之后,Java设 计者们和从事此问题相关工作的各个团队决策认为擦除是唯一可行的解决方案。通过允许非泛 型代码与泛型代码共存,擦除使得这种向着泛型的迁移成为可能。
    例如,假设某个应用程序具有两个类库X和Y,并且Y还要使用类库Z°随着Java SE5的出现, 这个应用程序和这些类库的创建者最终可能希望迁移到泛型上。但是,当进行这种迁移时,他 们有着不同动机和限制。为了实现迁移兼容性,每个类库和应用程序都必须与其他所有的部分 是否使用了泛型无关。这样,它们必须不具备探测其他类库是否使用了泛型的能力。因此,某 个特定的类库使用了泛型这样的证据必须被“擦除”。如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到Java 泛型上的开发者们说再见了。但是,类库是编程语言无可争议的一部分,它们对生产效率会产 生最重要的影响,因此这不是一种可以接受的代价。擦除是否是最佳的或者唯一的迁移途径, 还需要时间来证明。

    2.擦出问题

    因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类 库的情况下,将泛型融入Java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况下 继续使用,直至客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为它不会突然间 破坏所有现有的代码。
    掠除的代价是显著的。泛型不能用于显式地引用运行时类型的操作之中,例如转型、 instanceo噪作和new表达式。因为所有关于参数的类型信息都丢失了,无论何时,当你在编写 泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。

    泛型边界问题。使用extends 关键字可以解决

    public class HasF {
    	public void f(){
    		System.out.println("Hasf f();");
    	}
    }
    // 给定泛型类的边界 如果不指定边界那么将会产生报错信息 
    class Manipulator<T extends HasF>{
    //class Manipulator<T>{
    	private T obj;
    	public Manipulator(T x){
    		this.obj=x;
    	}
    	public void manipulate(){
    		// 报错点
    		obj.f();
    	}
    }

       

    /Java泛型. 程序是从网上看到的,很具有代表性。
    //已知 Apple extends Fruit extends Object
    //指出下列语句的正确性,并解释。

    import java.util.ArrayList;
    import java.util.List;
     
     
    public class GenericTest {
     
     
        public static void main(String[] args) throws SQLException {
     
     
           List<? super Fruit> f0=new ArrayList<Fruit>();
           f0.add(new Apple());
           f0.add(new Fruit());
           f0.add(new SupApple());
           
           List<? super Fruit> f1=new ArrayList<Apple>();
           f1.add(new Apple());
           f1.add(new Fruit());
           
           List<? extends Fruit> f2=new ArrayList<Fruit>();
           f2.add(new Apple());
           f2.add(new Fruit());
           
           
           List<? extends Fruit> f3=new ArrayList<Apple>();
           f3.add(new Apple());
           f3.add(new Fruit());
            
           List<? super Apple> f5=new ArrayList<Fruit>();
           f5.add(new Apple());
           f5.add(new SupApple());
           f5.add(new Fruit());
        }
    }
     
     
    class Fruit{
    }
     
     
    class Apple extends Fruit{
    }
     
     
    class SupApple extends Apple{    
    }

     1、Java.util的集合类中的元素必须是对象化的,他们不能是基本类型。如不能声明Set<char>或List<int>。但对List<Integer>,可以往里面加int型数据,它会用Java的autoboxing机制自动转换成Integer对象。

        2、参数化类中的类型参数可以是数组类型,如Map<String[],int[]),注意int[]型是一个对象,而不是原始类型。
      
        3、如果不想用泛型功能,可以通过带-source1.4标记来编译;也可以在声明的域或方法前用@SuppressWarings("unchecked")标注来忽略。

        4、一个List<Integer>是一个Collection<Integer>,但不是一个List<Object>,否则List<Integer>可以转换成List<Object>,那么转换后什么类型的数据都可以加进去,没有达到编译期类型安全的目的。即:类可以上转型,类型参数不能上转型。

        5、Java中引入泛型的本质:为了提供编译期的类型安全检查,以免类型不安全的bug出现在运行期。(注意这与C++及C#中泛型的目的不同)

        6、一个List<T>上转型成List是合法的,这是为了向后兼容,但不推荐这样做。因为上转型后可以添加任意类型的元素,会在后面引入bug。
       注意:上转型是编译期行为,在运行时运行的还是未转型前的那个类型

        7、不能创建参数化类型的数组。如List<String>[] wordlists=new ArrayList<String>[10];编译通不过。编译器为什么要这样做呢?如下:

       可见不能保证编译期类型安全,所以编译器干脆在第一行就拒绝编译。

        8、如果类型是泛型,但您希望能添加各种类型的元素,可以把元素类型声明为Object类型,如Set<Object>,这样就可以往里面添加不同类型     的元素。如果这还不行,比如,您并不关心值的类型,您希望您的list可以是List<Integer>,也可以是List<String>,可以用List<?>,但     是不能在构造器中使用"?"通配符。

        9、List<?>既不是List<Object>也不是未经处理的List。?表示未知类型,它是只读的,调用get()返回Object类型,调用add()编译出错。

        10、上界通配符:如List<? extends Number>,?为未知类型,但必须是Number的子类,Number被认为是其自身的子类。注意由于类型仍然未知不允许添加未知类型的元素,故它仍然是只读的。

        11、下界通配符:如Set<? super Integer>,?为未知类型,但必须是Integer的父类,Integer被认为是自己的父类。

        12、切记,类型变量仅在编译时存在,所以不能使用instanceof和new这类运行时操作符来操作类型变量。

        13、一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而是不能被静态变量和方法调用。原因很简单,参数化的泛型      运行时是一些实例,类的类型参数不存在了。静态成员是被类的实例和参数化的类所共享的,所以静态成员不应该和类型参数关联。

        14、类的类型变量T可以用于声明变量的类型,声明构造函数和方法的入口类型和返回类型,未知类型?,? extends T,? super T可以用于声明    变量的类型,声明方法的入口类型和返回类型,但不能用于声明构造函数。

        15、方法、构造函数可以声明自己的类型变量,这时就叫做范型方法。调用范型方法时,根据传入的实际值的类型来确定类型变量,一般无需显式指明参数类型。但在非常罕见的情况下,可能必须指明。如java.util.Collections.emptySet(),返回一个空集,他虽不带入口参数但需要指明返回类型,可以这样写Set<String> empty=Collections.emptySet(),也可以这样写Set<String> empty=Collections.<String>emptySet(),但是在方法调用表达式中则必须显示的说明emptySet()的返回值类型,如:
        printWords(Collections.<String>emptySet())

        16、类型参数不能同没有限制的方法名结合使用:他们必须跟随在一个.后,或者在new后,或者在this前,或者构造函数的super前。

        17、若创建一个方法使用varargs和类型变量,记住调用varargs隐含创建一个数组。但是在方法里面我们不能创建参数化类型的数组,这点要注意。

        18、参数化异常是不允许的,因为异常是在运行时抛出和捕获的,没办法让编译器完成类型检查。因此不允许创建任何Throwable类型的子类。但是可以使用类型变量在throw块里的方法签名中。如:


        19、Integer的类标签:public final class Integer extends Number implements Comparable<Integer>
        java.math.BigInteger的类标签:public class BigInteger extends Number implements Comparable<BigInteger>

        20、java.util.Collections.max()为找集合中的最大元素,其方法标签为:
        public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
        方法中包含一个类型变量T,返回值类型为T,方法的参数是一个集合,集合中的元素类型上限为T,上限为T的任何类型都可作为返回类型
        T必须实现Comparable接口,且必须与T或T的父类比较。可见,T自己实现Comparable接口,或者T的父类实现Comparable接口都可以满足这个要求。

        21、考察Collections的addAll()方法:
        public static <T> boolean  addAll(Collection<? super T> c,T... a)
        这是一个varargs方法,接受任意数量的参数,并且传递给他们一个T[],命名为a。他将a中的所有元素都赋给集合c。集合的元素类型下界     为T,这样可以确保数组的元素都是类型的实例,是类型安全的,所以往集合中添加元素是合法的。回想上界通配符,它是只读的,不能添     加不可知类型的元素,可见下界通配符虽是不可知类型的,但是可写,因为可以保证类型安全。

        22、下界通配符不能实现只写的集合,如List<? super Integer>,它的get()方法一样返回Object。

        23、java.lang.Enum类的标签:public class Enum<E extends Enum<E>> implements Comparable<E>,Serializable
        Enum是可比较的,其元素本身必须是Enum类型,而且他们的子类可以同他们的父类比较。

  • 相关阅读:
    2491 玉蟾宫
    1704 卡片游戏
    1020 孪生蜘蛛
    1215 迷宫
    3149 爱改名的小融 2
    1316 文化之旅 2012年NOIP全国联赛普及组
    1664 清凉冷水
    157. [USACO Nov07] 奶牛跨栏
    [SCOI2005]繁忙的都市
    【NOIP2014模拟赛No.1】我要的幸福
  • 原文地址:https://www.cnblogs.com/liclBlog/p/15349541.html
Copyright © 2011-2022 走看看