>  기사  >  Java  >  Java의 객체 메소드는 무엇입니까?

Java의 객체 메소드는 무엇입니까?

coldplay.xixi
coldplay.xixi원래의
2020-10-28 14:30:5313329검색

Java의 객체 메소드는 다음과 같습니다. 1. [getClass()]는 공개 메소드입니다. 2. [hashCode()]는 공개 메소드이며 객체를 통해 직접 호출할 수 있습니다. 3. [equals()]가 사용됩니다. 비교를 위해 현재 객체와 대상 객체가 동일한지 여부입니다.

Java의 객체 메소드는 무엇입니까?

관련 무료 학습 권장사항: java 기본 튜토리얼

Java의 객체 메소드는 다음과 같습니다.

1. 소개

Object는 모든 Java 클래스의 기본 클래스이며 다음과 같습니다. 전체 클래스 상속 구조의 최상위 클래스는 가장 추상적인 클래스이기도 합니다. 모든 사람은 toString(), equals(), hashCode(), wait(), inform(), getClass() 및 기타 메소드를 매일 사용합니다. 아마도 그들은 Object의 메소드라는 것을 깨닫지 못하거나 무엇을 확인하지 않을 수도 있습니다. Object가 가지고 있는 다른 메소드들 그리고 왜 이러한 메소드들이 Object에 배치되어야 하는지 생각해 보세요. 이 기사에서는 각 방법의 특정 기능, 재작성 규칙 및 내가 이해한 일부 내용을 설명합니다.

2 객체 메소드에 대한 자세한 설명

객체에는 다음이 포함됩니다. , 총 12개의 메소드가 있습니다: wait(long,int), wait() 및 finalize(). 이 순서는 Object 클래스에 정의된 메소드 순서대로 나열됩니다. 아래에서도 이 순서대로 설명하겠습니다.

1.1,registerNatives()

public class Object {
    private static native void registerNatives();
    static {
        registerNatives();
    }
}

대체 뭐죠? 하하하, 방금 이 방법을 보고 헷갈렸습니다. 이름에서 우리는 이 메소드가 네이티브 메소드(JVM에 의해 구현되고 맨 아래 레이어가 C/C++로 구현되는 네이티브 메소드)를 등록하는 것임을 이해합니다. 물론, 프로그램이 기본 메소드를 호출하면 JVM이 이러한 기본 메소드를 찾아서 호출할 수 있습니다.

Object의 기본 메소드와 RegisterNatives()를 사용하여 JVM에 등록합니다. (이건 JNI 카테고리에 속합니다. 아직은 잘 모르겠습니다. 관심 있으신 분들은 직접 확인해보시면 좋을 것 같습니다.)

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

정적 메소드를 사용해서 정적 블록에 넣어야 하는 이유는 무엇일까요?

클래스가 초기화되면 상위 클래스에서 이 클래스까지의 클래스 변수와 클래스 초기화 블록의 클래스 변수 및 메소드가 정의 순서대로 < 상위 클래스 변수 및 메소드의 클래스 변수가 하위 클래스보다 먼저 초기화되어야 하는지 확인합니다. 따라서 서브클래스가 hashCode를 계산하는 등 해당 네이티브 메소드를 호출하면 JVM의 네이티브 메소드를 호출할 수 있음이 보장됩니다.

1.2, getClass()

public final Native Class getClass(): 객체를 통해 직접 호출할 수 있는 public 메소드입니다.

클래스 로딩의 첫 번째 단계는 .class 파일을 메모리에 로딩하고 java.lang.Class 객체를 생성하는 과정입니다. getClass() 메소드는 현재 클래스 객체의 런타임 클래스에 대한 모든 정보의 모음인 이 객체를 얻기 위한 것입니다. 이 방법은 세 가지 반영 방법 중 하나입니다.

1.2.1. 세 가지 반사 방법:

class name.class;

Class.forName();
class extends ObjectTest {
    private void privateTest(String str) {
        System.out.println(str);
    }
    public void say(String str) {
        System.out.println(str);
    }
}
public class ObjectTest {
    public static void main(String[] args) throws Exception {
        ObjectTest  = new ();
        //获取对象运行的Class对象
        Class<? extends ObjectTest> aClass = .getClass();
        System.out.println(aClass);
        //getDeclaredMethod这个方法可以获取所有的方法,包括私有方法
        Method privateTest = aClass.getDeclaredMethod("privateTest", String.class);
        //取消java访问修饰符限制。
        privateTest.setAccessible(true);
        privateTest.invoke(aClass.newInstance(), "private method test");
        //getMethod只能获取public方法
        Method say = aClass.getMethod("say", String.class);
        say.invoke(aClass.newInstance(), "Hello World");
    }
}
//输出结果:
//class test.
//private method test
//Hello World

