对象包装器与自动装箱
有时, 需要将 int 这样的基本类型转换为对象。所有的基本类型都冇一个与之对应的类。 例如,丨nteger 类对应基本类型 int。通常, 这些类称为包装器 ( wrapper) 这些对象包装器类 拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean (前 6 个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不 允许更改包装在其中的值。同时, 对象包装器类还是 final, 因此不能定义它们的子类。 假设想定义一个整型数组列表。而尖括号中的类型参数不允许是基本类型,也就是说, 不允许写成 ArrayList<int>。这里就用到了 Integer 对象包装器类。我们可以声明一个 Integer 对象的数组列表。
ArrayList<Integer> list = new ArrayList<>();
幸运的是, 有一个很有用的特性,从而更加便于添加 int类型的元素到 ArrayLisKlntegeP 中。下面这个调用
list.add(3);
将自动地变换成
list.add(Integer.value0f(3));
这种变换被称为自动装箱(autoboxing)。
相反地, 当将一个 Integer 对象赋给一个 int 值时, 将会自动地拆箱。也就是说, 编译器 将下列语句:
int n = list.get(i);
翻译成
int n = list.get(i).intValue();
甚至在算术表达式中也能够自动地装箱和拆箱。例如,可以将自增操作符应用于一个包装器 引用:
Integer n = 3;
n++;
编译器将自动地插人一条对象拆箱的指令, 然后进行自增计算, 最后再将结果装箱。
参数数量可变的方法
在 Java SE 5.0 以前的版本中,每个 Java方法都有固定数量的参数。然而,现在的版本 提供了可以用可变的参数数量调用的方法(有时称为“ 变参” 方法)。
前面已经看到过这样的方法:printfo 例如,下面的方法调用:
System.out.printf("%d", n);
和
System.out.printf("%d %s\ n, "widgets");
在上面两条语句中,尽管一个调用包含两个参数,另一个调用包含三个参数,但它们调用的 都是同一个方法。
printf方法是这样定义的:
public class PrintStream { public PrintStream printf(String fmt, Object... args) { return format(fmt, args); } }
这里的省略号 . . . 是 Java 代码的一部分,它表明这个方法可以接收任意数量的对象。
实际上,printf方法接收两个参数,一个是格式字符串, 另一个是 Object ] 数组, 其中 保存着所有的参数(如果调用者提供的是整型数组或者其他基本类型的值, 自动装箱功能 将把它们转换成对象 )。现在将扫描 Ant 字符串, 并将第 i 个格式说明符与 args[i] 的值匹配 起来。 换句话说,对于 printf 的实现者来说,Object… 参数类型与 Object[ ]完全一样。 编译器需要对 printf的每次调用进行转换, 以便将参数绑定到数组上,并在必要的时候 进行自动装箱:
System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );
枚举类
下面是一个典型的例子:
public enum Size { SMALL, MEDIUM, LARGE, EXTRAJARGE };
实际上, 这个声明定义的类型是一个类, 它刚好有 4 个实例, 在此尽量不要构造新对象。
因此, 在比较两个枚举类型的值时, 永远不需要调用 equals, 而直接使用“ = =” 就 可以了。
如果需要的话, 可以在枚举类型中添加一些构造器、方法和域。当然,构造器只是在构 造枚举常量的时候被调用。下面是一个示例:
public enum Size { SMALLf("S"), MEDIU(”M"), LARGE("L"), EXTRA_LARGE("XL"); private String abbreviation; private Size(String abbreviation) { this,abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } }
所有的枚举类型都是 Enum 类的子类。它们继承了这个类的许多方法。其中最有用的一 个是 toString, 这个方法能够返回枚举常量名。例如, Size.SMALL.toString( ) 将返回字符串 “ SMALL”。
程序清单 5-12 演示了枚举类型的工作方式。
//程序清单 5-12 enums/EnumTest.java package enums; import java.util.*; /** * This program demonstrates enumerated types. * @version 1.0 2004-05-24 * @author Cay Horstmann */ public class EnumTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) "); String input = in.next().toUpperCase(); Size size = Enum.valueOf(Size.class, input); System.out.println("size=" + size); System.out.println("abbreviation=" + size.getAbbreviation()); if (size == Size.EXTRA_LARGE) System.out.println("Good job--you paid attention to the _."); } } enum Size { SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL"); private Size(String abbreviation) { this.abbreviation = abbreviation; } public String getAbbreviation() { return abbreviation; } private String abbreviation; }
反射
反射库(reflection library) 提供了一个非常丰富且精心设计的工具集, 以便编写能够动 态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans 中, 它是 Java组件的体系结构 。
能够分析类能力的程序称为反射(reflective)。反射机制的功能极其强大,在下面可以看 到, 反射机制可以用来:
•在运行时分析类的能力。
•在运行时查看对象, 例如, 编写一个 toString方法供所有类使用。
•实现通用的数组操作代码。
•利用 Method 对象, 这个对象很像中的函数指针。
反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序 员P
Class 类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
Class 类实际上是一个泛型类。例如, Employee.class 的类型是 Class<Employee>。 没有说明这个问题的原因是: 它将已经抽象的概念更加复杂化了。在大多数实际问题中, 可以忽略类型参数, 而使用原始的 Class 类。
捕获异常
当程序运行过程中发生错误时, 就会“ 抛出异常'抛出异常比终止程序要灵活得多, 这是因为可以提供一个“ 捕获” 异常的处理器(handler) 对异常情况进行处理。
异常有两种类型: 未检查异常和已检查异常。对于已检查异常, 编译器将会检查是否提 供了处理器。 然而,有很多常见的异常, 例如,访问 null 引用, 都属于未检查异常。
将可能抛出已检査异常的一个或多个方法调用代码放在 try块中,然后在 catch 子句中提 供处理器代码。
try { statements that might throwexceptions } catch (Exception e) { handleraction }
下面是一个示例:
try { String name = . . .; // get class name Class cl = Class.forName(name); // might throw exception do something with cl } catch (Exception e) { e.printStackTrace(); }
如果类名不存在, 则将跳过 try块中的剩余代码,程序直接进人 catch 子句(这里,利用 Throwable 类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类) 。如 果 try块中没有抛出任何异常, 那么会跳过 catch 子句的处理器代码。
利用反射分析类的能力
在java.lang.reflect 包中有三个类 Field、Method 和 Constructor 分别用于描述类的域、 方 法和构造器。 这三个类都有一个叫做 getName 的方法, 用来返回项目的名称。Held 类有一 个 getType 方法, 用来返回描述域所属类型的 Class 对象。Method 和 Constructor 类有能够 报告参数类型的方法,Method 类还有一个可以报告返回类型的方法。这 三个类还有一个叫 做 getModifiers 的方法, 它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。
Class类中的 getFields、 getMethods 和 getConstructors方 法 将 分 别 返 回 类 提 供 的 public 域、 方法和构造器数组, 其中包括超类的公有成员。Class 类的 getDeclareFields、 getDeclareMethods 和 getDeclaredConstructors方法将分别返回类中声明的全部域、 方法和构 造器, 其中包括私有和受保护成员,但不包括超类的成员。
程序清单 5-13 显示了如何打印一个类的全部信息的方法。这个程序将提醒用户输入类 名,然后输出类中所有的方法和构造器的签名, 以及全部域名。假如输入
java.lang.Double
程序将会输出:
public final class java.lang.Double extends java.lang.Number { public java.lang.Double(double); public java.lang.Double(java.lang.String); public boolean equals(java.lang.Object); public static java.lang.String toString(double); public java.lang.String toString(); public int hashCode(); public static int hashCode(double); public static double min(double, double); public static double max(double, double); public static native long doubleToRawLongBits(double); public static long doubleToLongBits(double); public static native double longBitsToDouble(long); public volatile int compareTo(java.lang.Object); public int compareTo(java.lang.Double); public byte byteValue(); public short shortValue(); public int intValue(); public long longValue(); public float floatValue(); public double doubleValue(); public static java.lang.Double valueOf(java.lang.String); public static java.lang.Double valueOf(double); public static java.lang.String toHexString(double); public static int compare(double, double); public static boolean isNaN(double); public boolean isNaN(); public static boolean isFinite(double); public static boolean isInfinite(double); public boolean isInfinite(); public static double sum(double, double); public static double parseDouble(java.lang.String); public static final double POSITIVE_INFINITY; public static final double NEGATIVE_INFINITY; public static final double NaN; public static final double MAX_VALUE; public static final double MIN_NORMAL; public static final double MIN_VALUE; public static final int MAX_EXPONENT; public static final int MIN_EXPONENT; public static final int SIZE; public static final int BYTES; public static final java.lang.Class TYPE; private final double value; private static final long serialVersionUID; }
这个程序可以分析 Java 解释器能够加载的任何类。
//程序清单 5-13 reflection/ReflectionTest.java package reflection; import java.util.*; import java.lang.reflect.*; /** * This program uses reflection to print all features of a class. * @version 1.1 2004-02-21 * @author Cay Horstmann */ public class ReflectionTest { public static void main(String[] args) { // read class name from command line args or user input String name; if (args.length > 0) name = args[0]; else { Scanner in = new Scanner(System.in); System.out.println("Enter class name (e.g. java.util.Date): "); name = in.next(); } try { // print class name and superclass name (if != Object) Class cl = Class.forName(name); Class supercl = cl.getSuperclass(); String modifiers = Modifier.toString(cl.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print("class " + name); if (supercl != null && supercl != Object.class) System.out.print(" extends " + supercl.getName()); System.out.print("\n{\n"); printConstructors(cl); System.out.println(); printMethods(cl); System.out.println(); printFields(cl); System.out.println("}"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.exit(0); } /** * Prints all constructors of a class * @param cl a class */ public static void printConstructors(Class cl) { Constructor[] constructors = cl.getDeclaredConstructors(); for (Constructor c : constructors) { String name = c.getName(); System.out.print(" "); String modifiers = Modifier.toString(c.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print(name + "("); // print parameter types Class[] paramTypes = c.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /** * Prints all methods of a class * @param cl a class */ public static void printMethods(Class cl) { Method[] methods = cl.getDeclaredMethods(); for (Method m : methods) { Class retType = m.getReturnType(); String name = m.getName(); System.out.print(" "); // print modifiers, return type and method name String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print(retType.getName() + " " + name + "("); // print parameter types Class[] paramTypes = m.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /** * Prints all fields of a class * @param cl a class */ public static void printFields(Class cl) { Field[] fields = cl.getDeclaredFields(); for (Field f : fields) { Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.println(type.getName() + " " + name + ";"); } } }