第15章泛型(上)

1、一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码。这种刻板的限制对代码的束缚就会很大。
2、凡是需要说明类型的地方,如果都使用基类,确实能够具备更好的灵活性。但是,考虑到除了final类不能扩展,其他任何类都可以被扩展,所以这种灵活性大多数时候也会有一些性能损耗。
3、泛型实现了参数化类型的概念,使代码可以应用于多种类型。
15.1 与C++比较
15.2 简单泛型
Java泛型的核心概念;告诉编译器想使用什么类型,然后编译器帮你处理一切细节。

class Automobile{

}
public class Holder3<T> {
    private T a;

    public Holder3(T a) {
        this.a = a;
    }

    public T get() {
        return a;
    }

    public void set(T a) {
        this.a = a;
    }

    public static void main(String[] args) {
        //当创建holder3对象的时候,必须指明想持有什么类型的对象,将其置于尖括号内,从Holder3种取出持有的对象时,
        //自动地就是正确的类型
        Holder3<Automobile> holder3 = new Holder3<>(new Automobile());
        Automobile a = holder3.get();
      //  holder3.set(1); Error
    }
}

15.2.1 一个元组类库
1、元组:将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。(这个概念也称为数据传送对象或信使)
2、通常,元组具有任意长度,同时元组中的对象可以是任意不同的类型。不过,希望能够为每一个对象指明其类型,并且从容器中读取出来时,能够得到正确的类型。要处理不同长度的问题,需要创建多个不同的元组。
3、继承机制实现长度更长的元组
//有了泛型,可以很容易创建元组,令其返回一组任意类型的对象。而你所需要做的,就是编写表达式。

package com15;
/**
 * Created by Panda on 2018/5/15.
 */
class TwoTuple<A,B>{
    public final A first;
    public final B second;

    public TwoTuple(A first, B second) {
        this.first = first;
        this.second = second;
    }

    @Override
    public String toString() {
        return "TwoTuple{" +
                "first=" + first +
                ", second=" + second +
                '}';
    }
}
class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
    public final C third;
    public ThreeTuple(A first, B second, C third) {
        super(first, second);
        this.third = third;
    }

    @Override
    public String toString() {
        return "ThreeTuple{" +
                "first=" + first +
                ", second=" + second +
                ", third=" + third +
                '}';
    }
}
class Amphibian{}
class Vehicle{}
public class TupleTest{
    static TwoTuple<String,Integer> f(){
        return new TwoTuple<String,Integer>("hi",47);
    }
    static ThreeTuple<Amphibian,String ,Integer> g(){
        return new ThreeTuple<Amphibian,String,Integer>(new Amphibian(),"hi",47);
    }

    public static void main(String[] args) {
        TwoTuple<String, Integer> twoTuple=f();
         /// twoTuple.first="three"; Compile error final
        System.out.println(twoTuple);
        System.out.println(g());
    }
}

15.2.2一个堆栈类
1、不用LinkedList,实现内部链式存储机制

package com15;
/**
 * Created by Panda on 2018/5/15.
 */
public class LinkedStack<T> {
    private static class Node<U>{
        U item;
        Node<U> next;
        Node(){item=null;next=null;}
        public 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> linkedStack = new LinkedStack<>();
        for (String s:"Phasers on stun".split(" ")) {
            linkedStack.push(s);
        }
        String s;
        while ((s=linkedStack.pop())!=null){
            System.out.println(s);
        }
    }
}

15.2.3RandomList
1、容器的另外一个例子:需要一个持有特定类型对象的列表,每次调用其上的select()方法时,可以随机地选取一个元素。如果希望以此构建一个可以应用于各种类型的对象的工具,就需要使用泛型。
15.3泛型接口
1、泛型也可以应用于接口。例如:生成器,是一种专门负责创建对象的类。实际上,是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。
2、接口使用泛型与类使用泛型没有什么区别。
3、


/**
 * Created by Panda on 2018/5/15.
 */
interface Generator<T>{T next();}
//基本类型无法作为类型参数
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 fibonacci = new Fibonacci();
        for (int i = 0; i <18 ; i++) {
            System.out.print(fibonacci.next()+" ");
        }
    }
}

15.4 泛型方法
1、是否拥有泛型方法,与其所在的类是否是泛型没有关系。
2、泛型基本指导规则:无论何时,只要能做到就应该尽量使用泛型方法。也就是说,使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为泛型方法可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
3、定义泛型方法,只需要将泛型参数列表置于返回值之前。
4、当使用泛型类的时候,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必致命参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断。
5、显示的类型说明:在泛型方法中,可以显示地指明类型。不过这种语法很少使用,要显示地指明类型,必须在点操作符和方法名之间插入尖括号,然后把类型置于尖括号内。如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。
15.4.2可变参数与泛型方法

package com15;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by Panda on 2018/5/15.
 */
public class Test1 {
    public static <T> List<T> makeList(T...args){
        List<T> result=new ArrayList<>();
        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(("ABCD").split(" "));
        System.out.println(ls);
    }

}

15.4.3用于Generator的泛型方法
15.4.4一个通用的Generator

package com15;


/**
 * Created by Panda on 2018/5/15.
 */

//这个类提供了一个基本的实现,用以生成某个类的对象。这个类必须具备两个特点:
    //①它必须声明为public(因为BasicGenerator要处理的类在不同的包中,所以该类必须声明为public
    //并且不只具有包内访问权限。②它必须具备默认的构造器(无参构造器)。要创建这样的BasicGenerator
    //对象,只需调用create()方法,而不必执行new
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<>(type);
    }


    public static void main(String[] args) {
        Generator<BasicGeneratorDemo> generatorDemoGenerator =BasicGenerator.create(BasicGeneratorDemo.class);
        for (int i = 0; i < 5; i++) {
            System.out.println(generatorDemoGenerator.next());
        }
    }

}
class BasicGeneratorDemo{
    private static long count=0;
    private final long id=count++;
    public long id(){return id;}
    public String toString(){return "demo"+id;}
}
/**
demo0
demo1
demo2
demo3
demo4
*/

15.4.5简化元组的使用
15.4.6一个Set实用工具

package com15;

import java.util.HashSet;
import java.util.Set;

/**
 * Created by Panda on 2018/5/15.
 */
public class Sets {
    //将两个参数合并在一起
    public static <T> Set<T> union(Set<T> a, Set<T> b){
        Set<T> result=new HashSet<>(a);
        result.addAll(b);
        return result;
    }
    //交集
    public static <T> Set<T> intersection(Set<T> a,Set<T> b){
        Set<T> result =new HashSet<>(a);
        result.retainAll(b); //交集
        return result;
    }
    //移除部分元素 从superset中移除subset包含的元素
    public static <T> Set<T> difference(Set<T> superset,Set<T> subset){
        Set<T> result=new HashSet<>(superset);
        result.removeAll(subset);
        return result;
    }
    //除交集之外的所有元素
    public static <T> Set<T> complement(Set<T> a,Set<T> b){
        return difference(union(a,b),intersection(a,b));
    }
}

15.5 匿名内部类
1、泛型还可以应用于内部类以及匿名内部类。

package com15;

import java.util.*;

/**
 * Created by Panda on 2018/5/15.
 */
class Customer{
    private static long counter=1;
    private final long id=counter++;
    private Customer(){}
    public String toString(){return "customer:"+id;}
    public static Generator<Customer> generator(){
        return new Generator<Customer>() {
            @Override
            public Customer next() {
                return new Customer();
            }
        };
    }
}
class Teller{
    private static long counter=1;
    private final long id=counter++;
    private Teller(){}
    public String toString(){return "Teller:"+id;}

    public static Generator<Teller> generator=new Generator<Teller>() {
        @Override
        public Teller next() {
            return new Teller();
        }
    };

}
class Generators{
    public static <T> Collection<T> fill(Collection<T> coll,Generator<T> generator,int n){
        for (int i = 0; i <n ; i++) {
            coll.add(generator.next());
        }
        return coll;
    }
}
public class BankTeller {
    public static void server(Teller teller ,Customer customer){
        System.out.println(teller+" servers "+customer);
    }
    public static void main(String[] args) {
        Random random = new Random(47);
        Queue<Customer> line =new LinkedList<>();
        Generators.fill(line,Customer.generator(),15);
        List<Teller> tellers=new ArrayList<>();
        Generators.fill(tellers,Teller.generator,4);
        for (Customer c:line) {
            server(tellers.get(random.nextInt(tellers.size())),c);
        }
    }
    /**
     * Teller:3 servers customer:1
     Teller:2 servers customer:2
     Teller:3 servers customer:3
     Teller:1 servers customer:4
     Teller:1 servers customer:5
     Teller:3 servers customer:6
     Teller:1 servers customer:7
     Teller:2 servers customer:8
     Teller:3 servers customer:9
     Teller:3 servers customer:10
     Teller:2 servers customer:11
     Teller:4 servers customer:12
     Teller:2 servers customer:13
     Teller:1 servers customer:14
     Teller:1 servers customer:15
     */
}

15.6 构建复杂模型
1、使用泛型类构建复杂模型。即使每个类都是作为一个构建块创建的,但是整个还是包含许多部分。

package com15;

