1.Object类:所有类的超类
①
Object类是所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确地指定超类,Object类就被认为是这个类的超类。可以使用Object类型的变量引用任何类型的对象。
在Java中只有基本类型不是对象。
Object obj=new Employee("Harry Hacker",35000);
②equals方法:用于判断两个对象是否具有相同的引用
在子类定义equals方法时,首先调用超类的equals。如果检验失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。
下面实例展示了equals方法的实现机制:
public class Employee{
public boolean equals(Object otherObject){
if(this==otherObject)
return true;
if(otherObject==null)
return false;
if(this.getClass()!=otherObject.getClass())
return false;
Employee other=(Employee)otherObject;
return name.equals(other.name)&& salary.equals(other.salary) && hireDay.equals(other.hireDay);
}
③相等测试与继承
Java要求equals方法具有下面的特性:自反性、对称性、传递性、一致性和对于任意非空引用x,x.equals(null)应该返回false。
下面给出编写一个完美的equals方法的建议:
- 显示参数为otherObject,稍后需要将它转换成另一个叫other的bianl
- 检测this与otherObject是否引用同一个对象
- 检测otherObject是否为null,如果为null,返回fasle
- 比较this与otherObject是否属于同一个类。如果equals的语义在每个子类有所改变,使用getClass来检测。如果所有子类都拥有统一的语义,就使用instanceof来检测
- 将otherObject转换为相应的类类型变量
- 开始对所有需要比较的域进行比较,使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配则返回true,否则返回false。
④hashCode方法
散列码是对象导出的一个整型值。是没有规律的。
由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。而字符串的散列码是由内容导出的。
hashCode方法应该返回一个整数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。
如果要组合多个散列码时可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Object.hashCode,并组合这些散列码。
Equals与hashCode的定义必须一致。(如果x.equals(y)返回true,则x.hashCode()就必须与y.hashCode()返回的值相同)
public int hashCode(){
return Objects.hash(name,salary,hireDay);
}
⑤toString方法:用于返回对象值的字符串
绝大多数的toString方法遵循这样的格式:类的名字,随后是一对方括号括起来的域值。
随处可见toString方法的原因是:只要对象与一个字符串通过操作符“+”连接起来,Java编译就会自动地调用toString方法,以便获得这个对象的字符串描述。
Object类定义了toString方法,用来打印输出对象所属的类名和散列码。
public String toString(){
return getClass().getName()+"[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
}
2.对象包装器与自动装箱
①对象包装器
所有的基本类型都有与之对应的类,这样的类称为包装类。对象包装类是不可变的,一旦构造了包装类就不允许更改包装在其中的值。同时包装类被定义为final,不能定义它的子类。
自动装箱:
ArrayList<Integer> list=new ArrayList();
list.add(3);
/* 将自动变换成
list.add(Integer.valueOf(3));
*/
自动拆箱:
int n=list.get(i);
/* 自动地转换成
int n= list.get(i).intValue();
*/
甚至可以在算术表达式中自动地装箱插箱。
==运算符也可以应用于对象包装器对象,只不过检测的是对象是否指向同一个存储区域。
关于自动装箱有几点需要注意:
1)由于包装类引用可以为null,所以自动装箱可能回抛出一个NullPointException异常
2)如果在一个表达式中混合使用Integer和Double类型,Integer就会拆箱提升为Double,再装箱为Double。
3.枚举类
public enum Size{ SMALL,MEDIUM,LARGE,EXTRA_LARGE};
在比较两个枚举类型的值时,直接使用==,不需要调用equals方法。
所有枚举类型都是Enum类的子类,继承了这个类的许多方法。
1)其中最常用的就是toString方法,这个方法返回枚举常量名。
2)toString的逆方法是静态方法valueOf。
3)每个枚举类型都有一个静态方法values方法,返回一个包含全部枚举值的数组。
4)ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。
Size s=Enum.valueOf(Size.class,"SMALL");
Size[] values=Size.values();
4.反射
反射的定义:能够分析类能力的程序称为反射。
①Class类
Java运行时系统始终为所有的对象维护一个被称为 运行时 的类型标识。可以通过Class类访问这些信息。
Object类中的getClass()方法终会返回一个Class类型的实例。一个Class对象将表示一个特定类的属性。
1)最常用的Class方法就是getName():这个方法返回类的名字。如果类在一个包中,包的名字也将作为类名的一部分。
2)还可以调用静态方法forNmae()得到类名对应的Class对象。这个方法只有在className是类名或接口名才可以执行,否则将抛出一个checked exception。因此无论何时使用这个方法,都应该提供一个异常处理器。
String className="java.util.Random";
Class cl=Class.forName(className);
3)获得Class类对象: 如果T是任意的Java类型,T.class代表匹配的类对象。一个Class对象实际上表示的是一种类型,而这个类型未必一定是一种类。例如int。
4)可以利用==运算符实现两个类对象比较的操作。newInstance():可以动态地创建一个类的实例。newInstance()调用默认的构造器初始化新创建的对象,如果这个类没有默认的构造器将会抛出一个异常。
if(e.getClass()==Employee.class)...
e.getClass().newInstance();
②利用反射分析类的能力
这一小节我们简要地介绍反射中最重要的内容:检查类的结构。
在java.lang.reflect包中有三个类:Field、Method和Constructor分别描述类的域、方法和构造器。
1)这三个类都有getName方法,用来返回项目的名称。
2)Field类有getType方法,发挥描述域所属类型的Class对象
3)Method和Constructor类有能够报告参数类型的方法。Method还有报告返回类型的方法。
4)三个类还有getModifiers的方法,返回整型数值,用不同的位开关描述public和state这样的修饰符使用状况。 5) java.lang.reflect包中有Modifier类中isPublic、isPrivate、isFinal判断方法或构造器是否为public、private和final。还可以使用Modifier.toString方法将修饰符打印出来。
6)Class类中getFileds、getMethods、getConstructors方法将分别返回public域、方法和构造器数组。Class类的getDeclaredFields、getDeclaredMethods和getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器数组。
③在运行时使用反射分析对象
从前面一节中,我们已经知道如何查看任意对象的数据域名称和类型:
- 获得相应的Class对象
- 通过Class对象调用getDeclaredFields
本节我们进一步查看数据域的实际内容。
1)查看对象域的关键方法是Filed类中get方法。
Employee harry=new Employee("harry hacker",35000);
Class cl=harry.getClass();
Field f=cl.getDeclaredFields("name");
Object j=f.get(harry);
/*
这段代码中存在问题。
由于name为私有域,get方法会抛出IllegalAccessException异常
除非拥有权限,否则Java安全机制只允许查看有哪些域而不允许查看这些域的值
*/
2)为了达到覆盖访问控制,我们可以调用Filed、Method或Constructor对象的setAccessible方法
setAccessible方法是AccessibleObject类的方法,它是Filed、Method和Constructor类的公共超类。这个特性是为了调试、持久存储和相似机制提供的。
f.setAccessible(true);
3)如果我们需要查看salary域,它属于Double类型,而非对象。要解决这个问题,可以使用Filed类中的getDouble方法,也可以使用get方法。此时反射机制自动地将这个域值打包到相应的对象包装类中。
4)可以获得就可以设置。
f.set(obj,value);
//将obj对象的f域设置为新值value
④使用反射编写泛型数组代码
java.lang.reflect包中的Array类允许动态地创建数组。
Employee[] a=new Employee[100];
a=Arrays.copyOf(a,2*a.length);
编写一个通用的copyOf方法,需要能够创建与原数组类型相同的新数组。为此,需要使用到lang.reflect包中的Array类的一些方法。其中最为关键的是Array类中的静态方法newInstance,它能够构造新数组。
Object newArray=Array.newInstance(componentType,newLength);
为了能够实际地运行,需要获得新数组的长度和元素类型。可以调用Array.getLength(a)获得数组长度。而要获得新数组的类型需要进行一下工作:
- 首先获得a数组的类对象
- 确认它是个数组
- 使用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;
}
⑤调用任意方法
反射机制允许你调用任意方法。
1)在Method类中有一个invoke方法,它允许调用包装在当前Method对象总的方法
Object invoke(Object obj,Object...args);
如果ml代表Employee类的getName方法,下面语句表示了如何调用这个方法。如果返回类型是基础类型,invoke方法会返回其包装器类型。
String n=(String)ml.invoke(harry);
double s=(Double)m2.invoke(harry);
2)如何得到Method对象
- 可以调用getDeclaredMethods方法,对返回的Method对象数组进行查找
- 也可以调用Class类的getMethod犯法得到想要的方法。
Method getMethod(String name,Class...parameterTypes);