对象老祖宗Object:我有话说

什么是Object类

Object是所有Java类的祖先,位于 java.lang 包下,(这个包中所有类在使用时无需手动导入,系统会在程序编译期间自动导入)。当一个类没有直接继承某个类时,默认继承Object类,也就是说任何类都直接或间接继承Object,Object 类中能访问的方法在所有类中都可以调用。

也就是下面是相等的。

public class User {
}

public class User  extends Object{
}

Object类下所有方法。
在这里插入图片描述

clone()

用于对象克隆,是指创建此对象的完整副本,并使用当前对象的相应字段的内容初始化新实例的所有字段,也就是克隆出来的对象中所有字段是等于原对象中所有字段的。

public class User extends Object implements Cloneable {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    protected User clone()  {
        User user =null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}


public class Main {
    public static void main(String[] args) {
        User user =new User();
        user.setAge(11);
        user.setName("name");
        User clone =user.clone();
        System.out.println(user ==clone);
        System.out.println(user.toString());
        System.out.println(clone.toString());
    }
}

运行结果:

false
User{age=11, name='name'}
User{age=11, name='name'}

首先被克隆的对象一定要实现Cloneable接口,否则会报CloneNotSupportedException错误。

从第一个输出可以发现这两个对象不是同一个对象,而内容都是一样的。但上面的克隆被称为浅克隆,还有一种是深克隆。浅克隆是指创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址,而深克隆创建的新对象属性中引用的其他对象也会被克隆,不再指向原有对象地址。

如下代码,增加一个Dog类。

package com.hxl;

public class User extends Object implements Cloneable {
    private int age;
    private String name;
    private Dog dog;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    protected User clone()  {
        User user =null;
        try {
            user = (User) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public class Dog {
    private String dogName;

    public String getDogName() {
        return dogName;
    }

    public void setDogName(String dogName) {
        this.dogName = dogName;
    }
}

public class Main {
    public static void main(String[] args) {
        User user =new User();
        user.setAge(11);
        user.setName("name");
        user.setDog(new Dog());
        User clone =user.clone();
        System.out.println(user.getDog() ==clone.getDog());
        System.out.println(user.toString());
        System.out.println(clone.toString());
    }
}

运行结果:

true
User{age=11, name='name'}
User{age=11, name='name'}

从第一个输出可以看到,Dog对象是相等的,如果克隆出来的对象修改其中的Dog属性,那么原来对象中的Dog属性也会被改变,如果要完成深度克隆,可以这样做,

@Override
protected User clone()  {
    User user =null;
    try {
        user = (User) super.clone();
        Dog cloneDog = (Dog) user.getDog().clone();
        user.setDog(cloneDog);
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return user;
}

同样Dog也要实现Cloneable接口。这样克隆出来的对象修改其中Dog实例,原来对象是不会发生变化的。

equals()

用来判断对象是否相等,但在 Object 类中,== 运算符和 equals 方法是等价的,都是比较两个对象的引用是否相等。 也就是如果不重写 equals 方法,那么在比较对象的时候就是调用 == 运算符比较两个对象。所以,是不是有点冲动想看String.equals方法是怎么比较字符串的?
在这里插入图片描述
在做一个小测试,如果我们不重写它的话,下面判断只会输出false。

public class Main {
    public static void main(String[] args) {
        User user1 =new User(11,"张三");
        User user2 =new User(11,"张三");
        System.out.println(user1.equals(user2));
    }
}

重写后就不一样了。

public class User extends Object implements Cloneable {
	........
	@Override
	public boolean equals(Object o) {
	    if (this == o) return true;
	    if (!(o instanceof User)) return false;
	    User user = (User) o;
	    return getAge() == user.getAge() &&
	            Objects.equals(getName(), user.getName()) &&
	            Objects.equals(getDog(), user.getDog());
	}
}

但在 在Java规范中,对 equals 方法的使用必须遵循以下几个原则:
1、自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
2、对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
3、传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
4、一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改
5、对于任何非空引用值 x,x.equals(null) 都应返回 false。

finalize()

一般由GC在回收对象之前自动调用,子类可以覆盖该方法以实现资源清理工作,类似于Android中Activity的onDestroy方法。但是Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行,虽然System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们。

public class User extends Object implements Cloneable {
	@Override
	protected void finalize() throws Throwable {
	    System.out.println("finalize");
	    super.finalize();
	}
}
public class Main {
    public static void main(String[] args)  {
        User user1 =new User(11,"张三");
        User user2 =new User(11,"张三");
        user1=null;
        System.gc();
    }
}

运行结果:

finalize

或者是

public class Main {
    public static void main(String[] args) {
        User user1 = new User(11, "张三");
        User user2 = new User(11, "张三");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.gc();
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

getClass()

这是一个用 native 关键字修饰的方法,首先说什么是Class对象,Java每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。

getClass()就是获得此实例对象的Class,可以通过返回的Class对象获取相关信息,也可以通过Class.forName获取。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        User user = new User(11, "name");
        Class<? extends User> userClass1 = user.getClass();
        Class<?> userClass2 = Class.forName("com.hxl.User");
        System.out.println(userClass1.equals(userClass2));
    }
}

可以获取此类的字段、方法等信息,进而更改,详细可以学习Java反射知识。

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        User user = new User(11, "name");
        Class<? extends User> userClass1 = user.getClass();
        Field[] declaredFields = userClass1.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField.getName());
        }
        System.out.println("----------------");
        Field name = userClass1.getDeclaredField("name");
        name.setAccessible(true);
        name.set(user,"张三");
        System.out.println(user.getName());
    }
}

运行结果

age
name
dog
----------------
张三

hashCode()

返回此对象的哈希码,在HashSet、HashMap以及HashTable中,内部会经常用到。

下面这段话摘自Effective Java一书:

在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。

wait()、notify()、notifyAll()

为了支持多线程之间的协作,JDK提供了两个非常重要的方法:等待wait()方法和通知notify()方法。

当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。比如,在线程A中,调用了obj.wait()方法,那么线程A就会停止继续执行,转为等待状态。直到其他线程调用了obj.notify()方法为止。

他是这样工作的,如果一个线程调用了object. wait()方法,那么它就会进入object对象的等待队列,这个等持队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。 当object.notify()方法被调用时,他就会从这个等待队列中随机选择一个线程,并将其唤醒,这个选择是不公平的,并不是先等待的线程就会被优先选择。

除了notify()方法外,还有notifyAll()方法,他和notify()功能基本一致,区别是,他会唤醒在这个等待队列中的所有等待线程。

另外,在调用这三个方法时,并不能随意调用,必须包含在synchronized语句中,都先要获取目标对象的监视器。

public class Main {
    public static void main(String[] args) {
        User user = new User(11, "name");
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait");
                    user.wait();
                    System.out.println("wait end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (user) {
                System.out.println("notify");
                user.notify();
                System.out.println("notify end");
            }
        }).start();
    }
}

