Java面向对象_30 泛型


泛型概念

  • 泛型(Generics)本质上就是提供类型的“类型参数”,它们也被称为参数化类型(parameterized type)或参量多态(parametric polymorphism);
  • 也指具有在多种数据类型上皆可操作的含义;
  • 声明时用参数化类型代替,使用时指定具体类型;
  • GJ(Generic Java)是对Java语言的一种扩展,用GJ编写的程序看起来和普通Java程序基本相同,只不过多了一些参数化的类型同时少了一些类型转换;
    实际上,编译时这些GJ程序也是首先被转化成一般的不带泛型的Java程序后再进行处理的;
  • 泛型的特点:类型安全、向后兼容、层次清晰、性能收益

泛型类

  • 泛型类就是具有一个或多个类型参数的类;
  • 类型参数就跟在方法中普通的参数一样。就像一个方法有“形参”来描述它操作的参数的种类,一个泛型也有“形式类型参数”;
  • 当一个方法被调用,“实参” 替换“形参”,方法被执行;
  • 当一个泛型声明被调用,“实际类型参数” 取代“形式类型参数”;
  • 类型参数通常用单个大写字母命名;
    一般为:
    E表示集合的元素类型,
    T表示任意类型,
    K,V表示表的键和值;

要注意,泛型参数一定是类类型,不能是基本数据类型;泛型类不能继承Throwable及其子类。

泛型类的定义

public class Structure<E> {
    private E element;

    public E getElement() {
        return element;
    }

    public void setElement(E element) {
        this.element = element;
    }
}
public class StructureTest {
    public static void main(String[] args) {
        Structure structure = new Structure();
        structure.setElement(new Object()); //没有指定泛型默认为Object

        Structure<String> structure1 = new Structure<String>();
        structure1.setElement("Hello");
        structure1.setElement(20); // 后期添加的元素的类型与泛型不一致会报编译异常

		Structure<int> structure2 = new Structure(); //泛型只支持引用类型,基本类型会报编译错误
		
		Structure<int[]> structure2 = new Structure(); //泛型支持类、接口、数组

    }
}

泛型接口

public interface Testable<T> {
    T show();
}
public class TestImpl implements Testable<String>{ //可以指定类型
    public String show() { 
        return "Hello!";
    }
}

泛型方法

实现类可以继承泛型接口的特性

public class TestImpl<E> implements Testable<E> {

    public <T> T show() {
        return null;
    }
}

泛型接口也可以直接使用

public interface Testable<T> {
    //该T只在当前方法内有效,注意类名上的T和其他方法属性上的T与该方法的T无关
    <T> T show();
    
    //该方法上的T与其他的T冲突时,生效的是当前方法的T
    <T> T show(T t); 
}
public class TestImpl implements Testable<Integer>{
    public <String> String show() {
        return (String) "Hello";
    }

    public <String> String show(String t) {
        return t;
    }
}

如何使所有泛型都生效?使其命名不同就可以了。

public interface Testable<E> {
    <T> T show(T t,E e);
}

通俗理解泛型

在谈到泛型应用时,我个人遇到使用泛型的场景大都是与集合指定类型相关,因此我有个建议就是查看Java的ArrayList、HashMap等集合的源码,看看他们是怎么写的。

上面的关于泛型的抽象概念你看起来可能一知半解,不是很清晰,不过没关系,下面的3个例子认真看着,要注意注释的内容,它能帮助你快速掌握泛型的使用。

例1:List集合应用泛型

首先我们来谈谈泛型是如何使用的,我拿ArrayList集合举例。

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		//1.ArrayList<E>的使用方式为ArrayList<类型>
		//2.List是接口,这意味着除了类,接口也可以使用泛型
		//3.当数据类型声明时指定了泛型时,创建对象时就不需要再加泛型了
		List<String> list = new ArrayList<>();
		//4.当泛型指定了类型,如指定了String类型,那么就只能接收String类型的对象
		list.add("Hello");
//		list.add(34);//报编译错误
		//5.集合中不能存放基本数据类型,因此在指定泛型时,需要使用对应的包装类
//		ArrayList<int> list1 = new ArrayList<>();//报编译错误
		ArrayList<Integer> list1 = new ArrayList<>();
	}

}

例2:树的结点

如果你刷过算法题,我想你一定见到过二叉树,但是像leetcode这样的平台提供的题目,一般都是用int作为结点的value,这样太固化了,不容易使用。
这里我就把value加上泛型,就可以指定对象类型了。

public class TreeNode<T> {
    /**
     * 在TreeNode指定了类型T,那么类中的T就代表了这个类型
     * value是结点中的值
     * 这是一个二叉树的结点,所以left和right表示左右叶
     */
    private T value;
    TreeNode<T> left;
    TreeNode<T> right;

