您的位置 首页 java

JAVA脱水学习-java超类Object解析,Object常用方法

Java 中的父类也称为超类,而 Java 是个面向对象语言,面向对象的基础就是类。Java 中所有类都有一个共同的祖先 Object 类,Object 类是唯一没有父类的类,Object 类引用变量可以存储任何类对象的引用。

一、Object 类中定义的方法

既然所有类都继承自 Object 类,那么所有类都拥有 Object 类中定义的方法。看下 Object 类中有哪些方法:

//  构造器 
public Object();

// 获取对象的散列码
// 可自定义
public native int hashCode();

// 返回运行时对象的 Class 类型对象
// 不能自定义
public final native Class<?> getClass();

// 比较对象是否相同,也即引用地址是否指向同一对象
// 可自定义
public boolean equals(Object obj);

// 克隆对象,产生一个新对象。
// 可自定义
protected native Object clone() throws CloneNotSupportedException;

// 返回对象的字符串说明
// 可自定义
public String toString();

// 唤醒一个正在该对象上等待的 线程 
// 不能自定义
public final native void notify();

// 唤醒所有正在该对象上等待的线程
// 不能自定义
public final native void notifyAll();

// 使当前线程进入等待,直到被唤醒
// 不能自定义
public final void wait() throws InterruptedException;

// 使当前线程进入等待,直到被唤醒或等待超时(毫秒)
// 不能自定义
public final native void wait(long timeoutMillis) throws InterruptedException;
public final void wait(long timeoutMillis, int nanos) throws InterruptedException;

// 对象在被释放前调用
// 可自定义
protected void finalize() throws Throwable;
 

关于 native 修饰词的说明:

二、Object 类方法解说

1. public Object() 构造方法

我们都知道,对象是类通过构造器创建的。而普通类可以不实现 构造函数 ,那是因为所有类都是继承自 Object 类,普通类没有构造器时就会调用父类的构造器,即 Object 类中构造器。但是 Object 是个始祖类,没有父类,自己不实现构造器的话就不能创建对象了。所以 Object 类中需要定义一个空的构造函数。

2. public native int hashCode () 获取对象散列码

是个本地方法,不在 Object 类中实现。hashCode 方法返回一个整数,这个整数是对象根据特定算法计算出来的一个散列值(hash 值)。子类可以重写 hashCode 方法,但是重写后 euqals 方法通常也需要重写。而且 hashCode() 和 equals() 方法具有以下特性:

1) 两个对象通过 equals() 判断为 true,则这两个对象的 hashCode() 返回值也 必须相同

2) 两个对象的 hashCode() 返回值相同,equals() 比较不一定需要为 true,可以是不同对象。

为什么重写 hashCode() 或 equals() 中的一个方法,另外一个方法也建议重写呢? 而且必须具备上面的特性呢?

我们经常会碰到一些需求,如将一类元素放到一个容器里。那么放入容器前,肯定需要知道这个元素是否已在容器中了。对于只存放几个元素的小容器,我们可以循环容器中的每个元素进行 equals() 比较,都是 false 表明不存在,那么就将这个元素放入容器。但当我们需要存放大量数据,而我们又不关心排列顺序时怎么处理,肯定不能一个个循环匹配,这样效率太低。Java 标准库已经提供了一些集合接口实现,其中就有无序集合,如 哈希表 :hashTable、hashMap 等。

哈希表将数据分成很多块,每一块称之为“桶”,桶的个数是2的幂,默认16个,当然桶的个数可以自定义。当一个新对象 A 需要放入哈希表中时,先取对象 A 的 hashCode 一个整数,再对桶的总数求余。假如余数为3,则将 A 放入第4个桶中。如果第4个桶中没有其它元素,那么这次数据插入很顺利,直接将 A 放入第4个桶中,如果第4个桶中已经存中其它元素了,那么就需要判断 A 是否已存在,这时就遍历桶中的元素,通过 equals() 比较是否相同,都不相同则插入数据,有相同则不插入。新数据插入到末尾,多个数据对象形成一个链表。如图:

既然桶的个数是固定的,那总会有填满的时候,当桶都填满后,每次插入新数据不是又要整个链表循环比较?为了避免出现这种情况,哈希表有个阈值(装填因子),默认值为 0.75(可以自定义)。当表的 75% 被填入值后,再次插入数据时就会创建一个两倍原桶数的新表,再把旧表数据移到新表,旧表被丢弃。

3. public final native Class<?> getClass() 返回运行时对象的 Class 类型对象

先看一段示例:

可以看出:

1.Person 对象 p 和 Class 对象 cp 都是对象实例。

2.Class 类是继承自 Object 类的。

3.Class 对象 cp 其实是对象 p 的类信息的一个对象。通过 cp 对象可以获取到类 Person 中定义的属性和方法。

所有类都继承自 Object 超类,那么所有对象都可以通过其 getClass() 方法获得生成该对象的类实例,并通过该类实例创建许多相同类型的对象,甚至访问对象的属性或方法。其实这就是 Java 中的 反射 ,而 Class 类是反射得以实现的基础。

定义一个 Person 类:

package human;

public class Person implements Cloneable
{
 private String name;
 private int age;

 public Person(String name, int age)
 {
 this.name = name;
 this.age = age;
 }

 public String getName()
 {
 return name;
 }

 public int getAge()
 {
 return age;
 }
}
 

通过 getClass() 获取 Person 的 Class 类对象,并创建新的 Person 对象实例。

package human;

import java.lang.reflect. Constructor ;

