Java之泛型基础

这次简单学习一下Java中的泛型,参考《Java 编程的逻辑》。

基本用法

顾名思义,就是指广泛的类型,通过一个Demo来理解一下:

public class Pair<T>{
	T one;
	T two;
	
	public Pair(T first,T second) {
		this.one = first;
        this.two = second;
	}
	
	public T getone() {
        return one;
    }
    
    public T gettwo() {
        return two;
    }
    
	public static void main(String[] args) {
		Pair<Integer> minmax = new Pair<>(1,100);
		Integer min = minmax.getone();
		Integer max = minmax.gettwo();
        System.out.println("Integer one: " + min);
        System.out.println("Integer twp: "+ max);
     
        Pair<String> ps = new Pair<>("Andy","23");
        System.out.println("String one: " + ps.getone());
        System.out.println("String twp: "+ ps.gettwo());
	}
}

Pair就是一个泛型类,􏰌􏲆􏰛􏲢􏰡􏱼􏱁􏱖􏰚􏰙􏰌􏲆􏰛􏲢􏰡􏱼􏱁􏱖􏰚􏰙与普通类的区别在于类后面有个<T>,并且实例变量one 与 two的类型也是T,T􏱍􏱸􏲢􏱱􏱗􏱅􏲀􏲮表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以通过参数传入。
Pair<Integer> minmax = new Pair<>(1,100);与 Pair<String> ps = new Pair<>("Andy","23");就分别传入了Integer与String类型的参数,即代表T为Integer和String类型,可以减少代码重用,运行结果如下:
在这里插入图片描述
当然也可以写成两个不同类型的:

public class Pair<T,V>{
	T one;
	V two;
	
	public Pair(T first,V second) {
		this.one = first;
        this.two = second;
	}
	
	public T getone() {
        return one;
    }
    public V gettwo() {
        return two;
    }
	public static void main(String[] args) {
		Pair<Integer,String> pair = new Pair<>(23,"Andy");
		System.out.println(pair.getone());
		System.out.println(pair.gettwo());

	}

}

运行结果如下:
在这里插入图片描述
可以给类型参数设定一个上界类型,通过extends关键字实现,比如这里新建Pair的子类并且规定子类类型参数的上界类型为Number,Number的子类就包含我们常见的Integer、Double、Boolean等。

public class Pairchild<T extends Number,V extends Number> extends Pair<T,V>{
	public Pairchild(T first, V second) {
		super(first, second);
	}
	
	public double sum() {
		return getone().doubleValue() + gettwo().doubleValue();
	}
}

然后在main方法中调用:

public static void main(String[] args) {
	Pairchild<Integer,Double> pair = new Pairchild<>(23,13.5);
	System.out.println(pair.sum());
}

运行结果如下:
在这里插入图片描述

基本原理

定义普通类直接使用Object也行,比如Pair类可以写为:

public class Pair {
    Object one;
    Object two;
    
    public Pair(Object one, Object two){
        this.one = one;
        this.two = two;
    }
    
    public Object getone() {
        return one;
    }
    
    public Object gettwo() {
        return two;
    }
}

这种普通的非泛型代码也是可以的,那为什么一定要定义类型参数呢? 实际上,Java泛型的内部原理就是这样的。Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型而言,Java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Pair类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。因此Java虚拟机实际执行的时候,其实只知道普通的类及代码而不知泛型为何物。具体在这个例子中,Java虚拟机在运行中只知道Pair,而不知道Integer。

这里就有个问题,然只使用普通类和Object就是可以的,而且泛型最后也转换为了普通类,那为什么还要用泛型呢?主要有两个好处:

  • 更好的安全性
  • 更好的可读性

例如如下代码:

Pair<Integer,String> pair = new Pair<>(23,"Andy");
Integer id = (String)pair.getone();
String name = (Integer)pair.gettwo();

写代码时类型弄错,并且在代码编译时是没有任何问题的,但在运行时,程序就会抛出类型转换异常ClassCastException,而如果使用泛型,则不可能犯这个错误。

容器类

泛型类最常见的用途是作为容器类,所谓容器类,就是容纳并管理多项数据的类。这里利用泛型实现一个简单的动态数组容器,相比底层数组而言动态数组长度可变。当然Java中已经有这样的实现例如ArrayList了,我们目的主要通过泛型来学习实现一个简化版的ArrayList。

public class DynamicArray<E> {
    private static final int DEFAULT_CAPACITY = 10;
    private int size;
    private Object[] elementData;

    public DynamicArray() {
        this.elementData = new Object[DEFAULT_CAPACITY];
    }

    private void ensureCapacity(int minCapacity) {
        int oldCapacity = elementData.length;
        if(oldCapacity>=minCapacity){
            return;
        }
        int newCapacity = oldCapacity * 2;
        if (newCapacity < minCapacity)
            newCapacity = minCapacity;
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    public void add(E e) {
        ensureCapacity(size + 1);
        elementData[size++] = e;
    }

    public E get(int index) {
        return (E)elementData[index];
    }
    
    public int size() {
        return size;
    }

    public E set(int index, E element) {
        E oldValue = get(index);
        elementData[index] = element;
        return oldValue;
    }

}

DynamicArray就是一个动态数组,通过ensureCapacity方法来根据需要扩展数组,扩展的主要逻辑是分配一个足够长度的新数组,然后将原内容拷贝到这个新数组中,最后让内部的字符数组指向这个新数组。作为一个容器类,它容纳的数据类型是作为参数传递过来的,例如存放Double类型:

DynamicArray<Double> arr = new DynamicArray<Double>();
Random rnd = new Random();
int size = 1+rnd.nextInt(100);
for(int i=0; i<size; i++){
    arr.add(Math.random());
}

Double d = arr.get(rnd.nextInt(size));
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值