Reflection은 주로 Java와 같은 정적 언어를 얻는 데 사용됩니다. 코드에서 상위 클래스의 참조에 하위 객체를 할당하면 런타임 객체의 모든 정보는 다형성의 표현인 런타임 시 리플렉션을 통해 얻을 수 있습니다. 성찰에 대해서는 아직 지식이 많기 때문에 여기서는 자세히 다루지 않겠습니다.

1.3, hashCode()

public Native int hashCode(); 이는 공개 메서드이므로 하위 클래스에서 재정의할 수 있습니다. 이 메소드는 현재 객체의 hashCode 값을 반환하는데, 이는 정수 범위(-2^31 ~ 2^31 - 1)의 숫자입니다.

hashCode에는 다음과 같은 제약이 있습니다

Java 애플리케이션 실행 중에 동일한 객체에 대해 hashCode 메서드가 여러 번 호출되면 객체를 비교하는 데 사용되는 정보를 전제로 동일한 정수가 일관되게 반환되어야 합니다.

두 개체의 x.equals(y) 메서드가 true를 반환하는 경우 두 개체 x와 y의 hashCode가 동일해야 합니다.

두 개체의 x.equals(y) 메서드가 false를 반환하는 경우 두 개체 x와 y의 hashCode는 같거나 다를 수 있습니다. 그러나 동일하지 않은 객체에 대해 서로 다른 정수 결과를 생성하면 해시 테이블 성능이 향상될 수 있습니다.

기본 hashCode는 메모리 주소에서 변환된 해시 값입니다. 다시 작성한 후 System.identityHashCode(Object)를 통해 원래 hashCode를 반환할 수도 있습니다.

public class HashCodeTest {
    private int age;
    private String name;
    @Override
    public int hashCode() {
        Object[] a = Stream.of(age, name).toArray();
        int result = 1;
        for (Object element : a) {
            result = 31 * result + (element == null ? 0 : element.hashCode());
        }
        return result;
    }
}

