当前位置: 首页 > 后端技术 > Java

Object,所有类的基类

时间:2023-04-01 14:04:03 Java

Java是典型的面向对象语言,它提供了extends关键字,使子类可以继承父类。publicclassStudentextendsPerson{...}但在创建Person类时,不需要使用extends来继承Object类。publicclassPersonextendsObject{...}因为,当创建的类没有明确指明继承关系时,编译时会自动继承Object类。您可以使用Object类型的变量来引用任何类型的实例:Objectsample=newStudent(1001,"LittleCoffee",20,"Male");您还可以将任何Object实例转换为所需的类型:Personp=(Person)sample;以上两个例子在编译器中是不会报错的。因此,Object类是Person类的父类,每个类都继承自Object。因此,熟悉Object类中提供的服务是非常重要的。Java中的基本类型不是对象,但是也提供了相应的包装类型,比如int基本类型对应Integer包装类型。但是使用原始类型创建的数组扩展了Object类并且实际上是引用。对象样本=newint[10];事实上,所有数组类型都扩展了Object类。下表列出了Object类的一般方法。方法说明ExceptionfinalnativeClassgetClass()返回对象的运行时类NonenativeinthashCode()返回对象的哈希码Nonebooleanequals(Objectobj)等于其他对象NonenativeObjectclone()克隆并返回对象的副本CloneNotSupportedExceptionStringtoString()返回对象的字符串表示形式Nonefinalnativevoidnotify()唤醒一个等待对象侦听器的线程NonefinalnativevoidnotifyAll()唤醒所有等待对象的线程listenerNonefinalnativevoidwait()导致当前线程等待,直到另一个线程对该对象调用notify()或notifyAll()。finalnativevoidwait(longtimeout)导致当前线程等待,直到另一个线程调用此对象上的notify()或notifyAll(),或者指定时间已过InterruptedExceptionfinalvoidwait(longtimeout,intnanos)导致当前线程等待另一个线程调用该对象的notify()或notifyAll(),或者指定的时间已经过去,当InterruptedExceptionvoidfinalize()时GC确定不再有对该对象的引用时,该方法被该对象的调用GCThrowableequals()Object类提供了equals()方法来测试对象是否相等。为了实现平等,必须满足五个条件:自反性。任何非空引用都需要x.equals(x)==true。对称。x.equals(y)==y.equals(x)供任何参考。传递性。(x.equals(y)&&y.equals(z))==x.equals(z)供任何参考。一致性。对于没有发生任何变化的引用,满足x.equals(y)==x.equals(y),多次调用equals()方法的结果不变。与null的比较。x.equals(null)返回false。对任何非null的对象调用equals()方法与null进行比较将返回false。引用类型最好使用equals()方法进行比较;使用==比较原始类型。其中,在子类中定义equals()方法时,需要先对比超类的equals()方法。如果检查失败,则对象不可能相等。如果超类中的字段都相等,则需要比较子类中的实例字段。下面从两种不同的情况来看这个问题:如果子类能够有自己的平等概念,对称性要求会强制getClass进行检查。如果相等的概念是由超类确定的,那么可以使用instanceof来检查它,它允许不同子类的对象之间进行相同的比较。在对象中使用equals()方法要实现的步骤如下:检查是否是同一个引用,如果是则直接返回true。检查传入的值是否为null,如果是,直接返回false。检查它们是否属于同一类型。如果不是,直接返回false。将Object对象类型转换为正在比较的类型。比较每个关键字段是否相等。publicbooleanequals(Objecto){if(this==o)returntrue;如果(o==null||getClass()!=o.getClass())返回false;Personperson=(Person)o;returnObjects.equals(name,person.name)&&Objects.equals(sex,person.sex)&&(age!=person.age);}如果子类重定义equals,则必须包含super.equals(other)。hasCode()hashCode()返回一个整数值,它是一个不规则的哈希码。hashCode()定义在Object类中,每个类都可以使用hashCode()方法调用自己的哈希码,其值为对象的存储地址。Objectsample=newStudent(1001,"小咖",20,"男");System.out.println(示例);//[i@5b464ce8如果在创建的类中重写equals()方法,则可以hashCode()方法必须被重写。这是hashCode()的一般约定。下面是重写hashCode()方法的约定:程序执行过程中,对象的equals()方法中比较的信息不变,同一对象的hashCode()方法的返回值不变.两个程序执行过程中,hashCode()方法返回的值可能不一致。如果两个对象根据equals(Object)方法比较相等,那么hashCode()方法的返回值也必须相等。如果根据equals(Object)方法,两个对象不相等,则hashCode()方法的返回值最好不相等。如果相等,在使用Map时会造成hashcode冲突。总结就是两个对象是相等的,它们的哈希码一定要相同;但是具有相同哈希码的两个对象不一定相等。由于计算哈希码的随机性,两个不同值的对象可能会计算出相同的哈希码。理想的散列函数会将集合中的不相等实例均匀分布在所有可能的整数上。但难度很大,只能比较接近这种理想状态。计算哈希码时会考虑每个字段。每个字段都可以看作是R系统的某个位,然后组成一个R系统整数。R一般取奇数31,取偶数会造成乘法溢出,信息丢失。因为乘以2相当于左移一位,所以最左边的位丢失了。而一个数乘以31可以转化为移位和减法:31*x==(x<<5)-x,编译器会自动进行这个优化。以下是如何轻松计算hashcode的参考:基本类型调用Type.hashCode(value)生成。类型类型是原始类型的相应包装类。如果是引用类型,需要重写equals()方法,equals()方法使用哪些字段比较,hashCode()方法也会递归调用并计算这些字段的哈希码。如果是数组类型,则数组中的每个值都被视为一个单独的字段。也可以使用Arrays.hashCode()方法计算。不要试图通过从哈希码计算中排除对象的关键字段来提高性能。下面重写Student类的hashCode()方法:@OverridepublicinthashCode(){intresult=super.hashCode();结果=31*结果+sid;returnresult;}hashCode()方法返回的哈希码也可以为负数,合理组合实例字段的哈希码,使不同对象生成的哈希码更加统一。toString()默认的toString()方法返回类型为java.lang.Object@511baa65的字符串。Studentsample=newStudent(1001,"小咖",20,"男");System.out.println(sample);//java.lang.Object@511baa65上面的System.out.println(sample)会自动调用sample.toString()方法将值输出到控制台。但是,提供良好的toString()实现可以获得有关对象状态的必要信息,并且也易于调试。因此建议为每个自定义类覆盖toString()方法。下面重写Student类的toString()方法:@OverridepublicStringtoString(){returngetClass().getName()+"{sid="+sid+",name="+super.getName()+",age="+super.getAge()+",sex="+super.getSex()+"}";}如果父类Person也重写了类的toString()方法:@OverridepublicStringtoString(){returngetClass().getName()+"{"+'\''+"name='"+name+'\''+",age="+age+",sex='"+sex+'\''+'}';}并且子类toString()也可以调用:@OverridepublicStringtoString(){returnsuper.toString()+"{sid="+sid+"}";}//xxx.Student{'name='小咖',age=20,sex='male'}{sid=1001}clone()Object类提供了克隆实例的clone()方法,但是因为是protected修饰符修饰的方法,所以不会显式覆盖克隆()。实现Cloneable接口的类可以覆盖clone()方法以提供克隆。如果未实现,将抛出CloneNotSupportedException。正确的写法如下:publicclassPersonimplementsCloneable{privateStringname;私人年龄;私人字符串性别;私人字符串[]地址;publicPerson(Stringname,intage,Stringsex){this.name=name;这个。年龄=年龄;this.sex=性别;}...省略getter和setter...@OverrideprotectedPersonclone()throwsCloneNotSupportedException{return(Person)super.clone();}}Java支持协变返回类型,即重写方法的返回类型可以是被重写方法返回类型的子类。并在clone()方法中调用super.clone()方法得到一个功能齐全的克隆对象。使用Person创建对象并调用clone()方法克隆它时。Persons1=newPerson("小卡",22,"男");s1.setAddress(newString[]{"浙江省","江苏省","湖南省"});Persons2=sample.clone();System.out.println(s1.hashCode());//873415566System.out.println(s2.hashCode());//818403870System.out.println(s1==s2);//falseSystem.out.println(s1.getAddress()==s2.getAddress());//true从上面的代码可以看出,调用clone()方法得到的对象是一个新的对象,但是对象中的引用还是原来的引用,而不是新的引用。这个克隆被称为浅拷贝。使用clone()方法其实和通过构造函数创建对象是一样的,确保不伤害原始对象,并确保克隆对象中的约束被正确创建。这种克隆称为深拷贝。因此,在Person类内部,地址数组也递归调用了clone()方法:result.address=address.clone();returnresult;}请记住,Cloneable与引用可变对象的final字段的正常用法不兼容。因此,实现clone()方法禁止给final赋新值。上述复制方法比较复杂。可以在类中提供拷贝构造函数或拷贝工厂来实现克隆的替换功能。publicPerson(Personvalue){...}publicstaticPersonnewInstance(Personvalue){...}finalize()当GC确定不再有对该对象的引用时,GC会调用finalize()方法对象的清除回收。protectedvoidfinalize()throwsThrowable{}因此子类可以通过覆盖此方法来处理一些额外的清理工作。但是,什么时候调用finalize()方法取决于JavaVM,并不能保证finalize()方法会及时执行。因此,不要依赖finalize()方法来更新重要的持久状态。JavaVM会保证一个对象的finalize()方法只被调用一次,程序中不能直接调用finalize()方法。finalize()方法通常也是不可预测和危险的,一般情况下,没有必要重写finalize()方法。wait和notifyObject对象提供了wait()和notify()方法,这两个方法的使用是相对的:wait():线程进入等待状态。notify():唤醒等待对象的线程。必须在同步区域内调用wait()方法,该同步区域将对象锁定在调用wait()方法的对象上。下面是使用wait()方法的标准模式:synchronized(obj){while(){obj.wait();}}此时obj对象所在的线程会处于等待状态,需要使用notify()方法唤醒obj所在的线程。synchronized(obj){obj.notify();}唤醒线程最好使用notifyAll()方法。因为总是会产生正确的结果,所以可以保证唤醒所有需要唤醒的线程。虽然它也会唤醒其他线程,但不影响程序的正确性,notifyAll()方法取代了notify()方法,避免不相关线程的意外或恶意等待。注意:必须使用synchronized,否则会报IllegalMonitorStateException。综上所述,这里对Object类做一个简单的了解,知道Object类是所有类的基类,可以引用所有的引用类型,包括数组类型。而Object中提供的方法需要在合适的场景下覆盖才能得到最好的效果。最好始终覆盖toString()方法。更多内容请关注公众号《海人的故事》