object类的基本方法,object类的常用6个方法

  object类的基本方法,object类的常用6个方法

  00-1010 I、getClass () II、finalize () III、toString () IV、equals()和hashcode () V、wait()、notify()和notifyAll () VI、clone()前言:

  Java对象类的完整路径是java.lang.Object,是所有类的父类编译。当我们创建一个类时,如果我们没有显式继承一个父类,那么它会自动继承Object,成为Object的子类(隐式继承)。Object有九个常用的方法,分别是getClass()、finalize()、toString()、equals()、hashcode()、wait()、notify()、notifyAll()和clone()。

  00-1010首先用getClass()方法获取一个对象的运行时类,然后通过返回的类对象获取Person的相关信息,比如类的构造方法,类的方法,类的成员变量。不同的虚拟机针对类进行了不同的优化,因此getClass()的实现是不同的:

  //Java用native方法实现getclass () public final native类?getClass();//Android特殊实现私有瞬态类?暗影$ _ klass _公共课?getClass(){ return shadow $ _ klass _;}这是因为它是Java默认的Hotspot虚拟机,不开辟单独的方法区空间,而是用GC Heap的老一代Metaspace实现的。安卓用的是ART VM,造成了这种差异。你需要看很多书才能理解不同VM实现的运行时数据分区、类加载器和类,所以本文不会过多讨论。

  00-1010finalize()是对象的受保护方法,在GC发生时触发。一般的流程是,当对象变成GC根不可达时,GC判断对象是否覆盖finalize()方法,如果不覆盖,则直接回收。否则,如果对象没有执行finalize()方法,它将被放入F-Queue队列中,低优先级线程将执行队列中对象的finalize()方法。finalize()方法执行后,GC会再次判断对象是否可达,如果不可达,则回收。否则,对象“复活”。

  您可以子类化override方法来实现(1)防止对象被回收,以及(2)防止对象被回收。为了防止对象被回收,只需要在对象和GC根之间有一个可到达的链。让我们来关注一下FileInputStream、FileOutputStream、Connection等类是如何防止用户忘记释放资源的。以下是FileInputStream的一些源代码:

  受保护的Void Finalize()抛出IO异常{//Android增加了CloseGuard,保证了如果(Guard!=null){ guard . warnif open();}//Java使用FileDescriptor确保FileInputStream不可达,并且可以安全回收,如果((fd!=null) (fd!=file descriptor . in)){ close();}}

  

目录

toString()方法返回对象的字符串表示,这也是每个初级程序员都熟悉的方法。仔细看了Java源代码,你知道很多种类的toString()方法都是精雕细琢的,就像Integer的toString()方法,适配Android:

 

  公共字符串toString(){ return toString(this . value);}//返回指定十进制整数的字符串公共静态字符串tostring(int I){ if(I==integer . min _ value)return -2147483648 ;//small是Android特有的变量,较小(两位数)数的字符串boolean negative=i 0用二维数组缓存;小型布尔型

  = negative ? i > -100 : i < 100; if (small) { final String[] smallValues = negative ? SMALL_NEG_VALUES : SMALL_NONNEG_VALUES; if (negative) { i = -i; if (smallValues[i] == null) { smallValues[i] = i < 10 ? new String(new char[]{-, DigitOnes[i]}) : new String(new char[]{-, DigitTens[i], DigitOnes[i]}); } } else { if (smallValues[i] == null) { smallValues[i] = i < 10 ? new String(new char[]{DigitOnes[i]}) : new String(new char[]{DigitTens[i], DigitOnes[i]}); } } return smallValues[i]; } int size = negative ? stringSize(-i) + 1 : stringSize(i); // getChars()方法略 char[] buf = new char[size]; getChars(i, size, buf); return new String(buf);}在实际开发中,复杂对象的toString()方法用Gson生成JSON来实现。

  

 

  

四、equals()和hashcode()

equals()方法和hashcode()方法,我要放在一起说。

 

  

// 其实Object方法默认的equals()也是比较引用是否相同public boolean equals(Object obj) { return (this == obj);}

一般来说==比较的是引用是否相同,而equals()则是需要重写来比较值是否相同。重写equals()要注意以下几点注意事项:

 

  (1)对任意x,x.equals(x)一定返回true

  (2)对任意x,y,如果x.equals(y)返回true,则y.equals(x)也一定返回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

  (5)对任意不是null的x,x.equals(null)一定返回false,如果两个对象equals()方法相等则它们的hashCode返回值一定要相同。我们先看一下String是如何实现equals()的:

  

//比较这个String和另一个对象,当且仅当那个对象不为null且与这个String有相同的字符排列顺序时返回truepublic boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = length(); if (n == anotherString.length()) { int i = 0; while (n-- != 0) { if (charAt(i) != anotherString.charAt(i)) return false; i++; } return true; } } return false;}

我们再看一下String是如何实现hashcode()的:

 

  

// 缓存String的hashcode()private int hash; // 默认为0public int hashCode() { int h = hash; final int len = length(); if (h == 0 && len > 0) { for (int i = 0; i < len; i++) { h = 31 * h + charAt(i); } hash = h; } return h;}

反之,如果两个对象的hashCode返回值相同,它们的equals()方法可以不返回true。这种情况叫做hash碰撞。HashMap处理hash碰撞的方法叫链地址法,除此以外hash碰撞还可以用ArrayMap采用的开放地址法解决,这些不在今天的话题讨论范围之内,不做赘述。

 

  

 

  

五、wait()、notify()和notifyAll()

wait()、notify()和notifyAll()三个方法实现了Java的wait-notify机制。

 

  先看wait()方法,wait()方法用来让持有此对象的监视器的线程处于阻塞状态,有参数不同的三个同名方法:

  

// 无参方法的Java与Android实现方式没有区别// 如果不在synchronized修饰的方法或代码块里调用,如果没有获取锁,则会抛出IllegalMonitorStateException 异常// 如果当前线程在等待时被中断,则抛出InterruptedException异常public final void wait() throws InterruptedException { wait(0);}// timeout是线程等待时间,时间结束则自动唤醒,单位ms// Java默认的实现方式,native实现public final native void wait(long timeout) throws InterruptedException;// Android的特殊处理public final void wait(long timeout) throws InterruptedException { wait(timeout, 0);}// nanos是更精确的线程等待时间,单位ns(1 ms == 1,000,000 ns)// Java默认的实现方式public final void wait(long timeout, int nanos) throws InterruptedException { 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);}// Android的特殊处理,改为native实现@FastNativepublic final native void wait(long timeout, int nanos) throws InterruptedException;

ns是纳秒的意思,1s == 1,000,000,000ns,光速是世界上最快的速度,光在1ns时间内仅能传播0.3m。一般PC的CPU计算一道简单指令,比如2+3=5的时间为2~4ns。我们一般只用一个参数的wait()方法或者无参方法就足够了,第二个参数在现实开发中几乎用不到。

 

  含参的wait()方法调用后,线程可以在等待时间结束后进入就绪状态(以下简称唤醒);无参的wait()方法调用后,则必须等待持有该对象监视器的线程主动调用notify()或notifyAll()方法后才能被唤醒。区别在于notify()方法唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会随机唤醒其中一个线程;而notifyAll()方法则唤醒在此对象监视器上等待的所有线程。

  

public final native void notify();public final native void notifyAll();

wait()、notify()和notifyAll()都是final native方法,我们暂时不需要深入理解内部是怎样实现的,我们只要知道这就是Java的等待-通知(wait-notify)机制,学习它们的应用场景就好了。

 

  打个比方:

  (1)用人单位决定录用程序员的时间是不确定的,比如可能要面试好多人,需要综合考虑,不能及时反馈(2)每个程序员面试结束后需要wait()(3)用人单位综合考虑之后觉得最合适的程序员,让HR notify()(4)最后被选中程序员高高兴兴去上班了,其他程序员就等着吧在开发中,wait-notify机制的最广泛用途就是实现生产者/消费者模型,生产者/消费者模型能解决绝大多数并发问题,通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

  使用wait-notify机制的注意事项:

  (1)wait()、notify()和notifyAll()必须在synchronized修饰的方法或代码块中使用(2)在while循环里而不是if语句下使用wait(),确保在线程睡眠前后都检查wait()触发的条件(防止虚假唤醒)(3)wait()方法必须在多线程共享的对象上调用我们先定义注意(1)和(2)的一个生产者,往队列里添加元素:

  

// 生产者,有详细的注释public class Producer implements Runnable{ private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize){ this.queue = queue; this.maxSize = maxSize; } @Override public void run() { // 这里为了方便演示做了一个死循环,现实开发中不要这样搞 while (true){ //(1)wait()、notify()和notifyAll()必须在synchronized修饰的方法或代码块中使用 synchronized (queue){//(2)在while循环里而不是if语句下使用wait(),确保在线程睡眠前后都检查wait()触发的条件(防止虚假唤醒) while (queue.size() == maxSize){ try{ System.out.println("Queue is Full");// 生产者线程进入等待状态,在此对象监视器上等待的所有线程(其实只有那个消费者线程)开始争夺锁 queue.wait(); }catch (InterruptedException ie){ ie.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Produce " + i); queue.add(i);// 唤醒这个Queue对象的等待池中的所有线程(其实只有那个消费者线程),等待获取对象监视器 queue.notifyAll(); } } }}

再定义一个一模一样的消费者,除了从队列里移除元素之外,其他代码同上

 

  

// 消费者,注释略public class Consumer implements Runnable{ private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize){ this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true){ synchronized (queue){ while (queue.isEmpty()){ System.out.println("Queue is Empty"); try{ queue.wait(); }catch (InterruptedException ie){ ie.printStackTrace(); } } int v = queue.remove(); System.out.println("Consume " + v); queue.notifyAll(); } } }}

最后编写符合(3)的测试代码:

 

  

public void test(){ //(3)wait()方法必须在多线程共享的对象上调用 // 这个队列就是给消费者、生产者两个线程共享的对象 Queue<Integer> queue = new LinkedList<>(); int maxSize = 5; Producer p = new Producer(queue, maxSize); Consumer c = new Consumer(queue, maxSize); Thread pT = new Thread(p); Thread pC = new Thread(c);// 生产者线程启动,获取锁 pT.start();// 消费者线程启动 pC.start();}

查看运行结果:

 

  

Produce 1604006010

 

  Produce 1312202442

  Produce -1478853208

  Produce 1460408111

  Produce 1802825495

  Queue is Full

  Consume 1604006010

  Consume 1312202442

  Consume -1478853208

  Consume 1460408111

  Consume 1802825495

  Queue is Empty

  

除了以上介绍的用synchronized关键字配合Object的wait()/notity()实现,生产者-消费者模型还可以用Lock接口配合Condition的await()、signalAll()实现,此外还可以用BlockingQueue实现,但这些都不在本文的话题讨论范围之内,就不再赘述了。

 

  

 

  

六、clone()

Java语言的Object类实现了Cloneable接口,一个对象可以通过调用Clone()方法生成对象。需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口却调用了clone()方法就会报错。

 

  

// protected native Object clone() throws CloneNotSupportedException;protected Object clone() throws CloneNotSupportedException { if (!(this instanceof Cloneable)) { throw new CloneNotSupportedException("Class " + getClass().getName() + " doesnt implement Cloneable"); } return internalClone();}// Native helper method for cloning.private native Object internalClone();

对象除了new出来和clone()出来,还可以通过反射和反序列化两种方式产生,但这两种方式不在我们今天的话题讨论范围之内。

 

  所谓原型模式,就是利用clone()生成对象的设计模式。需要提前了解一下深拷贝和浅拷贝的概念。Java中的数据类型分为基本类型和引用类型,在一个方法里的变量如果是基本类型的话,变量就直接存储在这个方法的栈帧里,例如int、long等;而引用类型则在栈帧里存储这个变量的指针,指向堆中该实体的地址,例如String、Array等。深拷贝和浅拷贝是只针对引用数据类型的

  比如一个方法有一个基本类型参数和一个引用类型参数,在方法体里对参数重新赋值,会影响传入的引用类型参数,而不会影响基本类型参数,因为基本类型参数是值传递,而引用类型是引用传递。需要注意的是,较真来说Java只有值传递,因为Java的引用传递传的是引用类型对象在堆内存空间的地址,引用传递只是一种习惯性的说法,这个涉及到JVM和操作系统,不做过多讨论。

  先定义一个用户类:

  

// 这是一个非常简单的用户类public class User { private String name; private int age; public User(String name, int age) { this.name=name; this.age=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 "User{name=" + name + ", age=" + age +}; }}

编写测试代码:

 

  

private int x=10;public void updateValue(int value){ value = 3 * value;}private User user= new User("唐茜靖",18);public void updateUser(User student){ student.setName("管晨辰"); student.setAge(16);}public void test(){ System.out.println("调用前x的值:"+x); updateValue(x); System.out.println("调用后x的值:"+x); System.out.println("调用前user的值:"+user.toString()); updateUser(user); System.out.println("调用后user的值:"+user.toString());}

Log打印结果如下:

 

  调用前x的值:10

  调用后x的值:10

  调用前user的值:User{name='唐茜靖, age=18}

  调用后user的值:User{name='管晨辰, age=16}

  传递基本类型的方法(updateValue())流程图:

  

 

  传递引用类型的方法(updateUser())流程图:

  

 

  但也有例外,比如String类型和<=127的Long类型虽然也是引用类型,却像基本类型一样不受影响,这是因为它们会先比较常量池维护的值,这涉及VM的内容,今天不做过多讨论。浅拷贝是在按位(bit)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。我们结合应用场景分析一下,还是刚才的User类,我们增加一个存放地址的内部类Address,我们需要用户信息可以被其他module查询,但是不允许它们被其他module修改,新增代码如下:

  

// 这是一个稍微复杂的、支持拷贝的用户类public class User implements Cloneable {// ……省略上文代码…… private Address address; @NonNull @NotNull @Override public User clone() { try{ return (User)super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address{ // 地市 public String city; // 区县 public String county; // 乡镇街道 public String street; } }

我们可以注意到Address还是指向以前的引用,浅拷贝会带来数据安全方面的隐患,这就到了需要深拷贝的时候了。对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,才可以实现了对象的串行层层拷贝。就像这样:

 

  

// 这是一个更复杂的、支持深拷贝的用户类public class User implements Cloneable { // ……省略上文代码…… @NonNull @NotNull @Override public User clone() { try{ User newUser = (User)super.clone(); newUser.setName(this.name); newUser.setAddress(this.address.clone()); return newUser; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public class Address implements Cloneable{ // ……省略上文代码…… @NonNull @NotNull @Override public Address clone() { try{ Address newAddress = (Address)super.clone(); newAddress.city = this.city; newAddress.county = this.county; newAddress.street = this.street; return newAddress; }catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }}

至于彻底深拷贝几乎是不可能实现的,不但可能存在引用关系非常复杂的情况,也可能存在引用链的某一级上引用了一个没有实现Cloneable接口的第三方对象的情况。

 

  最后总结一下,原型模式的用途之一是保护性拷贝,防止外部对只读对象进行修改,刚才我举的例子就是保护性拷贝。另一个重要用途则是解决构建复杂对象的资源消耗问题,提升创建对象的效率,这是因为clone()方法的原理是在内存中拷贝二进制流,比new一个对象的性能好很多,非常适用于需要在循环体内产生大量对象的时候。绝大多数设计模式都是牺牲性能提升开发效率的,原型模式是为数不多的牺牲开发效率提升性能的。

  我们做一下new和clone的对比:

  

private User user= new User("唐茜靖",18);public void testNew(){ User user1 = new User("管晨辰",16);}public void testClone(){ User user2 = user.clone();}

通过ASM工具查看bytecode,可以对比出二者对栈资源的消耗:

 

  

// access flags 0x1  public testNew()V   ……省略……    MAXSTACK = 4    MAXLOCALS = 2  // access flags 0x1  public testClone()V   ……省略……    MAXSTACK = 1    MAXLOCALS = 2

此外还需要注意一点,拷贝不会执行构造函数,所以有时候我们需要注意这个潜在的问题。幸好这个问题不是不可避免的,这是Android第五大组件Intent的clone()的实现,没有用拷贝:

 

  

@Overridepublic Object clone() { return new Intent(this);}

到此这篇关于Java的Object类九个方法技巧的文章就介绍到这了,更多相关Java的Object类方法内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: