Java基础12——深入理解Class类和Object类

44 篇文章 0 订阅
5 篇文章 0 订阅

深入理解Class类和Object类

一、所有类的类型信息的记录员——Class类

先来简单了解一下Java虚拟机中类的加载过程:
「加载」阶段是「类加载」过程的第一个阶段,虚拟机需要完成以下三件事情:
1. 通过一个类的全限定名来获取定义此类的二进制字节流(.class文件即保存 着类的二进制数据)。
2. 将该字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3. 在Java堆中生成一个代表该类的java.lang.Class对象,作为方法区中该数据结构的访问入口。

也就是说,Class是一个保存着运行时类所有信息的类,即在程序运行时跟踪类且掌握着类的全部信息,故其也被称为反射的源头(有点儿小抽象)。

  Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。

这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。

简单点说就是:

  1. Class类也是类的一种,只是名字和class关键字高度相似。Java是大小写敏感的语言。
  2. Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象。
  3. Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数。
   /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
     //私有构造方法,只能由jvm进行实例化
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

Class类的作用是运行时提供或获得某个对象的类型信息,和C++中的typeid()函数类似。这些信息也可用于反射。

源码:

//Class类中封装了类型的各种信息。在jvm中就是通过Class类的实例来获取每个Java类的所有信息的。

public class Class{
    Class aClass = null;

//    private EnclosingMethodInfo getEnclosingMethodInfo() {
//        Object[] enclosingInfo = getEnclosingMethod0();
//        if (enclosingInfo == null)
//            return null;
//        else {
//            return new EnclosingMethodInfo(enclosingInfo);
//        }
//    }

    /**提供原子类操作
     * Atomic operations support.
     */
//    private static class Atomic {
//        // initialize Unsafe machinery here, since we need to call Class.class instance method
//        // and have to avoid calling it in the static initializer of the Class class...
//        private static final Unsafe unsafe = Unsafe.getUnsafe();
//        // offset of Class.reflectionData instance field
//        private static final long reflectionDataOffset;
//        // offset of Class.annotationType instance field
//        private static final long annotationTypeOffset;
//        // offset of Class.annotationData instance field
//        private static final long annotationDataOffset;
//
//        static {
//            Field[] fields = Class.class.getDeclaredFields0(false); // bypass caches
//            reflectionDataOffset = objectFieldOffset(fields, "reflectionData");
//            annotationTypeOffset = objectFieldOffset(fields, "annotationType");
//            annotationDataOffset = objectFieldOffset(fields, "annotationData");
//        }

        //提供反射信息
    // reflection data that might get invalidated when JVM TI RedefineClasses() is called
//    private static class ReflectionData<T> {
//        volatile Field[] declaredFields;
//        volatile Field[] publicFields;
//        volatile Method[] declaredMethods;
//        volatile Method[] publicMethods;
//        volatile Constructor<T>[] declaredConstructors;
//        volatile Constructor<T>[] publicConstructors;
//        // Intermediate results for getFields and getMethods
//        volatile Field[] declaredPublicFields;
//        volatile Method[] declaredPublicMethods;
//        volatile Class<?>[] interfaces;
//
//        // Value of classRedefinedCount when we created this ReflectionData instance
//        final int redefinedCount;
//
//        ReflectionData(int redefinedCount) {
//            this.redefinedCount = redefinedCount;
//        }
//    }
        //方法数组
//    static class MethodArray {
//        // Don't add or remove methods except by add() or remove() calls.
//        private Method[] methods;
//        private int length;
//        private int defaults;
//
//        MethodArray() {
//            this(20);
//        }
//
//        MethodArray(int initialSize) {
//            if (initialSize < 2)
//                throw new IllegalArgumentException("Size should be 2 or more");
//
//            methods = new Method[initialSize];
//            length = 0;
//            defaults = 0;
//        }

    //注解信息
    // annotation data that might get invalidated when JVM TI RedefineClasses() is called
//    private static class AnnotationData {
//        final Map<Class<? extends Annotation>, Annotation> annotations;
//        final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
//
//        // Value of classRedefinedCount when we created this AnnotationData instance
//        final int redefinedCount;
//
//        AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
//                       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
//                       int redefinedCount) {
//            this.annotations = annotations;
//            this.declaredAnnotations = declaredAnnotations;
//            this.redefinedCount = redefinedCount;
//        }
//    }
}

我们都知道所有的java类都是继承了object这个类,在object这个类中有一个方法:getclass().这个方法是用来取得该类已经被实例化了的对象的该类的引用,这个引用指向的是Class类的对象。

我们自己无法生成一个Class对象(构造函数为private),而 这个Class类的对象是在当各类被调入时,由 Java 虚拟机自动创建 Class 对象,或通过类装载器中的 defineClass 方法生成。

//通过该方法可以动态地将字节码转为一个Class类对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}

  我们生成的对象都会有个字段记录该对象所属类在CLass类的对象的所在位置。如下图所示:
在这里插入图片描述

如何获得一个Class类对象(How?)

  请注意,以下这些方法都是指某个类对应的Class对象已经在堆中生成以后,我们通过不同方式获取对这个Class对象的引用。而上面说的DefineClass才是真正将字节码加载到虚拟机的方法,会在堆中生成新的一个Class对象。

第一种办法,Class类的forName函数
public class shapes{}
Class obj= Class.forName(“shapes”); // 只要通过给定的类的名称就可以获取这个类,更为扩展

第二种办法,使用对象的getClass()函数
public class shapes{}
shapes s1=newshapes(); //需要明确具体的类,并创建对象
Class obj=s1.getClass();
Class obj1=s1.getSuperclass();//这个函数作用是获取shapes类的父类的类型

第三种办法,使用类字面常量
Class obj=String.class;// 相对简单,但是还是要明确用到类中的静态成员
Class obj1=int.class;
注意,使用这种办法生成Class类对象时,不会使JVM自动加载该类(如String类)。而其他办法会使得JVM初始化该类。

第四种方法,通过类的加载器获取
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(className);
System.out.println(clazz4.getName());

Class类的实例可以做什么?

获取到运行时类的Class实例后,通过Class类的实例可以:

  • 通过newInstance()方法创建对应运行类的对象。
  • 获取其对应类的完整结构,如构造器、属性、方法、内部类、父类、所在的 包、异常和注解等。
  • 调用对应的运行时类的指定结构,如属性、方法和构造器。
  • 反射的应用,即动态代理(又挖一坑,下一章重点说明)。
    注意事项:
      请注意,一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。
      另外,调用Class类实例的 newInstance()方法动态创建类对象时,需要对应的 运行时类中有空参的构造器。通过Class类的实例获取运行时类的所有描述信息的代码较长,在此仅给出对应的方法描述,如下:
    在这里插入图片描述

二、所有类的祖宗——Object类

  Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起。作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现。
  Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入。Object类没有定义属性,一共有13个方法,13个方法之中并不是所有方法都是子类可访问的,一共有9个方法是所有子类都继承了的。
在这里插入图片描述

1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
2.getClass方法
final方法,获得运行时类型。
3.toString方法
该方法用得比较多,一般子类都有覆盖。
4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。
6.hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法
该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。

源码阅读:

package java.lang;
public class Object {
/** 
* 私有的,在静态代码块中执行,且仅在Object类首次被加载时执行一
* 次。 
*/ 
private static native void registerNatives(); 
static { 
	registerNatives(); 
}
/** 
* final方法:不能被子类所重写 
*/ 
public final native Class<?> getClass();
/** 
* 在子类中重写该方法时,可直接调用Objects.hash(Object...)来获取
* 对象的哈希值 
*/ 
public native int hashCode();
/** 
* 默认实现中,仅比较两个对象是否引用的同一个对象 
* 实际开发中,需要重写该方法来比较两个对象的具体内容是否相等 
*/ 
public boolean equals(Object obj) { 
	return (this == obj); 
}
/** 
* 仅对本包下的所有类和当前类的子类可见。 
* 只有实现Cloneable接口的类的对象才能调用该方法,否则会抛出异常 */ 
protected native Object clone() throws CloneNotSupportedException;
/** 
* 返回:运行时类名@十六进制哈希值字符串 
*/ 
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
/** 
* 唤醒单个线程,但不确定具体唤醒的是哪个线程 
*/ 
public final native void notify();
/** 
* 唤醒所有线程,但并不是所有线程都可以拿到对象锁而进入就绪状态 
*/ 
public final native void notifyAll();
/** 
* 使当前线程释放对象锁,即线程暂停执行,直到其他线程调用 notify()/notifyAll() 
*/ 
public final native void wait(long timeout) throws InterruptedException
/** 
* 大等待时间:1000000*timeout+nanos 
*/ 
public final void wait(long timeout, int nanos) throws InterruptedExcept 
	if (timeout < 0) { 
		throw new IllegalArgumentException("timeout value is negative"); 
	}
	if (nanos < 0 || nanos > 999999) { 
		throw new IllegalArgumentException( "nanosecond timeout value out of range"); 
	}
	if (nanos > 0) { 
		timeout++; 
	}
	wait(timeout);
}
/** 
* 未设定大等待时间,即只能被notify()/notifyAll()唤醒 
*/ public final void wait() throws InterruptedException {
	 wait(0); 
	}
/** 
* 子类可通过重写该方法,以在垃圾回收前整理系统资源或执行其他清理操作 
* 在该方法中,若将该对象重新与引用链建立关联关系,则会逃离本次垃圾回收 
*/ 
protected void finalize() throws Throwable { }
}

1、registerNatives()方法

  registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用。
  方法的具体实现体在dll文件中,对于不同平台,其具体实现应该有所不同。用native修饰,即表示操作系统,需要提供此方法,Java本身需要使用。
  具体到registerNatives()方法本身,其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。
  既然如此,可能有人会问,registerNatives()修饰符为private,且并没有执行,作用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块。

2、深拷贝和浅拷贝的区别和具体实现

浅拷贝和深拷贝有什么区别?
先来了解一下两个概念:「引用拷贝」「对象拷贝」
「引用拷贝」是指创建一个指向对象的引用变量的拷贝,例如:

Employee emp1 = new Employee("Taylor", 26); 
Employee emp2 = emp1; 
System.out.println(emp1); // Employee@355da254 
System.out.println(emp2); // Employee@355da254

而「对象拷贝」是指创建对象本身的一个副本,例如:

Employee emp1 = new Employee("Swift", 26); 
Employee emp2 = (Employee) emp1.clone(); 
System.out.println(emp1); // Employee@7852e922 
System.out.println(emp2); // Employee@4e25154f

  即emp1和emp2分别指向堆空间中的不同对象,这就叫「对象拷贝」,但需要注意的是,使用clone()方法进行对象拷贝时,必须要求Employee类实现 Cloneable接口并重写clone()方法,且上述代码段所在的方法还需要处理 CloneNotSupportedException异常。

其中,「浅拷贝」和「深拷贝」都属于「对象拷贝」。
  对于基本数据类型的成员变量,无论「浅拷贝」还是「深拷贝」都会直接进行值传递,原对象和新对象的该属性值为两份数据,相互独立,且互不影响。
  而对于引用类型的成员变量,「浅拷贝」仅复制该属性的引用而不复制引用所指向的对象,即原对象和新对象的该属性指向的是同一个对象;「深拷贝」则会直接复制该属性所指向的对象,即原对象和新对象的该属性指向的是堆空间中内存地址不同的对象。

如何实现「浅拷贝」?

要求待拷贝对象所属类:

  • 实现Cloneable接口;
  • 重写clone()方法,并指定public访问修饰符。

要求在调用待拷贝对象的clone()方法时:

  • 处理编译时异常:CloneNotSupportedException。

另外,也可以手动采用赋值的方式将原对象的各个属性值拷贝到新的对象。

代码报错:

package com.corn.objectsummary;  
  
import com.corn.Person;  
  
public class ObjectTest {  
  
    public static void main(String[] args) {  
  
        Object o1 = new Object();  
        Object clone = o1.clone(); // The method clone() from the type Object is not visible  
    }  
  
}  

