您的位置:首页 > 编程语言 > Java开发

《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]);
}
}

}


挺正常的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: