Java范型核心概念:
有许多原因促成了范型的出现,其中最瞩目的一个原因,就是为了创建容器类。通常我们只会使用容器来存储一种类型的对象,范型的主要目的之一就是指定要容器持有什么类型的对象,并且由编译器来确定类型的正确性。
使用类型参数,即:暂时不指定类型,而是稍后再决定使用什么类型(用尖括号括住类型,放在类名后面)。实例1:
//: generics/Holder3.java
public class Holder3<T> {
private T a;
public Holder3(T a) { this.a = a; }
public void set(T a) { this.a = a; }
public T get() { return a; }
public static void main(String[] args) {
Holder3<Automobile> h3 =
new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // No cast needed
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
}
} ///:~
当你创建Holder3对象时,必需指明持有什么类型,将其放于尖括号内(就像:Holder3 h3 =new Holder3(new Automobile()))。然后就只能往Holder3中存入Automobile类型(或者它的子类型,范型和多态不冲突);并且从Holder3中取出它持有的对象时,(不需要手动转型)自动就是正确的类型(Automobile)。
以上描述的就是Java范型的核心概念:只需要告诉编译器,你想要使用什么类型,编译器帮你处理一切细节。
实例2:传统的下推堆栈。不用LinkedList数据结构,来实现自己的内部链式存储机制。
package generics;
import static utils.Print.print;
public class LinkedList<T> {
private Node<T> top = new Node<T>();
//将接口实现为一个私有内部类,只在该外部类(LinkedList<T>)内部可以访问;
private static class Node<U>{
U value;
Node<U> next;
public void Node(){
this.value = null;
this.next = null;
}
//在类名后定义了范型,其中的方法后面将不再需要写范型符号,参见其回答:http://ask.csdn.net/questions/357182
public void Node(U value,Node<U> next){
this.value = value;
this.next = next;
}
public boolean end(){
return this.value == null && this.next == null;
}
}
public void push(T tmp){
Node<T> node = new Node<T>();
node.value = tmp;
node.next = top;
top = node;
}
public T pop(){
T reslut = top.value;
if(!top.end()){
top = top.next;
}
return reslut;
}
public static void main(String args[]){
LinkedList<String> stack= new LinkedList<String>();
for(String s : "there is LinkedList".split(" ")){
stack.push(s);
}
String s;
while((s = stack.pop())!=null){
print(s);
}
}
}
元组
元组的应用场景:你应该经常需要这样的功能吧:仅一次方法调用能产生多个返回对象。可是return语句每次只能返回一个对象,因此,解决办法为产生一个对象让它持有需要返回的多个对象。
元组的概念:将一组对象打包直接存储于一个单一对象中,这个容器对象允许读取其中元素,但是不允许再向其中放入新对象(这个概念也被称为信使或者数据传送对象)。元组可以拥有不同长度,且元组中的对象的类型可以相异。要处理不同长度的(返回值)问题,我们只需要创建不同长度的元组,可以利用继承机制实现更长的元组。
为了使用元组,你只需要定义一个长度合适的元组(由于有了范型,你可以很容易的创建元组,任其返回一组任意类型的对象(根据你创建元组对象时指定的类型)),然后在return语句中创建该元组,并返回即可。实例3:
public class TwoTuple<A,B> {
/*
*定义为public final,在构造器中初始化之后,便不再能被更改。其中缘由可参考:下一篇博客:“Java中的代码块与final变量的初始化”;
*/
public final A first;
public final B second;
public TwoTuple(A a, B b) { first = a;
second = b; }
public String toString() {
return "(" + first + ", " + second + ")";
}
}
//利用继承机制来实现长度更长的元组;
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return "(" + first + ", " + second + ", " + third +")";
}
}
class Amphibian {}
class Vehicle {}
public class TupleTest {
static TwoTuple<String,Integer> f1() {
// Autoboxing converts the int to Integer:
return new TwoTuple<String,Integer>("hi", 47);
}
static TwoTuple<Vehicle,Integer> f2() {
// Autoboxing converts the int to Integer:
return new TwoTuple<Vehicle,Integer>(new Vehicle(), 47);
}
g1() {
return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(), "hi", 47);
}
static ThreeTuple<Vehicle,Amphibian,Integer>
g2() {
return new ThreeTuple<Vehicle,Amphibian, Integer>(new Vehicle(),new Amphibian(), 47);
}
public static void main(String[] args) {
TwoTuple<String,Integer> ttsi = f();
System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
System.out.println(f1());
System.out.println(f2());
System.out.println(g1());
System.out.println(g2());
}
/*outPut:
(hi, 47)
(generics.Vehicle@60e53b93, 47)
(generics.Amphibian@5e2de80c, hi, 47)
(generics.Vehicle@1d44bcfa, generics.Amphibian@266474c2, 47)
*/
范型接口
范型也可以用于接口,接口使用范型和类使用范型没什么区别。都是为了告诉编译器你想让该类的对象持有什么类型,以确保在对象中正确无误的使用该类型(域,方法中使用),如:
接口:public interface Generator {T next();}
实现类:
CoffeeGenerator<T> implement Generator <Coffee> {
T field;
Coffee next(){}
T CoffeeGeneratorf(){}
}
以上体现了,参数化接口,确保next()的返回值是Generator<Coffee>中的指定的参数的类型(Coffee);确保CoffeeGeneratorf()返回值是CoffeeGenerator<T>中指定的参数类型T。
范型方法
一个方法是否是范型方法与其所在的类是否是范型没有关系。范型方法使得该方法能够独立于类而产生,如果可以使用范型方法替代将整个类范型化,那就应该使用范型方法,static方法要想使用范型能力,就必须成为泛型类(因为static方法无法访问泛型类的类型参数)。
当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用范型方法时,通常不用指明参数类型,因为编译器会为我们找出具体类型(类型参数推断)。当像普通方法一样调用范型方法时,就好像该方法被无限次的从载过。
- 类型参数推断:在泛型方法中,编译器可以从泛型参数列表中的一个参数推断出另外一个参数。编译器类型参数推断避免了重复的泛型参数列表,比如:public static <T> List<T>(){ return new ArrayList<T>();},使用方法:List<String> list = List()。
类型参数推断只对赋值操作有作用,其他时候并不起作用,比如:将一个泛型方法调用的结果作为一个参数,传递给另外一个方法,这是编译器并不会执行类型推断(编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量)。这时通过显式的类型说明来解决(详情可参考Java编程思想15.4.1)。
范型方法的几个应用场景实例:
- 可变参数与泛型方法:泛型方法与可变参数列表能够很好的共存,例4:
package generics;
import java.util.ArrayList;
import java.util.List;
import static utils.Print.print;
public class GenericVarargs {
public static <T> List<T> makeList(T... args){
ArrayList<T> result = new ArrayList<T>();
for(T t: args){
result.add(t);
}
return result;
}
public static void main(String[] args){
List<String> list1 = makeList("A");
print(list1);
List<String> list2 = makeList("A","B","C");
print(list2);
List<String> list3 = makeList("ABCDEFHIJKLMNOPQRST".split(""));
print(list3);
}
}
- 泛型方法应用于Generator(生成器):利用生成器我们可以很方便地填充一个Collection。
以下以生成Collection的subtypes:Set、Queue、List为例,并且不让它们失去容器的类型属性,由于LinkedList同时实现了List 和 Queue接口,如果不进行明确的转型,编译器将会报错“调用模糊的方法”,需要重载一个专门的用于LinkedList的fill泛型方法,用于区别List和LinkedList。
package generics;
import generics.coffee.Breve;
import generics.coffee.Coffee;
import utils.Generator;
import java.util.*;
public class E13_Generators {
public static <T> List<T> fill(List<T> coll, Generator<T> gen,int n){
for(int i = 0; i<n; i++)
coll.add(gen.next());
return coll;
}
public static <T> Set<T> fill(Set<T> coll, Generator<T> gen,int n){
for(int i = 0; i<n; i++)
coll.add(gen.next());
return coll;
}
public static <T> Queue<T> fill(Queue<T> coll, Generator<T> gen,int n){
for(int i = 0; i<n; i++)
coll.add(gen.next());
return coll;
}
public static <T> LinkedList<T> fill(LinkedList<T> coll,Generator<T> gen,int n){
for(int i = 0; i<n; i++)
coll.push(gen.next());
return coll;
}
public static void main(String args[]){
//此处持有的必须是Coffee类型,因为:从fill的定义可以看出CoffeeGenerator持有的类型和ArrayList持有的类型必须一致,
// 同时,还必须与CoffeeGenerator中的next方法的返回类类型一样(由于next方法的返回类型必须与CoffeeGenerator持有类型一致)。
List<Coffee> list = fill(new ArrayList<Coffee>(),new CoffeeGenerator(),5);
Set<Coffee> set = fill(new HashSet<Coffee>(),new CoffeeGenerator(),5);
LinkedList<Coffee> linkedList = fill(new LinkedList<Coffee>(),new CoffeeGenerator(),5);
Queue queue = fill((Queue<Integer>) new LinkedList<Integer>(),new Fibonacci(),5);
}
}
- 为任何类构造一个Generator,只要该类具有默认的构造器。
这个BasicGenerator提供了一个基本实现,用以生成某个类对象。可以看出使用泛型方法创建Generator对象,大大减少了编写的代码(和上一例子做对比可以感受到明显的减少了代码量)。给Create方法传入Class对象,如:BasicGenerator.Create (Default .class)来为任何类创建Generator,则可以将BasicGenerator类中所有使用<T>泛型表示的类型都关联为Default。
package generics;
import utils.Generator;
import java.util.HashSet;
import java.util.Set;
import static utils.Print.print;
class Default{
static int id = 0;
String string = "generate Default Class";
public String toString(){
return string + id++;
}
}
public class BasicGenerator<T> implements Generator<T>{//当导出类定义为泛型时<T>,被继承的接口可以使用泛型<T>;
private Class<T> type;
//使用泛型中定义的类型
public BasicGenerator(Class<T> type){
this.type = type;
}
public T next(){
try{
return type.newInstance();
}catch(Exception e){
print(e);
}
return null;
}
//produce a Default generator given a type token;
public BasicGenerator<T> Create(){
return new BasicGenerator(type);
}
public static <T> Generator<T> Create(Class<T> type){
return new BasicGenerator<T>(type);
}
public static void main(String args[]){
Generator<Default> basicGenerator = BasicGenerator.Create(Default.class);
Generator<HashSet> hashSetGenerator = BasicGenerator.Create(HashSet.class);
Generator<LinkedList> linkedListGenerator = BasicGenerator.Create(LinkedList.class);
for(int i =0 ;i<5; i++)
print(basicGenerator.next());
for(int i =0 ;i<5; i++)
print(hashSetGenerator.next());
for(int i =0 ;i<5; i++)
print(linkedListGenerator.next());
}
}
简化元组的使用
一个Set实用工具
泛型也可用于匿名内部类