Java—泛型详解和使用

1 泛型介绍

1.1 泛型的出现

  泛型的出现还得从集合说起,没有泛型的时候,我们将一个对象存入集合时,集合不care这个对象的数据类型是什么,当我们再次从这个对象取出来的时候,对象的编译类型会变成Object类型,这时候我们就需要强制类型转换,但是这种行为每次都要去指定类型进行强制转换,并且有可能强制转换不了,比如我存的是Integer类型,误转换为String类型,那就可能会引发ClassCastException异常。
  当Java 5引入了一个叫做“参数化类型”的概念后,我们可以在创建集合时去指定集合,这样我们再从集合取出数据时,这个数据就是我们当初指定的类型,不会出现需要强制类型转换的情况了。这种参数化类型就是泛型

1.2 泛型在集合中的使用示例

  在集合接口或者类后面增加尖括号<>,里面注明数据类型,创建这个集合后,这个集合就只能存储这个特定的数据类型对象。
  从Java 7开始,在使用带泛型的接口、类定义变量,我们调用构造器创建对象时构造器后面不需要带完整的泛型信息,只需要带一对尖括号<>就行。如List<String> strList = new ArrayList<>();只需要前面声明<String>泛型,后面只需要<>即可。
示例:

public class DemoApplication {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("t1");
        stringList.add("t11");
        Map<String, List<String>> map = new HashMap<>();
        map.put("t", stringList);
        stringList.forEach(str -> System.out.println(str.length()));
        map.forEach((key, value) -> System.out.println(key + "---->" + value));
    }
}

结果:

2
3
t---->[t1, t11]

2 泛型的进阶

  泛型就是允许在定义接口、类和方法时使用类型形参,通过传入实际的类型参数(类型实参),该类型形参会在声明变量、创建对象、调用方法的时候动态指定。类型形参可以在整个接口、类内当作类型使用。这种方式可以动态生成无限个逻辑上的子类(实际物理中没有)。
  无论泛型的类型形参传入的是什么类型实参,系统最后都是当作一个类来处理,内存中也只是占用一块内存空间。如List<Integer> intList = new ArrayList<>();List<String> strList = new ArrayList<>();通过intList.getClass()strList.getClass()得到的是相等的结果,这就表明两个类是相同的。

另: 在静态变量和静态方法声明中不可以使用类型形参。

2.1 泛型接口举例

public interface List<T> {
	void create(T e);
	void delete(T e);
	void update(T e);
	void find(T e);
}

其中,List为例,若形参T传入的是String类型的实参,则会产生一个List<String>,逻辑上是List的子接口,只不过这个接口内的E类型都为String类型。

2.2 泛型类举例

定义泛型类Person

//创建带泛型声明的自定义类
public class Person<T>{

    //使用T类型形参来定义实例变量
    private T info;

    public Person(){}

    //使用T类型形参定义构造器,注意:构造器不用增加泛型声明,直接使用原类名即可;
    public Person(T info) {
        this.info = info;
    }

    public T getInfo() {
        return info;
    }

    public void setInfo(T info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return info.toString();
    }
}

泛型形参传入实参测试:

public class DemoApplication {
    public static void main(String[] args) {
    	
    	//实参传入String类型给T形参,则构造器构造对象时传参为String类型;
		Person<String> personStr = new Person<>("小明");
        System.out.println("姓名: " + personStr.getInfo());
        
    	//实参传入Integer类型给T形参,则构造器构造对象时传参为Integer类型;
        Person<Integer> personInt = new Person<>(28);
        System.out.println("年龄: " + personInt.getInfo());
	}
}

结果:

姓名: 小明
年龄: 28

从上面的实验中,我们可以看出:通过泛型类Person<T>传入实参后,可以生成诸如Person<String>Person<Integer>Person<Float>等多个逻辑子类。

2.3 泛型类无参子类

创建ChinesePerson类继承无参泛型类。

public class ChinesePerson extends Person {
    @Override
    public String getInfo() {
        return super.getInfo().toString();
    }
}

测试:

public class DemoApplication {
    public static void main(String[] args) {
    
        Person<String> chinesePerson = new ChinesePerson();
        chinesePerson.setInfo("中国人");

        System.out.println("Chinese person: " + chinesePerson.getInfo());
	}
}

结果:

Chinese person: 中国人

2.5 泛型类带参子类

创建JiangsuPerson类继承带参泛型类。

public class JiangsuPerson extends Person<String> {
    //重写父类方法,返回值类型需保持一致
    public String getInfo() {
        return "城市信息: " + super.getInfo();
    }
}

