Java中泛型(Java编程思想--15章"核心概念、元组、泛型方法")

Java范型核心概念:
        有许多原因促成了范型的出现,其中最瞩目的一个原因,就是为了创建容器类。通常我们只会使用容器来存储一种类型的对象,范型的主要目的之一就是指定要容器持有什么类型的对象,并且由编译器来确定类型的正确性。
        使用类型参数,即:暂时不指定类型,而是稍后再决定使用什么类型(用尖括号括住类型,放在类名后面)。实例1:

//: generics/Holder3.java

public class Holder3<T> {
  private T a;
  public Holder3(T a) { this.a = a; }
  public void set(T a) { this.a = a; }
  public T get() { return a; }
  public static void main(String[] args) {
    Holder3<Automobile> h3 =
      new Holder3<Automobile>(new Automobile());
    Automobile a = h3.get(); // No cast needed
    // h3.set("Not an Automobile"); // Error
    // h3.set(1); // Error
  }
} ///:~

        当你创建Holder3对象时,必需指明持有什么类型,将其放于尖括号内(就像:Holder3 h3 =new Holder3(new Automobile()))。然后就只能往Holder3中存入Automobile类型(或者它的子类型,范型和多态不冲突);并且从Holder3中取出它持有的对象时,(不需要手动转型)自动就是正确的类型(Automobile)。
        以上描述的就是Java范型的核心概念:只需要告诉编译器,你想要使用什么类型,编译器帮你处理一切细节。
实例2:传统的下推堆栈。不用LinkedList数据结构,来实现自己的内部链式存储机制。

package generics;

import static utils.Print.print;

public class LinkedList<T> {
    private Node<T> top = new Node<T>();
    //将接口实现为一个私有内部类,只在该外部类(LinkedList<T>)内部可以访问;

    private static class Node<U>{
        U value;
        Node<U> next;
        public void Node(){
            this.value = null;
            this.next = null;
        }
        //在类名后定义了范型,其中的方法后面将不再需要写范型符号,参见其回答:http://ask.csdn.net/questions/357182
        public void Node(U value,Node<U> next){
            this.value = value;
            this.next = next;
        }
        public boolean end(){
            return this.value == null && this.next == null;
        }
    }
    public void push(T tmp){
        Node<T> node = new Node<T>();
        node.value = tmp;
        node.next = top;
        top = node;
    }
    public T pop(){
        T reslut = top.value;
        if(!top.end()){
            top = top.next;
        }
        return reslut;
    }

    public static void main(String args[]){
        LinkedList<String> stack= new LinkedList<String>();
        for(String s : "there is LinkedList".split(" ")){
            stack.push(s);
        }
        String s;
        while((s = stack.pop())!=null){
            print(s);
        }
    }
}

元组
        元组的应用场景:你应该经常需要这样的功能吧:仅一次方法调用能产生多个返回对象。可是return语句每次只能返回一个对象,因此,解决办法为产生一个对象让它持有需要返回的多个对象。
        元组的概念:将一组对象打包直接存储于一个单一对象中,这个容器对象允许读取其中元素,但是不允许再向其中放入新对象(这个概念也被称为信使或者数据传送对象)。元组可以拥有不同长度,且元组中的对象的类型可以相异。要处理不同长度的(返回值)问题,我们只需要创建不同长度的元组,可以利用继承机制实现更长的元组。
        为了使用元组,你只需要定义一个长度合适的元组(由于有了范型,你可以很容易的创建元组,任其返回一组任意类型的对象(根据你创建元组对象时指定的类型)),然后在return语句中创建该元组,并返回即可。实例3:

public class TwoTuple<A,B> {
/*
*定义为public final,在构造器中初始化之后,便不再能被更改。其中缘由可参考:下一篇博客:“Java中的代码块与final变量的初始化”;
*/
  public final A first;
  public final B second;
  public TwoTuple(A a, B b) { first = a; 
                            second = b; }
  public String toString() {
    return "(" + first + ", " + second + ")";
  }
} 
//利用继承机制来实现长度更长的元组;
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 +")";
  }
}


