java中Object 类

一. Object类简介

Object类是Java.java.lang包下的核心类,Object类是所有类的父类,任何一个类如果没有明确的继承一个父类的话,那么它就是Object的子类;

 (使用无需导包,它所属JDK -> SRC.ZIP -> java -> lang  包下)

 

二. Object的方法
Object 提供9种方法(Clone、equals、 hashcode、wait、notify、notifyall、finalize、toString、getClass)

 1 public final native Class<?> getClass();

 2 public native int hashCode();
 
 3 public boolean equals(Object obj);
 
 4 protected native Object clone() throws CloneNotSupportedException;
 
 5 public String toString();
 
 6 public final native void notify();
 
 7 public final native void notifyAll();
 
 8 public final native void wait(long timeout) throws InterruptedException;
 
 9 public final void wait(long timeout, int nanos) throws InterruptedException;
 
10 public final void wait() throws InterruptedException ;
 
11 protected void finalize() throws Throwable;

 

三.方法的作用

 1. getclass()      public final native Class<?> getClass();      final修饰,用于反射机制中获取对象,返回Class类型。

1  User user = new User();
2  Class<? extends User> userClass = user.getClass();
3  System.out.println(userClass);
打印结果:
  class org.xinhua.entity.User

 

  2. hashCode()      public native int hashCode();      hash code是一种编码方式,在Java中,每个对象都会有一个hashcode,Java可以通过这个hashcode来识别一个对象。hashtable等结构,就是通过这个哈希实现快速查找键对象。返回值是int类型的散列码。

1  User user = new User();
2  int userHashCode = user.hashCode();
3  System.out.println(userHashCode);
打印结果:
  549374472
--------------------------------------------
List<String> strList = new ArrayList<>();
strList.add("个人分类")
每次往List中添加元素,都会得到不同的hashCode值。
即:对于可变对象,hashCode应该在它们以某种方式改变时改变,使它们不等于它们以前的状态。

 

 3. equals(Object obj)      public boolean equals(Object obj);      用于比较对象是否相等,可通过重写equals()获取不同效果。返回布尔类型的值。

1 String str1 = "aaa", str2 = "bbb";
2 boolean equals = str1.equals(str2);
3 System.out.println(equals);
4 打印结果: false

注意:如果需要重写equals方法,应该重写hashcode方法.原生equals是比较对象是否相同,hashcode内存地址换算出来的一个值,如果只重写equals方法,而不重写hashCode方法的话,就回出现因为equals()两个对象相同,但是原先的hashCode()对应的值不同

 

 4. clone()      protected native Object clone() throws CloneNotSupportedException;      用于创建并返回一个对象的拷贝。clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。建议使用深拷贝.

① 类实现Cloneable接口,

②重写Object中clone() 方法

③有异常CloneNotSupportedException 抛出

④对象.clone()

ClassName类
// 实现Cloneable接口
public class User implements Cloneable {
   private String name;// 略去构造方法、getter()、setter()、toString();
// 重写clone方法
    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone(); // 这里做了强转
    }
}

main方法
 public static void main(String[] args) throws CloneNotSupportedException {
        User user= new User("张三");
        User u2= user.clone();
        System.out.println(user);
        System.out.println(u2);
    }

 

 5. toString()      public String toString();        其返回值是String类型,返回类名和它 的引用地址。可以通过重写toString(),在打印对象时获得理想的格式。

User user = new User();
user.setRealName("张三");
user.setEmail("zhangsan@163.com");
System.out.println(user);

重写toString前:
打印输入:
org.xinhua.entity.User@bdf5e5 // 输入的是类名+地址

重写toString:
@Override
public String toString() {return "获取到的姓名是:" + this.getRealName() + ",邮箱是:" + this.getEmail(); }
打印输出:
获取到的姓名是:张三,邮箱是:zhangsan@163.com

 

 6. notify()      public final native void notify();      多线程编程中比较常见,从当前对象锁的等待队列中获取一个线程(如果有多个线程则获取优先级高的,优先级都一样则随机),移入到同步队列中,参与锁的竞争。

