JAVA 泛型 入门

什么是泛型

本质而言,指的是参数化类型。
参数化类型很重要,因为使用该特性创建的类,接口以及方法,可以作为参数指定所操作数据的类型。
上面的这个是名词解释,看了是不是一头雾水,跟天书一样。
风.fox
可以这么理解
规定了变量数据类型是数值型,你就必须传输数值型的,传输其他类型的就会错误。

class Box<T>{
    T ob;
    Box(T o){
        ob=o;
    }
    T getob{
       return ob;
    }
    public staic void main(String args[]){
        Box<Integer> test=new Box<Integer>(88);//  主要在这里,规定了 integer 类型,如果是其他类型就错误
        // 88 是数值类型,是正确的,符合要求
        // 如果 改为 "ABC" ,它是字符型的,不符合要求,必然报错
        Box<Integer> test=new Box<Integer>("ABC");//报错
    }
}

泛型形式

class Box<T>{

T是类型参数的名称(可以改变的,例如:A)。
这个名称是实际类型的站位符。
注意T 被包含在<>中。
T是将在创建Box对象时指定的实际类型的占位符。
因此 ob 是传递给T的那种实际类型的对象。
那么,开始的泛型,实际类型是 Integer

泛型只使用对象(Integer,Double,String,Object等等),不能使用基本类型(如:int或char)

如果要使用基本类型,可以使用类型封装器封装基本类型

基于不同类型参数的泛型类型是不同的

带两个或更多类型参数的泛型类

泛型中可以声明多个类型参考。
为了指定两个或更多个类型参数,只需要使用逗号分隔参数列表既可以。

class Box<T,V>{
    T ob1;
    V ob2;
    Box(T o1,V o2){
        ob1=o1;
        ob2=o2;
    }
    T getob1(){
        return ob1;
    }
    V getob2(){
        return ob2;
    }
    void show(){
        System.out.println(ob1.getClass().getName());
        System.out.println(ob2.getClass().getName());
    }
    public static void main(String args[]){
        Box<Integer,String> my=new Box<Integer,String>(88,"fox");
        my.show();
        int v=my.ob1();
        String str=my.ob2();

        System.out.println(v);
        System.out.println(str);
    }
}

命名规则

类型参数的命名有一套默认规则,为了提高代码的维护性和可读性,强烈建议遵循这些规则。JDK中,随处可见这些命名规则的应用。
E - Element (通常代表集合类中的元素)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. – 第二个,第三个,第四个类型参数……
注意,父类定义的类型参数不能被子类继承。

有界类型或通配符

public void boxTest(Box<Number> n){  
             ……  
}

该方法只能接受Box这一种类型的参数,当我们输入一个Box或者Box时会报错,尽管Integer与Double是Number的子类。可是如果我们希望该方法可以接受Number以及它的任何子类,该怎么办呢?
这时候就要用到通配符了,改写如下:

public void boxTest(Box<? extends Number> n){  
             ……  
}

“? extends Number”就代表可以接受Number以及它的子类作为参数。这种声明方式被称为上限通配符(upper bounded wildcard)。
相反地,如果我们希望该方法可以接受Integer,Number以及Object类型的参数怎么办呢?应该使用下限通配符(lower bounded wildcard):

public void boxTest(Box<? super Integer> n){  
        ……  
}  

“? super Integer”代表可以接受Integer以及它的父类作为参数。

如果类型参数中既没有extends 关键字,也没有super关键字,只有一个?,代表无限定通配符(Unbounded Wildcards)。
通常在两种情况下会使用无限定通配符:
(1)如果正在编写一个方法,可以使用Object类中提供的功能来实现
(2)代码实现的功能与类型参数无关,比如List.clear()与List.size()方法,还有经常使用的Class

public static void printList(List<Object> list) {  
    for (Object elem : list)  
        System.out.println(elem + "");  
    System.out.println();  
} 

该方法只能接受List型的参数,不接受其他任何类型的参数。但是,该方法实现的功能与List之中参数类型没有关系,所以我们希望它可以接受包含任何类型的List参数。代码改动如下:

public static void printList(List<?> list) {  
    for (Object elem : list)  
        System.out.println(elem + " ");  
    System.out.println();  
} 
需要特别注意的是,List<?>与List<Object>并不相同,无论A是什么类型,List<A>是List<?>的子类,但是,List<A>不是List<Object>的子类。

例如:
List lb = new ArrayList<>();
List la = lb; // 会报编译错误,尽管Integer是Number的子类,但是List不是List的子类
List与List的关系如下:
这里写图片描述
所以,下面的代码是正确的:

List<? extends Integer> intList = new ArrayList<>();  
List<? extends Number>  numList = intList;  // 不会报错, List<? extends Integer> 是 List<? extends Number>的子类  

下面这张图介绍了上限通配符、下限通配符、无限定通配符之间的关系:
这里写图片描述
编译器可以通过类型推断机制来决定通配符的类型,这种情况被称为通配符捕获。大多时候我们不必担心通配符捕获,除非编译器报出了包含“capture of”的错误。例如:
编译器可以通过类型推断机制来决定通配符的类型,这种情况被称为通配符捕获。大多时候我们不必担心通配符捕获,除非编译器报出了包含“capture of”的错误。例如:

public class WildcardError {  

