Чтение исходного кода JDK (1): Анализ исходного кода объекта

Java

Недавно, по предложению большого парня, я готов прочитать исходный код JDK, чтобы улучшить себя.
Поэтому я начал писать статью об анализе исходного кода JDK.

Читать версию JDK 1.8


  • содержание
    • Схема структуры объекта
    • Конструктор
    • метод равенства
    • метод getClass
    • метод hashCode
    • метод toString
    • завершить метод
    • метод registerNatives

1. Схема структуры объекта

2. Конструктор класса

Конструктор класса    — это один из способов создания объектов Java. Как правило, мы используем ключевое слово new для выполнения экземпляров, а также можем выполнять соответствующие операции инициализации в конструкторе.
   В классе Java должен быть конструктор. Если он не добавлен, система по умолчанию создаст конструктор без аргументов во время компиляции.

/*实例一个Object对象*/
Object obj = new Object()

3. метод равных

На собеседованиях интервьюеры часто спрашивают о разнице между методом equals() и оператором == Оператор == используется для сравнения того, совпадают ли значения базовых типов, а equals используется для сравнения того, равны ли два объекта. , так вот вопрос. , как можно два объекта считать равными? Посмотрите на реализацию equals в объекте

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

В Object equals и == эквивалентны. Следовательно, ссылки двух объектов в Object одинаковы, поэтому они должны быть одинаковыми. Когда мы настраиваем объект, мы должны переопределить метод equals. Я ссылаюсь на следующие онлайн-материалы, чтобы проанализировать метод equals, переопределенный в String:


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;
    }

Строка является ссылочным типом. При сравнении невозможно сравнить, равны ли ссылки. Основное внимание уделяется тому, равно ли содержимое строк. Таким образом, класс String определяет равенство двух объектов, поскольку содержимое строки одинаково.

В спецификации Java использование метода equals должно соответствовать следующим принципам:

  • Рефлексивный: x.equals(x) должен возвращать true для любого ненулевого ссылочного значения x.
  • Симметрия: для любых ненулевых ссылочных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
  • Транзитивность: для любых ненулевых ссылочных значений x, y и z, если x.equals(y) возвращает true, а y.equals(z) возвращает true, тогда x.equals(z) возвращает true.
  • Непротиворечивость: для любых ненулевых ссылочных значений x и y многократные вызовы x.equals(y) всегда возвращают true или всегда возвращают false, при условии, что информация, используемая при сравнении равного объекта, не была изменена.
  • x.equals(null) должен возвращать false для любого ненулевого ссылочного значения x

Определите класс ниже и переопределите метод equals в этом классе. Свойства объекта равны, если они одинаковы, в противном случае они не равны.
public class Student {
    private String name;

    /**
     * 无参构造方法
     */
    public Student() {

    }

    /**
     * 无参构造方法
     */
    public Student(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object obj) {
        //引用相同 两个对象肯定是相同的
        if(this==obj){
            return true;
        }
        //对象等于空 或者不是Student 是不想等的
        if(obj==null || !(obj instanceof Student)){
            return false;
        }
        //转为Student对象
        Student student = (Student)obj;
        //属性相同 返回true
        return this.getName()==student.getName();
    }

}

Затем создайте тестовый класс для тестирования:

        Student t1 = new Student("yes");
        Student t2 = new Student("slm");
        System.out.println("对象不同 属性不同 ==  "+(t1==t2));
        System.out.println("对象不同 属性不同 equals  "+(t1.equals(t2)));
        Student t3 = new Student("slm");
        System.out.println("对象不同 属性相同"+(t2.equals(t3)));

Выходной результат:

Разные объекты имеют разные свойства == false
Разные объекты, разные свойства равно false
Объекты разные, свойства одинаковые правда

Теперь видно, что если здесь не переписать метод equals, то всегда будут выполняться только equals Объекта, то есть совпадают ли адреса ссылок на объекты по ==.
Давайте рассмотрим другой пример, в этот раз, если появится подкласс Student, мы его сравним.

/**
 * @Author: sunluomeng
 * @CreateTime: 2019-06-06 23:35
 * @Description:
 */
public class Language  extends Student{
    private String name;

    /**
     * 无参构造
     */
    public Language(){

    }

    /**
     * 有参构造
     * @param name
     */
    public Language(String name){
        this.name=name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object obj) {
        //引用相同 两个对象肯定是相同的
        if(this==obj){
            return true;
        }
        //对象等于空 或者不是Student 是不想等的
        if(obj==null || !(obj instanceof Language)){
            return false;
        }
        //转为Student对象
        Language language = (Language)obj;
        //属性相同 返回true
        return this.getName()==language.getName();
    }

}

В это время наш только что созданный класс Language наследует Student, а затем создает два объекта для сравнения.

Выходной результат:

Родительский класс сравнивает дочерний класс с теми же свойствами --- true
Подкласс имеет те же свойства, что и родительский класс --- false

Видно, что когда родительский класс сравнивает дочерний класс, результат student.equals(language) равен true, а дочерний класс сравнивает родительский класс, а language.equals(student) возвращает false.

Это нарушает упомянутую выше симметрию.

Для любых ненулевых ссылочных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true

