《JAVA编程思想》学习笔记---第十五章:泛型
2016-11-15 22:03
288 查看
2,简单泛型
一个元组类库
return只能返回一个对象,要是想返回多个对象,解决方法就是让返回的这个对象持有多个想返回的对象,但是每次返回都需要不同类型的对象,那就需要创建很多类,使用泛型可以很好地避免此类问题。同时,我们在编译时就可以确保类型安全。这种概念就成为元组。这个容器对象允许读取对象,但是允许向其中添加对象。
package com.generic; public class TwoTuple<A,B> { public final A first; public final B second; public TwoTuple(A a,B b){ first = a; second = b; } public String toString(){ return "("+first+ " "+second+")"; } }
这里变量设置为Final,可以保证其不被修改。
可以使用继承机制实现长度更长的元组:
package com.generic; 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+")"; } }
实现更长的元祖以此类推。
使用元组:
package com.generic; class Amp{} public class TupleTest { static TwoTuple<String,Integer> f(){ return new TwoTuple<String,Integer>("hi",47); } static ThreeTuple<Amp,String,Integer> g(){ return new ThreeTuple(new Amp(),"three",48); } public static void main(String[] args) { System.out.println(f()); System.out.println(g()); } }
一个堆栈类
不用Linkedlist的Stack:package com.generic; 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>(); 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<>(); for(String s:"Phasers on stun!".split(" ")){ lss.push(s); } String s; while((s = lss.pop()) != null){ System.out.println(s); } } } /* 输出: stun! on Phasers */
很有意思的例子。
个人理解相当于一个链表的样子。
RandomList
一个使用泛型实现随机访问的例子:package com.generic; import java.util.ArrayList; import java.util.Random; public class RandomList<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(47); public void add(T item){ storage.add(item); } public T select(){ return storage.get(rand.nextInt(storage.size())); } public static void main(String[] args) { RandomList<String> rs = new RandomList<>(); for(String s:"the quick brown fox jumped over".split(" ")){ rs.add(s); } for(int i = 0; i < 5; i++){ System.out.print(rs.select()+" "); } } } /* 输出: brown over quick over quick */
3,泛型接口
package com.generic; public interface generator<T> { T next() throws InstantiationException, IllegalAccessException; }
定义一些Coffee类:
package com.generic; public class Coffee { private static long counter = 9; private final long id = counter++; public String toString(){ return getClass().getSimpleName(); } } public class Latte extends Coffee{} ....
编写类,实现这个接口,随机生成不同类型的Coffee对象:
package com.generic; import java.util.*; 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() throws InstantiationException, IllegalAccessException { return (Coffee)types[rand.nextInt(types.length)].newInstance(); } class CoffeeIterator implements Iterator<Coffee>{ int count = size; @Override public boolean hasNext() { return count > 0; } @Override public Coffee next() { count--; Coffee coffee = null; try { coffee = CoffeeGenerator.this.next(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return coffee; } } @Override public Iterator<Coffee> iterator() { return new CoffeeIterator(); } public static void main(String[] args) throws InstantiationException, IllegalAccessException{ CoffeeGenerator gen = new CoffeeGenerator(); for(int i = 0; i < 5; i++){ System.out.print(gen.next()+" "); } System.out.println(); for(Coffee c: new CoffeeGenerator(5)){ System.out.print(c+" "); } } } /* 输出: Americano Latte Americano Mocha Mocha Breve Americano Latte Cappuccino Cappuccino */
接口泛型的另一个实例:
package com.generic; import java.util.Iterator; public class Fibonacci implements generator<Integer>,Iterable<Integer>{ private int count = 0; private int n = 0; public Fibonacci(){} public Fibonacci(int n){ this.n = n; } @Override public Integer next() throws InstantiationException, IllegalAccessException { return fib(count++); } private int fib(int n){ if(n < 2){ return 1; } return fib(n-2)+fib(n-1); } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>(){ @Override public boolean hasNext() { return n > 0; } @Override public Integer next() { n--; int i = 0; try { i = Fibonacci.this.next(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return i; } }; } public static void main(String[] args) throws InstantiationException, IllegalAccessException{ Fibonacci gen = new Fibonacci(); for(int i = 0;i < 18;i++){ System.out.print(gen.next()+" "); } System.out.println(); for(int i: new Fibonacci(18)){ System.out.print(i+" "); } } } /* 输出: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 */
4,泛型方法
package com.generic; public class GenericMethods { public static <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.0); gm.f(1.0f); gm.f('c'); gm.f(gm); f(""); } }
泛型方法可以不依赖与泛型类,如果使用泛型方法可以取代泛型类,那就尽量使用泛型方法!
这种利用参数判断泛型类型。下面使用返回值判断泛型类型。
类型参数推断
package com.generic; import java.util.*; public class New { public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } public static <T> void list(){ } 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>(); } public static void main(String[] args) { Map<String,List<String>> sls = New.map(); New.list(); LinkedList<String> lls = New.lList(); Set<String> ss = New.set(); Queue<String> qs = New.queue(); } }
但是这种方法只在赋值操作时有效,但在其他地方并不适用,看如下示例:
package com.generic; import java.util.*; public class LimitsOfInference { static void f(Map<Coffee,Coffee> coffee){ System.out.println(coffee.getClass()); coffee.put(new Coffee(), new Latte()); } public static void main(String[] args) { f(New.map()); } }
这段代码书上说不可以,但是实际是可以的!
可以显示的指明类型:
package com.generic; import java.util.Map; public class ExplicitTypeSpecification { static void f(Map<Coffee,Coffee> coffee){ System.out.println(coffee.getClass()); coffee.put(new Coffee(), new Latte()); } public static void main(String[] args) { f(New.<Coffee,Coffee>map()); } }
这种必须在方法前指明调用者,在方法前指明泛型类型,这种方法不常用。
可变参数与泛型方法
package com.generic; 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); } }
用于Generator的泛型方法
就是一个使用泛型的例子:package com.generic; import java.util.*; public class Generators { public static <T> Collection<T> fill(Collection<T> coll,generator<T> gen,int n) throws InstantiationException, IllegalAccessException{ for(int i = 0; i < n;i++){ coll.add(gen.next()); } return coll; } public static void main(String[] args) throws InstantiationException, IllegalAccessException { Collection<Coffee> coffee = fill(new ArrayList<Coffee>(),new CoffeeGenerator(),4); for(Coffee c:coffee){ System.out.println(c); } } }
一个通用的Generator
package com.generic; public class BasicGeneratorDemo { public static void main(String[] args) throws InstantiationException, IllegalAccessException { generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for(int i = 0; i < 5; i++){ System.out.println(gen.next()); } } }
package com.generic; public class CountedObject { private static long counter = 0; private final long id = counter++; public long id(){ return id; } public String toString(){ return "CountedObject"+id; } }
package com.generic; public class BasicGeneratorDemo { public static void main(String[] args) throws InstantiationException, IllegalAccessException { generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for(int i = 0; i < 5; i++){ System.out.println(gen.next()); } } }
挺简单的,不解释了。
简化元组的使用
package com.generic; public class Tuple { public static <A,B> TwoTuple<A,B> tuple(A a, B b){ return new TwoTuple<A,B>(a,b); } public static <A,B,C> ThreeTuple<A,B,C> tuple(A a, B b,C c){ return new ThreeTuple<A,B,C>(a,b,c); } }
package com.generic; import static com.generic.Tuple.*; public class TupleTest2 { static TwoTuple<String,Integer> f(){ return tuple("hi",47); } static TwoTuple f2(){ return tuple("hi",47); } static ThreeTuple<Amphibian,String,Integer> h(){ return tuple(new Amphibian(),"hi",47); } public static void main(String[] args) { System.out.println(f()); System.out.println(f2()); System.out.println(h()); } }
一个Set实用工具
有一个例子,略!5,匿名内部类
泛型的一个好处就是可以简单而安全的创建复杂的模型,下面实例很容易的创建了List元组:package com.generic; import java.util.*; public class TupleList<A,B,C> extends ArrayList<ThreeTuple<A,B,C>> { public static void main(String[] args){ TupleList<Amphibian,String,Integer> t1 = new TupleList<>(); t1.add(TupleTest.g()); t1.add(TupleTest.g()); for(ThreeTuple<Amphibian,String,Integer> i : t1){ System.out.println(i); } } }
另一个复杂模型,真够复杂的,有点搞不懂:
package com.generic; import java.util.*; class Product{ private final int id; private String description; private double price; public Product(int IDumber,String descr,double price){ id = IDumber; 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){ try { Generators.fill(this, Product.generator, nProducts); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } } 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)); } }
逻辑有点复杂,智商不够用。
7,擦除的神秘之处
尽管可以声明ArrayList.class但是不能声明
ArrayList<Integer>
package com.generic; import java.util.*; 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); } } /* 输出: true */
利用反射得到泛型信息:
package com.generic; import java.util.*; class Frob{} class Fnorkle{} class Quark<Q>{} class Particle<POSITION,MOMENTUM>{} public class LostInformation { public static void main(String[] args) { List<Frob> list = new ArrayList<>(); Map<Frob,Fnorkle> map = new HashMap<>(); Quark<Fnorkle> quark = new Quark<>(); Particle<Long,Double> p = new Particle<>(); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); System.out.println(Arrays.toString(map.getClass().getTypeParameters())); System.out.println(Arrays.toString(quark.getClass().getTypeParameters())); System.out.println(Arrays.toString(p.getClass().getTypeParameters())); } } /* 输出: [E] [K, V] [Q] [POSITION, MOMENTUM] */
输出的只是用作参数的占位符的表示符。
因此,残酷的现实是:
在泛型代码内部,无法获得任何有关泛型参数类型的信息
java泛型是使用擦除来实现的,这意味着使用泛型时,任何具体的类型信息都被擦除了!
C++的方式
看不懂,不看迁移兼容性
好多字,大体的意思就是泛型不是在java出现时就有的特性,而是后来添加的。为了不使之前的类库代码报废,只好使用了向前兼容的方式,所有使用了擦除的方式。擦除的问题
擦除的代价是显著的,泛型不能用于显示的引用运行时类型的操作之中,例如转型,instanceof,和new表达式。因为所有关于参数的类型的信息都丢失了,所以无论何时,在编写泛型代码时,必须时刻提醒自己,这只是看起来拥有有关参数的类型信息而已,因此,如果看到如下代码:class Foo<T>{ T var; }
创建实例时:
Foo<Cat> f = new Foo<Cat>();
Foo类的代码应该知道工作在Cat类之上,而泛型语法也强烈暗示,在整个类的所有地方,类型T都被Cat替换。但是事实并非如此,当在编写这个类的代码时,必须提醒自己:“这只是一个Object”;
所以,擦除和迁移兼容性以为这使用泛型并不是强制的,不使用泛型只会出现警告而不是报错!
package com.generic; class GenericBase<T>{ private T element; public void set(T arg){ element = arg; System.out.println("set"); } public T get(){ return element; } } class Derived1<T> extends GenericBase<T>{} class Derived2 extends GenericBase{}//不警告 public class ErasureAndInheritance { public static void main(String[] args) { Derived2 d2 = new Derived2(); Object obj = d2.get(); d2.set(obj);//警告 } }
边界处的动作
正式有了擦除,泛型可以表示没有任何意义的实物:package com.generic; import java.lang.reflect.Array; import java.util.Arrays; public class ArrayMaker<T> { private Class<T> kind; public ArrayMaker(Class<T> kind){ this.kind = kind; } T[] create(int size){ //T[] t = new T[]{kind.newInstance(),kind.newInstance()};报错 return (T[])Array.newInstance(kind, size); } public static void main(String[] args) { ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class); String[] stringArray = stringMaker.create(9); System.out.println(Arrays.toString(stringArray)); } } /* 输出: [null, null, null, null, null, null, null, null, null] */
注意,在泛型中,推荐使用Array.newInstance()。
创建一个容器而不是一个数组:
package com.generic; import java.util.*; public class FilledListMaker<T> { List<T> create(T t, int n){ List<T> result = new ArrayList<T>(); for(int i = 0; i <n;i++){ result.add(t); } return result; } public static void main(String[] args) { FilledListMaker<String> stringMaker = new FilledListMaker<>(); List<String> list = stringMaker.create("Hello", 4); System.out.println(list); } }
擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力。任何在运行时需要知道确切类型信息的操作都无法总做!package com.generic; public class Erased<T> { private final int SIZE = 100; public void f(Object arg){ /*if(arg instanceof T){ } T var = new T(); T[] array = new T[SIZE];*///报错 T[] array = (T[])new Object[SIZE];//警告 } }
绕过这些问题来编程,可以使用Class:
package com.generic; class Building{} class House extends Building{} public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind){ this.kind = kind; } public boolean f(Object arg){ return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt = new ClassTypeCapture<>(Building.class); System.out.println(ctt.f(new Building())); System.out.println(ctt.f(new House())); ClassTypeCapture<House> ctt2 = new ClassTypeCapture<>(House.class); System.out.println(ctt2.f(new Building())); System.out.println(ctt2.f(new House())); } }
这样,可以确保类型标签可以匹配泛型参数。
个人理解:
泛型 T 只可以用来标识另一种东西,如容器类型,返回值类型,参数类型等,但不可以像类一样生成对象!即不能用在运行时。
new T[]这样的操作无法实现部分原因是因为擦除,而另一部分原因是因为编译器不能检查T具有默认构造器
这里写代码片,下面代码将解决这一问题:
package com.generic; class ClassAsFactory<T>{ public T x; public ClassAsFactory(Class<T> kind){ try { x = kind.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } } class Employee{ public void print(){ System.out.println("Employee"); } } public class InstantiateGenericType { public static void main(String[] args) { ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class); fe.x.print(); ClassAsFactory<Integer> in = new ClassAsFactory<>(Integer.class); } }
可以看到生成
ClassAsFactory<Integer>实例时会报错,因为Integer没有默认的构造器。同时,这个错误不是在编译期捕获的。
使用显示的工厂方法避免运行时异常,个人认为这种方法不太灵活。
package com.generic; interface Factory<T>{ T create(); } class Foo2<T>{ private T x; public <F extends Factory<T>> Foo2(F factory){ x = factory.create(); } } class IntegerFactory implements Factory<Integer>{ public Integer create(){ return new Integer(0); } } class Widght{ public static class FactoryDemo implements Factory<Widght>{ public Widght create(){ return new Widght(); } } } public class FactoryConstraint { public static void main(String[] args) { new Foo2<Integer>(new IntegerFactory()); new Foo2<Widght>(new Widght.FactoryDemo()); } }
使用模板方法模式也能解决这个问题:
package com.generic; abstract class GenericWithCreate<T>{ final T ele; GenericWithCreate(){ ele = create(); } abstract T create(); } class X{} class Creator extends GenericWithCreate<X>{ X create(){ return new X(); } void f(){ System.out.println(ele.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { Creator creator = new Creator(); creator.f(); } }
泛型数组
如前面所示,不能创建泛型数组一般的解决档案是使用ArrayList:package com.generic; import java.util.*; public class ArrayOfGenericReference<T> { private List<T> array = new ArrayList<T>(); public void add(T item){ array.add(item); } public T get(int index){ return array.get(index); } }
package com.generic; import java.lang.reflect.Array; class Generic<T>{} public class ArrayOfGeneric { static final int SIZE = 5; static Generic<Integer>[] gia; public static void main(String[] args) { gia = (Generic<Integer>[])new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<Integer>(); //gia[1] = new Generic<Double>();报错 for(int i = 0; i < SIZE; i++){ System.out.println(gia[i]); } } }
挺正常的代码。
相关文章推荐
- C++ Primer学习笔记之第12章-泛型-踩在巨人的脚背上-prog12.cpp程序
- [.net泛型学习笔记之一]泛型介绍
- 泛型(Generics Types)学习笔记
- j2ee学习笔记——初试ssh之泛型通用dao(转)
- 看完think in java第8章后的想法以及对java 1.5以后版本泛型的学习笔记
- Java6学习笔记30——Java6的泛型
- 泛型(Generics Types)学习笔记<一>
- 笔记十一 泛型学习二 常用操作
- 泛型学习笔记
- JAVA学习笔记-------泛型,约束
- Generics Types 泛型学习笔记
- 泛型(Generics Types)学习笔记
- java编程思想第四版第二章学习笔记
- Generics Types 泛型学习笔记
- 泛型学习笔记
- Generics Types 泛型学习笔记<三>
- Java 学习笔记:泛型(二)
- 泛型学习笔记
- java学习笔记---第13章泛型程序设计
- C#学习笔记-泛型