Objects.hash(Object…values) 방식을 사용하는 것을 권장합니다. 소스 코드를 보면 hashCode를 계산하는 기본 승수로 31이 사용되는 것을 볼 수 있을 거라 믿습니다. 동의하며 결과 * 31 = (결과

1.4, equals()

public boolean equals(Object obj); 현재 객체와 대상 객체가 동일한지 비교하는 데 사용됩니다. 기본값은 참조가 동일한 객체를 가리키는지 비교하는 것입니다. 이는 공개 메서드이며 하위 클래스에서 재정의될 수 있습니다.

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

Equals 메소드를 재정의해야 하는 이유는 무엇인가요?

因为如果不重写equals方法,当将自定义对象放到map或者set中时;如果这时两个对象的hashCode相同,就会调用equals方法进行比较,这个时候会调用Object中默认的equals方法,而默认的equals方法只是比较了两个对象的引用是否指向了同一个对象,显然大多数时候都不会指向,这样就会将重复对象存入map或者set中。这就破坏了map与set不能存储重复对象的特性,会造成内存溢出。

重写equals方法的几条约定:

自反性:即x.equals(x)返回true,x不为null;

对称性:即x.equals(y)与y.equals(x)的结果相同,x与y不为null;

传递性:即x.equals(y)结果为true, y.equals(z)结果为true,则x.equals(z)结果也必须为true;

一致性:即x.equals(y)返回true或false,在未更改equals方法使用的参数条件下,多次调用返回的结果也必须一致。x与y不为null。

如果x不为null, x.equals(null)返回false。

我们根据上述规则来重写equals方法。

public class EqualsTest{
    private int age;
    private String name;
    //省略get、set、构造函数等
     @Override
    public boolean equals(Object o) {
        //先判断是否为同一对象
        if (this == o) {
            return true;
        }
        //再判断目标对象是否是当前类及子类的实例对象
        //注意:instanceof包括了判断为null的情况,如果o为null,则返回false
        if (!(o instanceof )) {
            return false;
        }
         that = () o;
        return age == that.age &&
                Objects.equals(name, that.name);
    }
     public static void main(String[] args) throws Exception {
         EqualsTest1 equalsTest1 = new EqualsTest1(23, "9龙");
        EqualsTest1 equalsTest12 = new EqualsTest1(23, "9龙");
        EqualsTest1 equalsTest13 = new EqualsTest1(23, "9龙");
        System.out.println("-----------自反性----------");
        System.out.println(equalsTest1.equals(equalsTest1));
        System.out.println("-----------对称性----------");
        System.out.println(equalsTest12.equals(equalsTest1));
        System.out.println(equalsTest1.equals(equalsTest12));
        System.out.println("-----------传递性----------");
        System.out.println(equalsTest1.equals(equalsTest12));
        System.out.println(equalsTest12.equals(equalsTest13));
        System.out.println(equalsTest1.equals(equalsTest13));
        System.out.println("-----------一致性----------");
        System.out.println(equalsTest1.equals(equalsTest12));
        System.out.println(equalsTest1.equals(equalsTest12));
        System.out.println("-----目标对象为null情况----");
        System.out.println(equalsTest1.equals(null));
    }
}
//输出结果
//-----------自反性----------
//true
//-----------对称性----------
//true
//true
//-----------传递性----------
//true
//true
//true
//-----------一致性----------
//true
//true
//-----目标对象为null情况----
//false

从以上输出结果验证了我们的重写规定是正确的。

注意:instanceof 关键字已经帮我们做了目标对象为null返回false,我们就不用再去显示判断了。

建议equals及hashCode两个方法,需要重写时,两个都要重写,一般都是将自定义对象放至Set中,或者Map中的key时,需要重写这两个方法。

1.4、clone()

protected native Object clone() throws CloneNotSupportedException;

此方法返回当前对象的一个副本。

这是一个protected方法,提供给子类重写。但需要实现Cloneable接口,这是一个标记接口,如果没有实现,当调用object.clone()方法,会抛出CloneNotSupportedException。

public class CloneTest implements Cloneable {
    private int age;
    private String name;
    //省略get、set、构造函数等
    @Override
    protected CloneTest clone() throws CloneNotSupportedException {
        return (CloneTest) super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneTest cloneTest = new CloneTest(23, "9龙");
        CloneTest clone = cloneTest.clone();
        System.out.println(clone == cloneTest);
        System.out.println(cloneTest.getAge()==clone.getAge());
        System.out.println(cloneTest.getName()==clone.getName());
    }
}
//输出结果
//false
//true
//true

从输出我们看见,clone的对象是一个新的对象;但原对象与clone对象的String类型的name却是同一个引用,这表明,super.clone方法对成员变量如果是引用类型,进行是浅拷贝。

那什么是浅拷贝?对应的深拷贝?

浅拷贝:拷贝的是引用。

深拷贝:新开辟内存空间,进行值拷贝。

那如果我们要进行深拷贝怎么办呢?看下面的例子。

class Person implements Cloneable{
    private int age;
    private String name;
     //省略get、set、构造函数等
     @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        //name通过new开辟内存空间
        person.name = new String(name);
        return person;
   }
}
public class CloneTest implements Cloneable {
    private int age;
    private String name;
    //增加了person成员变量
    private Person person;
    //省略get、set、构造函数等
    @Override
    protected CloneTest clone() throws CloneNotSupportedException {
        CloneTest clone = (CloneTest) super.clone();
        clone.person = person.clone();
        return clone;
    }
    public static void main(String[] args) throws CloneNotSupportedException {
       CloneTest cloneTest = new CloneTest(23, "9龙");
        Person person = new Person(22, "路飞");
        cloneTest.setPerson(person);
        CloneTest clone = cloneTest.clone();
        System.out.println(clone == cloneTest);
        System.out.println(cloneTest.getAge() == clone.getAge());
        System.out.println(cloneTest.getName() == clone.getName());
        Person clonePerson = clone.getPerson();
        System.out.println(person == clonePerson);
        System.out.println(person.getName() == clonePerson.getName());
    }
}
//输出结果
//false
//true
//true
//false
//false

可以看到,即使成员变量是引用类型,我们也实现了深拷贝。如果成员变量是引用类型,想实现深拷贝,则成员变量也要实现Cloneable接口,重写clone方法。

1.5、toString()

public String toString();这是一个public方法,子类可重写,建议所有子类都重写toString方法,默认的toString方法,只是将当前类的全限定性类名+@+十六进制的hashCode值。

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