原因:
  根据提示,第一反应是ObjectTest类中定义的Oject对象无法访问其clone()方法。回到Object类中clone()方法的定义,可以看到其被声明为protected,估计问题就在这上面了, protected修饰的属性或方法表示:在同一个包内或者不同包的子类可以访问。显然,Object类与ObjectTest类在不同的包中,但是ObjectTest继承自Object,是Object类的子类,于是,现在却出现子类中通过Object引用不能访问protected方法,原因在于对"不同包中的子类可以访问"没有正确理解。
  "不同包中的子类可以访问",是指当两个类不在同一个包中的时候,继承自父类的子类内部且主调(调用者)为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员。!(super关键字除外)

代码报错:

package com.corn.objectsummary;  
  	public class ObjectTest {  
  
    public static void main(String[] args) {  
        ObjectTest ot1 = new ObjectTest();  
  
        try {  
            ObjectTest ot2 = (ObjectTest) ot1.clone();  
        } catch (CloneNotSupportedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
  
}  

正常运行:

//clone()的正确调用是需要实现Cloneable接口,如果没有实现Cloneable接口,并且子类直接调用Object类的clone()方法,则会抛出CloneNotSupportedException异常。
package com.corn.objectsummary;  
  
public class ObjectTest implements Cloneable {  
  
    public static void main(String[] args) {  
  
        ObjectTest ot1 = new ObjectTest();  
  
        try {  
            ObjectTest ot2 = (ObjectTest) ot1.clone();  
            System.out.println("ot2:" + ot2);  
        } catch (CloneNotSupportedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
  
}  

如何实现「深拷贝」?

方式1: 通过实现Cloneable接口并重写clone()方法来实现,即将原对象及其所引用的所有对象所属的类均实现Cloneable接口并重写clone()方法。
方式2: 通过序列化方式来实现。

3、完整的栗子说明深拷贝和浅拷贝

浅拷贝:
package csdn.cn.copytest;

public class ShallowCopy {

	public static void main(String[] args) throws CloneNotSupportedException {
		Teacher teacher = new Teacher();
        teacher.setName("Delacey");
        teacher.setAge(29);

        Student student1 = new Student();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        Student student2 = (Student) student1.clone();
        System.out.println("拷贝后");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("修改老师的信息后-------------");
        // 修改老师的信息
        teacher.setName("Jam");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
			
	}
}
// Teacher类	
package csdn.cn.copytest;

public class Teacher implements Cloneable {
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Teacher [name=" + name + ", age=" + age + "]";
	}
	
}
//Student类
package csdn.cn.copytest;

public class Student implements Cloneable {
	private String name;
	private int age;
	private Teacher teacher;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	public Teacher getTeacher() {
		return teacher;
	}
	public void setTeacher(Teacher teacher) {
		this.teacher = teacher;
	}

	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", teacher_name=" + teacher.getName() +", teacher_age=" + teacher.getAge()+"]";
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		
		return super.clone();
	}
	
}
//运行结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam

结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
在这里插入图片描述
浅拷贝的特点:
  被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。

深拷贝:
package csdn.cn.copytest;

public class DeepCopy {
    public static void main(String[] args) throws Exception
    {
        Teacher2 teacher = new Teacher2();
        teacher.setName("Delacey");
        teacher.setAge(29);

        Student3 student1 = new Student3();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        Student3 student2 = (Student3) student1.clone();
        System.out.println("拷贝后");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("修改老师的信息后-------------");

        // 修改老师的信息
        teacher.setName("Jam");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
    }
}

class Teacher2 implements Cloneable {
    private String name;
    private int age;

    public String getName()
    {
        return name;
    }

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

    public int getAge()
    {
        return age;
    }

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

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }

}

class Student3 implements Cloneable {
    private String name;
    private int age;
    private Teacher2 teacher;

    public String getName()
    {
        return name;
    }

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

    public int getAge()
    {
        return age;
    }

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

    public Teacher2 getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher2 teacher)
    {
        this.teacher = teacher;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        // 浅复制时:
        // Object object = super.clone();
        // return object;

        // 改为深复制:
        Student3 student = (Student3) super.clone();
        // 本来是浅复制,现在将Teacher对象复制一份并重新set进来
        student.setTeacher((Teacher2) student.getTeacher().clone());
        return student;
    }

}
输出结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey

结果分析:
两个引用student1和student2指向不同的两个对象,两个引用student1和student2中的两个teacher引用指向的是两个对象,但对teacher对象的修改只能影响student1对象,所以说是深拷贝。

在这里插入图片描述
在这里插入图片描述

利用序列化实现深拷贝:(后面流的知识,回头看)
package csdn.cn.copytest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class DeepCopyServiable {
    public static void main(String[] args) throws Exception {
        Teacher3 t = new Teacher3();
        t.setName("Taylor");
        t.setAge(28);

        Student3 s1 = new Student3();
        s1.setAge(20);
        s1.setName("blank space");
        s1.setTeacher(t);

        Student3 s2 = (Student3) s1.deepClone();

        System.out.println("拷贝后:");
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getTeacher().getName());
        System.out.println(s2.getTeacher().getAge());
        System.out.println("---------------------------");

        t.setName("swift");

        System.out.println("修改后:");
        System.out.println(s1.getTeacher().getName());
        System.out.println(s2.getTeacher().getName());
    }

}

class Teacher3 implements Serializable
{
    private String name;
    private int age;

    public String getName()
    {
        return name;
    }

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

    public int getAge()
    {
        return age;
    }

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

}

class Student3 implements Serializable
{
    private String name;
    private int age;
    private Teacher3 teacher;

    public String getName()
    {
        return name;
    }

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

    public int getAge()
    {
        return age;
    }

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

    public Teacher3 getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher3 teacher)
    {
        this.teacher = teacher;
    }

    public Object deepClone() throws Exception
    {
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }

}
输出结果:
拷贝后:
blank space
20
Taylor
28
---------------------------
修改后:
swift
Taylor

Object类的clone()方法为什么要声明为 protected?
  若声明为public的,则在子类不重写clone()方法时,调用的还是Object类的 clone()方法,只能实现浅拷贝。而声明为protected的,就要求子类在需要拷贝对象时,必须要要实现Cloneable接口并重写clone()方法,在其中既可实现浅拷贝也可实现深拷贝,但通常都需要实现深拷贝

4、重写equals方法的原因、方式和注意事项

Object类中equals()方法的源码:

public boolean equals(Object obj) {  
     return (this == obj);  
}  

由此可见,Object原生的equals()方法内部调用的正是==,与= =具有相同的含义。

为什么要重写equals()方法?

  Object类中equals()方法的默认实现主要是用于判断两个对象的引用是否相同。而在实际开发过程中,通常需要比较两个对象的对应属性是否完全相同,故需要重写equals()方法。
  在object类中,判断相等的标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。

如何重写 equals()方法?

假设equals()方法的形参名为otherObj,稍后需要将其转换为另一个叫做 other的变量。
第一步,检测this与otherObj是否引用同一对象:

if(this == otherObject) return true;

第二步,检测otherObj是否为空:

if(otherObject == null) return false;

第三步,判断this与otherObj是否属于同一个类,具体分两种情况:
(1). 如果equals()方法的语义在每个子类中均有所改变,则使用getClass()方 法进行检测:

if(getClass() != otherObject.getClass()) return false;

(2). 如果equals()方法在所有子类中均有统一的语义,则使用instanceof关键 字进行检测:

if (!(otherObject instanceof ClassName)) return false;

第四步,将otherObj转换为相应类的类型变量:

ClassName other = (ClassName) otherObject;

第五步,对所有需要比较的域进行一一比较,若全匹配则返回true,否则返回 false。