class Amphibian {}
class Vehicle {}

public class TupleTest {
  static TwoTuple<String,Integer> f1() {
    // Autoboxing converts the int to Integer:
    return new TwoTuple<String,Integer>("hi", 47);
  }
  static TwoTuple<Vehicle,Integer> f2() {
    // Autoboxing converts the int to Integer:
    return new TwoTuple<Vehicle,Integer>(new Vehicle(), 47);
  }
g1() {
    return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(), "hi", 47);
  }
  static ThreeTuple<Vehicle,Amphibian,Integer> 
  g2() {
    return new ThreeTuple<Vehicle,Amphibian, Integer>(new Vehicle(),new Amphibian(), 47);
  }
public static void main(String[] args) {
    TwoTuple<String,Integer> ttsi = f();
    System.out.println(ttsi);
    // ttsi.first = "there"; // Compile error: final
    System.out.println(f1());
    System.out.println(f2());
    System.out.println(g1());
    System.out.println(g2());
  }
/*outPut:
(hi, 47)
(generics.Vehicle@60e53b93, 47)
(generics.Amphibian@5e2de80c, hi, 47)
(generics.Vehicle@1d44bcfa, generics.Amphibian@266474c2, 47)
*/

范型接口
        范型也可以用于接口,接口使用范型和类使用范型没什么区别。都是为了告诉编译器你想让该类的对象持有什么类型,以确保在对象中正确无误的使用该类型(域,方法中使用),如:
接口:public interface Generator {T next();}
实现类:
CoffeeGenerator<T> implement Generator <Coffee> {
    T field;
    Coffee next(){}
    T CoffeeGeneratorf(){}
}
        以上体现了,参数化接口,确保next()的返回值是Generator<Coffee>中的指定的参数的类型(Coffee);确保CoffeeGeneratorf()返回值是CoffeeGenerator<T>中指定的参数类型T。
范型方法
        一个方法是否是范型方法与其所在的类是否是范型没有关系。范型方法使得该方法能够独立于类而产生,如果可以使用范型方法替代将整个类范型化,那就应该使用范型方法,static方法要想使用范型能力,就必须成为泛型类(因为static方法无法访问泛型类的类型参数)。
        当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用范型方法时,通常不用指明参数类型,因为编译器会为我们找出具体类型(类型参数推断)。当像普通方法一样调用范型方法时,就好像该方法被无限次的从载过。

  • 类型参数推断:在泛型方法中,编译器可以从泛型参数列表中的一个参数推断出另外一个参数。编译器类型参数推断避免了重复的泛型参数列表,比如:public static <T> List<T>(){ return new ArrayList<T>();},使用方法:List<String> list = List()。
            类型参数推断只对赋值操作有作用,其他时候并不起作用,比如:将一个泛型方法调用的结果作为一个参数,传递给另外一个方法,这是编译器并不会执行类型推断(编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量)。这时通过显式的类型说明来解决(详情可参考Java编程思想15.4.1)。

范型方法的几个应用场景实例:

  • 可变参数与泛型方法:泛型方法与可变参数列表能够很好的共存,例4:
package generics;

import java.util.ArrayList;
import java.util.List;
import static utils.Print.print;

public class GenericVarargs {
    public static <T> List<T> makeList(T... args){
        ArrayList<T> result = new ArrayList<T>();
        for(T t: args){
            result.add(t);
        }
        return result;
    }
    public static void main(String[] args){
        List<String> list1 = makeList("A");
        print(list1);
        List<String> list2 = makeList("A","B","C");
        print(list2);
        List<String> list3 = makeList("ABCDEFHIJKLMNOPQRST".split(""));
        print(list3);
    }
}
  • 泛型方法应用于Generator(生成器):利用生成器我们可以很方便地填充一个Collection。
            以下以生成Collection的subtypes:Set、Queue、List为例,并且不让它们失去容器的类型属性,由于LinkedList同时实现了List 和 Queue接口,如果不进行明确的转型,编译器将会报错“调用模糊的方法”,需要重载一个专门的用于LinkedList的fill泛型方法,用于区别List和LinkedList。
