五、参数数量可变的方法
在Java中使用省略号...的形式来创建参数数量可变的方法。
public static double max( double... values);
调用方式:1> max(3.1, 40.4,-5);
2>也运行将一个数组传递给可变参数方法的最后一个参数。
注:因此,可以将已经存在且最后一个参数是数组的方法重新定义为可变参数的方法,而不会破坏任何已经存在的代码。
例如:main方法可修改为 public static void main(String... args)
六、枚举类
例如: public enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE};
这个声明定义的类型时一个类,它刚好有4个实例,在此尽量不要构造新对象。
注:比较两个枚举类型的值,不需要调用equals,而直接使用“==”就可以了。
1>可以在枚举类型中添加一些构造器、方法和域。当然,构造器只是在构造枚举常量的时候调用。
所有枚举类型都是Enum类的子类。它们继承了这个类的许多方法。其中最有用的一个是toString,这个方法能够返回枚举常量名。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; } }
2>toString的逆方法时静态方法valueOf。
3>每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。
4>ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数
如同Class类一样,鉴于简化的考虑,Enum类省略了一个类型参数。
七、反射
反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
使用反射,Java可以支持Visual Basic用户习惯使用的工具。特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力。
1>能够分析类能力的程序称为反射。
2>反射机制可以用来:
a>在运行中分析类的能力。
b>在运行中查看对象,例如,编写一个toString方法供所有类使用。
c>实现通用的数组操作代码。
d>利用Method对象,这个对象很想C++中的函数指针。
注:反射是一种功能强大且复杂的机制。使用的主要人员是工具构造者,而不是应用程序员。
( 1 )Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。
虚拟机利用运行时类型信息选择相应的方法执行。
可以通过专门的Java类访问这些信息。保存这些信息的类被称为Class。
1>获取Class类型实例的方式:
a>Object类中的getClass()方法。
b>Class类中的静态方法forName获取Class对象。
注:使用forName方法将抛出一个checkedexception(已检查异常)
c>如果T是任意的Java类型,T.class将代表匹配的类对象。
注:一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。
例如:int不是类,但是int.class是一个Class类型的对象。
从Java SE5.0开始,Class类已参数化。例如,Class<Employee>
2>虚拟机为每个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。
3>使用newInstance()可以快速创建一个类的实例。
e.getClass().newInstance();
newInstance方法调用默认的构造器初始化新创建的对象,如果这个类没有默认的构造器,将抛出一个异常。
注:如果需要以这种方式向希望按名称创建的类的构造器提供参数,就不要使用上面那条语句,而必须使用Constructor类中的newInstance方法。
( 2 )捕获异常
1>异常有两种类型:未检测异常和已检查异常。
对于已检查异常,编译器将会检查是否提供了处理器;对于未检查异常,编译器不会查看是否为这些错误提供了处理器。
如访问null引用。
2>将可能抛出已检查异常的一个或多个方法调用代码放在try块中,然后在catch子句中提供处理器代码。
注:在catch子句中可以利用Throwable类的printStackTrace方法打印出栈的轨迹。Throwable是Exception类的子类。
( 3 )利用反射分析类的能力
在java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
Field类有一个getType方法,用于返回描述域所属类型的Class对象。
Method和Constructor类有能够报告参数类型的方法。
如下代码为本人学习反射的初步应用:
public static void testClass() { //从控制台获取类全名 Scanner in = new Scanner(System.in); String className = in.next(); try { Class myClass = Class.forName(className); //获取全部的方法 Method[] myMethod = myClass.getDeclaredMethods(); //调用无参构造器创建一个实例对象 Object obj = myClass.newInstance(); for (Method m : myMethod) { //判断当前方法是否含有参数 if(m.getParameterTypes().length == 0){ //打印当前方法名和调用结果 System.out.println(m.getName()+" "+m.invoke(obj, null)); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } in.close(); }
( 4 )在运行时使用反射分析对象
1>使用反射可以查看任意对象的数据域名称和类型,但是如果查看私有域将会抛出一个IllegalAccessException。
除非拥有访问权限,否则Java安全机制只运行查看任意对象的那些域,而不允许读取它们的值。
2>反射机制的默认行为受限于Java的访问控制,但是可以通过调用Field、method或Constructor对象的setAccessible方法来不受安全管理器的控制,达到覆盖访问控制。
3>setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为了调试、持久存储和相似机制提供的。
注:
a> void setAccessible(boolean flag) //java.lang.reflect.AccessibleObject
为反射对象设置可访问标志。flag为true表明屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。
b> static void setAccessible(AccessibleObject[] array,boolean flag)
设置对象数组可访问标志的快捷方式。
( 5 )使用反射编写泛型数组代码import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; /** * @author Dyce * @date 2015年12月28日 上午11:05:36 * @version 1.0 */ public class ObjectAnalyzer { private ArrayList<Object> visited = new ArrayList<>(); /** * 通用的toString方法 * @param obj * 调用方式: 重写本类的toString方法 * new ObjectAnalyzer().toString(this); * @return */ public String toString(Object obj) { if (obj == null) return "null"; if (visited.contains(obj)) return "..."; visited.add(obj); Class cl = obj.getClass(); if (cl == String.class) return (String) obj; if (cl.isArray()) { String r = cl.getComponentType() + "[]{"; for (int i = 0; i < Array.getLength(obj); i++) { if (i > 0) r += ","; Object val = Array.get(obj, i); if (cl.getComponentType().isPrimitive()) r += val; else r += toString(val); } return r + "}"; } String r = cl.getName(); // inspect the fields of this class and all superclasses do { r += "["; Field[] fields = cl.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); // get the names and values of all fields for (Field f : fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += ","; r += f.getName() + "="; try { Class t = f.getType(); Object val = f.get(obj); if (t.isPrimitive()) r += val; else r += toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl != null && cl != Object.class); return r; } }
java.lang.reflect包中的Array类允许动态地创建数组。
过程:
1>首先获得a数组的类对象
2>确定它是一个数组
3>使用Class类的getComponentType方法确认数组对应的类型。
public static Object goodCopyOf(Object a, int newLength) { Class cl = a.getClass(); if (!cl.isArray()) return null; Class componentType = cl.getComponentType(); int length = Array.getLength(a); Object newArray = Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//通过已有的数组来创建新数组 return newArray; }
( 6 )调用任意方法
Method类中有一个invoke方法,它允许调用包装在当前Method对象中的方法。
invoke方法的签名是:Object invoke(Object obj,Object... args)
第一个参数是隐式参数,其余的对象提供显示参数。
注:对于静态方法,第一个参数可以被忽略,即可以将它设置为null。
总结:
1>invoke的参数和返回值必须是Object类型,这意味着必须进行多次的类型转换,这会使编译器错过检查代码的机会。等到测试阶段才发现错误,找到和修改变得更加困难。
2>使用反射获得方法指针的代码要比仅仅直接调用方法明显慢一些。
3>鉴于Java开发者不要使用Method对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。