zoukankan      html  css  js  c++  java
  • 泛型的定义、用法与类型通配符的使用方式

    泛型是什么?

    泛型本质是指类型参数化。意思是允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型,所有使用该泛型参数的地方都被统一化,保证类型一致。如果未指定具体类型,默认是Object类型。集合体系中的所有类都增加了泛型,泛型也主要用在集合。
     

    泛型的定义

    泛型类:public class Demo<T> {} ,T表示未知类型。
    泛型接口:public interface ImplDemo<T,V>{} ,和定义类一样(接口就是一个特殊类)。
    泛型方法:public <T>  void demo1(T name){System.out.println(name);} , public <T> T demo2(T t){ return t;}
     

    泛型的好处

    1. 编译时确定类型,保证类型安全,避免类型转换异常。
    2. 避免了强制类型转换。
    3. 代码利于重用,增加通用性。

    泛型的限制和规则

    • 泛型的类型参数只能是引用类型,不能使用值类型。
    • 泛型的类型参数可以有多个。
    • 泛型类不是真正存在的类,不能使用instanceof运算符。
    • 泛型类的类型参数不能用在静态申明。
    • 如果定义了泛型,不指定具体类型,泛型默认指定为Ojbect类型。
    • 泛型使用?作为类型通配符,表示未知类型,可以匹配任何类型。因为是未知,所以无法添加元素。
    • 类型通配符上限:<? extends T>,?代表是T类型本身或者是T的子类型。常用于泛型方法,避免类型转换。
    • 类型通配符下限。<? super T>,?代表T类型本身或者是T的父类型。
    • 除了通配符可以实现限制,类、接口和方法中定义的泛型参数也能限制上限和下限。
     

    泛型代码实例

    接下来,使用代码来演示泛型的用途,建议使用目录查看具体内容。
     

    集合类演示泛型

    		//未指定泛型
    		TreeSet ts = new TreeSet();
    		ts.add(10);
    		ts.add(25);
    		ts.add("30");
    		System.out.println(ts);//运行时报错,类型转换异常
    		
    		//mode 2
    		TreeSet<Integer> ts2 = new TreeSet<>();
    		ts2.add(10);
    		ts2.add(25);
    		ts2.add("30"); //编译器提示报错,无法添加非Integer类型
    	

    未使用泛型时,可以添加任意元素,因为TreeSet会比较每一个元素,所以运行时会引发类型转换异常。使用泛型后,只能添加同一个类型,所以不会出错。

    定义泛型类

    public class Person<T> {
    	private T name;//类型是未知的
    	
    	public Person(T name) {
    		this.name = name;
    	}	
    	
    	public T getName() {
    		return name;
    	}
    	
    	public void sexName(T name) {
    		this.name = name;
    	}
    
    }
    在上面实例中,Person类定义成泛型类,成员变量name的类型指定为T,表示未知类型。实例化该类对象后,可以看到name的类型是Object,表示可以接收任何类型。
    		Person p = new Person(10);	//new Person(Object name)
    加上泛型后
    		//使用泛型两种方式
    		Person<String> ps = new Person<String>(); //new Person<String>(String name)
    		Person<String> ps = new Person<>();//new Person<>(T name)
    第一种,会直接确定参数类型是什么,而第二种的参数类型是T ,但如果加入的非String类型,编译器会检查并报错。两者区别不大。在JDK1.7之后使用第二种,会自动检查泛型,可以省略后部分<>的泛型参数,建议使用第二种。


    定义泛型接口

    interface A<T>{
    	void display(T value); 
    	T getValue(T v);
    }
    
    //未对泛型接口指定具体类型
    public class Person implements A{
    
    	@Override
    	public void display(Object obj) {
    		System.out.println();
    	}
    
    	@Override
    	public Object getValue(Object v) {
    		return null;
    	}
    	
    }
    如果我们定义了泛型,不指定具体类型,默认就是Object类型。当我们为泛型接口指定具体类型后,代码如下:
    //泛型接口
    interface A<T>{
    	void display(T value); 
    	T getValue(T v);
    }
    
    //为泛型接口指定具体类型
    public class Person implements A<String>{
    	@Override
    	public void display(String value) {
    	}
    	@Override
    	public String getValue(String v) {
    		return null;
    	}
    }
    
    泛型接口指定具体类型后,所有使用了该泛型参数的地方都被统一化。其实泛型接口和泛型类是一样的写法。

    定义泛型方法

    先使用常规方法进行对比。

    	public static void main(String[] args) {
    		int[] arr = new int[] {1, 8, 15, 6, 3};
    		double[] douArr = {10.5, 25.1, 4.9, 1.8};
    		String[] strArr = {"我","是","字","符","串"};
    		forArr(strArr);
    		
    	}
    	
    //遍历数组的重载方法,支持int和double类型	
    	public static void forArr(int[] arr) {
    		for(int i=0; i<arr.length; i++) {
    			System.out.println(arr[i]);
    		}
    	}
    	//重载了
    	public static void forArr(double[] arr) {
    		for(double d : arr) {
    			System.out.println(d);
    		}
    	}
    	//……
    	//……
    如上所示,如果想遍历Stirng类型数组,那就还要再次重载代码,如果是八种类型都有,代码量非常庞大。使用泛型方法全部通用,代码如下:
    	public static void main(String[] args) {
    		Integer[] arr =  {1, 8, 15, 6, 3};
    		Double[] douArr = {10.5, 25.1, 4.9, 1.8};
    		String[] strArr = {"我","是","字","符","串"};
    		
    		forArrGenric(strArr);
    		
    	}
    	//泛型方法
    	public static <T> void forArrGenric(T[] arr) {
    		for(int i=0; i < arr.length; i++) {
    			System.out.println(arr[i]);
    		}
    	}
    只需定义一个泛型方法,根据运行时传入的参数类型,动态地获取类型,就能做到遍历所有类型数组。但需要注意,泛型的类型参数只能是引用类型,值类型无法在泛型中使用,所以上面的数组都改成了引用类型。值类型需要使用对应的包装类类型。
     

    使用类型通配符

    使用之前,先使用常规方式来进行比较。
    	public static void main(String[] args) {
    		HashSet hs = new HashSet();
    		hs.add("A");
    		hs.add("QQ");
    		hs.add("Alipay");
    
    		new Test2().test2(hs);
    	}	
    	//普通遍历Set集合,Set是泛型接口,没指定具体泛型参数会引起警告
    	public void test(Set s) {
    		for(Object o : s)
    			System.out.println(o);
    	}	
    	//增加泛型参数,参数类型是Set<Object>
    	public void test2(Set<Object> s) {
    		for(Object o : s)
    			System.out.println(o);
    	}
    方法参数的Set集合使用了泛型参数<Object>,方便将参数类型转换成Object,看起来没什么错。当传入一个带泛型参数的集合时,会出现编译错误。代码如下:
    	public static void main(String[] args) {
    		HashSet<String> hs = new HashSet();
    		hs.add("A");
    		hs.add("QQ");
    		hs.add("Alipay");
    		new Test2().test2(hs); //error
    	}	
    	//增加泛型参数,参数类型是Set<Object>
    	public void test2(Set<Object> s) {
    		for(Object o : s)
    			System.out.println(o);
    	}
    因为泛型类不是真正存在的类,所以Set<String>和Set<Object>不存在关系,自然无法作为参数传入进去。这时我们就可以使用类型通配符,如下:
    	//使用类型通配符作为类型参数
    	public void test2(Set<?> s) {
    		for(Object o : s)
    			System.out.println(o);
    	}
    Set<?>表示可以匹配任意的泛型Set。虽然可以使用各种泛型Set了。但弊端就是类型未知,所以无法添加元素。还有范围过于广泛,所以这时可以考虑限制的类型通配符。
     

    限制的类型通配符

    上面代码只要是泛型Set都允许被遍历,如果只想类型通配符表示一个类和其子类本身呢?设置类型通配符上限,代码如下:
    public class Test2 {
    	public static void main(String[] args) {
    		ArrayList<Test2> ar = new ArrayList<>();
    		List<Test3> lt = new ArrayList<>();
    		List<String> lStr = new ArrayList<>();
    		demo(ar);
    		demo(lt);
    		demo(lStr); //error
    	}	
    	//限制的类型通配符
    	public static void demo(List<? extends Test2> t) {
    		for(int i = 0; i < t.size(); i++) {
    			System.out.println(t.get(i));
    		}
    	}
    }
    class Test3 extends Test2{}//子类
    <? extends T>:表示类型是T本身或者是T类型的子类类型。

    <? super T>:表示类型是T类型本身或者是T类型的父类类型。叫做类型通配符的下限。使用方式都差不多。

    泛型为何不能应用于静态申明的实例解析

    先给一个例子,在静态变量中和静态代码块中使用泛型。
    public class Test<T> {
    	public static T name;  //error
    	public T sex ;
    	
    	static {
    		T ab; //error
    	}
    }
    
    报出异常:不能使一个静态引用指向一个非静态的类型 T。静态和非静态之分就在于静态是编译时类型,动态是运行时类型。T代表未知类型,如果可以用于静态申明,因为是未知类型,系统没法指定初始值,手动赋值也不行,因为不知道啥类型,只有运行时才可以指定。而泛型存在的意义就是为了动态指定具体类型,增强灵活性和通用性,所以用于静态声明违背了使用原则。为什么实例变量和实例方法可以使用呢?因为当你使用实例变量或者方法时,就说明对象存在了,即代表泛型参数也指定了。未指定具体类型默认是Object类型。
     

    为什么静态方法中可以定义泛型方法呢?

    先给三个实例,我们来慢慢分析。
    public class Test<T> {
    	public static void main(String[] args) {
    		
    	}
    	
    	//泛型方法
    	public T demo1(T t) {
    		return t;
    	}
    	
    	//静态方法使用泛型参数
    //	public static T demo2(T t) { return t;}
    				
    	//定义泛型静态方法
    	public static <W> void demo3(W w) {
    		System.out.println(w);
    	}  
    }
    首先,要明确一点,泛型作用是确定具体类型。先看一个泛型方法,使用了泛型参数T作为返回值,当使用对象时来调用该方法时,T类型会变成具体类型。第二个泛型方法是静态的,使用了T作为返回值和方法参数类型,但是静态方法是属于类的,类直接调用的话,T类型无法指定具体类型,那么该方法就没有意义。所以直接报错。第三个也是静态方法,但是该静态方法是自定义一个泛型参数,并非使用类型参数。所以当传入一个具体类型时,该静态方法的<W>就是具体类型了。两者静态方法的区别就是一个是使用泛型参数,一个是定义泛型方法。
     
     
  • 相关阅读:
    Docker(五)-Dcoker容器
    Docker(二)-Docker安装
    Docker(四)-Dcoker镜像
    Docker(三)-Docker中Image、Container与Volume的迁移
    Docker(一)-Docker介绍
    coredump分析
    linux下生成core dump文件方法
    软件测试流程清单
    软件测试风险清单
    [测试管理]测试周报该如何写
  • 原文地址:https://www.cnblogs.com/fwnboke/p/8529670.html
Copyright © 2011-2022 走看看