Java语法基础:泛型(Generic)

1、泛型的作用

泛型:允许在定义类、接口时指定类型形参,这个形参健在声明变量、创建变量的、创建对象的时候再确定的(即传入的实际参数类型,也可以称为类型实参)
泛型出现的版本
泛型出现的原因(即为什么要有泛型)
在jdk1.5之前,集合类像ArrayList、LinkList、Set存放的Object类型的,无法对存入的元素进行自检查,在取数据的需要对数据的强制转换,如果一旦集合中存入了不一致的类型,例如存放integer类型的集合中错误存放String类型,就会出现类型转化错误的异常

使用泛型的原因:
当我们从泛型类的对象中取出值的时候,如果不使用泛型需要的步骤是:

  1. 获取到 Object 类型的值
  2. 将 Object 类型的强制转换为 对应的类型,才能使用该类型的相关方法

使用泛型的好处:

  • 获取到泛型指定的类的值,(这里我们不用进行强转,这一步骤交给了虚拟机,即 JVM 会帮我们转换为指定的类)

从上面的步骤看:我们减少了泛型的强转工作

public static void main(String[] args) {
    
    
		
        List objectList = new ArrayList();
        // 存数据
        objectList .add(5);
        objectList .add(6);
        objectList .add("我是一个字符");
        //取出数据
        for(int i=0;i<objectList.size();i++){
    
    
            // 在取出前两个的时候可是正常
            //在获取字符的时候,会出现ClassCastException异常,因为这里把强制转化成了Integer类型的
            Integer element = (Integer )objectList.get(i);

            System.out.println("正在取出第"+i+"元素:"+element);
        }
    }

在main方法中执行上面的语句会出现这样的异常ClassCastException

在jdk1.5之后,使用了泛型之后在上面的代码中片段使用泛型声明了intListInteger类型的,在编写阶段就进行检查,intList.add(“我要插入一个字符串”); 这里插入的是一个字符串,无法通过编译。


public static void main(String[] args){
    
    
	// 这是jdk7之前的写法
	List<Integer> intList = new ArrayList<Integer>();
	// 这是jdk7之后的写法,因为jdk7 的新特性类型推断
	List<Integer> intList = new ArrayList<>();
	// 可以通过编译的
	intList.add(5);
	intList.add(6);
	// 不能通过编译的,在编写阶段就会报错的
	intList.add("我要插入一个字符串");
}

上面的方法在编译阶段就报错,说明集合要求的是Integer类型,传入的是字符串类型
编译阶段就进行报错

上面如何使用泛型

2、泛型的使用

常见的泛型主要作用在普通类

2.1、定义泛型接口、泛型类

定义泛型接口:
常用 TKV等大写的字母去表示泛型,就可以在整个类中将 TKV当作一种类型去使用,可以用于成员变量的定义、返回值、形参上使用

ListIteratorMap的定义来看如何定义一个泛型接口

// 定义接口时指定了一个类型参数,参数名用E表示
// List接口
public inteface List<E>{
    
    
	
	void add(E data);
	// 这里使用了下面的泛型接口
	Iterator<E> iterator();

}
// Iterator接口
interface Iterator<E> {
    
    
	E next();
	boolean hasNext();
}
// Map接口
interface Map<K,V>{
    
    
	Set<K> keySet();
	V put(K key, V value);
}

定义方型类

定义一个自己的泛型类: BaseBean<T>

package learn.demo.generic;

public class BaseBean<T> {
    
    

    private  T value;

    public BaseBean() {
    
    
    }

    public BaseBean(T value) {
    
    
        this.value = value;
    }

    @Override
    public String toString() {
    
    
        return "BaseBean{" +
                "value=" + value +
                '}';
    }

    public T getValue() {
    
    
        return value;
    }

    public void setValue(T value) {
    
    
        this.value = value;
    }
}

2.2、从泛型类派生子类

当我们为带泛型申明的接口、类创建实现类的时候(从泛型类派生子类)

下面的这代码是错误的
在这里插入图片描述

一般是继承父类的泛型,要么省略不写要么指定泛型的类型

情况一:省略不写

package learn.demo.generic;
// 情况一:省略不写
public class SubBean extends BaseBean{
    
    
    // ...
}

情况二:要么指定泛型的类型

package learn.demo.generic;
// 
public class SubBean extends BaseBean<String>{
    
    
    // ...
}

3、类型通配符 ?

当我们使用一个泛型类的时候,应该为该泛型类传入一个实际参数,如果没有的话,就会引起泛型警告。

例如:在我们使用String的容器ArrayList的时候:

	/**
	* 建议传入泛型的类型
	* 这样比较清晰知道该ArrayList集合中的类型
	*/
	ArrayList<String> srtList = new ArrayList<String>();

	// 下面的方式不建议
	ArrayList list = new ArrayList();

3.1、泛型统配符出现的原因

先看一下这里错误的代码:
在这里插入图片描述
我们都知道ObjectString 的父类,但是当我们在写出上面的代码的时候,IDEA却提示我们该语句是无法通过编译的,说明了虽然Object是String的父类,但是ArrayList<Object> 却不是ArrayList<String>的父类

