zoukankan      html  css  js  c++  java
  • 8.2 深入泛型


    所谓泛型就是允许在定义类、接口、方法时使用类型形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态指定(即传入实际的类型参数,也可叫做类型实参)。

    一、定义泛型接口、类

    下面是Java 5改写的List接口、Iterator接口和Map接口:

    //定义接口时,创建一个泛型形参,该形参名为E
    public interface List<E>
    {
        //在接口里E可以作为类型使用
        //下面方法可以使用E作为参数类型
        void add(E x);
        Iterator<E> iterator();
    }
    
    //定义接口时指定一个泛型形参,该形参名为E
    public interface Iterator<E>
    {
    //在接口里完全可以作为类型使用
        E next();
        boolean hasNext();
        ...
    }
    
    //定义接口时指定两个泛型参数,其形参名为K,V
    public interface Map<K,V>
    {
    //在接口里定义的K,V完全可以作为类型使用
    Set<k> keySet()
    V put(K key,V value)
    ...
    }
    

    泛型的实质:允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可以当作类型使用,几乎所有可使用普通类型的地方都可以使用这种泛型。
    例如使用List类型时,如果为E形参传入String类型实参,则产生一个新的类型:List类型,可以把List想象成E被全部替换成String的特殊List子接口。

    //List<String>等同于如下接口
    public interface List<String>
    {
        void add(String x);
        Iterator<String> iterator();
        ...
    }
    

    注:包含泛型声明的类型可以在定义变量、创建对象(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的主要使用场景)。下面定义一个Apple类,这个Apple类里就可以包含一个泛型声明。

    //定义Apple类时使用泛型声明
    public class Apple<T> 
    {
    	//使用T类型定义实例变量
    	private T info;
    	public Apple(){};
    	//下面使用T类型来定义构造器
    	public Apple(T info)
    	{
    		this.info=info;
    	}
    	public void setInfo(T info)
    	{
    		this.info=info;
    	}
    	public T getInfo()
    	{
    		return this.info;
    	}
    	public static void main(String[] args) 
    	{
    		//传给T形参的是String,所以构造器参数只能是String
    		Apple<String> a1=new Apple<>("苹果");
    		System.out.println(a1.getInfo());//苹果
    		////传给T形参的是Double,所以构造器参数只能是Double或double
    		Apple<Double> a2=new Apple<>(5.67);
    		System.out.println(a2.getInfo());//5.67
    	}
    }
    

    上面程序定义了一个带泛型声明的Apple类,使用Apple类就可以为T形参传入实际类型,这样就可以生成Apple、Apple...形式的多个逻辑子类(物理上不存在)。
    当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。

    二、从泛型类派生子类

    当创建了带泛型声明的接口、类之后,可以为该接口创建实现类,或从该父类派生子类,需要指出的是,当使用这些接口、父类时不能再包含泛型形参。
    例如下面代码时错误的:

    //定义类A继承Apple类,Apple类不能跟泛型形参
    public class A extends Apple<T>{}
    

    方法中的形参代表变量、常量、表达式等数据,本书把它们直接称为形参,或者称为数据形参。定义方法时可以声明数据形参,调用方法时必须为这些数据形参传入实际的数据;依次类时的是,定义类、接口、方法时可以声明泛型形参,使用类、接口、方法时应该为泛型形参传入实际的类型。
    从Apple类派生一个子类:

    //使用Aplle类为T形参传入String类型
    public class A extends Apple<String>
    

    调用方法时必须为所有数据形参传入参数值,与调用方法不同的是,使用类接口、接口时可以不为泛型形参传入实际的类型参数,即下面的代码是正确的:

    //使用Aplle类时,没有为T形参传入类型参数
    public class A extends Apple//省略泛型的形式称为原始类型
    

    如果Apple类派生子类,则在Apple类中所有使用T类型的地方都被替换成String类型,即它的子类将会继承到String getInfo()和void setInfo(String info)两个方法,如果子类需要重写父类的父类的放啊必须注意到这一点。例如:

    public class A1 extends Apple<String>
    {
    	// 正确重写了父类的方法,返回值
    	// 与父类Apple<String>的返回值完全相同
    	public String getInfo()
    	{
    		return "子类" + super.getInfo();
    	}
    	/*
    	// 下面方法是错误的,重写父类方法时返回值类型不一致
    	public Object getInfo()
    	{
    		return "子类";
    	}
    	*/
    	public static void main(String[] args)
    	{
    		var a=new A1();
    		System.out.println(a.getInfo());
    	}
    }
    

    如果使用Aplle类时没有传入实际的类型(即使用原始类型),Java编译器可能发出警告:使用了未经检查或不安的操作————这就是泛型警告。如果需要看到该警告提示的更详细的信息,则可以通过为javac命令增加-Xlint:unchecked选项来实现。此时系统会把Apple类里的T形参当成Object类型处理:

    public class A2 extends Apple 
    {
    	//重写父类的方法
    	public String getInfo()
    	{
    		//下面重写将报错,原始类型是Object.错误: 不兼容的类型: Object无法转换为String
    		//return super.getInfo();//方法返回的值是Object
    		return super.getInfo().toString();
    	}
    }
    

    三、并不存在泛型类

    可以把ArrayList类当成ArrayList的子类,事实上,ArrayList类是一种特殊的ArrayList类:该ArrayList对象只能添加String对象作为集合元素。但实际上系统并没有为ArrayList生成新的class文件,而且也不会把ArrayList当成新类使用。

    import java.util.*;
    public class ArrayListTest
    {
    	public static void main(String[] args)
    	{
    		//分别创建List<String>对象和List<Integer>对象
    		List<String> l1=new ArrayList<>();
    		List<Integer> l2=new ArrayList<>();
    		System.out.println(l1.getClass()==l2.getClass());//true
    	}
    }
    

    不管为泛型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,再内存中也只有一块内存空间,因此在静态方法、静态初始化块或者静态变量(它们都是类相关的)的声明和初始化块不允许使用泛型。下面程序演示这种错误:

    import java.util.*;
    public class R<T>
    {
    	public T age;
    	//不能在静态变量声明中使用泛型形参
    	//public static T info;//错误: 无法从静态上下文中引用非静态 类型变量 T
    
    	public void foo(T msg){System.out.println(msg);}
    	
    	//R.java:12: 错误: 不兼容的类型: String无法转换为T
    	/*{
    		age="二十";
    	}*/
    
    	//不能在静态方法中使用泛型形参
    	//public static void bar(T msg){}//错误: 无法从静态上下文中引用非静态 类型变量 T
    	public static void main(String[] args)
    	{
    		R<String> r=new R<>();
    		r.foo("测试泛型");
    	}
    }
    输出:测试泛型
    
  • 相关阅读:
    前端html+css标签简介(可能就我自己看的懂-。-)
    python-day43(正式学习)
    python-day42(正式学习)
    python-day40(正式学习)
    python-day39(正式学习)
    python-day38(正式学习)
    python-day37(正式学习)
    python-day31(正式学习)
    python-day30(正式学习)
    python-day29(正式学习)
  • 原文地址:https://www.cnblogs.com/weststar/p/12591344.html
Copyright © 2011-2022 走看看