public class Main
{
 public static void main(String[] args)
 {
 Person p = new Person("张三", 22);
 Class cp = p.getClass();
 Constructor[] constructors = cp.getConstructors(); // 获取 Person 类所有构造器
 try {
 Person person = (Person) constructors[0].newInstance("小王", 11);
 System.out.println(person.getName() + " " + person.getAge()); // 输出:小王 11
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
}
 

Constructor 构造器类,Class 类对象中的构造器可以通过下面的方法获取:

getConstuctors() 获取类中定义的所有构造函数

getContructor() 获取类中不带参数的构造函数

获取到构造器就可以通过 newInstance() 方法创建对象了。

Class 类型对象还可以通过类装载器获得,如:Class.foName(“human.Person”)

package human;

public class Main
{
 public static void main(String[] args)
 {
 Person p = new Person("张三", 22);
 Class cp = p.getClass();
 System.out.println(cp.getName()); // 输出: human.Person
 try {
 System.out.println(cp.equals(Class.forName(cp.getName()))); // 输出: true
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }
}
 

通过反射获取对象属性:

package human;

import java.lang.reflect.Field;

public class Main
{
 public static void main(String[] args)
 {
 Person p = new Person("张三", 22);
 Class cp = p.getClass();
 try {
 // 获取属性值,获取 public 属性用 getField() 方法
 Field fieldName = cp.getDeclaredField("name");
 fieldName.setAccessible(true); // 设置访问权限

 System.out.println((String) fieldName.get(p));
 fieldName.set(p, "李四"); // 更改对象的属性值
 System.out.println(p.getName()); // 输出: 李四
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
}
 

可以看到通过反射可以获取某个对象的属性,还能改变其值。所以 反射会造成安全问题,需要谨慎使用 。反射是在运行时动态创建对象,不是编译过程中产生,所以反射操作的效率要比非反射低得多。更多反射相关的内容,这里不多介绍。

Class 与 Object 的区别?开头有个示例:

package human;

public class Main
{
 public static void main(String[] args)
 {
 Person p = new Person("张三", 22);
 Class cp = p.getClass();
 System.out.println(cp == Person.class); // 输出:true
 System.out.println(cp instanceof Object); // 输出:true
 }
}
 

Class 类型实例 cp 可以通过类 Person 的 class 获得,那么肯定已经存在一个 Class 类型实例对应 Person 类,依此类推,那 java.lang.Object 类也会与一个 java.lang.Class 实例相对应,而 java.lang.Class 类又是 java.lang.Object 类的派生类,咋一看有点混乱,不知道哪个是最先生成的。

实际上 JVM 在初始化的时候会对重要类型先分配内存空间(分配 java.lang.Class、java.lang.Object 等核心类型内存空间),再把对应的元数据生成好(创建 Object、Class 等实例),然后把元数据和类的引用关系串联好,这样整个对象系统就能正常工作了,也即初始化完成。接下来就可以执行 java 程序字节码。

这个几个概念有点绕,需要慢慢理解,本人水平有限解释不清,看不懂不要纠结。

4. public boolean equals(Object obj) 判断两个对象是否相同

判断两个变量是否是同一个对象的引用,即内存地址相同。如下:

5. protected native Object clone() 克隆一个新对象

clone() 方法是个本地方法,效率要高很多。当需要复制一个相同的对象时一般都通过 clone() 来实现,而不是 new 一个新对象再把原对象的属性值等复制给新对象。Object 类中定义的 clone() 方法是 protected 受保护的,必须在子类中重载这个方法才能使用。clone() 方法返回的时个 Object 对象,必须进行强制类型转换才能得到需要的类型。

实现 clone() 需要继承 Cloneable 接口,不继承会抛出 CloneNotSupportedException 异常。如下:

创建一个 Person 类,并重载 clone() 方法:

package human;

public class Person implements Cloneable
{
 private String name;
 private int age;

 public Person(String name, int age)
 {
 this.name = name;
 this.age = age;
 }

 public String getName()
 {
 return name;
 }

 public int getAge()
 {
 return age;
 }

 public Object clone()
 {
 Object o = null;
 try {
 o = super.clone();
 } catch (CloneNotSupportedException e) {
 e.printStackTrace();
 }
 return o;
 }
}
 

测试下 clone() 方法:

测试发现 clone 后的对象与原对象拥有一样的属性值,但是两个不同的对象。

这里的复制还有两个概念,浅层复制和深层复制:

浅层复制: 复制的对象成员属性值与原对象一致,如果成员属性指向其它对象,浅层复制仅复制引用变量,指向的还是原对象成员所引用的对象值。

深层复制: 复制的对象成员属性值与原对象一致,如果成员属性指向其它对象,深层复制会将成员引用的其它对象也一同复制,即所有的引用都指向新的对象。

6. public String toString() 返回对象的字符串描述

可以通过重写 toString() 方法让返回的对象信息更加丰富。

public String toString()
{
 return this.getClass() + "需要返回的信息";
}
 

7. 多线程技术涉及方法

public final native void notify() 唤醒一个正在该对象上等待的线程

public final native void notifyAll() 唤醒所有正在该对象上等待的线程

public final void wait() 使当前线程进入等待,直到被唤醒

public final native void wait(long timeoutMillis) 使当前线程进入等待,直到被唤醒或超时(毫秒)

public final void wait(long timeoutMillis, int nanos)

多线程是另外一个复杂问题,这里不做过多介绍。

8. protected void finalize() 对象释放前调用方法

可以重写该方法来记录一些对象释放前的信息,但是释放时间不可靠,垃圾回收器无法确定什么情况下对象会被回收,所以最好不要依赖使用 finalize() 方法。

文章来源:智云一二三科技

文章标题:JAVA脱水学习-java超类Object解析,Object常用方法

文章地址:https://www.zhihuclub.com/197621.shtml

关于作者: 智云科技

热门文章

网站地图