数据结构与算法分析之泛型-Java语言描述(一)

本文内容基于《数据结构与算法分析 Java语言描述》第三版,冯舜玺等译。


1. 实现泛型构件 pre-Java 5

面向对象的一个重要目标是对代码重用的支持,支持这个目标的一个重要的机制就是泛型机制:如果除去对象的基本类型外,实现方法是相同的,那么就可以用泛型实现来描述这种基本的功能。

Java 5以前,Java不直接支持泛型实现,泛型编程的实现是通过使用继承的一些基本概念来完成的。

1.1 使用Object表示泛型

Java中的基本思想就是可以通过使用像Object这样适当的超类来实现泛型类。

class Test {
    
    private Object storedValue;
    
    public Object read() {
        return storedValue;
    }
    
    public void write(Object x) {
        storedValue = x;
    }
}

有两个细节必须考虑:

  • 强制转型
MemoryCell memoryCell = new MemoryCell();
memoryCell.write("abc");
String value = (String) memoryCell.read();
  • 不能使用基本类型

1.2 基本类型的包装

Java中每一种引用类型都和Obect相容,但是8种基本类型却不能。于是,Java为这8种基本类型中的每一种都提供了一个包装类。每一个包装对象都是不可变的,就是说它的状态绝不能改变,它存储一种当该对象被构建时所设置的原值,并提供一种方法以重新得到该值。

MemoryCell memoryCell = new MemoryCell();
memoryCell.write(new Integer(37));
int value = ((Integer) memoryCell.read()).intValue();

1.3 使用接口类型表示泛型

只有在使用Object类中已有的那些方法能够表示所执行的操作的时候,才能使用Object作为泛型类型来工作。

例如:考虑在由一些项组成的数组中找出最大项的问题。基本的代码是类型无关的,但是它的确需要一种能力来比较任意两个对象,并确定哪个是大的,哪个是小的。因此,不能直接找出Object的数组中的最大元素,而需要更多的信息。

使用Comparable接口的compareTo方法的几点注意事项:

  • 只有实现Comparabale接口的对象才能作为Comparable数组的元素被传递;
  • 如果Comparable数组有两个不相容的对象,例如一个String和一个Shape,那么CompareTo方法将抛出异常ClassCastException;
  • 基本类型不能作为Comparable传递,但是包装类则可以;
  • 接口究竟是不是标准的库接口倒不是必须的。

1.4 数组类型的兼容性

public class Test {

   public static void func1(Super[] supers) {
       System.out.println(Arrays.toString(supers));
   }

   public static void main(String[] args) {
       Super[] supers = new Super[] {
               new Super("super1"),
               new Super("super2")
       };
       Sub[] subs = new Sub[] {
               new Sub("sub1"),
               new Sub("sub1")
       };

       func1(supers);
       func1(subs);
   }
}

class Super {

    String name;

    public Super() {
    }

