JAVA 基础重构 七 (Class 和 Object 类)

    技术2023-07-15  89

    Java中Class类及用法

        Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。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类的作用是运行时提供或获得某个对象的类型信息,这些信息也可用于反射。

    看一下Class类的部分源码

    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类的forName函数  :public class persons{} Class obj= Class.forName(“persons”); 

    第二种办法,使用对象的getClass()函数

    public class persons{} persons s1=new persons(); 

    Class obj=s1.getClass();

     Class obj1=s1.getSuperclass();//这个函数作用是获取persons 类的父类的类型

    第三种办法,使用类字面常量

    Class obj=String.class; Class obj1=int.class; 注意,使用这种办法生成Class类对象时,不会使JVM自动加载该类(如String类)。==而其他办法会使得JVM初始化该类。==

    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方法 该方法唤醒在该对象上等待的所有线程。

    类构造器public Object(); 大部分情况下,Java中通过形如 new A(args..)形式创建一个属于该类型的对象。其中A即是类名,A(args..)即此类定义中相对应的构造函数。通过此种形式创建的对象都是通过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一个无参数的构造函数,作为所有类的基类,Object类自然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。当然,并不是所有的类都是通过此种方式去构建,也自然的,并不是所有的类构造函数都是public。

    clone()方法

    clone方法实现的是浅拷贝,只拷贝当前对象,并且在堆中分配新的空间,放入这个复制的对象。但是对象如果里面有其他类的子对象,那么就不会拷贝到新的对象中。

    深拷贝和浅拷贝的区别

    浅拷贝 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

    深拷贝 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。 现在为了要在clone对象时进行深拷贝, 那么就要实现Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。如果只是用Object中默认的clone方法,是浅拷贝的。

    那么这两种方式有什么相同和不同呢?

    new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。

    分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

    而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,

    填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

    ==也就是说,一个对象在浅拷贝以后,只是把对象复制了一份放在堆空间的另一个地方,但是成员变量如果有引用指向其他对象,这个引用指向的对象和被拷贝的对象中引用指向的对象是一样的。当然,基本数据类型还是会重新拷贝一份的。==

    equals()方法 public boolean equals(Object obj); 与equals在Java中经常被使用,大家也都知道与equals的区别: ==表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址); equals表示的是对象的内容完全相同,此处的内容多指对象的特征/属性。 实际上,上面说法是不严谨的,更多的只是常见于String类中。首先看一下Object类中关于equals()方法的定义: public boolean equals(Object obj) {        return (this == obj);   }  由此可见,Object原生的equals()方法内部调用的正是==,与==具有相同的含义。既然如此,为什么还要定义此equals()方法? equals()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么? 如上,在object类中,此标尺即为==。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能型和实际编码的灵活性。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。 如下场景的实际业务需求,对于User bean,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。则可以重写equals以重定义User对象相等的标尺。 ObjectTest中打印出true,因为User类定义中重写了equals()方法,这很好理解,很可能张三是一个人小名,张三丰才是其大名,判断这两个人是不是同一个人,这时只用判断uid是否相同即可。 如上重写equals方法表面上看上去是可以了,实则不然。因为它破坏了Java中的约定:重写equals()方法必须重写hasCode()方法。

    hashCode()方法;

    public native int hashCode() hashCode()方法返回一个整形数值,表示该对象的哈希码值

    1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致; 2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等; 3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。 即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。 可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦? 其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。 以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。

    toString()方法

    7.public String toString();

    toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:

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

    wait() notify() notifAll()

    一说到wait(…) / notify() | notifyAll()几个方法,首先想到的是线程。确实,这几个方法主要用于java多线程之间的协作。先具体看下这几个方法的主要含义: wait():调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。 wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。 notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。

    Class类和Object类的关系

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

    微信公众号:

    Processed: 0.011, SQL: 9