java泛型的相关介绍

由于本人能力有限,一下为个人理解的观点,如果错误,请留言,我会在验证后修改。

为什么使用泛型?

泛型简而言之就是当我们定义类,接口,和方法的时候允许类型参数化,通过这种方式我们可以实现代码的复用。

使用泛型有几点好处:

1)在编译时更强大的类型检查。

2)消除强制类型转化

3)通过使用泛型可以实现通用算法(比如集合框架)

泛型类和泛型方法

class A <T> {
	private T i;

	public T getI() {
		return i;
	}

	public void setI(T i) {
		this.i = i;
	}
	public  <U> void method(U s,T a) {
		
	}
} 

原始类型

当我们定义了一个泛型类,在不指定泛型参数的情况下创建对象,,那么这就是原始类型的对象。需要说明一点的时无泛型的类和接口不是原始类型。

例如:

@Test
	public void dome() {
		//泛型
		NOGBox<Integer> intBox = new NOGBox<>();
		//原始类型
		NOGBox box = new NOGBox();
	}

因为在JDK5.0之前有很多的原始类型,为了向后兼容我们可是用原始类型去接受泛型类型的对象:

//泛型
		NOGBox<Integer> intBox = new NOGBox<>();
		//原始类型
		NOGBox box = intBox;

但是如果你使用泛型类型去接受一个原始类型的对象会出现warning:

@Test
	public void dome() {
		//泛型
		Box<Integer> intBox ;
		//原始类型
		Box box =  new Box();
		intBox = box;
		System.out.println(box);
	}
当我们讲到类型擦除部分有更多关于Java编译器如何使用原始类型的信息。

有界类型参数

有时候我们可能会想在泛型中限制泛型类型,例如一个方法操作一个变量可能想接收Number或者他的子类的对象,这就是限制泛型类型的作用。

关于限制的声明我们这样来做:

public class Box<T> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
//此处加了泛型类型的限制
    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}

还需要提一点的是但我们进行了泛型边界的限定之后,我们就可以调用调用定义在边界的对象的方法。可能这么说不容易理解我们可以参看代码:

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        //此处调用了泛型边界Integer的 
        //intValue()方法
        return n.intValue() % 2 == 0;
    }

    // ...
}

多重边界

上面讲解了单层边界,下面我们讲解多重边界。

//多重边界
<T extends B1 & B2 & B3>

需要注意的时,如果其中有一个类的话,他一定是在第一个位置,如果没有这麽做的话,将会报编译时异常。

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }

边界类型参数是一个非常重要的概念,他是实现通用算法的关键,假设我们有一个数组,我们计算数组中比给定参数大的个数

我们是不是会这麽写:

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

但是返现报错了,这是应为只有基本数据类型可以使用>号进行判断。为了解决这个问题我们可以使用边界类型了:

/*
	 * public interface Comparable<T> {
    		public int compareTo(T o);
		}
	 */
	//此处加了泛型边界,上面是Comparable接口的描述,这样我们就可以实现++了
	public<T extends Comparable<T>> int count(T[] array,T element) {
		int counts = 0;
		for (int i = 0; i < array.length; i++) {
			if(array[i].compareTo(element) > 0) {
				counts++;
			}
		}
		return counts;
	}

泛型下继承关系

只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。例如,您可以将一个Integer分配给一个Object,因为Object是Integer的超类型之一,泛型也是如此。您可以执行泛型类型调用,将Number作为其类型参数传递,如果参数与Number兼容,则允许任何后续的add调用

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK

关于泛型的继承关系,这是我们经常在程序中犯的错误:


我们那 集合框架来看一看:

但我们定义一个自己的List<E>的实现类:

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...
}

并且声明了一下几个对象:

PayloadList<String,String>
PayloadList<String,Integer>
PayloadList<String,Exception>

我们在来看看他们的继承关系:


这个概念不好解释,我感觉还是看自己的理解比较重要就不过多的解释了

通配符

通配符有三种使用方式:

限定上边界

无边界

限定下边界

one by one

限定上边界(?extends xxx)

例如如果你想接一个方法使其能够工作于List<Integer>, List<Double>, and List<Number>我们可以这样写:

public static void process(List<? extends Number> list) { /* ... */ }

在函数里面我们就可以使用Number中定义个任何方法:

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
//Number的成员函数
        s += n.doubleValue();
    return s;
}

无边界(?)

我们大部分使用无边界的场景如下:

1)如果您正在编写的方法全部使用Object类中提供的功能实现。

2)当代码使用泛型类中不依赖于类型参数的方法时。例如:

public static void printList(List<?> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

无边界其实也是比较常用的,比如我们的Class类就是,这是因为他的所有方法都不依赖与参数类型。

还有一个观点我们需要明白的就是List<Objecg> 和List<?> 是不同的,这是因为在List<Objecg>中你可以插入Object类型和其子类型的对象,但是你不能插入null.

限定下边界<? super XXX>

假设您想编写一个将Integer对象放入列表的方法。为了最大限度地提高灵活性,您希望该方法在List <Integer>,List <Number>和List <Object>上工作 -- 任何可以保存Integer值的东西。这是我们的<? super Integer>就闪亮出场了~!

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

通配符和其继承关系


尽管Number是Integer的父类,但是List<Number> 和 List<Integer> 就是没有任何关系。

为了在代码中通过Number的方法访问List<Integer>的元素,我们可以创建一个关系(通过使用限制上边界的方式):

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a //subtype of List<? extends Number>

这可能有些难以理解我们来解释一下:因为Integer是Number的子类,并且numList是一个存储Number元素的List对象,那么这个关系就是成立的,如果还不能理解的话我们可以查看下面的图解:


范围限定的使用指南

说到这里可能我们会为如何使用限制上边界 无边界 限制下边界很是苦恼,下面我们就来谈谈他们的使用场景。

为了方便讨论我们给出两个思想

In

“in”:将数据提供给代码去执行某些功能。想象一下带有两个参数的复制方法:copy(src,dest)。 src参数提供要复制的数据,因此它是“in”。


Out

    不用说dest就是out了

好了下面我们就来说一说他们各自的应用场景

“in“变量用限制上边界的通配符来定义,使用extends关键字。
"out"变量使用限制下边界通配符来定义,使用super关键字。
方法中使用的都是Object类中定义的方法去访问“in”变量的情况下,使用无界通配符
在方法中需要以“in”和“out”变量自身的方法去访问变量的情况下,不要使用通配符。

以我们的编程功底可能写不出非常好的泛型类,但是通过对泛型的认知,我们可以更好的理解现有的框架比如Collection



猜你喜欢

转载自blog.csdn.net/c1523456/article/details/80470365