泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型,也就是说所操作泛型时需要指定一个参数。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中(可以事先指定泛型的类型)。泛型只有在编译时期才有作用,泛型的背后实际上是Object,在编译期间进行类型检查,避免类型出错。
- 泛型的意义:
a:在存放数据的时候,对数据进行类型检查
b:不需要进行强制类型转换,泛型可以自动进行类型的转换。 - < T > :T是一个占位符,表明当前类是一个泛型类
- 不能new T类型的数组:不能new泛型类型的数组。
- 泛型的参数,不能是简单类型,只能引用类型。
- 泛型到底怎么编译的?
泛型真正起作用的时候,是在编译时期。
泛型是在编译的时候的一种机制——擦除机制。
MyStack<Integer> myStack = new MyStack<>();
MyStack<String> myStack = new MyStack<>();
在编译时,会拿着Integer、Sting等引用类型对传入数据进行检查,而泛型的参数,并不参与类型的组成,在编译时都被擦除成了Object。
1 泛型方法和泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。 和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。 一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为 参数化的类 或 参数化的类型 。
class Box<T,E> {
private T t;
private E e;
public void add(T t,E e ) {
this.t = t;
this.e = e;
}
public T getT() {
return t;
}
public E getE() {
return e;
}
public static<E> void log(E str){
System.out.println(str);
}
}
public class Main {
public static void main(String[] args) {
Box<String,Integer> box = new Box<>();
box.add("hello",302);
System.out.println(box.getE()+" "+box.getT());
box.log("world");
}
}
2 类型擦除
Java 中的泛型基本上都是在编译器这个层次来实现的 。在生成的Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>
和List<String>
等类型,在编译之后都会变成List。JVM 看到的只是List,而由泛型附加的类型信息对JVM 来说是不可见的。 类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的上界类。
3 泛型类的栈:
class MyStack<T>{
public T[] elem;
public int top;
public MyStack() {
//this.elem = new T[10];
//new数组时要用new Object
this.elem = (T[]) new Object[10];
this.top = 0;
}
public void push(T data) {
this.elem[top++] = data;
}
public T pop() {
T oldData = this.elem[top-1];
top--;
return oldData;
}
}
public static void main(String[] args) {
MyStack<Integer> myStack = new MyStack<>();
myStack.push(1);
int a = myStack.pop();
System.out.println(a);
MyStack<String> myStack2 = new MyStack<>();
myStack2.push("wang");
String s = myStack2.pop();
System.out.println(s);
System.out.println(myStack);//MyStack@16d3586
System.out.println(myStack2);
}
结果为:
MyStack@154617c:类名+哈希值,并不包含<String>,
说明泛型的参数并不参与类型的组成。
注:
此处 MyStack<Integer> myStack = new MyStack<>();
的Integer即指定了栈中的数据类型,在向栈中增加数据时,会拿着Integer做数据类型检查。
4 泛型链表:
public class TestLink<T> {
static class Node<T> {
public T data;
public Node<T> next;
public Node() {
}
public Node(T data) {
this.data = data;
}
}
public Node<T> head = null;
public void insertHead(T data) {
Node<T> node = new Node<>(data);
if(head == null) {
head = node;
}else {
node.next = head;
head = node;
}
}
public void insertTail(T data) {
Node<T> node = new Node<>(data);
if(head == null) {
head = node;
}else {
Node<T> cur = this.head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
}
public void show() {
Node<T> cur = this.head;
while (cur != null) {
System.out.print(cur.data+" ");
cur = cur.next;
}
System.out.println();
}
public void show2(Node<T> newHead) {
Node<T> cur = newHead;
while (cur != null) {
System.out.print(cur.data+" ");
cur = cur.next;
}
System.out.println();
}
}
5 单链表:两个有序链表合并
T extends Comparable:Comparable为泛型T的上界,表示传入的参数必须是实现Comparable接口的。
public class TestDemo {
//TestLink.Node<T>为返回值类型
public static<T extends Comparable<T>> TestLink.Node<T>
mergeList(TestLink.Node<T> headA,TestLink.Node<T> headB)
{
/*实例内部类
TestLink testLink = new TestLink();
TestLink.Node newHead = testLink.new Node(-1);*/
//静态内部类实现
TestLink.Node<T> newHead = new TestLink.Node<>();
TestLink.Node tmp = newHead;
while (headA != null && headB != null) {
if(headA.data.compareTo(headB.data) < 0) {
tmp.next = headA;
headA = headA.next;
tmp = tmp.next;
}else {
tmp.next = headB;
headB = headB.next;
tmp = tmp.next;
}
}
if (headA != null){
tmp.next = headA;
}
if (headB != null){
tmp.next = headB;
}
return newHead.next;
}
public static void main(String[] args) {
TestLink<Integer> testLink = new TestLink<>();
testLink.insertTail(1);
testLink.insertTail(3);
testLink.insertTail(5);
testLink.insertTail(7);
testLink.show();
TestLink<Integer> testLink2 = new TestLink<> ();
testLink2.insertTail(2);
testLink2.insertTail(4);
testLink2.insertTail(6);
testLink2.insertTail(8);
testLink2.show();
TestLink.Node<Integer> newHead =
mergeList(testLink.head,testLink2.head);
testLink.show2(newHead);
}
6 找出数组中的最大值
- Comparable表示泛型T的上界,泛型是没有下界的。
- T extends Comparable:表示泛型参数T一定要实现Comparable接口,此处在编译时,T被擦除成了Comparable。
类型擦除主要看其类型边界而定:
class MyArrayList<E> {
// E 会被擦除为Object
}
class MyArrayList<E extends Comparable<E>> {
// E 被擦除为Comprable
}
class Algorithm<T extends Comparable<T>> {
public T findMaxVal(T[] array) {
T maxVal = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(maxVal) > 0) {
maxVal = array[i];
}
}
return maxVal;
}
}
public static void main(String[] args) {
Algorithm<Integer> algorithm = new Algorithm<>();
Integer[] array = {10,3,1,67,32,45};
Integer ret = algorithm.findMaxVal(array);
System.out.println(ret);
}
7 静态方法中的泛型
静态方法中需要在static后指定泛型, 而在类名Algorithm后面增加泛型指示对静态方法来说是没有作用的,因为static方法中没有类Algorithm的this引用。通过实参的类型可以推导出形参的T是什么类型。
class Algorithm {
//通过实参的类型可以推导出形参的T是什么类型!
public static<T extends Comparable<T>> T findMaxVal(T[] array) {
T maxVal = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(maxVal) > 0) {
maxVal = array[i];
}
}
return maxVal;
}
}
public static void main(String[] args) {
Integer[] array = {10,3,1,67,32,45};
//通过实参的类型,确定泛型的类型
Integer ret = Algorithm.findMaxVal(array);
//也可以通过这种方法指定
//Integer ret = Algorithm2.<Integer>findMaxVal(array);
System.out.println(ret);
}
8 通配符?
- ? 用于在泛型的使用,即为通配符
- 相较于泛型来说,通配符不能修改,只能读。
类型通配符一般是使用?
代替具体的类型参数。例如List<?>
在逻辑上是
List,List 等所有List<具体类型实参>的父类。
通配符的上界:<? extends 上界>
?必须是上界的子类
通配符的下界: <? super 下界>
?必须是 下界的父类
泛型的限制:
- 泛型类型参数不支持基本数据类型
- 无法实例化泛型类型的对象
- 无法使用泛型类型声明静态的属性
- 无法使用instanceof 判断带类型参数的泛型类型
- 无法创建泛型类数组
- 无法create、catch、throw 一个泛型类异常(异常不支持泛型)
- 泛型类型不是形参一部分,无法重载