运行结果如下(无论怎么运行,都会是这个结果)

wait
notify
notify end
wait end

首先要等待的线程获取到user的对象锁,然后wait()进行等待,并释放user的对象锁!!!,不然要通知的线程是无法进入代码块的,通知的线程拿到user的对象锁后,执行notify(),当等待线的程得到notify(),还是会尝试重新获取user的对象锁,也就是如果通知线程没释放的话,等待线程也没办法继续下去。

notifyAll()的用法,运行之后两个等待线程也都会接到通知,进而结束。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        User user = new User(11, "name");
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait");
                    user.wait();
                    System.out.println("wait end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(() -> {
            synchronized (user) {
                try {
                    System.out.println("wait2");
                    user.wait();
                    System.out.println("wait2 end");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        Thread.sleep(1000);
        synchronized (user){
            user.notifyAll();
        }
    }
}

toString()

用的最多的可能就是它了,返回一个"以文本方式表示"此对象的字符串,在打印对象的时候,其实就是调用object.toString()。

public class Main {
    public static void main(String[] args)  {
        User user = new User(11, "name");
        System.out.println(user);
    }
}

默认的输出是类名@十六进制的hashCode

 public String toString() {
     return getClass().getName() + "@" + Integer.toHexString(hashCode());
 }

重写他也很容易,也可以借助开发工具快速生成(Alt+Insert)。
在这里插入图片描述
想返回什么返回什么。

 @Override
 public String toString() {
     return "User{" +
             "age=" + age +
             ", name='" + name + '\'' +
             ", dog=" + dog +
             '}';
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章