package generics;

import generics.coffee.Breve;
import generics.coffee.Coffee;
import utils.Generator;

import java.util.*;

public class E13_Generators {
    public static <T> List<T> fill(List<T> coll, Generator<T> gen,int n){
        for(int i = 0; i<n; i++)
            coll.add(gen.next());
        return coll;
    }
    public static <T> Set<T> fill(Set<T> coll, Generator<T> gen,int n){
        for(int i = 0; i<n; i++)
            coll.add(gen.next());
        return coll;
    }
    public static <T> Queue<T> fill(Queue<T> coll, Generator<T> gen,int n){
        for(int i = 0; i<n; i++)
            coll.add(gen.next());
        return coll;
    }
    public static <T> LinkedList<T> fill(LinkedList<T> coll,Generator<T> gen,int n){
        for(int i = 0; i<n; i++)
            coll.push(gen.next());
        return coll;
    }
    public static void main(String args[]){
        //此处持有的必须是Coffee类型,因为:从fill的定义可以看出CoffeeGenerator持有的类型和ArrayList持有的类型必须一致,
        // 同时,还必须与CoffeeGenerator中的next方法的返回类类型一样(由于next方法的返回类型必须与CoffeeGenerator持有类型一致)。
        List<Coffee> list = fill(new ArrayList<Coffee>(),new CoffeeGenerator(),5);
        Set<Coffee> set = fill(new HashSet<Coffee>(),new CoffeeGenerator(),5);
        LinkedList<Coffee> linkedList = fill(new LinkedList<Coffee>(),new CoffeeGenerator(),5);
        Queue queue = fill((Queue<Integer>) new LinkedList<Integer>(),new Fibonacci(),5);
    }
}
  • 为任何类构造一个Generator,只要该类具有默认的构造器。
            这个BasicGenerator提供了一个基本实现,用以生成某个类对象。可以看出使用泛型方法创建Generator对象,大大减少了编写的代码(和上一例子做对比可以感受到明显的减少了代码量)。给Create方法传入Class对象,如:BasicGenerator.Create (Default .class)来为任何类创建Generator,则可以将BasicGenerator类中所有使用<T>泛型表示的类型都关联为Default。
package generics;

import utils.Generator;

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

import static utils.Print.print;

class Default{
    static int id = 0;
    String string = "generate Default Class";
    public String toString(){
        return string + id++;
    }
}
public class BasicGenerator<T> implements Generator<T>{//当导出类定义为泛型时<T>,被继承的接口可以使用泛型<T>;
    private Class<T> type;
    //使用泛型中定义的类型
    public BasicGenerator(Class<T> type){
        this.type = type;
    }
    public T next(){
        try{
            return type.newInstance();
        }catch(Exception e){
            print(e);
        }
        return null;
    }
    //produce a Default generator given a type token;
    public BasicGenerator<T> Create(){
        return new BasicGenerator(type);
    }
    public static <T> Generator<T> Create(Class<T> type){
        return new BasicGenerator<T>(type);
    }
    public static void main(String args[]){
        Generator<Default> basicGenerator = BasicGenerator.Create(Default.class);
        Generator<HashSet> hashSetGenerator = BasicGenerator.Create(HashSet.class);
        Generator<LinkedList> linkedListGenerator = BasicGenerator.Create(LinkedList.class);
        for(int i =0 ;i<5; i++)
            print(basicGenerator.next());
        for(int i =0 ;i<5; i++)
            print(hashSetGenerator.next());
        for(int i =0 ;i<5; i++)
            print(linkedListGenerator.next());
    }
}
  • 简化元组的使用

  • 一个Set实用工具

  • 泛型也可用于匿名内部类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值