import java.util.ArrayList;
import java.util.Random;

/**
 * Created by Panda on 2018/5/15.
 */
class Product{
    private int id;
    private String description;
    private double price;

    public Product(int id, String description, double price) {
        this.id = id;
        this.description = description;
        this.price = price;
        toString();
    }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", description='" + description + '\'' +
                ", price=" + price +
                '}';
    }
    public void priceChange(double change){
        price+=change;
    }
    public static Generator<Product> generator= new Generator<Product>() {
        private Random random = new Random(47);
        @Override
        public Product next() {
            return new Product(random.nextInt(1000),"Test",Math.round(random.nextDouble()*1000.0)+0.99);
        }
    };
}
class Shelf extends ArrayList<Product>{
    public Shelf(int nProducts){
        Generators.fill(this,Product.generator,nProducts);
    }
}
class Aisle extends ArrayList<Shelf>{
    public Aisle(int nShelves,int nProducts){
        for (int i = 0; i < nShelves; i++) {
            add(new Shelf(nProducts));
        }
    }
}
class CheckOut{}
class Office{}
public class Store extends ArrayList<Aisle> {
    private ArrayList<CheckOut> checkOuts=new ArrayList<>( );
    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(){
        StringBuffer stringBuffer = new StringBuffer();
        for (Aisle a:this) {
           for(Shelf s:a){
               for (Product p:s) {
                   stringBuffer.append(p);
                   stringBuffer.append("\n");
               }
           }
        }
        return stringBuffer.toString();
    }

    public static void main(String[] args) {
        System.out.println(new Store(14,5,10));
    }
}

15.7 擦除的神秘之处

//在泛型代码内部,无法获得任何有关泛型参数类型的信息。
public class Test2 {
    public static void main(String[] args) {
        Class c1=new ArrayList<String>().getClass();
        Class c2=new ArrayList<Integer>().getClass();
        System.out.println(c1==c2);  //true
    }
}

15.7.1C++的方式
1、 声明T必须具有类型B或者从B导出的类型。
15.7.2
1、擦除减少了泛型的泛化性。泛型在Java中仍旧是游泳的,只是不如本来设想的游泳,而原因就是擦除。
2、在基于擦除的实现中,泛型类型被当做第二类类型处理,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦书,替换为它们的非泛型上界。
3、Java泛型不仅必须支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义;而且还要支持迁移兼容性,使得类库按照自己的步调变为泛型的,并且当某个类库变为泛型时,不会破坏依赖于它的代码和应用程序。
15.7.3擦除的问题
1、擦除主要的正当理由是从非泛华代码到泛华代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入Java语言。擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直至客户端准备好用泛型重写这些代码。
2、擦除的代价是显著的。泛型不能用于显示地引用运行时类型的操作之中,例如转型instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了。
15.7.4边界处的动作

package com15;
import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * Created by Panda on 2018/5/15.
 */
public class ArrayMaker<T> {
    private Class<T> kind;
    public ArrayMaker(Class<T> kind){this.kind=kind;}
    T[] create(int size){
        //在泛型中创建数组,使用Array.newInstance() 推荐
        return (T[]) Array.newInstance(kind,size);
    }

    public static void main(String[] args) {
        ArrayMaker<String> stringArrayMaker = new ArrayMaker<>(String.class);
        String[] strings=stringArrayMaker.create(9);
        System.out.println(Arrays.toString(strings));
    }
}

1、擦除在方法或类内部移除了有关实际类型的信息,编译器仍旧可以确保在方法或类中使用的类型的内部一致性。
2、擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。对传递进来的值进行额外的编译期检查,并插入对传递出去的值的类型。
15.8 擦除的补偿

package com15;

/**
 * Created by Panda on 2018/5/15.
 */
//对使用instanceof的尝试失败,因为类型信息已经被擦除了。如果引入类型标签,就可以转而使用动态的isInstance()
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> buildingClassTypeCapture = new ClassTypeCapture<>(Building.class);
        System.out.println(buildingClassTypeCapture.f(new Building())); //true
        System.out.println(buildingClassTypeCapture.f(new House()));    //true
        ClassTypeCapture<House> houseClassTypeCapture = new ClassTypeCapture<>(House.class);
        System.out.println(houseClassTypeCapture.f(new Building()));   //false
        System.out.println(houseClassTypeCapture.f(new House()));      //true
    }
}

15.8.1 创建类型实例

package com15;

/**
 * Created by Panda on 2018/5/15.
 */
//java无法对new T()进行实现,部分原因是擦除,另一部分原因是因为编译器不能验证T具有默认(无参)构造qi
    //Java中解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签
    //就可以使用newInstance()来创建这个类型的新对象。
    class ClassAsFactory<T>{
        T x;
        public ClassAsFactory(Class<T> kind){
            try {
                x = kind.newInstance();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
}
class Employee{}
public class Test3 {
    public static void main(String[] args) {
        ClassAsFactory<Employee> employeeClassAsFactory = new ClassAsFactory<>(Employee.class);
        System.out.println("employee succeeded"); //运行正常
        try{
            ClassAsFactory<Integer> factory = new ClassAsFactory<>(Integer.class);
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("Integer failed");  //运行出错,因为Integer没有任何默认的构造器。
        }
    }
}
package com15;

/**
 * Created by Panda on 2018/5/15.
 */
//建议使用工厂模式
    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>{
    @Override
    public Integer create() {
        return new Integer(0);
    }
}
class Wideget{
        public static class Fac implements Factory<Wideget>{
            public Wideget create(){
                return  new Wideget();
            }
        }
}
public class Test4 {
    public static void main(String[] args) {
        new Foo2<Integer>(new IntegerFactory());
        new Foo2<Wideget>(new Wideget.Fac());
    }
}
/**
 * Created by Panda on 2018/5/15.
 */
//模板方法设计模式
abstract class GenericWithCreate<T>{
    T element;
    GenericWithCreate(){element=create();}
    abstract T create();
}
class X{}
class Creator extends GenericWithCreate<X>{
    @Override
    X create() {
        return new X();
    }
    void f(){
        System.out.println(element.getClass().getSimpleName());  //X
    }
}
public class CreatorGeneric {
    public static void main(String[] args) {
        Creator creator = new Creator();
        creator.f();
    }
}

15.8.2

/**
 * Created by Panda on 2018/5/15.
 */
//1、成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型。
public class Test5<T> {
    private T[] array;
    public Test5 (int size){
        array=(T[])new Object[size];
    }
    public void put(int index ,T item){
        array[index]=item;
    }
    public T get(int index){return array[index];}
    public T[] rep(){return array;}

    public static void main(String[] args) {
        Test5<Integer> test5 = new Test5<>(10);
       // Integer[] integers = test5.rep();   //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer
        Object[] objects = test5.rep();  //运行正常
    }
}

2、因为有了擦除,数组的运行时类型就只能是Object[] 。如果立即将其转型为T[],那么在编译期该数组的实际类型就将丢失,而编译器可能会错过某些潜在的错误检查。正是因为这样,最好在集合内部使用Object[],然后当你使用数组元素时,添加一个对T的转型。
3、对于新代码,应该传递一个类型标记。

package com15;

import java.lang.reflect.Array;

/**
 * Created by Panda on 2018/5/15.
 */
public class Test6<T> {
    private T[] array;
    public Test6(Class<T> type,int size){
        array=(T[]) Array.newInstance(type,size);
    }
    public void put(int index ,T item){
        array[index]=item;
    }
    public T get(int index){return array[index];}
    public T[] rep(){return array;}

    public static void main(String[] args) {
        Test6<Integer> test6 = new Test6<>(Integer.class,10);
        Integer[]  integers = test6.rep();  //运行正常
    }
}

15.9 边界
1、边界使得可以在用于泛型的参数类型上设置限制条件。
2、通配符被限制为单一边界
15.10 通配符
15.10.1 编译器多聪明

package com15;


/**
 * Created by Panda on 2018/5/15.
 */

public class Holder<T> {
    private T value;
    public Holder(){}
    public Holder(T val){value=val;}
    public void set(T val){value=val;}
    public T get(){return value;}
    public boolean equals(Object object){return value.equals(object);}

    public static void main(String[] args) {
        Holder<Apple> appleHolder = new Holder<Apple>(new Apple());
        Apple d = appleHolder.get();
        appleHolder.set(d);

     //   Holder<Fruit> fruitHolder = appleHolder; //cannot upcast
        Holder<?extends Fruit> holder = appleHolder; //ok
        Fruit p = holder.get();
        d=(Apple)holder.get();

        try{
            Orange orange = (Orange)holder.get(); //No waring
        }catch (Exception e){
           // holder.set(new Apple());  // not ok
           // holder.set(new Fruit()); // not ok
            System.out.println(holder.equals(d));
        }
    }
}

15.10.2 逆变
限定通配符总是包括自己
上界类型通配符:add方法受限
下界类型通配符:get方法受限
如果你想从一个数据类型里获取数据,使用 ? extends 通配符 上届
如果你想把对象写入一个数据结构里,使用 ? super 通配符 下届
如果你既想存,又想取,那就别用通配符
不能同时声明泛型通配符上界和下界

阅读更多
文章标签: Thinking in java
个人分类: Thinking in Java
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