我们思考一下为什么需要toString方法?

我这么理解的,返回当前对象的字符串表示,可以将其打印方便查看对象的信息,方便记录日志信息提供调试。

我们可以选择需要表示的重要信息重写到toString方法中。为什么Object的toString方法只记录类名跟内存地址呢?因为Object没有其他信息了,哈哈哈。

1.6、wait()/ wait(long)/ waite(long,int)

这三个方法是用来线程间通信用的,作用是阻塞当前线程,等待其他线程调用notify()/notifyAll()方法将其唤醒。这些方法都是public final的,不可被重写。

注意:

此方法只能在当前线程获取到对象的锁监视器之后才能调用,否则会抛出IllegalMonitorStateException异常。

调用wait方法,线程会将锁监视器进行释放;而Thread.sleep,Thread.yield()并不会释放锁。

wait方法会一直阻塞,直到其他线程调用当前对象的notify()/notifyAll()方法将其唤醒;而wait(long)是等待给定超时时间内(单位毫秒),如果还没有调用notify()/nofiyAll()会自动唤醒;waite(long,int)如果第二个参数大于0并且小于999999,则第一个参数+1作为超时时间;

 public final void wait() throws InterruptedException {
        wait(0);
    } 
public final native void wait(long timeout) throws InterruptedException;
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);
    }

1.7、notify()/notifyAll()

前面说了,如果当前线程获得了当前对象锁,调用wait方法,将锁释放并阻塞;这时另一个线程获取到了此对象锁,并调用此对象的notify()/notifyAll()方法将之前的线程唤醒。这些方法都是public final的,不可被重写。

public final native void notify(); 随机唤醒之前在当前对象上调用wait方法的一个线程

public final native void notifyAll(); 唤醒所有之前在当前对象上调用wait方法的线程

下面我们使用wait()、notify()展示线程间通信。假设9龙有一个账户,只要9龙一发工资,就被女朋友给取走了。

