Блок Примитивные типы
Скачать 6.67 Mb.
|
Как правильно переопределять метода equals()?Придерживаясь следующих правил, ваша реализация метода equals() будет стабильна и в рамках выполнения каждого пункта контракта. Используйте оператор “==” для проверки ссылается ли объект на сравниваемый. Используйте оператор instanceof для проверки корректности типа Приводите аргумент к корректному типу Для каждого значимого поля в классе проверяйте соответствие пришедшего значение из объекта и его соответствующего поля. Для проверки примитивов (кроме float и double) используйте “==”, для проверки объектов – equals() или форму Objects.equals(Object, Object) чтобы избежать NullPointerException. Для массивов проверяйте рекурсивно каждый его объект. Для примитивов float и double используйте вызов статических методов Float.compare(float, float) и Double.compare(double, double). В данном случае простое “==” не подойдет, потому что есть значения Float.NaN, -0.0f и аналоги в double. Метод equals() для их сравнения лучше не использовать из-за автобоксинга, это безусловно скажется на производительности. Метод hashCode() При переопределении метода equals() мы всегда должны переопределять метод hashCode(). Метод hashCode() – вычисляет целочисленное значение для конкретного элемента класса, чтобы использовать его для быстрого поиска и доступа к этому элементу в hash-структурах данных, например, HashMap, HashSet и прочих. Почему важно переопределять hashCode() всегда вместе с методом equals()? Развернуто ответим на этот вопрос. Пожалуй, необходимо и достаточно знать два важных аспекта, чтобы понять, почему необходимо делать переопределение методов вместе: Hash-код объекта используется для быстрой навигации в Hash – таблицах. Поэтому достаточно понять сам процесс поиска/вставки/удаления элемента в этих таблицах Связь equals() и hashCode(). Если два объекта o1 и o2 являются equals() (o1.equals(o2) = true), то они должны иметь одинаковый hash-код. Обратное – не обязательно. Правильная реализация hashCode()Чтобы избежать проблем нам необходимо корректно переопределять hashCode(). Рассмотрим два варианта. Использовать метод hash класса Objects. Для класса Person это будет выглядеть следующим образом :
Прекрасная реализация в одну строчку. Из минусов – не подойдет вам, в случаем высоких требований к производительности приложения. В этом случае используйте второй способ 2. Напишите свою реализацию, используя следующий алгоритм: Создайте переменную result и положите в нее значение hashCode() первого значимого поля класса. Если поле примитив, то используйте вызов Type.hashCode(value), если экземпляр класса, то рекурсивно вычисляйте hashCode() для полей класса, либо используйте уже готовый метод hashCode() этого класса. Если поле массив, то рекурсивно вычисляйте hashCode() каждого элемента. Вычислите hashCode() каждого значимого поля и скомбинируйте следующим образом : result = 31 * result + Type.hashCode(value) Верните результат Где хранятся ссылки на объекты? age - это поле. Оно имеет примитивный тип, но храниться будет там же, где и объект - в куче. Так же и поле ссылочного типа name. someVar и user - это переменные и они хранятся в стеке. В первой хранится значение 2, во втором ссылка на объект класса User. Ссылки на объекты хранятся в стеке. Позднее и раннее связывание Связывание означает наличие связи между ссылкой и кодом. Например, переменная, на которую вы ссылаетесь, привязана к коду, в котором она определена. Аналогично, вызываемый метод привязан к месту в коде, где он определен. Присоединение вызова метода к телу метода. Если связывание проводится компилятором (компоновщиком) перед запуском программы, то оно называется статическим или ранним связыванием (early binding). В свою очередь, позднее связывание (late binding) это связывание, проводимое непосредственно во время выполнения программы, в зависимости от типа объекта. Позднее связывание также называют динамическим (dynamic) или связыванием на стадии выполнения (runtime binding). В языках, реализующих позднее связывание, должен существовать механизм определения фактического типа объекта во время работы программы, для вызова подходящего метода. Иначе говоря, компилятор не знает тип объекта, но механизм вызова методов определяет его и вызывает соответствующее тело метода. Механизм позднего связывания зависит от конкретного языка, но нетрудно предположить, что для его реализации в объекты должна включаться какая-то дополнительная информация. Итак, фундаментальное различие между статическим и динамическим связыванием в Java состоит в том, что первое происходит рано, во время компиляции на основе типа ссылочной переменной, а второе – позднее, во время выполнения, с использованием конкретных объектов. Для всех методов Java используется механизм позднего (динамического) связывания, если только метод не был объявлен как static или final (приватные методы являются final по умолчанию). Ключевые различия между ранним и поздним связыванием в языке Java: Статическое связывание происходит во время компиляции, а динамическое – во время выполнения. Поскольку статическое связывание происходит на ранней стадии жизненного цикла программы, его называют ранним связыванием. Аналогично, динамическое связывание называют также поздним связыванием, поскольку оно происходит позже, во время работы программы. Статическое связывание используется в языке Java для разрешения перегруженных методов, в то время как динамическое связывание используется в языке Java для разрешения переопределенных методов. Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при помощи динамического связывания. В случае статического связывания используются не конкретные объекты, а информация о типе, то есть для обнаружения нужного метода используется тип ссылочной переменной. С другой стороны, при динамическом связывании для нахождения нужного метода в Java используется конкретный объект. varargs varargs short для Аргументов переменной длины-это функция, которая позволяет методу принимать переменное количество аргументов (ноль или более). С varargs стало просто создавать методы, которые должны принимать переменное количество аргументов. Функция аргумента переменной была добавлена в Java 5. public void myMethod(String... strings){ // method body } myMethod("foo", "bar"); myMethod("foo", "bar", "baz"); myMethod(new String[]{"foo", "var", "baz"}); Важное Замечание: аргумент(ы), переданный таким образом, всегда является массивом-даже если есть только один. Убедитесь, что вы относитесь к нему таким образом в теле метода. Важное Примечание 2: аргумент, который получает ... должен быть последним в сигнатуре метода. Итак,myMethod(int i, String... strings) - это хорошо, но myMethod(String... strings, int i) - это не нормально. другое ограничение с varargs заключается в том, что должен быть только один varargs параметр. Передача параметров в методы Данные передаются между методами через параметры. Есть два способа передачи параметров: Передача по значению (by value). Значения фактических параметров копируются. Вызываемый метод создает свою копию значений аргументов и затем ее использует. Поскольку работа ведется с копией, на исходный параметр это никак не влияет. Передача по ссылке (by reference). Параметры передаются как ссылка (адрес) на исходную переменную. Вызываемый метод не создает свою копию, а ссылается на исходное значение. Следовательно, изменения, сделанные в вызываемом методе, также будут отражены в исходном значении. В Java переменные хранятся следующим образом: Локальные переменные, такие как примитивы и ссылки на объекты, создаются в стеке. Объекты — в куче (heap). Теперь вернемся к основному вопросу: переменные передаются по значению или по ссылке? Java всегда передает параметры по значению то здесь происходит? Если Java передает параметры по значению, то почему был изменен исходный список? Похоже, что Java все-таки передает параметры не по значению? Нет, неправильно. Повторяйте за мной: "Java всегда передает параметры по значению". Чтобы с этим разобраться, давайте посмотрим на следующую диаграмму. В программе, приведенной выше, список fruits передается методу processData. Переменная fruitRef — это копия параметра fruit. И fruits и fruitsRef размещаются в стеке. Это две разные ссылки. Но самое интересное заключается в том, что они указывают на один и тот же объект в куче. То есть, любое изменение, которое вы вносите с помощью любой из этих ссылок, влияет на объект. JVM, JRE, JDK JVM, Java Virtual Machine (Виртуальная машина Java) — основная часть среды времени исполнения Java (JRE). Виртуальная машина Java исполняет байт-код Java, предварительно созданный из исходного текста Java-программы компилятором Java. JVM может также использоваться для выполнения программ, написанных на других языках программирования. JRE, Java Runtime Environment (Среда времени выполнения Java) - минимально-необходимая реализация виртуальной машины для исполнения Java-приложений. Состоит из JVM и стандартного набора библиотек классов Java. JDK, Java Development Kit (Комплект разработки на Java) - JRE и набор инструментов разработчика приложений на языке Java, включающий в себя компилятор Java, стандартные библиотеки классов Java, примеры, документацию, различные утилиты. Коротко: JDK - среда для разработки программ на Java, включающая в себя JRE - среду для обеспечения запуска Java программ, которая в свою очередь содержит JVM - интерпретатор кода Java программ. Ключевое слово native Java Native Interface (JNI) — стандартный механизм для запуска кода, под управлением виртуальной машины Java (JVM), который написан на языках С/С++ или Ассемблера, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот. Модификатор native сигнализирует о том, что метод реализован в платформо-зависимом коде, часто на языке С. Этот модификатор может быть применен только к методам, но не классам и переменным. Тело нативного метода должно заканчиваться на (;) как в абстрактных методах, идентифицируя то, что реализация опущена. Так что понятно, что это позволяет вам: вызовите скомпилированную динамически загружаемую библиотеку (здесь написанную на C) с произвольным кодом assembly из Java и получить результаты обратно в Java Это может быть использовано для: напишите более быстрый код на критическом разделе с лучшими инструкциями CPU assembly (не CPU portable) совершайте прямые системные вызовы (не OS portable) с компромиссом более низкой переносимости. ENUM Enums – это специальный тип, используемый для определения коллекций констант. Точнее, тип перечисления – это особый вид класса Java, который может содержать константы, методы и т. д. Были добавлены в Java 5. Это руководство объясняет, как создавать и использовать их. Основными методами класса Enum, помимо унаследованных от Object, являются метод ordinal(), который возвращает порядковый номер константы, name(), который возвращает имя при инициализации. Наследовать класс Enum не может, т.к. уже неявно наследуется от java.lang.Enum, а множественного наследования классов в Java нет. Но Enum может реализовывать интерфейсы. Блок 4. Обработка ошибок, исключения, отладка Иерархия исключений Чем отличаются исключения от обычных классов? Классы-исключения, как правило, наследуются от классов Exception или RuntimeException, а также имена их классов по соглашению должны заканчиваться на слово “Exception”. Все ключевые слова и всё что с ними связано. С исключениями связано несколько ключевых слов. Это: Блок try-catch. Он создан для отлова исключений и их обработки. Блок кода, в котором может возникнуть исключение, помещается внутрь блока try, а в блоке catch прописывается логика поведения программы в случае отлова какого-либо исключения (обработка исключения). finally. Блок finally является расширением блока try-catch, и его особенность заключается в том, что он выполняется в любом случае, было ли поймано исключение в блоке try или нет. throw. Это ключевое слово создано, чтобы выбрасывать исключения либо пробрасывать их вверх по стеку. throws. Это ключевое слово создано, чтобы обозначать методы, которые могут выбрасывать исключения. После слова throws перечисляются исключения, которые стоит ожидать в методе и которые нужно либо обработать, либо пробросить выше. Как создать, выбросить, поймать свое исключение? Чтобы создать свое исключение, нужно создать класс, который наследуется от Exception. Чтобы выбросить свое исключение, нужно прописать конструкцию throw new НашеИсключение е при возникновении условия исключения. Чтобы поймать свое исключение, нужно в скобках после catch при отлове исключения прописать экземпляр нашего исключения (НашеИсключение е), и прописать логику действий при отлове нашего исключения. Где возможно вызывать исключения? Исключение можно выбросить в методе, в блоке try-catch-finally. Конструкция try-finally Можно ли обрабатывать разные исключения в одном блоке catch? В одном блоке catch можно обрабатывать разные исключения одним и тем же способом, если при написании в скобках отлавливаемого исключения мы разделим ожидаемые исключения знаком |. Либо если, к примеру, отлавливать Exception e, то будут поймано и обработано любое исключение, которое наследуется от Exception. Обязателен ли блок finally? А catch? Конструкция может быть как try/catch так и try/finally (т.е. без catch или finally, но что-то одно обязательно должно быть). Finally будет выполнен всегда, за исключением редких случаев. Когда не будет выполнен(4 случая)? Будет ли выполнен finally при Error? Также блок finally не выполнится, если в try заложен бесконечный цикл, или если упала JVM. При Error блок finally будет выполнен, так как он вызывается всегда, когда выбрасывается объект Throwable (а Error является наследником Throwable). Разница try catch и if Первый плюс использования try-catch для исключений: Потому что так код более читабельный, а обработка ошибок выведена в отдельный блок. Второй: Теперь обработкой этих ошибок заниматься непосредственно разработчику не обязательно — достаточно выбросить исключение, а его обработкой можно заняться на более высоком уровне. Какой return вернется? Из try или finaly? Из finally. Что если в конструкции try finally вылетело исключение сначала в try а потом в finally? Какое исключение вылетит? Что будет с другим? Ответ аналогичный случаю с двумя return — будет обработано в finally блоке. Если было выброшено два исключения — одно в try, второе в finally, то исключение в finally «проглотит» исключение выше. Если до блока finally исключение было обработано, то мы можем получить информацию об исключении в блоке try и тем самым не потерять исключение, которое впоследствии может быть перезаписано в finally другим исключением. Расскажи про информацию, которая находится внутри исключения? Как с ней работать? Исключение содержит в себе stacktrace(стектрейс вызовов), т.е. последовательность вызванных методов, а также номер строки, на которой возникла ошибка. Также, в зависимости от использованного конструктора, объект исключения может содержать String поле message и экземпляр Throwable. Вытащить стек-трейс можно с помощью метода getStackTrace(), который возвращается массив StackTraceElement[], message при помощи метода getMessage(), экземпляр Throwable при помощи метода getCause(). |