Java 核心技术卷 I 学习记录六
Java 核心技术卷 I 学习记录六
- 4、对象包装器与自动装箱
- 5、参数数量可变的方法
- 6、枚举类
- 7、反射
- 1、Class 类
- 2、捕获异常
- 3、利用反射分析类的能力
- 4、在运行时使用反射分析对象
- 5、使用反射编写泛型数组代码
- 6、调用任意方法
- 8、继承的设计技巧
4、对象包装器与自动装箱
所有的基本类型都冇一个与之对应的类。例如,Integer
类对应基本类型int
。通常,这些类称为包装器(wrapper
)这些对象包装器类拥有很明显的名字:Integer
、Long
、Float
、Double
、Short
、Byte
、Character
、Void
和Boolean
(前6个类派生于公共的超类Number
)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final
,因此不能定义它们的子类。
不允许写成ArrayList<int>
。这里就用到了Integer
对象包装器类。我们可以声明一个Integer
对象的数组列表。
ArrayList<Integer> list = new ArrayList<>();
有一个很有用的特性,从而更加便于添加int
类型的元素到ArrayList<Integer>
中。
// 调用
list.add(3);
// 自动地变换成
list.add(Integer.valueOf(3));
// 这种变换叫做自动装箱(autoboxing)
当将一个Integer对象赋给一个int值时,将会自动拆箱。
// 编译器将下语句
int n = list.get();
// 翻译成:
int n = list.get(i).intValue();
甚至在算术表达式中也能够自动装箱和拆箱。
// 将自增操作符应用于一个包装器引用:
Integer n = 3;
n++;
// 编译器自动插入一条对象拆箱的命令,然后进行自增运算,再将结果装箱
大多数情况下,容易有一种假象,即基本类型与它们的对象包装器是一样的,只是它们的相等性不同。大家知道,==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域,因此,下面的比较通常不会成立:
Integer a = 1000;
Integer b = 1000;
if (a = b) ...
但是,Java却可能让他成立。
如果将经常出现的值包装到一个对象中,就有可能成立。要获得确定结果,在两个包装器对象比较时调用equals方法。
// 首先,包装类引用可以为null,所以自动装箱可能会抛出一个NullPointerException异常
Integer n = null;
System.out.println(2 * n); // Throws NullPointerException
// 如果在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提示为double,再装箱为Double:
Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // Prints 1.0
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插人必要的方法调用。虚拟机只是执行这些字节码。
使用数值对象包装器还有另外一个好处
// 将字符串转换为整型,可以将基本方法放置在包装器中
int x = Integer.parseInt(s);
// parseInt是一个静态方法,与Integer对象没有关系
java.lang.Integer 1.0
- int intValue():以int的形式返回Integer对象的值(在Number类中覆盖了intValue方法)。
- static String toString(int i):以一个新String对象的形式返回给定数值i的十进制表示。
- static String toString(int i ,int radix):返回数值i的基于给定radix参数进制的表示。
- static int parselnt(String s):返回字符串s表示的整型数值,给定字符串表示的是十进制的整数。
- static int parseInt(String s,int radix):返回字符串s表示的整型数值,给定字符串表示的是radix参数进制的整数。
- static Integer valueOf(String s):返回用s表示的整型数值进行初始化后的一个新Integer对象,给定字符串表示的是十进制的整数。
- Static Integer value Of(String s, int radix):返回用s表示的整型数值进行初始化后的一个新Integer对象,给定字符串表示的是radix参数进制的整数。
java.text.NumberFormat 1.1
- Number parse(String s):返回数字值,假设给定的String表示了一个数值。
5、参数数量可变的方法
在Java SE 5.0以前的版本中,每个Java方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法(有时称为“变参”方法)。
// 调用printf方法
// 包含两个参数的调用
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代码的一部分,表明这个方法可以接收任意数量的对象(除fmt
参数之外)。
printf
接收两个参数,一个是格式字符串,另一个是Object[]
数组,保存所有的参数(如果调用者提供的是整型数组或其他基本类型的值,直接装箱把它们转换成对象)。根据fmt
字符串,将第i
个格式说明符与args[i]
的值匹配起来。
也可以说,对于printf
,Object...
参数类型与Object[]
完全一样。
6、枚举类
枚举类的典型例子如下:
// 声明定义的类型是一个类,刚好又4个实例
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
在比较两个枚举类型的值时,不需要使用equals
,直接使用"=="
就可以。
需要的话也可以在枚举类型中添加一些构造器、方法和域。
构造器只是在构造枚举常量的时候被调用。
public enum Size {
SMALL("S"), MEDIUM("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"
toString
方法的逆方法是静态方法valueOf
。
// 将s设置成Size.SMALL
Size s = Enum.valueOf(Size.class, "SMALL");
每个枚举类型都有一个静态方法values
,将返回一个包含全部枚举值的数组。
Size[] values = Size.values();
// [Size.SMALL, Size.MEDIUM, Size.LARGE, Size.EXTRA_LARGE]
oridinal
方法返回enum
声明中枚举常量的位置,从0
开始计数。
Size.MEDIUM.ordinal(); // 1
java.Iang.Enum 5.0
static Enum valueOf(Class enumClass , String name)
:返回指定名字、给定类的枚举常量。String toString()
:返回枚举常量名。int ordinal ()
:返回枚举常量在enum
声明中的位置,位置从0开始计数。int compareTo(E other)
:如果枚举常量出现在Other
之前,则返回一个负值;如果this=other
,则返回0;否则,返回正值。枚举常量的出现次序在enum
声明中给出。
7、反射
能够分析类能力的程序称为反射(reflective
)。反射机制的功能极其强大。在下面可以看到,反射机制可以用来:
- 在运行时分析类的能力。
- 在运行时查看对象,例如,编写一个
toString
方法供所有类使用。 - 实现通用的数组操作代码。
- 利用
Method
对象, 这个对象很像中的函数指针。
反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。
1、Class 类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
可以通过专门的Java
类访问这些信息。保存这些信息的类被称为Class
,这个名字很容易让人混淆。Object
类中的getClass()
方法将会返回一个Class
类型的实例。
Employee e;
...
Class cl = e.getClass();
如同用一个Employee
对象表示一个特定的雇员属性一样,一个Class
对象将表示一个特定类的属性。
最常用的Class
方法是getName
。这个方法将返回类的名字。
System.out.println(e.getClass().getName() + " " + e.getName()); // Employee Harry Hacker
如果类在一个包里,包的名字也作为类名的一部分:
Random generator = new Random();
Class cl = generator.getClass();
String name = cl.getName(); // name is set to "java.util.Random"
// 还可以调用静态方法forName获得类名对应的Class对象
String dassName = "java.util .Random";
Class cl = Class.forName(dassName);
如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在dassName
是类名或接口名时才能够执行。否则,forName
方法将抛出一个checked exception
(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器( exception handler
)。
获得Class
类对象的第三种方法非常简单。如果T
是任意的Java
类型(或void
关键字),T.class
将代表匹配的类对象。
Class dl = Random.class; // if you import java.util
Gass cl2 = int.class;
Class cl3 = Double[].class;
请注意,一个Class
对象实际上表示的是一个类型,而这个类型未必一定是一种类。
虚拟机为每个类型管理一个Class
对象。因此,可以利用==
运算符实现两个类对象比较的操作;还有一个很有用的方法newlnstance()
,可以用来动态地创建一个类的实例。
// 利用==运算符实现两个类对象比较的操作
if (e.getClass() == Employee.class) ...
// 方法newlnstance(),可以用来动态地创建一个类的实例
e.getClass().newlnstance();
2、捕获异常
当程序运行过程中发生错误时,就会“抛出异常“。抛出异常比终止程序要灵活得多,这是因为可以提供一个“捕获”异常的处理器(handler
)对异常情况进行处理。
如果没有提供处理器,程序就会终止,并在控制台上打印出一条信息,其中给出了异常的类型。
异常有两种类型:未检查异常和已检查异常。对于已检查异常,编译器将会检查是否提供了处理器。然而,有很多常见的异常。例如,访问null
引用,都属于未检查异常。编译器不会査看是否为这些错误提供了处理器。
将可能抛出已检査异常的一个或多个方法调用代码放在try
块中,然后在catch
子句中提供处理器代码。
try {
statements that might throwexceptions
}
catch (Exception e) {
handler action
}
如果try
块中没有抛出任何异常,那么会跳过catch
子句的处理器代码。
java.lang.Class 1.0
static Class forName(String className)
:返回描述类名为className
的Class
对象。Object newlnstance()
:返回这个类的一个新实例。
java.Iang.reflect.Constructor 1.1
Object newlnstance(Object[] args)
:构造一个这个构造器所属类的新实例。
参数:args
这是提供给构造器的参数。
java.Iang.Throwable 1.0
void printStackTrace()
:将Throwable
对象和栈的轨迹输出到标准错误流。
3、利用反射分析类的能力
在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。这三个类都有一个叫做getName的方法,用来返回项目的名称。
Field类有一个getType方法,用来返回描述域所属类型的Class对象。
Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。这个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用状况。
另外,还可以利java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。Class类的getDeclareFields、getDeclareMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
java.lang.Class 1.0
- Field[] getFields() 1.1:getFields方法将返回一个包含Field对象的数组,这些对象记录了这个类或其超类的公有域。
- Filed[] getDeclaredFields() 1.1:getDeclaredField方法也将返回包含Field对象的数组,这些对象记录了这个类的全部域。如果类中没有域,或者Class对象描述的是基本类型或数组类型,这些方法将返回一个长度为0的数组。
- Method[] getMethods() 1.1:返回包含Method对象的数组:getMethods将返回所有的公有方法,包括从超类继承来的公有方法。
- Method[] getDeclareMethods() 1.1:返回包含Method对象的数组:getDeclaredMethods返回这个类或接口的全部方法,但不包括由超类继承了的方法。
- Constructor[] getConstructors() 1.1:返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有公有构造器(getConstructors)。
- Constructor[] getDeclaredConstructors() 1.1:返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有构造器(getDeclaredConstructors)。
java.lang.reflect.Field 1.1
java.lang.reflect.Method 1.1
java.lang.reflect.Constructor 1.1
- Class getDeclaringClass():返冋一个用于描述类中定义的构造器、方法或域的Class对象。
- Class[] getExceptionTypes():(在Constructor和Method类中)返回一个用于描述方法抛出的异常类型的Class对象数组。
- int getModifiers():返回一个用于描述构造器、方法或域的修饰符的整型数值。使用Modifier类中的这个方法可以分析这个返回值。
- String getName():返冋一个用于描述构造器、方法或域名的字符串。
- Class[] getParameterTypes():(在Constructor和Method类中)返回一个用于描述参数类型的Class对象数组。
- Class getReturnType() :(在Method类中)返回一个用于描述返回类型的Class对象。
java.lang.reflect.Modifier 1.1
- static String toString(int modifiers):返回对应modifiers中位设置的修饰符的字符串表示。
- static boolean isAbstract(int modifiers)
- static boolean isFinal (int modifiers)
- static boolean islnterface(int modifiers)
- static boolean isNative(int modifiers)
- static boolean isPrivate(int modifiers)
- static boolean isProtected(int modifiers)
- static boolean isPublic(int modifiers)
- static boolean isStatic(int modifiers)
- static boolean isStrict(int modifiers)
- static boolean isSynchronized(int modifiers)
- static boolean isVolatile(int modifiers)
这些方法将检测方法名中对应的修饰符在modffiers值中的位。
4、在运行时使用反射分析对象
已经知道如何查看任意对象的数据域名称和类型:
- 获得对应的Class对象。
- 通过Class对象调用getDeclaredFields。
java.Iang.reflect.AccessibleObject 1.2
- void setAccessible(boolean flag):为反射对象设置可访问标志。flag为true表明屏蔽Java语言的访问检查,使得对象的私有属性也可以被査询和设置。
- boolean isAccessible():返回反射对象的可访问标志的值。
- static void setAccessible(AccessibleObject[] array,boolean flag):是一种设置对象数组可访问标志的快捷方法。
java.lang.Class 1.1
- Field getField(String name):返回指定名称的公有域。
- Field[] getField():返回包含所有域的数组。
- Field getDeclaredField(String name):返回类中声明的给定名称的域。
- Field[] getDeclaredFields():返回包含声明的全部域的数组。
java.Iang.reflect.Field 1.1
- Object get(Object obj):返回obj对象中用Field对象表示的域值。
- void set(Object obj ,Object newValue):用一个新值设置Obj对象中Field对象表示的域。
5、使用反射编写泛型数组代码
java.lang.reflect包中的Array类允许动态地创建数组。
java.lang.reflect.Array 1.1
- static Object get(Object array,int index):
- static xxx getXxx(Object array,int index):(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类M)。这些方法将返回存储在给定位置上的给定数组的内容。
- static void set(Object array,int index,Object newValue):
- static setXxx(Object array,int index,xxx newValue):(xxx是boolean、byte、char、double、float、int、long、short之中的一种基本类型)。这些方法将一个新值存储到给定位置上的给定数组中。
- static int getLength(Object array):返回数组的长度。
- static Object newInstance(Class componentType,int length):
- static Object newInstance(Class componentType,int[]lengths):返回一个具有给定类型、给定维数的新数组。
6、调用任意方法
从表面上看,Java没有提供方法指针,即将一个方法的存储地址传给另外一个方法,以便第二个方法能够随后调用它。事实上,Java的设计者曾说过:方法指针是很危险的,并且常常会带来隐患。他们认为Java提供的接口(interface)是一种更好的解决方案。然而,反射机制允许你调用任意方法。
java.Iang.reflect.Method 1.1
- public Object invoke(Object implicitParameter,Object[] explicitParamenters):调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必
须是未包装的。
8、继承的设计技巧
对设计继承关系很有帮助的建议。
-
- 将公共操作和域放在超类
-
- 不要使用受保护的域
-
- 使用继承实现“ is-a” 关系
-
- 除非所有继承的方法都有意义,否则不要使用继承
-
- 在覆盖方法时,不要改变预期的行为
-
- 使用多态, 而非类型信息
-
- 不要过多地使用反射