简述
大家都知道封装,继承,多态是Java的三大特性。接下来的几篇文章我将会讲述一下有关多态的一个技术那就是泛型。首先泛型是在JDK1.5版本的时候加入的,目的在于使代码可以应用于多种类型。
泛型是什么
在我看来泛型是一种限制,限制被泛型修饰的代码块或容器的适用范围。
泛型怎么用
根据上述的是什么,泛型相关的代码块就是:类和方法。容器则是JDK中的Collection和Map容器。
因为应用不是本篇的重点我会列出一些代码,出自Java编程思想希望大家可以浏览一下代码。
一个堆栈类
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);
}
}
泛型方法
public class GenericMethods {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f('c');
gm.f(gm);
}
}
利用泛型简化代码
import java.util.*;
public class New {
public static <K,V> Map<K,V> map() {
return new HashMap<K,V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> lList() {
return new LinkedList<T>();
}
public static <T> Set<T> set() {
return new HashSet<T>();
}
public static <T> Queue<T> queue() {
return new LinkedList<T>();
}
// Examples:
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
List<String> ls = New.list();
LinkedList<String> lls = New.lList();
Set<String> ss = New.set();
Queue<String> qs = New.queue();
}
}
可变参数与泛型
Java中可以使用(String... args)的方式表达可变参数import java.util.*;
public class GenericVarargs {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
for(T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
构建复杂的数据结构
构建一个零售店模型,包括走廊货架和商品对象,使用泛型来实现。
import java.util.*;
import net.mindview.util.*;
class Product {
private final int id;
private String description;
private double price;
public Product(int IDnumber, String descr, double price){
id = IDnumber;
description = descr;
this.price = price;
System.out.println(toString());
}
public String toString() {
return id + ": " + description + ", price: $" + price;
}
public void priceChange(double change) {
price += change;
}
public static Generator<Product> generator =
new Generator<Product>() {
private Random rand = new Random(47);
public Product next() {
return new Product(rand.nextInt(1000), "Test",
Math.round(rand.nextDouble() * 1000.0) + 0.99);
}
};
}
class Shelf extends ArrayList<Product> {
public Shelf(int nProducts) {
Generators.fill(this, Product.generator, nProducts);
}
}
class Aisle extends ArrayList<Shelf> {
public Aisle(int nShelves, int nProducts) {
for(int i = 0; i < nShelves; i++)
add(new Shelf(nProducts));
}
}
class CheckoutStand {}
class Office {}
public class Store extends ArrayList<Aisle> {
private ArrayList<CheckoutStand> checkouts =
new ArrayList<CheckoutStand>();
private Office office = new Office();
public Store(int nAisles, int nShelves, int nProducts) {
for(int i = 0; i < nAisles; i++)
add(new Aisle(nShelves, nProducts));
}
public String toString() {
StringBuilder result = new StringBuilder();
for(Aisle a : this)
for(Shelf s : a)
for(Product p : s) {
result.append(p);
result.append("\n");
}
return result.toString();
}
public static void main(String[] args) {
System.out.println(new Store(14, 5, 10));
}
}
因为应用不是本篇的重点所以我就偷懒贴了一些代码,不过提升编码能力的途径毕竟还是编码嘛所以希望大家最好能手打一下毕竟我选贴出来的都是我觉得很不错的也有实际意义的。接下来我将会说说本篇的重点部分。
擦除
先贴一段代码引出这段的主题
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
很多同学可能会认为结果为false因为它们存放的类型是不同的。但事实是这段代码结果为true,这就引出了主题类型擦除。所谓类型擦除就是在代码的编译阶段把泛型相关的类型信息擦除掉,使用其原始类型进行运行。在上述例子中编译后原始类型就变成了两个List相等的判断。
擦除带来的问题
既然说泛型是基于类型擦除实现的,那么这将会带来什么问题呢?是怎么解决的呢?
问题一 由于类型擦除,使得代码块无法使用参数化类型特有的方法。
下面是一段C++的代码,在manipulate方法中调用了T的f方法,这在C++中是可行的因为C++是使用模板来实现泛型的,而当模板类被实例化时T的类型就确定了所以当Manipulator<HasF>被实例化时,编译器会检查HasF是否存在f方法。
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj;
public:
Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }
};
class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};
int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipulate();
}
但是在Java中泛型类型的信息会被擦除从而导致无法在代码中直接调用f方法,因为我们不知道T究竟是什么类型。解决的办法就是加边界。使用extend加入边界来实现。
问题二 判断类型
在判断一个类的类型时我们可能会使用instanceOf来判断,但是在代码中这是无法使用的。解决办法是动态的使用isInstance()方法来判断。
问题三 return泛型类型
class GenericBase<T> {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}
当我们将element返回时得到的类型将不会是T而是Object这也是因为擦除后使用的是原始类型:GenericBase<Object> 解决办法使用持有Object强制类型转换的方式解决。