测试:

public class DemoApplication {
    public static void main(String[] args) {

        Person<String> jiangsuPerson = new JiangsuPerson();
        jiangsuPerson.setInfo("南京");
        System.out.println(jiangsuPerson.getInfo());

	}
}

结果:

城市信息: 南京

3 泛型的类型通配符

3.1 类型通配符<?>

  通配符?在类型形参中使用后,可以表示各种泛型的父类,如List<?>,即List的类型未知,可以传入任何类型的实参。
  这种List<?>不能直接添加元素,因为无法确定集合内的类型是什么,所以无法添加对象。(除了null,因为null是所有引用类型的实例)
  List<?>通过get()方法返回的一定是Object,所以可以将其赋值给Object类型变量。

3.2 类型通配符上限<? extends xx>

  通过<? extends xx>表示所有xx泛型的父类,如List<? extends Person>表示所有Person泛型List的父类。?代表的是一个未知类型,但这个未知类型又受到extends限制,只能是Person的自身及子类。

3.3 类型形参的上限

  类型形参的上限主要是限制实参只能是形参类型本身或子类。
示例:

public class Person<T extends Number> {
	T info;
	public static void main(String[] args) {
		Person<Integer> person = new Person<>();
		Person<Float> person2 = new Person<>();
	}
}

若上述代码中增加Person<String> person3 = new Person<>();,则编译报错,因为String类型不是Number或子类;通过<T extends xx>限制住形参的上限。

4 泛型方法

4.1 泛型方法定义

  泛型方法就是在声明方法时定义一个或多个类型形参;相比较于普通方法,泛型方法多了类型形参声明,多了<>,若有多个类型形参,使用逗号","隔开,并且在方法修饰符和方法返回值类型中间要有类型形参。

4.2 语法

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

4.3 举例

泛型方法:

public class GenericMethodExample {
    //定义泛型方法,传递T类型形参
    public static <T> void copyArrayToCollection(T[] array, Collection<T> collection) {
        for (T obj : array) {
            collection.add(obj);
        }
    }

    //定义泛型方法,通过类型通配符设置T类型上限
    public static <T> void copyCollectionToCollection(Collection<? extends T> fromCollection,
                                                      Collection<T> toCollection) {
        for (T obj : fromCollection) {
            toCollection.add(obj);
        }
    }
}

入口类:

public class DemoApplication {
    public static void main(String[] args) {
		//泛型方法
        Object[] objects = new Object[]{"10", 12, new Person("hello, i'm from jiangsu")};
        Collection<Object> collections = new ArrayList<>();
        GenericMethodExample.copyArrayToCollection(objects, collections);
        collections.forEach(obj -> System.out.println(obj.toString()));
        
        //泛型方法通过类型通配符设置上限类型形参
        List<Integer> fromCollection = new ArrayList<>();
        fromCollection.add(1);
        fromCollection.add(2);
        fromCollection.add(3);
        Collections.reverse(fromCollection);
        List<Number> toCollection = new ArrayList<>();
        GenericMethodExample.copyCollectionToCollection(fromCollection, toCollection);
        System.out.println("泛型方法上限测试:" + toCollection);
	}
}

运行结果:

10
12
hello, i'm from jiangsu
泛型方法上限测试:[3, 2, 1]

  从上述测试中,我们可以看到<T extends ?这种通配符设置类型形参上限,何时使用通配符,何时使用泛型方法?我们主要通过依赖关系来判断。如果方法中的一个或多个参数之间的类型是有依赖关系的,或者方法的返回值和参数类型有依赖关系,我们就使用泛型方法;反之,没有任何依赖关系的,我们就使用通配符去灵活的使用泛型。

5 泛型构造器

5.1 泛型构造器定义

  在构造器签名中声明类型形参就是泛型构造器。

5.2 语法

public <T> 类名(形参列表){
	//方法体
}

5.3 示例

给GenericMethodExample类定义泛型构造方法

public class GenericMethodExample {
    
    public <T> GenericMethodExample(T t) {
        System.out.println(t);
    }
}

主类入口:

public class DemoApplication {
    public static void main(String[] args) {
        new GenericMethodExample("泛型构造器01");
        new GenericMethodExample(100);

        new <String> GenericMethodExample("显示指定泛型构造器形参T为String类型");
        new <Integer> GenericMethodExample(50);
	}
}

运行结果:

泛型构造器01
100
显示指定泛型构造器形参T为String类型
50

参考《疯狂Java》

发布了97 篇原创文章 · 获赞 127 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Andya_net/article/details/104956020