一.注意点:
1.凭经验可知,如果需要返回一个可变数据域的拷贝,就应该使用克隆。
2.子类的构造器不能访问父类的私有域,所以必须利用父类的构造器对这部分私有数据进行初始化,可以使用super关键字调用父类构造器进行初始化,并且super语句必须是子类构造器的第一条语句。
3.如果子类构造器没有显式调用父类构造器进行初始化,那么系统将会自动调用父类构造器的默认构造器,即无参构造器。如果超类没有无参构造器,并且子类构造器没有显式调用父类构造器,Java编译器将会报错。
4.方法的名字和参数列表合称为方法的签名。返回类型不是签名的一部分,因此在覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。
5.在覆盖一个方法时,子类方法的可见性不能低于父类方法的可见性。
6.final类中的所有方法都是final的,不能被覆盖,但是final类中的域不一定是final的。
7.在早期的Java中,程序员避免动态绑定的系统开销而使用final关键字。如果一个方法没有被覆盖而且很短,编译器能对其进行优化处理,这个过程称为内联。如果虚拟机加载了另外一个子类,并且该子类中包含了对内联方法的覆盖,优化器将会取消对覆盖方法的内联。
8.进行类型转换的唯一原因:暂时忽视对象的类型后,使用对象的全部功能。
9.再将超类转换为子类时,应该使用instanceof进行检查。Java中,需要将instanceof运算符和类型转换结合起来使用。
如果x为null,x instanceof C不会产生异常,只是返回false。
10.类即使不含有抽象方法,也可以将类声明为抽象类。可以定义一个抽象类的对象变量,但是他们只能引用非抽象子类的对象。
11.字符串s和t拥有相同的hashcode,这是因为字符串的hashcode是根据内容计算的,而缓存字符串sb和st有着不同的哈希值,这是因为stringbuilder类中没有hashCode()方法,他们的散列码是由Object类默认的hashcode方法导出的对象存储地址。如果重新定义了equals方法,就必须重新定义hashCode()方法。
public static void main(String[] args) {
String s = "hello";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());
String t = "hello";
StringBuilder st = new StringBuilder(t);
System.out.println(t.hashCode() + " " + st.hashCode());
}
equals()和hashCode()的定义必须一致。
12.在程序运行期间,Java运行时系统始终为为每一个对象维护一个被称为运行时类型标识。虚拟机利用运行时类型信息选择相应的方法执行。
二.Java反射机制
1.获得class类对象的三种方法:
1.通过该类的对象获得
Employee e = new Employee();
String typeName = e.getClass().getName();//类名包括包名
2.通过forName()获得
已知类名,可以通过该方法获得类对象
String className = "java.util.Random";
Class c1 = Class.forNamw(className);
以上方法只有在className为类名或者接口名时才能够执行,否则forName方法会抛出一个checked exception。因此无论何时使用这个方法,都应该提供一个类型处理器。
3.通过类名.class获得
Class c1 = Random.class;
Class c2 = int.class;
Class c3 = Double[].class;
class对象实际上表示的是一个类型,而这个类型实际上并非是一种类。
int不是类,但是int.class是Class类型的对象。
#####2.将forName和newInstance方法结合可以根据存储在字符串中的类名创建对象
String s = "java.util.Random";
Object m = Class.forName(s).newInsrance();
以上创建对象的方式中,要求s表示的类名的必须有一个默认的构造器(没有参数的构造器),否则会抛出异常。
1.打印类中的实例域
/**
* print all fields of a class
* @param cl
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for(Field field : fields){
Class type = field.getType();
String fieldName = field.getName();
System.out.print(" ");
String modifiers = Modifier.toString(field.getModifiers());
if(modifiers.length() > 0)
System.out.print(modifiers + " ");
System.out.println(type.getName() + " " + fieldName + ";");
}
}
2.打印类中的构造函数
/**
* print all constructors of a class
* @param cl
*/
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor constructor : constructors){
String constructorName = constructor.getName();
System.out.print(" ");
String modifier = Modifier.toString(constructor.getModifiers());
if(modifier.length() > 0)
System.out.print(modifier + " ");
System.out.print(constructorName + "(");
//print parameter types
Class[] parameterTypes = constructor.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if(i > 0)
System.out.print(",");
System.out.print(parameterTypes[i].getName());
}
System.out.println(");");
}
}
3.打印类中的方法
/**
* print all methods of a class
* @param cl
*/
public static void printMethods(Class cl){
Method[] methods = cl.getDeclaredMethods();
for (Method method : methods){
System.out.print(" ");
String modifiers = Modifier.toString(method.getModifiers());
if(modifiers.length() > 0)
System.out.print(modifiers + " ");
//获取返回类型
Class returnType = method.getReturnType();
System.out.print(returnType.getName() + " " + method.getName() + "(");
//打印方法参数类型
Class[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if(i > 0)
System.out.print(",");
System.out.print(parameterTypes[i].getName());
}
System.out.println(");");
}
}
三.继承的设计技巧
1.将公共操作和域放在超类中;
2.不要使用受保护的域(protected);
3.使用继承实现"is-a"关系;
4.除非所有的继承方法都有意义,否则不要使用继承;
5.在覆盖方法时,不要改变预期的行为;
6.使用多态,而非根据类型检查执行响应的代码;
7.不要过多的使用反射,反射比较脆弱,编译器很难帮助人们发现程序中的错误,因此只有在运行时才发现错误并导致异常。