如果我们要使用一个来表示各种ArrayList的父类呢,这种情况就是泛型通配符 ?出现的原因。
在这里插入图片描述

3.2、设定类型通配符的上限

使用List<?> 表示的是任意泛型List的父类,但是如果我们不想然List<?>是任何泛型List的父类,只想让它表示某一类泛型

例如:只能是java.lang.Number的子类 对象的泛型List,这样就需要对List<?> 设定一个上限。

泛型通配符的上限用在变量定义上

@Test
    public void testGenericTag(){
    
    

        /**
         * ArrayList<Object> objects = new ArrayList<String>(); 错误
         * */
        ArrayList<?> objects = new ArrayList<String>();

        /**
         * ArrayList的泛型只能是Number的子类
         * */
        ArrayList<? extends Number> numbers = new ArrayList<Integer>();


    }

泛型通配符的上限用在形参上

	/**
     * 只能将Integer集合的元素添加到其父类的集合中
     * */
    public void addToSuperList(List<Integer> subList,List<? super Integer> superList){
    
    
        superList.addAll(subList);
    }
	
 	@Test
    public void testAddToSuperList(){
    
    
        List<Integer> integers = new ArrayList<>();
        // Object 是 Integer 的父类
        List<Object> objects = new ArrayList<>();
        // Number 是 Integer 的父类
        List<Number> numbers = new ArrayList<>();
        // String 不是 Integer 的父类
        List<String> strings = new ArrayList<>();

        TestGeneric testGeneric = new TestGeneric();
        // 编译通过
        testGeneric.addToSuperList(integers,objects);
        // 编译通过
        testGeneric.addToSuperList(integers,numbers);
        // 编译不通过,因为String 不是 Integer 的父类
        testGeneric.addToSuperList(integers,strings);

    }

3.3、设定类型通配符的下限

泛类型通配符的下限用在变量定义上


    @Test
    public void testGenericTag(){
    
    
    	// ...
        /**
         * ArrayList的泛型只能是Integer的父类
         * */
        ArrayList<? super Integer> integers = new ArrayList<Number>();

    }

泛型通配符的上限用在形参上

/**
     * 	功能:将List<Integer> 和 List<Double> 等 Number 子类的集合中数据中存放到父集合中,
     * 但是要求List<String>的集合不能存放
     */
    public void addNumberList(List<Number> superList,List<? extends Number> subList){
    
    
        superList.addAll(subList);
    }
    
	 @Test
    public void testAddNumberList(){
    
    
        List<Integer> integers = new ArrayList<>();
        // Object 是 Integer 的父类
        List<Double> doubles = new ArrayList<>();
        // Number 是 Integer 的父类
        List<Number> numbers = new ArrayList<>();
        // String 不是 Integer 的父类
        List<String> strings = new ArrayList<>();

        TestGeneric testGeneric = new TestGeneric();
        // 编译通过
        testGeneric.addNumberList(numbers,doubles);
        // 编译通过
        testGeneric.addNumberList(numbers,integers);
        // 编译不通过,因为Number 不是 String 的父类
        testGeneric.addNumberList(numbers,strings);
    }

4、定义泛型方法

在定义类的方法和属性的时候,在定义类、接口时使用的泛型可以当成普通类型来使用;但是在一些没有携带泛型的时候,我们也可以自己定义泛型的形参。

泛型方法的定义

修饰符 <T,V> 返回值类型  方法名(形参列表){
    
    
	// 方法体...
}

/**
* 示例:
* 输出该元素在List中第一次出现的索引,如果没有出现就返回-1
*/
public static <T> int printlnIndex(T t ,List<T> tList){
    
    
	int index = -1;
	for(int i; i<tList.size(); i++){
    
    
		T item = tList.get(i);
		if(t.equals(item)){
    
    
			index = i;
			break;
		}
	}
	return index;
}

使用泛型通配符

	public void test(List<?> list){
    
    
		for (int i = 0; i < list.size(); i++ ){
    
    
			Sysetm.out.printlen(list.get(i));
		}
	}

泛型方法:
语法:修饰符 <K,V> 返回值类型 方法名(参数列表…){ … }

	static <T> void tranArrayToList(T[] arr,List<T> list){
    
    
		for(int i = 0; i< arr.length,i++){
    
    
			list.add(arr[i]);
		}
	}

备注:泛型方法是定义为使用静态的

设置类型通配符的上限

	public void test1(List<? extends Number> numList){
    
    
		for (int i = 0; i < list.size(); i++ ){
    
    
			Sysetm.out.printlen(list.get(i));
		}
	} 

设置类型通配符的下限

	public void test2(List<? super Integer> numList){
    
    
		for (int i = 0; i < list.size(); i++ ){
    
    
			Sysetm.out.printlen(list.get(i));
		}
	} 

4、泛型擦除和转换

泛型信息只存在于代码编译阶段,在进⼊ JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除

在类中定义的泛型在jvm中执行最终都是Object,即所有泛型在jvm中执行的时候,都是以Object对象存在的,加泛型只是一种代码的规范,避免在可开发过程再次强转。

猜你喜欢

转载自blog.csdn.net/Hicodden/article/details/106056585