//账户
public class Account {
    private String accountNo;
    private double balance;
    private boolean flag = false;
    public Account() {
    }
    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }
    /**
     * 取钱方法
     *
     * @param drawAmount 取款金额
     */
    public synchronized void draw(double drawAmount) {
        try {
            if (!flag) {
                //如果flag为false,表明账户还没有存入钱,取钱方法阻塞
                wait();
            } else {
                //执行取钱操作
                System.out.println(Thread.currentThread().getName() + " 取钱" + drawAmount);
                balance -= drawAmount;
                //标识账户已没钱
                flag = false;
                //唤醒其他线程
                notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void deposit(double depositAmount) {
        try {
            if (flag) {
                //如果flag为true,表明账户已经存入钱,取钱方法阻塞
                wait();
            } else {
                //存钱操作
                System.out.println(Thread.currentThread().getName() + " 存钱" + depositAmount);
                balance += depositAmount;
                //标识账户已存入钱
                flag = true;
                //唤醒其他线程
                notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//取钱者
public class DrawThread extends Thread {
    private Account account;
    private double drawAmount;
    public DrawThread(String name, Account account, double drawAmount) {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    @Override
    public void run() {
        //循环6次取钱
        for (int i = 0; i < 6; i++) {
            account.draw(drawAmount);
        }
    }
}
//存钱者
public class DepositThread extends Thread {
    private Account account;
    private double depositAmount;
    public DepositThread(String name, Account account, double depositAmount) {
        super(name);
        this.account = account;
        this.depositAmount = depositAmount;
    }
    @Override
    public void run() {
        //循环6次存钱操作
        for (int i = 0; i < 6; i++) {
            account.deposit(depositAmount);
        }
    }
}
//测试
public class DrawTest {
    public static void main(String[] args) {
        Account brady = new Account("9龙", 0);
        new DrawThread("女票", brady, 10).start();
        new DepositThread("公司", brady, 10).start();
    }
}
//输出结果
//公司 存钱10.0
//女票 取钱10.0
//公司 存钱10.0
//女票 取钱10.0
//公司 存钱10.0
//女票 取钱10.0

例子中我们通过一个boolean变量来判断账户是否有钱,当取钱线程来判断如果账户没钱,就会调用wait方法将此线程进行阻塞;这时候存钱线程判断到账户没钱, 就会将钱存入账户,并且调用notify()方法通知被阻塞的线程,并更改标志;取钱线程收到通知后,再次获取到cpu的调度就可以进行取钱。反复更改标志,通过调用wait与notify()进行线程间通信。实际中我们会时候生产者消费者队列会更简单。

注意:调用notify()后,阻塞线程被唤醒,可以参与锁的竞争,但可能调用notify()方法的线程还要继续做其他事,锁并未释放,所以我们看到的结果是,无论notify()是在方法一开始调用,还是最后调用,阻塞线程都要等待当前线程结束才能开始。

为什么wait()/notify()方法要放到Object中呢?

因为每个对象都可以成为锁监视器对象,所以放到Object中,可以直接使用。

1.8、finalize()

protected void finalize() throws Throwable ;

此方法是在垃圾回收之前,JVM会调用此方法来清理资源。此方法可能会将对象重新置为可达状态,导致JVM无法进行垃圾回收。

我们知道java相对于C++很大的优势是程序员不用手动管理内存,内存由jvm管理;如果我们的引用对象在堆中没有引用指向他们时,当内存不足时,JVM会自动将这些对象进行回收释放内存,这就是我们常说的垃圾回收。但垃圾回收没有讲述的这么简单。

finalize()方法具有如下4个特点:

永远不要主动调用某个对象的finalize()方法,该方法由垃圾回收机制自己调用;

finalize()何时被调用,是否被调用具有不确定性;

当JVM执行可恢复对象的finalize()可能会将此对象重新变为可达状态;

当JVM执行finalize()方法时出现异常,垃圾回收机制不会报告异常,程序继续执行。

public class FinalizeTest {
    private static FinalizeTest ft = null;
    public void info(){
        System.out.println("测试资源清理得finalize方法");
    }
    public static void main(String[] args) {
        //创建FinalizeTest对象立即进入可恢复状态
        new FinalizeTest();
        //通知系统进行垃圾回收
        System.gc();
        //强制回收机制调用可恢复对象的finalize()方法
//        Runtime.getRuntime().runFinalization();
        System.runFinalization();
        ft.info();
    }
    @Override
    public void finalize(){
        //让ft引用到试图回收的可恢复对象,即可恢复对象重新变成可达
        ft = this;
        throw new RuntimeException("出异常了,你管不管啊");
    }
}
//输出结果
//测试资源清理得finalize方法

我们看到,finalize()方法将可恢复对象置为了可达对象,并且在finalize中抛出异常,都没有任何信息,被忽略了。

1.8.1、对象在内存中的状态

对象在内存中存在三种状态:

可达状态:有引用指向,这种对象为可达状态;

可恢复状态:失去引用,这种对象称为可恢复状态;垃圾回收机制开始回收时,回调用可恢复状态对象的finalize()方法(如果此方法让此对象重新获得引用,就会变为可达状态,否则,会变为不可大状态)。

不可达状态:彻底失去引用,这种状态称为不可达状态,如果垃圾回收机制这时开始回收,就会将这种状态的对象回收掉。

1.8.2、垃圾回收机制

垃圾回收机制只负责回收堆内存种的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源);

程序无法精确控制垃圾回收的运行,垃圾回收只会在合适的时候进行。当对象为不可达状态时,系统会在合适的时候回收它的内存。

在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能会将对象置为可达状态,导致垃圾回收机制取消回收。

1.8.3、强制垃圾回收

上面我们已经说了,当对象失去引用时,会变为可恢复状态,但垃圾回收机制什么时候运行,什么时候调用finalize方法无法知道。虽然垃圾回收机制无法精准控制,但java还是提供了方法可以建议JVM进行垃圾回收,至于是否回收,这取决于虚拟机。但似乎可以看到一些效果。

public class GcTest {
    public static void main(String[] args){
        for(int i=0;i<4;i++){
            //没有引用指向这些对象,所以为可恢复状态
            new GcTest();
            //强制JVM进行垃圾回收(这只是建议JVM)
            System.gc();
            //Runtime.getRuntime().gc();
        }
    }
    @Override
    public void finalize(){
        System.out.println("系统正在清理GcTest资源。。。。");
    }
}
//输出结果
//系统正在清理GcTest资源。。。。
//系统正在清理GcTest资源。。。。

System.gc(),Runtime.getRuntime().gc()两个方法作用一样的,都是建议JVM垃圾回收,但不一定回收,多运行几次,结果可能都不一致。

위 내용은 Java의 객체 메소드는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.