Java基础学习记录----泛型使用

本文详细介绍了Java中的泛型概念,包括泛型的历史背景、泛型类的定义与使用、泛型方法、通配符的运用以及泛型在JDK中的常见应用。通过示例代码展示了如何使用泛型来提高代码的类型安全性和重用性,并解释了泛型在实际编程中的重要性。
摘要由CSDN通过智能技术生成

泛型

什么是泛型?
个人间接: 泛型是一种广泛通用的类型;
JDK1.5之前, 可以用Object类来表示一种广泛通用的类型;

package algorithm.list;

public class MyLinkedList {
	
	private ListNode head;
	private int size = 0;		//
	
	public void insertHead(int data){		//插入链表的头部		data就是插入的数据
		ListNode newNode = new ListNode(data);
		//如果原来就有数据呢?
		newNode.next = head;		//栈内存的引用
		head = newNode;
		
		//插入O(1)
	}
	
	public void insertNth(int data,int position){		//插入链表的中间 假设定义在第N个插入 O(n)
		if(position == 0) {		//这个表示插入在头部了
			insertHead(data);
		}else{
			ListNode cur = head;
			for(int i = 1; i < position ; i++){
				cur = cur.next;		//一直往后遍历   p=p->next;  ->是c++里面的往后找指针
			}
			ListNode newNode = new ListNode(data);
			//
			newNode.next = cur.next;		//新加的点指向后面 保证不断链
			cur.next = newNode;			//把当前的点指向新加的点
		}
	}
	/*int a = 1;
	int b = a;
	int a = 2;*/
	
	public void deleteHead(){//O(1)
		head = head.next;
	}
	
	public void deleteNth(int position){//O(n)
		if(position == 0) {
			deleteHead();
		}else{
			ListNode cur = head;
			for(int i = 1; i < position ; i ++){
				cur = cur.next;
			}
			cur.next = cur.next.next; //cur.next 表示的是删除的点,后一个next就是我们要指向的
		}
	}
	
	public ListNode find(int data){//O(n)
		ListNode cur = head;
		while(cur != null){
			if(cur.value == data) break;
			cur = cur.next;
		}
		return cur;
	}
	
	public void print(){
		ListNode cur = head;
		while(cur != null){
			System.out.print(cur.value + " ");
			cur = cur.next;
		}
		System.out.println();
	}

}

class ListNode{
	
	int value;		//值
	ListNode next;	//下一个的指针

	ListNode(int value){
		this.value = value;
		this.next = null;
	}
}

像上面设计的链表中, 结点的值只能存放整形值, 如果需要表示通用类型, 则可以把结点存储的类型改为Object, 即如下所示:

class ListNode{
	
	Object value;		//值
	ListNode next;	//下一个的指针

	ListNode(int value){
		this.value = value;
		this.next = null;
	}
}

这样这个链表就可以存放整形,字符串等; 但是这样也存在一些问题,

public static void main(String[] args) {
		MyLinkedList myList = new MyLinkedList();
		myList.insertHead(5);
		myList.insertHead("XXX");
		ListNode node =myList.get(5);
		Object val = node.getValue();
		int intVal = (int)val;

		//更直接的
		List testList = new ArrayList();
		testList.add("1");
		testList.add("XXX")
		testList.add(2);
		Object val2 = testList.get(2);
		int intVal2 = (int)val2;
	}

这里存在一个强转的过程,容器中存在多种形式的值, 整形,字符串等, 在转换的过程中可能会发生强转异常;

因此,在JDK1.5后, 因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型;

个人理解: Object 可以称为泛型,但是不规范;

泛型的规范使用-自定义泛型结构

/**
 * 自定义泛型类
 *
 */
public class OrderTest<T> {

    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型, 然后创建OrderTest时, 需要指定
    T orderT;

    public OrderTest(){

    };

    public OrderTest(String orderName,int orderId,T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //如下的三个方法都不是泛型方法
    public T getOrderT(){
        return orderT;
    }

    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
    
    //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
    //换句话说,泛型方法所属的类是不是泛型类都没有关系。
    //泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
    public static <E>  List<E> copyFromArrayToList(E[] arr){

        ArrayList<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;

    }
}