// Object类中equals()方法的默认实现 
public boolean equals(Object obj) { 
	return (this == obj); 
}
// String类中equals()方法的具体实现 
public boolean equals(Object anObject) { 
	if (this == anObject) { 
		return true; 
	} // 具有统一语义 
	if (anObject instanceof String) { 
		String anotherString = (String)anObject; 
		int n = value.length; 
		if (n == anotherString.value.length) { 
			char v1[] = value; 
			char v2[] = anotherString.value; 
			int i = 0; 
			while (n-- != 0) { 
				if (v1[i] != v2[i]) 
					return false; 
				i++; 
			} 
			return true; 
		} 
	} 
	return false; 
}

5、重写hashCode方法的原因、方式和注意事项

  Object类中hashCode()方法默认是将对象的存储地址进行映射,并返回一个整形值作为哈希码。若重写equals()方法,使其比较两个对象的内容,并保留hashCode()方法的默认实现,那么两个明明「相等」的对象,哈希值却可能不同。因此,在重写equals()方法时,建议一定要重写hashCode()方法

hashCode()具有如下约定:

1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致
2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等

1> 即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 =>
hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等
=> equals()不相等 <=> 两个对象不相等。

2>可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?

3> 其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。

4>以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)

注意:
1、相同的对象必须有相同hashcode,不同对象可能有相同hashcode。
2、对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址。

// Object类中hashCode()方法的默认实现 
public native int hashCode();
// Arrays中计算哈希值的核心代码 
public static int hashCode(Object a[]) { 
	if (a == null) 
		return 0;
	int result = 1;
	for (Object element : a) 
		result = 31 * result + (element == null ? 0 : element.hashCode());
	return result;
}

6、wait() notify() notifAll()

  一说到wait(…) / notify() | notifyAll()几个方法,首先想到的是线程。确实,这几个方法主要用于java多线程之间的协作。先具体看下这几个方法的主要含义:
wait(): 调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。

wait(long timeout)/wait(long timeout, int nanos): 调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。

notify()/notifyAll(): 唤醒在此对象监视器上等待的单个线程/所有线程。

wait(…) / notify() | notifyAll() 一般情况下都是配套使用。

下面来看一个简单的例子:
生产者消费者的模型

public class wait和notify {
    //volatile保证线程可见性
    volatile static int flag = 1;
    //object作为锁对象,用于线程使用wait和notify方法
    volatile static Object o = new Object();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //wait和notify只能在同步代码块内使用
                synchronized (o) {
                    while (true) {
                        if (flag == 0) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread1 wait");
                                //释放锁,线程挂起进入object的等待队列,后续代码运行
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread1 run");
                        System.out.println("notify t2");
                        flag = 0;
                        //通知等待队列的一个线程获取锁
                        o.notify();
                    }
                }
            }
        }).start();
        //解释同上
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (flag == 1) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread2 wait");
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread2 run");
                        System.out.println("notify t1");
                        flag = 1;
                        o.notify();
                    }
                }
            }
        }).start();
    }

    //输出结果是
//    thread1 run
//    notify t2
//    thread1 wait
//    thread2 run
//    notify t1
//    thread2 wait
//    thread1 run
//    notify t2
//不断循环
}
--------------------- 
作者:How 2 Play Life 
来源:CSDN 
原文:https://blog.csdn.net/a724888/article/details/80153002 
版权声明:本文为博主原创文章,转载请附上博文链接!

从上述例子的输出结果中可以得出如下结论:

1、wait(…)方法调用后当前线程将立即阻塞,且适当其所持有的同步代码块中的锁,直到被唤醒或超时或打断后且重新获取到锁后才能继续执行;

2、notify()/notifyAll()方法调用后,其所在线程不会立即释放所持有的锁,直到其所在同步代码块中的代码执行完毕,此时释放锁,因此,如果其同步代码块后还有代码,其执行则依赖于JVM的线程调度。

7、finalize()

我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?

首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,==其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。==由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。

三、CLass类和Object类&“先有鸡先有蛋?”

Object类和Class类没有直接的关系。
Object类是一切java类的父类,对于普通的java类,即便不声明,也是默认继承了Object类。典型的,可以使用Object类中的toString()方法。
Class类是用于java反射机制的,一切java类,都有一个对应的Class对象,他是一个final类。Class 类的实例表示,正在运行的 Java 应用程序中的类和接口。
知乎讨论答案

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值