Java基础 -- 深入理解泛型
目录
一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。
而泛型很好的解决了这个问题,这也是Java SE5的重大变化之一,下面将会深入介绍泛型。
一 泛型的概念
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。
1、什么是泛型?为什么要使用泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2、一个的例子
在前面博客中,我曾经介绍过Java持有对象(容器)。不过,有一个问题当时忽略了:在Java SE5之前的容器是允许向其中插入不正确类型。例如,考虑一个Apple对象的容器,使用最基本最可靠的容器ArrayList,在本例中Apple和Orange都被放置在了容器中,然后通过get()将他们取出,正常情况下,Java编译器会报告警告信息,因为这个示例没有使用泛型。在这里,使用Java SE5所特有的注解来抑制警告信息,注解以“@”符号开头,接受参数rawtypes。
import java.util.*; class Apple{ private static long counter; private final long id = counter++; public long id() { return id; } } class Orange{} public class ApplesAndOrangesWithoutGenerics { @SuppressWarnings("rawtypes") public static void main(String[] args) { ArrayList apples = new ArrayList(); for(int i=0;i<3;i++) { apples.add(new Apple()); } apples.add(new Orange()); for(int i=0;i<apples.size();i++) { System.out.println(((Apple)apples.get(i)).id()); } } }
输出结果:
0 1 2 Exception in thread "main" java.lang.ClassCastException: Orange cannot be cast to Apple at ApplesAndOrangesWithoutGenerics.main(ApplesAndOrangesWithoutGenerics.java:25)
Apple和Orange类是有区别的,他们除了都是Object之外没有任何共性。因为ArrayList保存的是Object,因此我们不仅可以通过ArrayList的add()方法将Apple对象放进这个容器,还可以添加Orange对象,而且在无论在编译期还是运行期都不会有问题。但是当使用ArrayList的get()方法取出我们认为是Apple的对象时,我们得到的只是Object引用,必须将其转换为Apple,因此需要将整个表达式括起来,在调用Apple的id()方法前,强制类型转换。否则,将会得到语法错误。在运行时,当尝试将orange对象转换为Apple时,就会抛出异常。为了解决这样的问题,泛型应运而生,通过使用泛型,就可以在编译期防止将错误类型的对象放置到容器中。
下面还是这个例子,但是使用了泛型:
import java.util.ArrayList; public class ApplesAndOrangesWithGenerics { public static void main(String[] args) { ArrayList<Apple> apples = new ArrayList<Apple>(); for(int i=0;i<3;i++) { apples.add(new Apple()); } //apples.add(new Orange()); for(int i=0;i<apples.size();i++) { System.out.println(((Apple)apples.get(i)).id()); } } }
输出如下:
0 1 2
二 泛型的特性
1、擦除特性
泛型只在编译阶段有效。看下面的代码:
import java.util.*; public class Character { public static void main(String[] args) { List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)) { System.out.println("泛型测试:类型相同"); } } }
输出如下:
泛型测试:类型相同
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上可以看成是多个不同的类型,实际上是相同的基本类型(我们可以把这种基本类型假设为Object类型)。
Java泛型的这种特性,将会带来一个问题:
///java泛型存在的问题 class HasF{ public void f() { System.out.println("HasF.f()"); } } class Manipulator<T>{ private T obj; public Manipulator(T x) { obj = x; } //Error:cannot find symbol:method f() public void manipulate() { //这里是无法调用f()函数的 //obj.f(); } } public class Manipulation { public static void main(String[] args) { HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<HasF>(hf); manipulator.manipulate(); } }
由于有了擦除,Java编译器是无法将manipulate()方法在obj上调用f()这一需求映射到HasF拥有f()这一事实上。但是我们可以通过给定泛型类的边界,以此告知编译器只能接受遵循这个边界的类型。这里重用了extends关键字,由于有了边界,下面的代码就可以编译了:
class Manipulator<T extends HasF>{ private T obj; public Manipulator(T x) { obj = x; } public void manipulate() { obj.f(); } }
边界<T extends HasF>声明T必须具有类型HasF或者从HasF导出的类型。如果情况确实如此,那么就可以安全在obj上调用f()了。
此外,我们还需要注意:
- 泛型的类型参数只能是类类型,不能是简单类型;
- 由于程序在运行时,会擦除泛型信息,因此任何在运行时需要知道确切类型信息(类对应的Class对象)的操作都无法工作。如下面的操作是非法的,编译时会出错。
if(manipulator instanceof Manipulator<HasF>){} //ERROR if(manipulator instanceof Manipulator<?>){} //可以 T[] array = new T[5]; //ERROR T 创建T的对象,需要知道类型T对应的Class对象 var = new T(); //ERROR T[] array = (T)new Object[5]; //Unchecked warning
2、创建类型实例
我们对创建一个new T()的尝试无法实现,这主要有两个原因:
- 泛型在运行时的擦除特性;
- 编译器无法验证T具有默认的构造器。
Java有多种解决方案,我们可以传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签,那么就可以使用newInstance()来创建这个类型的新对象:
//使用内建的工厂对象 Class<T>是内建的工厂对象,可能存在一些问题,在编译器发现不了的,因此不推荐 class ClassAsFactory<T>{ T x; //创建一个kind类型对象实例 public ClassAsFactory(Class<T> kind) throws InstantiationException, IllegalAccessException{ x = kind.newInstance(); } } class Employee{} public class InstantiateGenericType { public static void main(String[] args) throws InstantiationException, IllegalAccessException { ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class); System.out.println("ClassAsFactory<Employee> succeeded"); try { //会失败,因为Integer没有任何默认的构造器,无法执行newInstance() ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class); }catch(Exception e){ System.out.println("ClassAsFactory<Integer> failed"); } } }
输出如下:
ClassAsFactory<Employee> succeeded
ClassAsFactory<Integer> failed
这可以编译,但是会因ClassAsFactory<Integer>而失败,因为Integer没有默认的构造器。因为这个错误不是在编译期捕获的,所以Sun对这种方式不推荐,他们推荐使用显示的工厂,并将限制其类型,使得智能接受实现了这个工厂的类:
///工厂模式 使用显式的工厂 推荐 interface FactoryI<T>{ T create(); } class Foo<T>{ private T x; //接受一个实现了工厂接口FactoryI的类 public <F extends FactoryI<T>> Foo(F factory){ x = factory.create(); } } //Integer工厂类 class IntegerFactory implements FactoryI<Integer>{ //生成一个Integer对象 @Override public Integer create() { // TODO Auto-generated method stub return new Integer(0); } } class Widge{ //内部工厂类 public static class Factory implements FactoryI<Widge>{ @Override public Widge create() { // TODO Auto-generated method stub return new Widge(); } } } public class FactoryConstraint { public static void main(String[] args){ new Foo<Integer>(new IntegerFactory()); new Foo<Widge>(new Widge.Factory()); } }
上面两种方式都传递了工厂对象,Class<T>碰巧是内建的工厂对象,而上面的方式创建了一个显式的工厂对象,但是却获得了编译期检测。
另外一种方式是模板设计模式:
package generic.factory; ///模板方法设计模式:create()是在子类中定义的、用来产生子类类型的对象 //模板类:用来创建T类型的对象 abstract class GenericWithCreate<T> { final T element; GenericWithCreate(){ element = create(); } //用来产生T类型的对象 abstract T create(); } //抽象类的实现类 用于创建Integer类型的对象 class Creator extends GenericWithCreate<Integer>{ Integer create() {return new Integer(0);} void f() { System.out.println(element.getClass().getSimpleName()); } } public class CreatorGeneric{ public static void main(String[] args) { Creator c = new Creator(); c.f(); } }
三 泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
泛型类的最基本写法(这么看可能会有点晕,会在下面的例子中详解):
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ private 泛型标识 /*(成员变量类型)*/ var; ..... } }
一个最普通的泛型类:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic<T>{ //key这个成员变量的类型为T,T的类型由外部指定 private T key; //泛型构造方法形参key的类型也为T,T的类型由外部指定 public Generic(T key) { this.key = key; } //泛型方法getKey的返回值类型为T,T的类型由外部指定 public T getKey(){ return key; } public static void main(String[] args) { //泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 //传入的实参类型需与泛型的类型参数类型相同,即为Integer. Generic<Integer> genericInteger = new Generic<Integer>(123456); //传入的实参类型需与泛型的类型参数类型相同,即为String. Generic<String> genericString = new Generic<String>("key_vlaue"); System.out.println("泛型测试:" + "key is " + genericInteger.getKey()); System.out.println("泛型测试:" + "key is " + genericString.getKey()); } }
输出结果如下:
泛型测试:key is 123456
泛型测试:key is key_vlaue
定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
看一个例子:
Generic generic = new Generic("111111"); Generic generic1 = new Generic(4444); Generic generic2 = new Generic(55.55); Generic generic3 = new Generic(false); System.out.println("泛型测试:" + "key is " + generic.getKey()); System.out.println("泛型测试:" + "key is " + generic1.getKey()); System.out.println("泛型测试:" + "key is " + generic2.getKey()); System.out.println("泛型测试:" + "key is " + generic3.getKey());
输出如下:
泛型测试:key is 111111 泛型测试:key is 4444 泛型测试:key is 55.55 泛型测试:key is false
四 泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生成器中,这是一种专门负责创建对象的类。一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。在这里,就是next()方法。
可以看一个例子:
//定义一个泛型接口 public interface Generator<T> { public T next(); }
当实现泛型接口的类,未传入泛型实参时:
/** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即:class FruitGenerator<T> implements Generator<T>{ * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class" */ class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
当实现泛型接口的类,传入泛型实参时:
/** * 传入泛型实参时: * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T> * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。 */ public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
下面看一个具体的例子,CoffeeGenerator 是一个生成器类,实现Generator<Coffee>接口,它能够随机生成不同类型的Coffee对象:
import java.util.*; ///泛型接口,生成器 interface Generator<T>{ T next(); } class Coffee{ private static long counter = 0; private final long id = counter++; public String toString() { return getClass().getSimpleName() + " " + id; } } class Latte extends Coffee{} class Mocha extends Coffee{} class Cappuccino extends Coffee{} class Americano extends Coffee{} class Breve extends Coffee{} //Coffee生成器(实现了Generator泛型接口、以及Iterable接口->可以用来迭代遍历生成器每个元素) public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{ private Class[] types = {Latte.class,Mocha.class,Cappuccino.class,Americano.class,Breve.class}; private static Random rand = new Random(47); public CoffeeGenerator() {} //设置生成器大小 private int size = 0; public CoffeeGenerator(int sz) {size = sz;} //每次生成一个对象 @Override public Coffee next() { // TODO Auto-generated method stub try { return (Coffee)types[rand.nextInt(types.length)].newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } //实现Iterator接口(这里只是一个简单实现,很多细节没有处理),从而可以迭代遍历每个元素 class CoffeeIterator implements Iterator<Coffee>{ //保存剩余元素的个数 int count = size; @Override public boolean hasNext() { // TODO Auto-generated method stub return count > 0; } @Override public Coffee next() { // TODO Auto-generated method stub count--; return CoffeeGenerator.this.next(); } public void remove() { throw new UnsupportedOperationException(); } } //用于创建迭代器对象 @Override public Iterator<Coffee> iterator() { // TODO Auto-generated method stub return new CoffeeIterator(); } public static void main(String[] args) { CoffeeGenerator gen = new CoffeeGenerator(); for(int i=0;i<5;i++) System.out.println(gen.next()); //循环语句之所以可以使用,只是因为实现了Iterable接口 for(Coffee c:new CoffeeGenerator(5)) System.out.println(c); } }
输出如下:
Americano 0 Latte 1 Americano 2 Mocha 3 Mocha 4 Breve 5 Americano 6 Latte 7 Cappuccino 8 Cappuccino 9
参数化的Generator接口确保next()的返回值是参数的类型。CoffeeGenerator同时还实现了Iterable接口,所以它可以在循环语句中使用。不过,它还需要一个"末端哨兵"来判断何时停止,这正是第二个构造器的功能。
五 泛型通配符
我们知道Ingeter是Number的一个子类,同时在泛型特性中我们也验证过Generic<Ingeter>与Generic<Number>实际上是相同的一种基本类型。那么问题来了,在使用Generic<Number>作为形参的方法中,能否使用Generic<Ingeter>的实例传入呢?在逻辑上类似于Generic<Number>和Generic<Ingeter>是否可以看成具有父子关系的泛型类型呢?
为了弄清楚这个问题,我们使用Generic<T>
这个泛型类继续看下面的例子:
public void showKeyValue(Generic<Number> obj){ System.out.println("泛型测试:" + "key value is " + obj.getKey()); }
Generic<Integer> gInteger = new Generic<Integer>(123); Generic<Number> gNumber = new Generic<Number>(456); //The method showKeyValue(Generic<Number>) in the type Generic<T> is not applicable for the arguments (Generic<Integer>) //showKeyValue(gInteger); showKeyValue(gNumber);
通过提示信息我们可以看到Generic<Integer>不能被看作为Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
回到上面的例子,如何解决上面的问题?总不能为了定义一个新的方法来处理Generic<Integer>类型的类,这显然与java中的多台理念相违背。因此我们需要一个在逻辑上可以表示同时是Generic<Integer>和Generic<Number>父类的引用类型。由此类型通配符应运而生。
我们可以将上面的方法改一下:
public void showKeyValue1(Generic<?> obj){ System.out.println("泛型测试:" + "key value is " + obj.getKey()); }
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。重要说三遍!此处’?’是类型实参,而不是类型形参 ! 此处’?’是类型实参,而不是类型形参 !再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
六 泛型方法
在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
要定义泛型方法,只需将泛型参数列表置于返回值之前,就像下面那样:
/** * 泛型方法的基本介绍 * @param tClass 传入的泛型实参 * @return T 返回值为T类型 * 说明: * 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。 * 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 * 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。 * 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 */ public <T> T genericMethod(Class<T> tClass)throws InstantiationException , IllegalAccessException{ T instance = tClass.newInstance(); return instance; }
Object obj = genericMethod(Class.forName("test"));
1、泛型方法的基本用法
光看上面的例子可能依然会非常迷糊,我们再通过一个例子,把我泛型方法再总结一下。
public class GenericTest { //这个类是个泛型类 public class Generic<T>{ private T key; public Generic(T key) { this.key = key; } //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。 //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。 //所以在这个方法中才可以继续使用 T 这个泛型。 public T getKey(){ return key; } /** * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E" * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。 public E setKey(E key){ this.key = keu } */ } //这不是一个泛型类 private static class GenericMethods{ //这是一个泛型方法 public <T> void f(T x) { System.out.println(x.getClass().getSimpleName()); } } /** * 这才是一个真正的泛型方法。 * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T * 这个T可以出现在这个泛型方法的任意位置. * 泛型的数量也可以为任意多个 */ public <T> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); //当然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已 public void showKeyValue1(Generic<Number> obj){ System.out.println("泛型测试:" + "key value is " + obj.getKey()); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类 public void showKeyValue2(Generic<?> obj){ System.out.println("泛型测试:" + "key value is " + obj.getKey()); } /** * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' " * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。 * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。 public <T> T showKeyName(Generic<E> container){ ... } */ /** * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' " * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。 * 所以这也不是一个正确的泛型方法声明。 public void showkey(T genericObj){ ... } */ public static void main(String[] args) { GenericTest.GenericMethods gm = new GenericTest.GenericMethods(); gm.f(""); gm.f(1); gm.f(1.0); gm.f(1.0F); gm.f('c'); gm.f(gm); } }
输出如下:
String
Integer
Double
Float
Character
GenericMethods
注意:当使用泛型类时,必须创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找到具体的类型,这称为类型参数推断(type argument inference)。
2、类型参数推断
当使用泛型类创建一个对象时,我们是需要指明参数类型的,就像下面程序这样:
Map<Person,List<? extends Pet>> petPeople = new HashMap<Person,List<? extends Pet>>(); //Map<Person,List<? extends Pet>> petPeople = new HashMap(); //虽然不会报错,但是会出现编译警告
上面这种情况时,编译器无法从泛型参数列表中的一个参数推断出另一个参数。但是,在泛型方法中,类型参数推断是可以工作的。例如,我们编写一个工具类,它包含各种各样的static方法,专门用来创建各种常用的容器对象:
///一个工具类,专门用来创建各种常用的容器对象 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>(); } }
main方法演示了如何使用这个工具类,类型参数推断避免了重复的泛型参数列表:
import java.util.*; class Person{} class Pet{} public class SimplePets { public static void main(String[] args) { Map<Person,List<? extends Pet>> petPeople = New.map(); } }
如果你将一个泛型方法调用的结果(例如New.map())作为参数,传递给另一个方法,这时编译器也会执行类型参数推断。下面的例子证明了这一点:
import java.util.*; public class LimitsOfInference { static void f(Map<Person,List<? extends Pet>> petPeople) { System.out.println(petPeople.getClass().getName()); } public static void main(String[] args) { f(New.map()); } }
输出:
java.util.HashMap
3、泛型方法与可变参数
泛型方法与可变参数可以很好的共存:
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("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")); System.out.println(ls); } }
输出如下:
[A]
[A, B, C]
[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
makeList()方法展示了与标准类库中java.util.Arrays.asList()方法相同的功能。
4、用于Generator的泛型方法
利用生成器,我们可以很方便的填充一个Collection,而泛型化这种操作是具有实际意义的:
///斐波那契数列 public class Fibonacci implements Generator<Integer>{ private int count = 0; @Override public Integer next() { // TODO Auto-generated method stub 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()); } } }
import java.util.*; public class Generators { public static <T> Collection<T> fill(Collection<T> coll,Generator<T> gen,int n){ for(int i=0;i<n;i++) coll.add(gen.next()); return coll; } public static void main(String[] args) { Collection<Coffee> coffee = fill(new ArrayList<Coffee>(),new CoffeeGenerator(),4); for(Coffee c:coffee) System.out.println(c); Collection<Integer> fnumbers = fill(new ArrayList<Integer>(),new Fibonacci(),12); for(int i:fnumbers) System.out.print(i + ", "); } }
输出如下:
Americano 0 Latte 1 Americano 2 Mocha 3 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
5、一个通用的Generator
下面的程序可以为任何类构造一个Generator,只要该类具有默认的构造器。为了减少类型声明,它提供一个泛型方法,用以生成BasicGenerator:
public class BasicGenerator<T> implements Generator<T> { private Class<T> type; //构造函数 public BasicGenerator(Class<T> type) { this.type = type; } @Override public T next() { // TODO Auto-generated method stub try { return type.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } //静态方法:用于创建一个生成器对象 public static <T> Generator<T> create(Class<T> type){ return new BasicGenerator<T>(type); } }
这个类提供了一个基本实现,用以生成某个类的对象。这个类它必须具备默认构造器(无参数构造器);
要创建这样的BasicGenerator对象,只需调用create()方法,并传入想要生成的类型。泛型化的create()方法允许执行BasicGenerator.create(MyType.class),而不必执行麻烦的new BasicGenerator<MyType>(MyType.class)。
例如下面是一个具有默认构造器的简单的类:
public class CountedObject { private static long counter = 0; private final long id = counter++; public long id() { return id; } public String toString(){ return "CountedObject " + id; } }
CountedObject类能够记录下它创建了多少个CountedObject实例,并通过toString()方法告诉我们其编号。
使用BasicGenrator,可以容易的为CountedObject创建一个Generator:
public class BasicGeneratorDemo { public static void main(String[] args) { //创建一个生成器对象 Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for(int i=0;i<10;i++) { System.out.println(gen.next()); } } }
输出如下:
CountedObject 0 CountedObject 1 CountedObject 2 CountedObject 3 CountedObject 4 CountedObject 5 CountedObject 6 CountedObject 7 CountedObject 8 CountedObject 9
可以看到,使用泛型方法可以创建Generator对象,大大减小了我们要编写的代码。Java泛型要传入Class对象,以便也可以在create()方法中用它进行类型参数推断。
6、一个Set实用工具
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了