java 基础 泛型

195 阅读5分钟

泛型:

要使代码能够应用于“某种具体的类型而不是一个具体的接口或类”。增加了泛型支持后的集合,完全可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果试图向集合中添加不满足类型要求的对象,编译器就会提示错误。增加泛型后的集合,可以让代码更加简洁,程序更加健壮(Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常)。除此之外,Java泛型还增强了枚举类、反射等方面的功能

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。

例如,为Apple类定义构造器,其构造器名依然是Apple,而不是Apple!调用该构造器时却可以使用Apple的形式,当然应该为T形参传入实际的类型参数。Java 7提供了菱形语法,允许省略<>中的类型实参。

元组:

将一组对象直接打包存储于其中的一个单一的对象。这个容器对象允许读取其中的元素,但是不允许对其存放新的对象(这也称为:数据传送对象)

比如一个二维的元组:

public class TwoTuple<A,B>{
	public final A first;
	public final B second;
	public TwoTuple(A a, B b){ first = a; second = b;} 
	public String toStirng() {
		return "("+first+","+second+")";
	}
}

构造一个堆栈类:

下面的例子使用了末端哨兵来判断堆栈何时为空,这个末端哨兵在构造LinkedStack时创建,然后每调用一次push()方法,就会创建一个Node对象,并将其链接到前一个Node对象。

public class LinkedStack<T> {
  private static class Node<U> {
    U item;
    Node<U> next;
    Node() { item = null; next = null; }
    Node(U item, Node<U> next) {
      this.item = item;
      this.next = next;
    }
    boolean end() { return item == null && next == null; }
  }
  private Node<T> top = new Node<T>(); // End sentinel
  public void push(T item) {
    top = new Node<T>(item, top);
  }	
  public T pop() {
    T result = top.item;
    if(!top.end())
      top = top.next;
    return result;
  }
  public static void main(String[] args) {
    LinkedStack<String> lss = new LinkedStack<String>();
    for(String s : "Phasers on stun!".split(" "))
      lss.push(s);
    String s;
    while((s = lss.pop()) != null)
      System.out.println(s);
  }
} /* Output:
stun!
on
Phasers
*///:~

泛型接口:

public interface Generator<T> {T next();}

用泛型接口实现生成Fibonacci数列:

public class Fibonacci implements Generator<Interger> {
	private int count=0;
	public Integer next(){return fib(count++);}
	private int fib(int n){
		if(n<2) return 1;
		return fib(n-2)+fib(n-1);
	}
	public static void main(String[] args){
		Fibonacci gen = new Fibonacci();
		for(int i=0;i<18;i++){
			System.out.println(gen.next()+" ");
		}
	}
}
/* output
1 1 2 3 5 8 13 21 34 55 89 144 233 377 ...

泛型方法:

泛型方法使得该方法能够独立于类而产生变化。无论何时,只要你能做到,就应该尽量使用泛型方法。也就是说如果使用泛型方法可以取代整个类泛型化,那么就应该使用泛型方法,因为他可以使得事情更清楚明白。

定义泛型方法: 修饰符 返回参数 方法名(接收参数){}

public class MyUtils{
// <T> 标明是一个泛型方法。
public static <T> void copy(Collection<T> dest , Collection<? extends T> src){..
    .} //①

public static <T> T copy(Collection<? super T> dest , Collection<T> src){.
    ..} //②

}

泛型中的类型转换,在未知类型时不能进行强转:用下面方式应该先判断

List<?>[] lsa=new ArrayList<?>[10];

Object[] oa=(Object[]) lsa;

List<Integer> li=new ArrayList<Integer>();

li.add(new Integer(3));

oa[1]=li;

Object target=lsa[1].get(0);

if (target instanceof String){

// 下面代码安全了
String s=(String) target;
}

使用泛型构建一个Set的使用工具(交集,差集):

import java.util.*;

public class Sets {
  // 将两个set合并为一个set  
  public static <T> Set<T> union(Set<T> a, Set<T> b) {
    Set<T> result = new HashSet<T>(a);
    result.addAll(b);
    return result;
  }
  // 返回共有的交集
  public static <T>
  Set<T> intersection(Set<T> a, Set<T> b) {
    Set<T> result = new HashSet<T>(a);
    result.retainAll(b);
    return result;
  }	
  // 从superset移除subset包含的元素  
  // Subtract subset from superset:
  public static <T> Set<T>
  difference(Set<T> superset, Set<T> subset) {
    Set<T> result = new HashSet<T>(superset);
    result.removeAll(subset);
    return result;
  }
  // 返回交集之外的所有元素
  // Reflexive--everything not in the intersection:
  public static <T> Set<T> complement(Set<T> a, Set<T> b) {
    return difference(union(a, b), intersection(a, b));
  }
} ///:~

泛型擦除:

在泛型代码内部,无法获取任何有关泛型参数类型的信息。java使用泛型擦除,比如List 和List 在运行时都是相同的类型,均被擦除为"原生"类型即List. 泛型擦除就是被擦除为父类。

泛型的面试题

Q&A Java中的泛型是什么 ? 使用泛型的好处是什么?

在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException

Q&A Java的泛型是如何工作的 ? 什么是类型擦除 ?

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。

Q&A什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限制。有两种限定通配符,一种是它通过确保类型必须是T的子类来设定类型的上界,另一种是它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面表 示了非限定通配符,因为<?>可以用任意类型来替代

Q&A List<? extends T> 上界 和List <? super T> 下界 之间有什么区别 ?

上界的list只能get不能add,下届的list只能add不能get

编译器可以支持像上转型,不支持像下转型。

Q&A 你可以把List传递给一个接受List参数的方法吗?

因为List可以存储任何类型的对象包括String, Integer等等,而List却只能用来存储Strings。

List objectList;

List stringList;

objectList = stringList; //compilation error incompatible types

public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Object> objectList = new ArrayList<>();
        stringList.add("add");
        stringList.add("123");
        objectList.add("123");
        objectList.add("234");
        // 让objectList转为stringList,编译错误stringList必须是接收的List<String> List<Object>之间不能转换。
        // stringList= objectList; // 编译错误
        // objectList=stringList; // 编译错误
        List<?> list = new ArrayList<>();
        list = stringList;
        System.out.println(list);
        list = objectList;
        System.out.println(list);
        // list.add("sss"); // 编译器不允许这样使用
        
    }