    public TreeNode(T e) {
        this.value = e;
        this.left = null;
        this.right = null;
    }

    public T getValue() {
        return value;
    }

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

使用TreeNode构建一棵二叉树:

    public static void main(String[] args) {
    	//结点指定为String,还可以为其他类型
        TreeNode<String> node1 = new TreeNode<>("Head");
        TreeNode<String> node2 = new TreeNode<>("Left");
        TreeNode<String> node3 = new TreeNode<>("Right");
        node1.left = node2;
        node1.right = node3;
    }

如果上面的代码你都看懂了,我想你已经理解了泛型的作用了。

例3:手写一个Map集合

public class HashMap<K,V> { //泛型可以指定多个,用逗号分隔
    /**
     * 定义键值对数组,并初始化容量
     * HashMap底层默认容量是16
     * HashMap中是依赖Entry对象来存储键值对的
     */
    private Entry[] entries = new Entry[10000];

    class Entry<K,V>{
        K k;
        V v;
        public Entry(K k,V v){
            this.k = k;
            this.v = v;
        }
    }

    /**
     * 使用hash算法,%
     * 通过key的hash值对entries数组长度取余,得到的结果就是该键值对在数组中存储的位置
     * @param k
     * @param v
     */
    public void put(K k,V v){
        int index = k.hashCode() % entries.length;
        entries[index] = new Entry(k,v);
    }

    /**
     * 通过key的hash值对entries数组长度取余,得到的结果就是该键值对在数组中存储的位置
     * 然后直接获取到该位置的value即可
     * 这种方式的只需要查询一次,时间复杂度降低到了O(1),效率提高
     * @param k
     * @return
     */
    public V get(K k){
        int index = k.hashCode() % entries.length;
        return (V)entries[index].v;
    }
}

使用自定义的HashMap:

    public static void main(String[] args) {
        HashMap<Integer, String> map = new HashMap<>();
        map.put(5,"value=5");
        System.out.println(map.get(5)); //value=5
    }

最后,我们总结,泛型一般应用于指定类中某些变量的数据类型,它解决的是数据类型的安全性问题。

类型通配符

  • 类型通配符<?>,一般用于接受使用,但不能够添加;
  • List<?>表示元素类型未知的list,它的元素可以匹配任何类型;
  • 带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中。

在正常情况下,每种类型只能接受对应类型的泛型

    public static void main(String[] args) {
        List<String> stringArrayList = new ArrayList<String>();
        List<Integer> integerArrayList = new ArrayList<Integer>();
        printList(stringArrayList);
        printList(integerArrayList); //会报错,原因是泛型内的对象类型不匹配
    }

    public static void printList(List<String> list){
        
    }

如果使用这个通配符,就可以接受任意类型

    public static void main(String[] args) {
        List<String> stringArrayList = new ArrayList<String>();
        printList(stringArrayList);
    }

    public static void printList(List<?> list){  //<?>表示可以接受任何类型,与(List list)等价
        
    }

另外,该通配符只能访问,不能添加。

    public static void printList(List<?> list){
        list.add(0); //会报错,原因是不能识别该添加什么类型
        Object o = list.get(0); //可以访问,但返回类型是Object类型
    }

泛型通配符可以指定接受参数的范围。

public class Father {
}
public class Son extends Father{
}

在这里插入图片描述
Father是Son的父类, <? extends Father> 表示只能接受Father类及其Father的子类,其他类都不通过。

    public static void main(String[] args) {
        ArrayList<Son> list1 = new ArrayList<Son>();
        ArrayList<Object> list2 = new ArrayList<Object>();
        printList(list1); //通过
        printList(list2); //报错
    }

    public static void printList(List<? extends Father> list){
        //表示只能接受Father以及Father子类的类型
    }

<? super Son>表示只能接受Son类及其Son的父类,其他类都不通过。

    public static void main(String[] args) {
        ArrayList<Son> list1 = new ArrayList<Son>();
        ArrayList<Object> list2 = new ArrayList<Object>();
        printList(list1); //报错,Son不是Father的父类
        printList(list2); //通过,Object是所有类的父类
    }

    public static void printList(List<? super Father> list){
        //表示只能接受Father以及Father子类的类型
    }

泛型的擦除机制

当将一个带泛型的集合放到没有泛型的集合中,泛型会被擦除。

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        List list1 = list;
        list1.add(new Object()); //泛型已经被擦除了
    }

泛型只是在编译阶段限制传递的类型,而在运行阶段会擦除掉泛型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值