作者:hubery, 时间:2018.11.30-12.01
说起范型,算是java中逼格较高的技能了吧,每每看到某个开源框架或者技术架构最顶层的接口实现,全是P/Q/R/T等的类型参数,瞬间跪着看。其实很多时候都是一叶障目,打算静下心来研究下,写几个简单的类,稍微一运行感受下。
期间查阅了多次 Java编程思想
这本圣经,本人理解的还是太浅,暂且一看。
推荐个项目: android-architecture
googlesample中的android-architecture项目,里面基本都是Google的各风格的项目,值得一看,其中大量用到了范型。
public class Main { static class Automobile {} public static class Holder1 { private Automobile a; public Holder1(Automobile a) { this.a = a; } Automobile get() { return a; } } public static class Holder2<T> { private T a; public Holder2(T a) {this.a = a;} public void set(T a) { this.a = a; } public T get() {return a;} } public static void main(String[] args) { Holder1 h1 = new Holder1(new Automobile()); Holder2<Automobile> h2 = new Holder2<Automobile>(new Automobile()); } } 复制代码
该测试代码中,Holder1是普通类使用,Holder2是用了范型T。 范型的主要目的是: 用来指定T是什么类型,由编译器来保证T的正确性
。 我们要的是暂时不指定类型,等用到的时候再决定是什么类型。要达到这个效果,需要用类型参数T。 用尖括号括住,放在类名后面:Holder2<T>
,后续使用该类的时候用实际类型来替换此类型参数T。
范型核心: 告诉编译器想用什么类型,然后编译器会帮你处理一切细节
。 一般来说,范型和其他类型差不多,只不过范型有类型参数而已。 使用范型时只需要 指定范型的名称和类型参数列表
即可。
范型可应用于接口。设计模式中工厂模式的应用:对象生成器。 如:
public interface Generator<T> { T next(); } 复制代码
写个生成器,next方法返回T。接口与类在使用范型上,没区别。
public class Fibonacci implements Generator<Integer> { private int count = 0; @Override 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 < 10; i++) { System.out.println(gen.next() + " "); } } } 复制代码
斐波那契数列数列的应用,实现范型接口,指定T为Integer型,相应next回调方法返回的T也替换成Integer型。感受一下,完美。 至于算法方面不做多深究,这里主要用来感受范型接口,找找感觉。
范型也可以用在方法上。范型方法所在的类,可以是范型类,也可以是普通类,没相关性。 范型方法指导原则: 无论何时,只要你能做到,尽量使用范型方
法。 范型方法的定义: 只需将范型参数列表放到返回值之前即可
。 看代码:
public class GenericsMethods { // 范型方法:将参数列表写到返回值之前就可以了 public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericsMethods gm = new GenericsMethods(); gm.f(""); gm.f(20); gm.f(1.1); gm.f(1.0F); gm.f(gm); } } 复制代码
打印结果:
java.lang.String java.lang.Integer java.lang.Double java.lang.Float cn.hubery.generic.GenericsMethods 复制代码
代码中,f()拥有类型参数,由该方法的返回类型前面的类型参数列表指明的。
注:
使用范型类时,创建对象的时候必须指定类型参数的具体类型;</p> 使用范型方法时,不必指明参数类型,编译器会自行推断出所需的具体类型; 复制代码
写个通用的Generator 范型类+范型方法
public class BasicGenerator<T> implements Generator<T> { private Class<T> type; public BasicGenerator(Class<T> type) { this.type = type; } @Override public T next() { try { return type.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } public static <T> Generator<T> create(Class<T> type) { return new BasicGenerator<T>(type); } } 复制代码
写个辅助对象类:
public class CountedObject { private static long counter = 0; private final long id = counter++; public long id() { return id; } public String toString() { return "CountedObject" + id; } } 复制代码
写个测试类:
public class BasicGeneratorTest { public static void main(String[] args) { Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for (int i = 0; i < 5; i++) { System.out.println(gen.next()); } } } 复制代码
打印情况:
CountedObject0 CountedObject1 CountedObject2 CountedObject3 CountedObject4 复制代码
通过测试看到,用范型方法创建Generator对象,大大减少代码量。范型方法的入参要求传入Class对象,故可以推断出确切类型。还是得多揣摩下。
待研究。
可以在范型的参数类型上设置限制条件。 由于擦除动作会移除类型信息,故用无界范型参数调用的方法只是那些Object可用的方法。 如果将这个参数限制在某个类型的子集中,那我们就可以调用这些类型子集的方法。 为了设置这种限制,java的范型用了extends关键字。 此处的extends与传统意义上的类继承不是一回事儿。 写法是: TestClass<T extends LineClass>
package cn.hubery.generic.line; import java.util.List; public class EpicBattle { interface SuperPower{} interface XRayVision extends SuperPower { void seeThroughWalls(); } interface SuperHearing extends SuperPower { void hearSubtleNoises(); } interface SuperSmell extends SuperPower { void trackBySmell(); } static class SuperHero<POWER extends SuperPower> { POWER power; SuperHero(POWER power) { this.power = power; } POWER getPower() { return power; } } class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER> { SuperSleuth(POWER power) { super(power); } void see() { power.seeThroughWalls(); } } static class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> { CanineHero(POWER power) { super(power); } void hear() { power.hearSubtleNoises(); } void smell() { power.trackBySmell(); } } static class SuperHearSmell implements SuperHearing, SuperSmell { @Override public void hearSubtleNoises() { } @Override public void trackBySmell() { } } static class DogBoy extends CanineHero<SuperHearSmell> { DogBoy() { super(new SuperHearSmell()); } } static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); } static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); hero.getPower().trackBySmell(); } public static void main(String[] args) { DogBoy dogBoy = new DogBoy(); useSuperHearing(dogBoy); superFind(dogBoy); List<? extends SuperHearing> audioBoys; // List<? extends SuperHearing & SuperSmell> dogBoy;// error } } 复制代码
讲真,有时候 意会不能言传,容我回头组织下语言再慢慢道来。
范型参数表达式中的问号 ?
public class TPF { static class Fruit{} static class Apple extends Fruit{} public static void main(String[] args) { List<? extends Fruit> flist = Arrays.asList(new Apple()); // flist.add(new Apple());// 不能随意向flist中添加对象 // flist.add(new Fruit()); Apple a = (Apple) flist.get(0); flist.contains(new Apple()); flist.indexOf(new Apple()); } } 复制代码
flist类型是List<? extends Fruit>,可以理解问:具有任何从Fruit继承的类型的列表。但,这实际上不代表该List可以持有任何类型的Fruit。 通配符?引用的是明确的类型,因此意味着:某种flist饮用没有指定的具体类型。 例子还没想清楚怎么写,回头补上。
如:不能创建ArrayList之类的。如果需要,jdk会启用自动包装机制,int->Integer。
因为编译器会擦除范型参数,所以要注意,使用范型类时尽量类型不一致。
由于存在擦除,重载方法会产生相同的类型签名,故:保险起见,方法名尽量不一致。
注: 刚通了个宵,白天睡了个回笼觉,平复心情中,现在整理思绪,关于范型的理解会慢慢在本文中补充,欢迎拍砖。
天星技术团QQ: 557247785
。