    void foo(List<?> i) {  
             i.set(0, i.get(0));  //会报编译错误  
}  
} 

上例中,调用List.set(int,E)方法的时候,编译器无法推断i.get(0)是什么类型,就会报错。
我们可以借助一个私有的可以捕获通配符的helper方法来解决这种错误:

public class WildcardFixed {  

    void foo(List<?> i) {  
        fooHelper(i);  
    }  


    // 该方法可以确保编译器通过通配符捕获来推断出参数类型  
    private <T> void fooHelper(List<T> l) {  
        l.set(0, l.get(0));  
    }  

} 

按照约定俗成的习惯,helper方法的命名方法为“原始方法”+“helper”,上例中,原始方法为“foo”,所以命名为“fooHelper”

关于什么时候该使用上限通配符,什么时候该使用下限通配符,应该遵循一下几项指导规则。
首先将变量分为in-变量与out-变量:in-变量持有为当前代码服务的数据,out-变量持有其他地方需要使用的数据。例如copy(src, dest)方法实现了从src源头将数据复制到dest目的地的功能,那么src就是in-变量,而dest就是out-变量。当然,在一些情况下,一个变量可能既是in-变量也是out-变量。
(1)in-变量使用上限通配符;
(2)out-变量使用下限通配符;
(3)当in-变量可以被Object类中的方法访问时,使用无限定通配符;
(4)一个变量既是in-变量也是out-变量时,不使用通配符
注意,上面的规则不适用于方法的返回类型。

擦除(擦拭)

编译JAVA代码时,所有泛型信息被移除(擦除)。
这意味着使用它们的界定类型替换类型参数,如果没有显式地指定界定类型,就使用Object然后应用适当的类型转换(根据类型参数而定),以保持与类型参数所指定类型的兼容性。
编译器偶尔需要为类添加桥接方法,用于处理如下情形:子类中重写方法的类型擦除不能产生与超类方法相同的擦除。对于这种情况,会生成使用超类类型擦除的方法,并且这个方法调用具有子类指定类型擦除方法。
这里让我了解 擦除和桥接原理

class Gen<T>{
    T ob;
    Gen(T o){
        ob=o;
    }
    T getob(){
    return ob;
    }
}
class Gen2 extends Gen<String>{
        Gen2(String o){
            super(o);
        }
        String getob(){
        return ob;
        }
        public static main(String args[]){
            Gen2 strob2=new Gen2("Test");
            System.out.println(strob2.getob());
        }
}

上面是我们自己编写的类。编译器会帮我们自动生成一个桥接方法【如下】。

class Gen2 extends Gen<String>{
        Gen2(String o){
            super(o);
        }
        String getob(){
        return ob;
        }
        //下面这个 是编译器 自动添加的桥接方法
        Object getob(){
        return ob;
        }
        public static main(String args[]){
            Gen2 strob2=new Gen2("Test");
            System.out.println(strob2.getob());
        }
}

使用泛型的一些限制

不能实例化类型参数

class Gen<T>{
    T ob;
    Gen(){
        ob= new T();
    }
}

试图创建T的实例,这是非法的。T在运行时不存在,编译器如何知道创建哪种类型的对象?
编译过程中,擦除了所有类型参数

对静态成员的一些限制

静态成员不能使用在类中声明的类型参数

class Gen<T>{
    static T ob;//错误
    static T Gen(){//错误
        return ob;
    }
}

尽管不能声明某些静态成员,他们使用有类型声明的类型参数,
但是可以声明静态的泛型方法,这种方法可以定义他们自己的类型参数。

对泛型数组的一些限制

不能实例化元素类型为类型参数的数组
不能创建特定类型的泛型引用数组。

对泛型异常的限制

泛型类不能扩展 Throwable,这意味着不能创建泛型异常类

部分来自
http://blog.csdn.net/u012152619/article/details/47253811

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页