    public Super(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Super{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Sub extends Super {

    String name;

    public Sub(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Sub{" +
                "name='" + name + '\'' +
                '}';
    }
}

Sub继承Super,可以说Sub IS-A Super。func1()方法的形参是Super[],代码中给func()方法传递实参Sub[]也是可行的,可以说明Sub[] IS-A Super[]。

Java中的数组是类型兼容的,叫做协变数组类型,每个数组都明了它所允许存储的对象的类型,如果将一个不兼容的类型插入到数组中,那么虚拟机将抛出一个ArrayStoreException异常。

2. 利用Java 5泛型特性实现泛型构件

2.1 简单的泛型类和接口

public class Test<AnyType> {
    
    private AnyType storedValue;
    
    public AnyType read() {
        return storedValue;
    }
    
    public void write(AnyType x) {
        storedValue = x;
    }
}

当指定一个泛型类时,类的声明则包含一个或多个类型参数,这些参数被放在类名后面的一对尖括号里。在类声明内部,可以声明泛型类型的域和使用泛型类型作为参数或返回类型的方法。

也可以声明接口是泛型的,Java 5以前,Comparable接口不是泛型的,它的compareTo方法需要一个Object作为参数,Java 5中,Comparable接口是泛型的,以前只有在运行时才能报告的许多错误如今变成了编译时的错误。

public interface Comparable<T> {
    public int compareTo(T o);
}

2.2 自动装箱/拆箱

Java 5中有自动装箱/拆箱的功能。例如:如果一个int型变量被传递到需要一个Integer对象的地方,那么,编译器将在幕后插入一个对Integer构造方法的调用,这就叫做自动装箱;而如果一个Integer对象被放到需要int型变量的地方,则编译器将在幕后插入一个对intValue方法的调用,这就叫做自动拆箱。

Test<Integer> m = new Test<Integer>();

m.write(37);
int value = m.read();

2.3 菱形运算符

Java 7中增加了一种新的语言特性,称为菱形运算符,使得

Test<Integer> m = new Test<Integer>();

可以写为:

Test<Integer> m = new Test<>();

2.4 带有限制的通配符

1.4中已经说明了Java中的数组是协变的,但是泛型集合不是协变的。

public class Test {

    public static void func2(List<Super> superList) {
        System.out.println(superList);
    }

   public static void main(String[] args) {
       List<Super> superList = new ArrayList<>();
       superList.add(new Super("super1"));
       superList.add(new Super("super2"));
       List<Sub> subList = new ArrayList<>();
       subList.add(new Sub("sub1"));
       subList.add(new Sub("sub1"));

       func2(superList);
       func2(subList);
   }
}

class Super {

    String name;

    public Super() {
    }

    public Super(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Super{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Sub extends Super {

    String name;

    public Sub(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Sub{" +
                "name='" + name + '\'' +
                '}';
    }
}

说明List<Sub> IS-NOT-A List<Super>。

Java 5中用通配符来弥补这个不足,通配符用来表示参数类型的子类或超类,通配符还可以不带限制使用(extends Object),或不用extends而用super来表示超类而不是子类。

public static void func2(List<? extends Super> superList) {
    System.out.println(superList);
}

2.5 泛型static方法

有时候特定类型很重要,如果满足以下某个条件,那么必须声明名一种带有若干类型参数的显示泛型方法:

  • 该特定类型用做返回类型;
  • 该类型用在多于一个的参数类型中;
  • 该类型用于声明一个局部变量。
public static <AnyType> boolean contains(AnyType[] arr, AnyType x) {
    for (AnyType value : arr) {
        if (x.equals(value)) {
            return true;
        }
    }
    return false;
}

泛型方法特别像是泛型类,因为类型参数表使用相同的语法,但在泛型方法中的类型参数位于返回类型之前。

2.6 类型限界

public static <AnyType> AnyType findMax(AnyType[] arr) {
    int maxIndex = 0;

    for (int i = 1; i < arr.length; i++) {
        if (arr[i].compareTo(arr[maxIndex]) > 0) {
            maxIndex = i;
        }
    }
    return arr[maxIndex];
}

上面代码中只有在AnyType是Comparable的情况下才能保证compareTo存在,可以使用类型限界来解决,类型限界在尖括号内指定,它指定参数类型必须具有的性质。

public static <AnyType extends Comparable<? super AnyType>> AnyType findMax(AnyType[] arr) {
    int maxIndex = 0;

    for (int i = 1; i < arr.length; i++) {
        if (arr[i].compareTo(arr[maxIndex]) > 0) {
            maxIndex = i;
        }
    }
    return arr[maxIndex];
}

2.7 类型擦除

泛型类可以由编译器通过所谓的类型擦除过程而转变成非泛型类,这样,编译器就生成一种与泛型类同名的原始类,但是类型参数都被删去了,类型变量由它们的类型限界来代替,当一个具有擦除返回类型的泛型方法被调用的时候,一些特性被自动地插入,如果使用一个泛型类而不带类型参数,那么使用的是原始类。

类型擦除的一个重要推论是:所生成的代码与程序员在泛型之前所写的代码并没有太多的差异,而且事实上运行的也并不快。其显著的优点在于,程序员不必把一些类型转换放到代码中,编译器将进行重要的类型检验。

2.8 对于泛型的限制

  • 基本类型不能用做类型参数;
  • instanceof检测和类型转换工作只对原始类型进行;
  • 在一个泛型类中,static方法和static域均不可引用类的类型变量,因为在类型擦除后类型变量就不存在了,另外,由于实际上只存在一个原始的类,因此static域在该类的诸泛型实例之间是共享的;
  • 不能创建一个泛型类型的实例;
  • 也不能创建一个泛型的数组;
  • 参数化类型的数组的实例化是非法的。

3. 函数对象

一个函数通过将其放在一个对象内部而被传递,这样的对象通常叫做函数对象。

public class Test {

    public static <AnyType> AnyType findMax(AnyType[] arr, Comparator<? super AnyType> cmp) {
        int maxIndex = 0;

        for (int i = 1; i < arr.length; i++) {
            if (cmp.compare(arr[i], arr[maxIndex]) > 0) {
                maxIndex = i;
            }
        }
        return arr[maxIndex];
    }

    public static void main(String[] args) {
        String[] arr = {"AEBAR", "alligator", "crocodile"};
        System.out.println(findMax(arr, new CaseInsensitiveCompare()));
    }
}

class CaseInsensitiveCompare implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareToIgnoreCase(o2);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值