Java,泛型

208 阅读7分钟

泛型的理解:

泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或某个方法的返回值或参数的类型。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。

集合中不使用泛型可能会:

·类型不安全,因为add( )在没有泛型时参数是Object类型的意味着任何类型的对象都可以添加成功。

·需要频繁地进行强转操作。可能会出现类型转换异常。

在集合中使用泛型:

在ArrayList中使用泛型:

List<Integer> ll = new ArrayList<Integer>();
ll.add(78);
ll.add(89);
ll.add(80);
ll.add(34);


Iterator<Integer> ii = ll.iterator();
while(ii.hasNext())
{
    Integer i = ii.next();
    int score = i;
    System.out.println(i);
}

此处用泛型限制ll集合中只能添加Integer类型的参数。

new对象时的后面的<>里的泛型的类型可以不写,因为会通过前面的声明处的泛型类型自行推断。

在Map中使用泛型:

//jdk7的特性,类型推断,即以下的后面的尖括号不用写泛型类型,会自行推断。
        HashMap<String,Integer> map = new HashMap<>();
        map.put("aa",11);
        map.put("bb",22);
        map.put("cc",33);


//        Iterator<Map.Entry<String,Integer>> ii = map.entrySet().iterator();
//        while(ii.hasNext())
//        {
//           Map.Entry<String,Integer> ee = ii.next();
//            System.out.println(ee.getKey() + "-->" + ee.getValue());
//        }


        //var也属于类型推断。
        var ii = map.entrySet().iterator();
        while(ii.hasNext())
        {
            var ee = ii.next();
            System.out.println(ee.getKey() + "-->" + ee.getValue());
        }
    }

此处的map的调用entrySet之后的返回值对象的类型应该是Map.Entry<String,Integer>类型,所以泛型里的类型应该是Map.Entry<String,Integer>,在新特性中,可以使用var代替声明,进行类型推断。

jdk5.0中,集合框架在声明接口和实现类时,使用了泛型,在实例化集合对象时,如果没有使用泛型,则认为操作的时Object类型的数据。如果使用了泛型,则需要指明泛型的具体类型。一旦指明了泛型的具体类型则在集合的相关方法中凡是使用泛型的位置,都替换为具体的泛型类型。

在接口中使用泛型:

例如,在声明处实现Comparable和Comparator时,加上<>,并指明泛型类型,相应的CompareTo方法和Compare方法的参数也就与泛型的参数一致,只能限制为指定的类型。

自定义泛型类/接口与自定义泛型方法

自定义类/接口:

class A{

}

interface B{

}

说明(以泛型类为例):

泛型类实例化时,若不指明相关的泛型参数类型,则默认为泛型参数的类型为Object类型。实例化时,可以指明泛型参数的类型,一旦指明了泛型的类型,则在泛型类中使用泛型参数的位置,都替换为指定的类型。

关于泛型类的子类:

若父类Order的声明为:

public class Order

关于泛型类的子类的声明,主要有五种情况(以下的父类都是指某个泛型类):

①子类和父类名处都不写<> :

public class SubOrder extends Order

此时的SubOrder不是泛型类,相当于继承已经确定了泛型参数类型为Object的Order。

②子类名处不写<>,父类名处的<>里指明类型。

public class SubOrder2 extends Order

也不是泛型类,因为继承的是已经指明了泛型参数类型为Integer的Order,已经确定了指定的地方的类型就是Integer。

③子类和父类名处都写<>,且其中内容都是相同的不指明类型的参数(比如都为T)。

public class SubOrder3 extends Order

此时的子类是泛型类,此时的两个T是相对应的。父类中用泛型参数T限制的地方继承到子类还是按相应T的类型来确定。

④子类名和父类名处都写<>,子类中的<>是不指明类型的参数,父类中的<>指明参数类型。

public class SubOrder4 extends Order

此时的子类是泛型类,继承时,父类中的使用泛型参数的地方继承到子类后,泛型参数变为了上面extend声明后面的<>中的类型。而是指子类中其他的地方要用到泛型来限制类型,与extend后面的<>的内容无关。

⑤子类和父类名处都写<>,且其中有内容都是相同的不指明类型的参数的基础上,子类名处的<>中,用逗号分隔,加上其他的不指明类型的参数。

public class SubOrder5<T,V> extends Order

此时的前后两个T是相对应的。而V是子类中除T之外有其他的地方要用到泛型来限制类型,父类中继承过来的需要用T来限制类型的由T决定,V则限制子类中相应的其他地方的类型。

注意点

①声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。

②创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。

③如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。

④泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。

⑤除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。如果给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。还可以在现有的父类的泛型参数的基础上,新增泛型参数。

注意点:

①泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

②JDK7.0开始,泛型的简化操作:ArrayList flist= new ArrayList<>( );

③如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

④不能使用new E[ ]。但是可以:E[ ] elements =(E[ ])new object[capacity];

参考:ArrayList源码中声明:Object[ ] elementData,而非泛型参数类型数组。

⑤在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。

⑥异常类不能是带泛型的。

自定义泛型方法:

格式:

权限修饰符 返回值类型 方法名(形参列表){         //通常在形参列表或返回值类型的位置会出现泛型参数T

}

说明:

①声明泛型方法时一定要添加泛型参数

②泛型参数在调用时指明具体的参数类型

③泛型方法可以根据需要声明为静态的

④泛型方法在泛型类或不是泛型类都可以。

泛型在继承上的体现以及通配符的使用:

泛型在继承上的体现:

G< superA>与G< A>(若superA是A的父类)并没有子父类的关系,它们是两个并列的无关联类,不能体现多态性。

SuperA< E>与A< E>的关系是子父类的关系,可以体现多态性。

通配符的使用:

通配符:?

格式:A<?>     (若A是一个泛型类)

此格式代表不确定A的泛型类型的类型,此类型是A的所有确定泛型类型的类型的父类,可以与其他确定泛型类型的类型体现多态性。

关于使用通配符的类的对象的操作:

读取数据:由于读取的数据类型不确定,所以读取的数据的操作都是返回Object类型。

写入数据:由于数据类型不确定,使用通配符的类的对象不能写入数据(特例,可以添加null),只能通过多态性接收其他对象的数据。

接受其他对象的数据:可以看作将A的对象赋值给G<?>的引用。

有限制条件的通配符:

A<? extend B> :可以将A<B的子类或B类>的对象赋值给A<? extend B>的引用。

A<? super B> :可以将A<B的父类或B类>的对象赋值给A<? super B>的引用。