  1. 泛型一般用于描述一种通用的算法、数据结构,对象类型其实并不关心。
  2. 可以用一个代号 T 来代表目标类型。
    在Java语言里,泛型的规范写法:
public class  Sample <T>
{
}
  1. 在类的定义里,T代表一个通用的类型,把T称为类型参数
  2. 定义泛型类后,如果属性类型为T, 则泛型属性;
  3. 创建类实例时, 如果不指定参数类型, 则体现为Object,泛型属性值可以随便设置,如果制定了参数类型, 如String, 那么泛型属性值就只能为String,如果使用了其他类型,编译不通过;
    public static void main(String[] args) {
        OrderTest<Object> orderTest = new OrderTest<>();
        orderTest.setName(1);
        orderTest.setName("你好");
        OrderTest<String> test = new OrderTest<>();
        //报错
        //test.setName(1);
        test.setName("nihao");
    }

再来看一下, 开头的链表代码的改动, 因为要结点的值设计为泛型, ListNode应该是一个泛型类;
即:

class ListNode<T> {

    T value;		//值
    ListNode next;	//下一个的指针

    ListNode(T value){
        this.value = value;
        this.next = null;
    }
}

再看看链表:
由于结点是一个泛型类,想要整个链表插入值,查询值等操作, 由于值是泛型,所以整个链表也是一个泛型类;

public class MyLinkedList<T> {

    private ListNode<T> head;
    private int size = 0;		//
	//结点值得类型是泛型,所以
    public void insertHead(T data){		//插入链表的头部		data就是插入的数据
        ListNode newNode = new ListNode(data);
        //如果原来就有数据呢?
        newNode.next = head;		//栈内存的引用
        head = newNode;

        //插入O(1)
    }

    public void insertNth(T data,int position){		//插入链表的中间 假设定义在第N个插入 O(n)
        if(position == 0) {		//这个表示插入在头部了
            insertHead(data);
        }else{
            ListNode cur = head;
            for(int i = 1; i < position ; i++){
                cur = cur.next;		//一直往后遍历   p=p->next;  ->是c++里面的往后找指针
            }
            ListNode newNode = new ListNode(data);
            //
            newNode.next = cur.next;		//新加的点指向后面 保证不断链
            cur.next = newNode;			//把当前的点指向新加的点
        }
    }
	/*int a = 1;
	int b = a;
	int a = 2;*/

    public void deleteHead(){//O(1)
        head = head.next;
    }

    public void deleteNth(int position){//O(n)
        if(position == 0) {
            deleteHead();
        }else{
            ListNode cur = head;
            for(int i = 1; i < position ; i ++){
                cur = cur.next;
            }
            cur.next = cur.next.next; //cur.next 表示的是删除的点,后一个next就是我们要指向的
        }
    }

    public void find(T data){//O(n)
        ListNode cur = head;
        while(cur != null){
            if(cur.value == data) break;
            cur = cur.next;
        }
    }

    public void print(){
        ListNode cur = head;
        while(cur != null){
            System.out.print(cur.value + " ");
            cur = cur.next;
        }
        System.out.println();
    }
     public static void main(String[] args) {
        MyLinkedList<String> linkedList = new MyLinkedList<>();
        linkedList.insertHead("name1");
        linkedList.insertHead("name2");
    }
}

class ListNode<T> {

    T value;		//值
    ListNode next;	//下一个的指针

    ListNode(T value){
        this.value = value;
        this.next = null;
    }
}

除了T之外, 还有集中泛型符号:

  1. E - Element (在集合中使用,因为集合中存放的是元素)
  2. T - Type(Java 类) T代表在调用时的指定类型
  3. K - Key(键)
  4. V - Value(值)
  5. N - Number(数值类型)
  6. ? - 表示不确定的java类型 一般用在通配

个人认为: E,T,K,V其实都可以用,只不过字面以上不符合我们使用场景,
例如向上面自定义的MyLinkedList的元素就设置为了T, 而不是E; 但是使用E更加规范,符合字面意思;

一些泛型使用:

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
    上面自定义MyLinkedList可以改成下面这样, 相应的,在创建实例, 也需要一一指定每个泛型的具体类型;
public class MyLinkedList<T, K, V> {
	//...省略
	public static void main(String[] args) {
        MyLinkedList<String, String, String> linkedList = new MyLinkedList<>();
        linkedList.insertHead("name1");
        linkedList.insertHead("name2");
    }
}