①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生

②调用notify方法后,当前线程不会马上释放该对象锁,要等到同步块或者同步方法执行完后,当前线程才会释放锁。

public static void main(String[] args) throws InterruptedException {

        Object o = new Object();

        new Thread(() -> {
            System.out.println("第一步:wait执行,");
            synchronized (o) {
                try {
                    o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("第四步:wait执行后");
        }).start();

        Thread.sleep(1000);

        new Thread(() -> {
            System.out.println("第二步:notify执行前");
            synchronized (o) {
                o.notify(); // 用于唤醒 o.wait的线程
            }
            System.out.println("第三步:notify执行后");
        }).start();
    }

 

  7. notifyAll()      public final native void notifyAll();      和notify一样,都是用来唤醒处于等待状态的线程参与锁的竞争,但也有一些区别。notifyAll会将当前对象锁的等待队列的所有线程,都移入到同步队列中,但与锁的竞争。

①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生。

②调用notifyAll方法后,当前线程不会马上释放该对象锁,要等到同步块或者同步方法执行完后,当前线程才会释放锁。

public static void main(String[] args) throws InterruptedException {

        Object o = new Object();

        new Thread(() -> {
            System.out.println("wait1执行前.");
            synchronized (o) {
                try {
                    o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("notifyAll执行后,wait1");
        }).start();

        Thread.sleep(1000);

        new Thread(() -> {
            System.out.println("wait2执行前..");
            synchronized (o) {
                try {
                    Thread.sleep(1000);
                    o.wait(); // 使当前执行代码的线程进行等待,把线程放在等待队列中,若没有唤醒,则持续等待。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("notifyAll执行后,wait2");
        }).start();

        Thread.sleep(1000);

        new Thread(() -> {
            System.out.println("notify线程执行前");
            synchronized (o) {
                o.notifyAll(); // 用于唤醒所有当前o锁的等待线程,
            }
            System.out.println("notify线程执行后");
        }).start();

    }

  

 8.  wait()        public final void wait() throws InterruptedException ;      让当前线程进入等待(阻塞)WAITTING 状态,将线程放入到等待队列中,并释放对象锁。

①搭配 synchronized 来使用,脱离synchronized使用notify会抛出IllegalMonitorStateException异常,目的是防止死锁或永久等待发生。

②调用wait方法后,会立即释放当前线程锁占有的对象锁。会暂停执行wait后面的代码块,需要等待,其它拿到该锁对象的线程执行notify或notifyAll方法唤醒该线程后,才会继续执行wait后面的代码块。

代码如上所示。

 

 9. wait(long timeout)      public final native void wait(long timeout) throws InterruptedException;     timeout(毫秒)时间后会被自动唤醒     wait(0)等同于wait()

 1 public static void main(String[] args) {
 2         Object o = new Object();
 3 
 4         new Thread(() -> {
 5             long start = System.currentTimeMillis();
 6             System.out.println("wait开始执行");
 7             synchronized (o) {
 8                 try {
 9                     o.wait(1000); 
10                 } catch (InterruptedException e) {
11                     e.printStackTrace();
12                 }
13             }
14             long end = System.currentTimeMillis();
15             System.out.println("wait执行结束");
16             System.out.println("执行时间:" + (end - start) + "毫秒");
17         }).start();
18 }
19 
20 打印输出:
21   wait开始执行
22   wait执行结束
23   执行时间:1010毫秒

 

 10.  wait(long timeout, int nanos)       public final void wait(long timeout, int nanos) throws InterruptedException;      nanos  这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫微秒。 wait(0, 0) 与 wait(0) 相同。 wait(500, 50000),先执行5000毫微秒,执行结束后调用timeout执行。 

 1   public static void main(String[] args) {
 2         Object o = new Object();
 3 
 4         new Thread(() -> {
 5             long start = System.currentTimeMillis();
 6             System.out.println("wait开始执行");
 7             synchronized (o) {
 8                 try {
 9                     o.wait(5000,50000);  // 先执行5000毫微秒,执行结束后调用timeout执行。
10                 } catch (InterruptedException e) {
11                     e.printStackTrace();
12                 }
13             }
14             long end = System.currentTimeMillis();
15             System.out.println("wait执行结束");
16             System.out.println("执行时间:" + (end - start) + "毫秒");
17         }).start();
18     }
19 
20 打印输出:
21     wait开始执行
22     wait执行结束
23     执行时间:513毫秒

 

 11. finalize()       protected void finalize() throws Throwable;      finalize()垃圾回收机器,方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。

垃圾回收机器有以下的特点:

①当对象不再被程序所使用的时候,垃圾回收器将会将其回收;

②垃圾回收是在后台运行的,我们无法命令垃圾回收器马上回收资源,但可以通过System.gc()或Runtime.getRuntime().gc()方法提示垃圾回收器执行回收操作。

③垃圾回收器在回收某个对象的时候,首先会调用该对象的finalize()方法

④GC主要针对堆内存

⑤垃圾回收的时机具有不确定性,finallize()也可能自始自终都不被调用。

 1 public class User {
 2 
 3     private Integer count;
 4 
 5     public User(Integer count) {
 6         this.count = count;
 7     }
 8 
 9 // 重写finalize()方法,用于标记count计数。
10     @Override
11     protected void finalize() throws Throwable {
12         System.out.println("gc标记,销毁。" + count++);
13         super.finalize();
14     }
15 }
16 
17 public class GCTest{
18     public static void main(String[] args) throws InterruptedException {
19         User user = null;
20         for (int i = 1; i <= 100; i++) {
21             Thread.sleep(100);
22             user = new User(i);
23             System.gc(); // 调用GC方法配合finalize()使用。
24         }
25 
26     }
27 }
28 
29 打印输出:
30 .........
31 gc标记,销毁。91
32 gc标记,销毁。92
33 gc标记,销毁。93
34 gc标记,销毁。94
35 gc标记,销毁。95
36 gc标记,销毁。96
37 gc标记,销毁。97
38 gc标记,销毁。98
39 gc标记,销毁。99 
// 从打印角度没有看到gc标记,销毁。100 有两种可能,第一种是java进程走完了,导致还没有执行回收就被停止了;第二种可能,已标记,暂时未回收。
// 项目中若有其他线程引用了该对象的引用值,会出现标记不清理的情况,长时间不适用,就会自动释放。

 

三. 拓展  

1. 为什么需要重写equals和hashCode方法?

 首先我们看下源码中的equals方法;

 public boolean equals(Object obj) {
        return (this == obj);  // 比较地址值是否相同
}

 Java中规定:

  (1)如果两个对象通过equals方法比较是相等的,那么它们的hashCode方法结果值也是相等的。

  (2)如果两个对象通过equals方法比较是不相等的,那么它们的hashCode方法结果值不一定不相等。

  如果只重写equals,而不重写hashcode,会导致地址值相同,而hashcode不同。在使用hashMap、hashSet中,通过计算hashcode来筛重,会出现错误。

public class User {

    private String name;

    public User(String name) {
        this.name = name;
    }
// 重写equals
    @Override
    public boolean equals(Object o) {
        if (o instanceof User) {
            User u = (User) o;
            if (this.name.equals(u.name)) return true;
            else return false;
        } else {
            return false;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        User u1 = new User("张三");
        User u2 = new User("张三");
        System.out.println(u1.equals(u2));
        System.out.println(u1.hashCode());
        System.out.println(u2.hashCode());
    }
}

打印输出:
    true
    23934342
    22307196

 

 2. clone() 浅拷贝和深拷贝

    浅拷贝:只拷贝栈内存中的数据,不拷贝堆内存的数据。(只拷贝基本类型数据,使用引用类型数据原地址);

    深拷贝:既拷贝栈内存中的数据,又拷贝堆内存中的数据。(拷贝基本数据类型,同时在堆内存中开辟一块空间,用于拷贝引用类型的数据);

    特殊类型:String类型。clone() 并不会克隆Sting类;String是基于堆内存、常量池,这种比较特殊,本身没有实现CloneAble,自身是由final修饰,每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。

 浅拷贝实现:

User类,没有实现Cloneable接口
public class User {

    private int age;

    private String name;

// 略:构造方法、getter、setter、toString...
// 没有重写clone()方法

}

UserDto类,实现Cloneable接口
public class UserDto implements Cloneable {

    private String phone;

    //引用user类
    private User user;

// 略:构造方法、getter、setter、toString...

    @Override
    protected UserDto clone() throws CloneNotSupportedException {
        return (UserDto) super.clone(); 
    }

}

Main方法
    public static void main(String[] args) throws CloneNotSupportedException {

        User u1 = new User(18, "张三");
        UserDto d1 = new UserDto("110",u1);

        UserDto d2 = d1.clone();   // clone() 克隆
        System.out.println(d1.getUser()); //打印d1的引用类型地址:
        System.out.println(d2.getUser());// 打印d2的引用类型地址
        System.out.println(System.identityHashCode(d1.getPhone())); // String类型的地址值
        System.out.println(System.identityHashCode(d2.getPhone()));
    }

打印输出:
    
    clone.User@16d3586 // 类名+地址值
    clone.User@16d3586 
    22307196
    22307196

可以看到引用类型地址值没有变化。

    深拷贝实现:

User类,实现Cloneable接口
public class User implements Cloneable {

    private int age;

    private String name;

// 略:构造方法、getter、setter、toString
// 引用类中重写clone()。

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }
}

UserDto类中,实现CloneAble接口
public class UserDto implements Cloneable {

    private String phone;

    private User user;
 
// 略:构造方法、getter、setter、toString
// 重写clone()

    @Override
    protected UserDto clone() throws CloneNotSupportedException {
        UserDto userDto = (UserDto) super.clone();
        userDto.user = user.clone();
        return userDto;
    }
}

Main类:未做改动
  public static void main(String[] args) throws CloneNotSupportedException {

        User u1 = new User(18, "张三");
        UserDto d1 = new UserDto("110",u1);

        UserDto d2 = d1.clone();   // clone() 克隆
        System.out.println(d1.getUser()); //打印d1的引用类型地址:
        System.out.println(d2.getUser());// 打印d2的引用类型地址
        System.out.println(System.identityHashCode(d1.getPhone())); // String类型的地址值
        System.out.println(System.identityHashCode(d2.getPhone()));
    }

打印输出:
    clone.User@16d3586 // 类名+地址值
    clone.User@154617c
    10568834
    10568834

引用类型地址值发生变化

  clone() 过去繁琐,推荐使用  BeanUtils.copyProperties()  方法。 包:import org.springframework.beans.BeanUtils;

 

 3. notify()和 notifyAll()有什么区别?

  ① 唤醒数量不同,notify()方法只会随机唤醒等待队列中的一个线程,而notifyAll()方法则会唤醒等待队列中的所有线程。

  ② 调用方式不同,notify()和notifyAll()方法都必须在同步代码块中调用,并且必须包含在synchronized块中,且必须是该对象的监视器对象才能够调用。而且只有在获取了锁之后才能调用,否则会抛出IllegalMonitorStateException异常。

  ③ 竞争情况不同,notify()方法只会唤醒等待队列中的一个线程,并使其与其他线程竞争获取锁,这可能会导致某些线程无法被唤醒或者一直处于等待状态。 notifyAll()方法则会唤醒等待队列中的所有线程,并使它们竞争获取锁,这样可以使所有线程都有机会获取锁并进入运行状态,从而避免了一些线程一直处于等待状态。

  因此,在多线程编程中,如果需要唤醒所有等待该对象的线程,则可以使用notifyAll()方法;如果只需要随机唤醒一个等待该对象的线程,则可以使用notify()方法。

 

 

 

 完!

posted @ 2023-07-06 18:35  一只礼貌狗  阅读(222)  评论(0编辑  收藏  举报