什么是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 +
'}';
}