Если y — студент, x — язык
Итак, теперь y.equals(x) равно true, и, в свою очередь, x.equals(y) также должно возвращать true, но почему теперь он возвращает false?

Давайте сначала посмотрим на код

Мы используем ключевое слово instanceof при оценке, чтобы определить, является ли он указанным типом при запуске.

Оператор instanceof в java используется для указания во время выполнения, является ли объект экземпляром определенного класса. instanceof возвращает логическое значение, чтобы указать, является ли объект экземпляром этого конкретного класса или одного из его подклассов.

Это означает, что Language является подклассом Student и возвращает true при оценке instanceof, и хотя Language наследует Student, при использовании instanceof для оценки он обнаружит, что Language и Student относятся к разным типам, а Student не является подклассом Language, поэтому вернет false.
И решение

Затем мы запускаем код прямо сейчас
Выходной результат:

Родительский класс сравнивает дочерний класс с теми же свойствами --- false
Подкласс имеет те же свойства, что и родительский класс --- false

Идеальное решение, удовлетворяющее требованиям симметрии
Примечание. Использование getClass зависит от ситуации, и использование getClass не соответствует определению полиморфизма.

Итак, когда использовать instanceof и когда использовать getClass?

  • Если подклассы могут иметь свое собственное понятие равенства, требование симметрии заставляет getClass проверять.
  • Если существует понятие равенства, определяемое суперклассом, то можно использовать instanceof для проверки на равенство, что позволяет проводить сравнение на равенство между объектами разных подклассов.

Также важно отметить, что всякий раз, когда этот метод переопределяется, метод hashCode обычно должен быть переопределен, чтобы сохранить общее соглашение метода hashCode, которое гласит, что одинаковые объекты должны иметь одинаковый хэш-код.

4. Метод getClass

Давайте сначала посмотрим на реализацию getClass в Object.

Мы видим, что getClass идентифицируется как native, а значит, это реализация вызова нативного метода
Для получения дополнительной информации о родном, пожалуйста, Baidu. Натив реализован операционной системой для нас
В документации сказано, что вызов getClass возвращает класс времени выполнения. Что это значит? Давайте посмотрим на реализацию кода ниже.

распечатать результат:

Видно, что getClass возвращает объект времени выполнения. class - это возвращаемый скомпилированный объект класса
Вы можете видеть, что метод getClass окончательно изменен, что указывает на то, что этот метод не может быть переопределен.

5.hashCode

Сначала посмотрите на реализацию hashCode в Object:

hashCode также является нативным методом, модифицированным нативным
В аннотации указано, что возвращается хэш-значение объекта. Так что же он делает?

В основном это делается для того, чтобы коллекции на основе хэшей, такие как HashSet, HashMap и HashTable, гарантировали, что элементы не повторяются при вставке элементов, и в то же время он предназначен для повышения удобства и эффективности вставки и удаления элементов. ; он существует в основном для удобства поиска.

Так же, как использование Set в качестве примера.
Коллекция Set не повторяется.Если вы используете equals для сравнения каждый раз, когда добавляете данные, очень медленно сравнивать 100 000 раз при вставке 100 000 фрагментов данных.
Поэтому при добавлении данных используется хеш-таблица.Алгоритм хеширования также называют хеш-алгоритмом.При добавлении значения сначала вычисляется его хеш-значение, и данные вставляются в указанную позицию в соответствии с вычисленным хеш-значением. Таким образом, можно избежать скрытой эффективности, вызванной постоянным вызовом equals. Также есть следующие условия:

  • Если местоположение пусто, добавьте его напрямую
  • Если позиция не пуста, проверьте, совпадают ли два элемента, и если они одинаковы, не сохраняйте их.

Также бывает случай, когда два элемента не совпадают, но хэш-код одинаков, что является хэш-коллизией.
Если встречается один и тот же хеш-ключ, для того же элемента создается связанный список. Храните все одинаковые элементы в связанном списке.

Видно, что хэш T1 такой же, как и у T2, но элементы разные, поэтому теперь будет формироваться цепочка для хранения.

6.toString

Сначала посмотрите на реализацию toString

Можно видеть, что toString — это возвращаемое имя класса плюс шестнадцатеричное целое число без знака для возврата строкового представления этого хэш-кода.
Рабочий вывод:

Вывод объекта напрямую аналогичен использованию toString.

Если вы хотите, чтобы toString выводил содержимое свойства, вам необходимо переопределить метод toString.

7.finalize

Метод реализации в исходном коде:

finalize пользовательская сборка мусора вызывается JVM.

8.registerNatives

Реализация исходного кода:

Как упоминалось выше, native должен вызывать локальный метод реализации, а registerNatives — регистрировать локальный метод и загружать локальную библиотеку. Выполняется при инициализации объекта.

Также есть notify()/notifyAll()/wait() и другой анализ при записи в многопоточность.

Наконец

Я не талантлив, пожалуйста, укажите на ошибки. Если хотите, обратите внимание и медленно обновляйте примечания к чтению исходного кода JDK.
** Официальный аккаунт младшего брата, ввод кода случайным образом. Добро пожаловать, чтобы поставить лайк и подписаться**