  1. 泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<>(){}
	//错误例子如下:
    public MyLinkedList<T,K,V>() {
    }
    public MyLinkedList<>() {
    }
	//正确如下:
	public MyLinkedList() {
    }

  1. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  2. 泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。

	//以下代码报错
    public static void main(String[] args) {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        MyLinkedList<String> list1 = new MyLinkedList<>();
        list = list1;
    }
    	//以下代码不报错
    public static void main(String[] args) {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        MyLinkedList<Integer> list1 = new MyLinkedList<>();
        list = list1;
    }

  1. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
    经验:泛型要使用一路都用。要不用,一路都不要用。
    public static void main(String[] args) {

        MyLinkedList<Object, Object, Object> linkedList1 = new MyLinkedList<>();
        linkedList1.insertHead("1");
        linkedList1.insertHead("nihao");
        linkedList1.insertHead(1);
    }
  1. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  2. dk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();

  3. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

    public static void main(String[] args) {
    	//报错
        MyLinkedList<int> list = new MyLinkedList<>();
        //不报错
         MyLinkedList<Integer> list2 = new MyLinkedList<>();
    }
  1. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。

异常类不能是泛型的

继承中的泛型

存在几种情况:

  1. 子类不保留父类的泛型:按需实现
    • 没有类型擦除
    • 具体类型
  2. 子类保留父类的泛型:泛型子类
    • 全部保留
    • 部分保留
class Father<T1, T2> {}
// 子类不保留父类的泛型
// 1)没有类型擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{}
}
// 2)具体类型
class Son2 extends Father<Integer, String> {}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {}

泛型通配符

通配符:?

    public static void main(String[] args) {
        MyLinkedList<?> list = new MyLinkedList<>();
    }
  1. 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object
  2. 不可以写入list中的元素。 因为不知道c的元素类型,不能向其中添加对象
  3. 唯一的例外是null,它是所有类型的成员
  4. MyLinkedList<?> 是所有的泛型MyLinkedList的父类,
  5. 调用get时,返回的是一个Object;

通配符使用注意

  1. 不能用在定义泛型类上
//以下报错
public class MyLinkedList<?> {
}
  1. 不能用在创建对象上。
    public static void main(String[] args) {
    	//以下编译通过,正确
    	//JVM 在创建对象时可以认为是默认Object
        MyLinkedList<?> list = new MyLinkedList<>();
		//以下编译不通过, 错误;
		//JVM 创建对象 不知道具体是啥类型;
        MyLinkedList<?> linkedList = new MyLinkedList<?>();
    }

通配符指定上限上限

  1. 上限
    extends:使用时指定的类型必须是继承某个类,或者实现某个接口
  2. 下限
    下限super:使用时指定的类型不能小于操作的类

例子:

  1. <?extends Number> (无穷小, Number]: 只允许泛型为Number及Number子类的引用调用
  2. <? super Number>(无穷大,Number) : 只允许泛型为Number或者Number的父类引用调用;

JDK的一些泛型

ArrayList

Arrayu

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    transient Object[] elementData; // non-private to simplify nested class access
	public void add(E e) {
           //....
            try {
                ArrayList.this.add(i, e);
  				//....
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
  }
  public void add(int index, E element) {
        elementData[index] = element;
        size++;
    }
}

插入值的参数类型为E, 容器时一个Object数组;

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
	//存储数据的是一个Node数组, 也是泛型
	transient Node<K,V>[] table;
	//存储数据的结点
	  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

    
    }
	
	public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
}

	public static void main(String[] args) {
        HashMap<String, Integer> hashMap = new HashMap<>();
    }

main方法创建了一个HashMap, K 为 String类型, V为Integer类型, 因此Node的K也为String, V也为Integer类型; 可以称为嵌套泛型;

HashSet

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;
    public HashSet() {
        map = new HashMap<>();
    }
	public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
}


HashSet内部实际是一个HashMap, 只不过定义属性的时候, HashMap<E,Object>, V为Object类型, Key的参数类型为 E ;

    public static void main(String[] args) {
        HashSet<String> objects = new HashSet<>();
    }

上面main方法, HashSet的E泛型 的具体类型为String, 属性map的HashMap<E, Object>的E 泛型的具体类型也成了String。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值