面向对象的各种思想以及那些概念性的东西都结合示例给大家过了一遍,因为是用 Java 给大家展开的,但是 Java 光会那些东西,貌似还不能写代码,实际开发的时候还会用到接口、抽象类、匿名类 、枚举、反射这些 Java 面向对象的特性,这篇文章我们就一起再来学习一下进阶部分的内容,内容的大纲如下:
万类始祖--Object类
Object全名java.lang.Object,java.lang包在使用的时候无需显示导入,编译时由编译器自动导入。Object类是类层次结构的根,Java中所有的类从根本上都继承自这个类。
Object类是Java中唯一没有父类的类。其他所有的类,都继承了Object类中的方法(所有的类都隐式的继承自Object,如果你没有显式地给类指定父类时,编译器会给类加一个父类Object,如果你给它指定了父类,编译器就不会多此一举了)。
Object具有哪些属性和行为,是 Java 语言设计背后的思维体现。Object类没有定义属性,一共有13个方法,具体的类定义结构如下图:
equals 方法
对 equlas() 方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么? 如上,在 Object 类中,此标尺即为两个实例 "=="。当然,这个标尺不是固定的,其他类中可以按照实际的需要 重写 equals() 方法,对此标尺含义进行重定义。
比如 String 类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能性和实际编码的灵活性。当然了,如果类本身没有重写 equals() 方法来重新定义此标尺,那么默认的将使用其父类的equals()方法,直到 Object基类。
如下场景的实际业务需求,对于User类的对象,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。这时我们就可以重写 equals() 以重定义User对象相等的标尺。
package com.corn.objectsummary;
public class User {
private int uid;
private String name;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
protected String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof User)) {
return false;
}
if (((User) obj).getUid() == this.getUid()) {
return true;
}
return false;
}
}
hashCode 方法
hashCode()方法返回一个整型数值,表示对象的哈希码值。
hashCode()具有如下约定:
- 在 Java 应用程序程序执行期间,对于同一对象多次调用 hashCode() 方法时,其返回的哈希码是相同的,前提是将对象进行 equals 比较时所用的标尺信息未做修改。针对 Java 应用程序的两次执行,同一对象的 hashCode() 返回的哈希码无须保持一致。
- 如果两个对象相等(依据:调用equals()方法),那么这两个对象调用 hashCode() 返回的哈希码也必须相等。
- 反之则不成立,两个对象调用 hasCode() 返回的哈希码相等,这两个对象不一定相等。
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。
可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是充要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?
其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。以集合类 Set 为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将 Set 进行一次遍历,并逐一用 equals() 方法判断两个对象是否相等,此种算法时间复杂度为 O(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)
在此需要纠正一个理解上的误区:对象的hashCode() 返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode() 相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode() 返回的哈希码可能相同。
因此,在上一个例程中,重写了 equals() 方法后,需要重写 hashCode() 方法。
package com.corn.objectsummary;
import java.util.Objects;
public class User {
private int uid;
private String name;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
protected String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof User)) {
return false;
}
if (((User) obj).getUid() == this.getUid()) {
return true;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(getName(), getUid());
}
}
toString 方法
toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()方法相信大家都经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的。
因为toString是Object里的方法,所以任何一个Java的对象,都可以调用这个方法,类一般会重写toString方法,定制自己对象的字符串表示形式。
其他几个Object里定义的方法等用到了再说,比如wait(...) / notify() | notifyAll()几个方法,都是 Java 线程操作相关的方法。
Class 类
Class 类是代表类的类,每个Class类的示例,都代表了一个类。
上面一节说了,所有的Java类都是继承了Object这个类,在Object这中有一个方法:getClass(),这个方法是用来取得类型对应的Class类的对象。
我们自己无法生成一个Class对象(构造函数为private),而这个Class类的对象是在当各类被载入时,由 Java 虚拟机自动创建其 Class 对象。
获取Class类对象的方法:
- 通过对象调用getClass 函数
package com.example;
import com.example.factory.Phone;
public class UseClassObjAppMain {
public static void main(String[] args) {
Phone phone = new Phone(
"手机001","Phone001",100, 1999, 999,
4.5,3.5,4,128,"索尼","安卓"
);
// 始祖 Object类里的getClass方法,可以得到Class 类的对象
Class claz = phone.getClass();
}
}
- 使用类的字面常量.class
package com.example;
import com.example.factory.Phone;
public class UseClassObjAppMain {
public static void main(String[] args) {
Class claz = Phone.class;
// Class claz = String.class
// Class claz = int.Class
}
}
注意,使用这种办法生成Class类对象时,不会使JVM自动加载该类(如String类,而其他办法会使得JVM初始化该类。
通过一个类的 Class 实例,可以获取一个类所有的信息,包括成员变量,方法,等
package com.example;
import com.example.factory.Phone;
public class UseClassObjAppMain {
public static void main(String[] args) {
Class claz = Phone.class;
// 获取类的全限定名
System.out.println(claz.getName());
// 获取不包含包名的类名
System.out.println(claz.getSimpleName());
Field countField = clazz.getField("count");
Method buyMethod = clazz.getMethod("buy", int.class);
Method equalsMethod = clazz.getMethod("equals", Object.class);
}
}
这里介绍的主要是获取 Class 类对象的方式,Class 类对象更多的是在反射里使用,具体使用方式到反射里再看。
反射
反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。其实反射并不是 Java 独有的,许多编程语言都提供了反射功能。
什么是反射
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
通过反射机制,可以在运行时访问 Java 对象的属性,方法,构造方法等。
反射的应用场景
反射的主要应用场景有: