Метод hashCode в Java. Его переопределение

Search Icon

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

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

Это случайное уникальное число, которое генерируется этим методом по умолчанию.

Переопределять его нужно если мы собираемся запихивать в HashSet или HashMap не простые элементы типа char, int, String и т.д., а объекты.

То есть, например, так – set.add(new MyClass(1, 34));.

Вот например мы только что записали в set объект new MyClass(1, 34) и у нас в классе MyClass пока не переопределен hashCode и если мы теперь запишем в этот же set такой же объект new MyClass(1, 34) еще раз вот так – set.add(new MyClass(1, 34));, то в set уже будет ДВА элемента.

А это не должно быть так! Так как мы помним, что ни в HashSet, ни в HashMap одинаковые ключи храниться не должны.

Почему же если мы записываем в hashset идентичные объекты, как ключи, то hashset рассматривает их как разные ключи?

Реализация метода hashCode по умолчанию генерирует разные ключи всем объектам ДАЖЕ ЕСЛИ ОНИ ИДЕНТИЧНЫ по своему содержанию.

То есть если мы создаем объект new MyClass(1, 34) в первый раз, то у него будет свой hashCode, когда мы создаем new MyClass(1, 34) второй раз, у него уже будет другой hashCode.

Каждый объект имеет свой hashCode. И hashset добавляет объекты в себя по этому hashCode.

Если hashCode у добавляемых объектов разный, значит эти объекты с наибольшей вероятностью попадут в разные linkedlist-ы в 16 linkedlist-ах, если же они одинаковые, то объекты будут попадать в один и тот же linkedlist.

Как же нам переопределить hashCode, чтобы идентичные объекты всегда записывались в один и тот же linkedlist?

Нам нужно переопределить hashCode так, чтобы он возвращал данные объекта представленные одним восьмибитным числом.

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

И теперь hashCode всех идентичных объектов, например, new MyClass(1, 34) всегда будет возвращать один и тот же hashCode, так как он является скомпонованными полями объекта, а поля у идентичных объектов new MyClass(1, 34) одинаковые – 1 и 34.

Но на этом еще не всё. Если объекты одинаковы по hashCode, это только значит, что они попадут в один и тот же самый linkedlist, это еще не обязательно значит, что они одинаковы полностью.

HashSet еще будет сравнивать добавляемый в него объект со всеми уже присутствующими в hashset элементами методом equals и если он НЕ найдет там методом equals идентичный элемент, но при этом объект с таким hashCode уже там присутствует, то в hashset всё равно добавиться этот добавляемый объект и в итоге в нем будет два элемента с одинаковыми hashCode.

Search Icon

Поэтому, чтобы в HashSet не было идентичных объектов, обязательно вместе с hashCode должен быть переопределен и equals.


Переопределение HashCode

Пример программы:

import java.util.*; class MyClass implements Cloneable { int myA; SomeClass myB; MyClass(int myA, SomeClass myB){ this.myA = myA; this.myB = myB; } // hashCode – данные класса представлены одним восьмибитным числом. // По умолчанию, если не переопределять, это случайное уникальное число. @Override public int hashCode() { System.out.println(“HashCode is called: ” + this); // Это стандартное переопределение. Не заморачивайтесь почему 31, // почему умножение. нам лишь важно в result // добавить все поля класса, так как result это и есть // это самое восьмибитное число, состоящее из полей класса. final int prime = 31; int result = 1; // Числовые переменные просто добавляем. result = prime * result + myA; // У полей объектов вызываем hashCode. // Метод hashCode в классе этих объектов // тоже должен быть определен таким же образом. result = prime * result + myB.hashCode(); return result; } // equals обязательно тоже переопределяем, как уже было сказано @Override public boolean equals(Object obj) { System.out.println(“Equals is called:” + this + ” : ” + obj); if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MyClass other = (MyClass) obj; if (myA != other.myA) return false; if (!myB.equals(other.myB)) return false; return true; } @Override public String toString() { return “MyClass{” + “myA=” + myA + “, myB='” + myB + ‘\” + ‘}’; } @Override public SomeClass clone() throws CloneNotSupportedException { Object obj = super.clone(); SomeClass someClass = (SomeClass) obj; return someClass; } } class SomeClass implements Cloneable { int someVar; SomeClass(int someVar){ this.someVar = someVar; } // Здесь тоже переопределены hashCode и equals @Override public int hashCode() { System.out.println(“HashCode is called:” + this); final int prime = 31; int result = 1; result = prime * result + someVar; return result; } @Override public boolean equals(Object obj) { System.out.println(“Equals is called:” + this + ” : ” + obj); if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; SomeClass other = (SomeClass) obj; if (someVar != other.someVar) return false; return true; } @Override public SomeClass clone() throws CloneNotSupportedException { Object obj = super.clone(); SomeClass someclass = (SomeClass)obj; return someclass; } public String toString(){ return “SomeClass{” + “someVar=” + someVar + ‘}’; } } public class hashCodeLesson { public static void main(String[] args) throws CloneNotSupportedException { Set< MyClass > set = new HashSet<>(); set.add(new MyClass(1, new SomeClass(34))); set.add(new MyClass(1, new SomeClass(36))); set.add(new MyClass(1, new SomeClass(34))); set.add(new MyClass(2, new SomeClass(26))); set.add(new MyClass(3, new SomeClass(75))); System.out.println(“SIZE:” + set.size()); // По результатам можно увидеть, что размер коллекции 4, // так как для 1 и 3 элементов хеш-коды одинаковые, // и equals сравнил поля, которые оказались одинаковыми. // Если бы hashCode не был переопределен, элементов было бы 5. } }

Вывод:


Последовательность добавления элементов в HashSet

Также стоит упомянуть некоторые детали последовательности добавления элементов в HashSet.

При добавлении ключа в HashSet и расчета его hashCode происходит сравнение этого hashCode с hashCode каждого элемента в HashSet, и если hashCode очередного добавляемого объекта отличается от всех остальных уже присутствующих в коллекции, то ключ добавляется СРАЗУ, без сравнения по equals.

Если же такой же hashCode нашелся, то происходит сравнение по equals, и если этим методом не найдет такого же элемента, то произойдет добавление.

В этом уроке был приведен пример стандартного переопределения hashCode.

Search Icon

Таким вот образом его нужно переопределять почти всегда когда вы работаете с hash коллекциями.

Метод Equals в Java. Его переопределение

Следующий метод Object – это equals.

Возвращает boolean значение.

equals по умолчанию проверяет только то, ссылаются ли две ссылки на один и тот же объект.

Если нам нужно проверить не только это, а является ли содержимое одного объекта одинаковым с другим объектом, то его нужно переопределять.


Переопределение Equals

Пример программы:    

mport java.util.*; class MyClass implements Cloneable { int myA; SomeClass myB; MyClass(int myA, SomeClass myB){ this.myA = myA; this.myB = myB; } // Переопределим equals чтоб он сравнивал не только то // ссылаются ли ссылки на один и тот же объект @Override // Как уже говорили все классы наследуют от Object // поэтому можно сделать так – equals(Object obj) // И теперь в equals параметром можно передавать // объекты любого типа – MyClass или SomeClass. public boolean equals(Object obj) { System.out.println(“Equals is called:” + this + “:” + obj); //проверка ссылки if (this == obj) return true; //проверка на пуст ли передаваемый объект if (obj == null) return false; //getClass возвращает название класса //переданного сюда параметром. //Проверка объектов на принадлежность одному классу if (getClass() != obj.getClass()) return false; //сравнение полей MyClass other = (MyClass) obj; if (myA != other.myA) return false; //для ссылочных полей сравнение по “!=” не подходит //нужно использовать equals и конечно в классах этих //ссылочных полей тоже должен быть определен //equals. в классе SomeClass ниже можно увидеть //пример такого equals. //Теперь мы его используем с строке кода ниже if (!myB.equals(other.myB)) return false; return true; } @Override public String toString(){ return “MyClass{” + “myA=” + myA + “, myB=” + myB + ‘}’; } @Override public MyClass clone() throws CloneNotSupportedException{ Object obj = super.clone(); MyClass myClass = (MyClass)obj; return myClass; } } class SomeClass implements Cloneable { int someVar; SomeClass(int someVar){ this.someVar = someVar; } // Тоже переопределяем equals @Override public boolean equals(Object obj){ System.out.println(“Equals is called:” + this + ” : ” + obj); // проверка ссылки if (this == obj) return true; // проверка на пуст ли передаваемый объект if (obj == null) return false; // Проверяем объекты на принадлежность одному классу if (getClass() != obj.getClass()) return false; // сравнение полей SomeClass other = (SomeClass) obj; if (someVar != other.someVar) return false; return true; } @Override public SomeClass clone() throws CloneNotSupportedException{ Object obj = super.clone(); SomeClass someclass = (SomeClass)obj; return someclass; } @Override public String toString(){ return “SomeClass{” + “someVar=” + someVar + ‘}’; } } public class EqualsLesson { public static void main(String[] args) throws CloneNotSupportedException{ SomeClass someClass = new SomeClass(10); MyClass myClass = new MyClass(10, someClass); SomeClass someClass1 = new SomeClass(10); MyClass myClass1 = new MyClass(10, someClass1); System.out.println(myClass.equals(myClass1)); // В консоли можно увидеть что equals включит true. // Это значит что myClass полностью // равен по значениям myClass1 // Давайте для дополнительной проверки изменим // какое-то поле myClass1 someClass1.someVar = 16; // проверим System.out.println(myClass.equals(myClass1)); // уже не полностью равны } }

Вывод:

Метод Clone в Java. Его переопределение

Второй метод Object это метод clone, который клонирует объект.

Происходит копирование всех полей клонируемого объекта в новый объект-клон.

Но по умолчанию методом clone в новый объект копируются только примитивные поля объекта, а ссылочные нет, поэтому clone тоже нужно переопределять.

В Java управление объектами осуществляется с помощью ссылочных переменных, и нет оператора для фактического копирования объекта, поэтому и существует clone.


Переопределение Clone

Напрямую вызвать clone у какого либо объекта в main нельзя, так как метод clone внутри Object объявлен protected.

Search Icon

Поэтому для того чтобы вызвать clone у какого либо объекта его всегда нужно переопределять.

То есть имеется ввиду, что даже чтобы использовать стандартную реализацию clone, которая копирует только примитивные поля всё равно нужно сделать минимальное переопределение clone, как это сделано в классе SomeClass ниже.

Создадим два класса, в которых будем переопределять clone.

Все классы, которые переопределяют clone должны реализовывать интерфейс Cloneable. То есть видим ниже в примере программы implements Cloneable

Пример программы:

import java.util.*; class SomeClass implements Cloneable { int someVar; SomeClass(int someVar){ this.someVar = someVar; } //неопределим clone для SomeClass @Override public SomeClass clone() throws CloneNotSupportedException{ //Слово super это супер класс то есть класс // от которого наследует текущий класс //Текущий класс наследует от Object. //Слово super это ссылка на родительский класс Object. //super.clone() – через супер класс клонируем //текущий объект. Как уже было сказано //у нового объекта copy копируются только примитивные //поля объекта SomeClass. В нашем случае someVar. Object obj = super.clone(); //Теперь новый объект clone SomeClass пока типа Object //нам нужно привести его к SomeClass. SomeClass someclass = (SomeClass)obj; //Мы можем это сделать так как //SomeClass наследует от Object. //class SomeClass – extends Object; //А значит имеет в своем составе //все поля и методы SomeClass и в него были //скопированы все НЕ ссылочные поля то есть someVar //Ссылочных полей здесь нет //поэтому такого переопределения достаточно return someclass;//возвращаем клон } public String toString(){ return “SomeClass{“+ “someVar=” + someVar + ‘}’; } } class MyClass implements Cloneable { int myA; SomeClass myB; MyClass(int myA, SomeClass myB){ this.myA = myA; this.myB = myB; } //clone должен копировать как простые //так и ссылочные типы объекта @Override public MyClass clone() throws CloneNotSupportedException{ //Здесь для клонирования не ссылочных полей //объекта MyClass производится те же действия //что и в предыдущем классе. Object obj = super.clone(); MyClass myclass = (MyClass)obj; //В этом классе уже есть ссылочное поле это myB, //его нужно клонировать. Для этого //мы должны вызвать метод clone() из переопределения //SomeClass. Он клонирует все примитивные поля //из объекта myB, который является объектом //SomeClass, который мы сейчас клонировали. //И возвращает myclass, который является клоном. myclass.myB = myB.clone(); return myclass; } public String toString(){ return “MyClass{” + “myA=” + myA + “, myB=” + myB + ‘}’; } } public class CloneLesson { public static void main(String[] args) throws CloneNotSupportedException { //Создаем объект SomeClass и объект MyClass SomeClass someClass = new SomeClass(10); MyClass myclass = new MyClass(10,someClass); //клонируем myclass. MyClass myclass1 = myclass.clone(); //Выведем на консоль поля клонируемого //объекта и клона. System.out.println(myclass); //Как можно увидеть по результатам все поля //примитивного типа и все типы ссылочные //очень someClass из myclass. System.out.println(myclass1); //Теперь самое главное. Нужно точно убедиться //что объекты myclass и myclass1 не ссылаются на один //и тот же объект и тогда точно можно будет сказать //что клонирование прошло успешно. Для этого создадим //новый объект someClass и присвоим его полю myB //объекта myclass1. Если клонирование прошло успешно //то объект someClass никак не должен измениться //в объекте myclass и убедимся что это не произошло. //Если клонирование прошло успешно то объекты //myclass и myclass1 будут разные, что они и отдельны. someClass = new SomeClass(12); myclass1.myB = someClass; System.out.println(myclass); System.out.println(myclass1); //Теперь изменим ссылочное поле в myclass1 //и убедимся что это не изменило myclass myclass1.myB.someVar = 12; System.out.println(myclass); System.out.println(myclass1); } }

Вывод:

Класс Object в Java. Метод toString и его переопределение

Все классы в Java наследуют от некого класса Object.

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

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

Рассмотрим самые важные.

Первым рассмотрим toString.

По умолчанию метод toString возвращает хеш-код объекта. Хеш-код мы затронем позже.

Но информация о хеш-коде объекта не особо полезна, поэтому принято переопределять метод toString так чтобы toString возвращал всю полезную информацию об объекте (содержимое его полей).

Пример программы:

class MyClass { int myA; Integer myB; MyClass(int myA, int myB) { this.myA = myA; this.myB = myB; } // toString – предоставление информации об объекте. // Как видим ниже, он возвращает строку // со значениями полей объекта, // то есть это вся необходимая информация о нем. // Переопределять этот метод желательно именно // в формате вот такой строки. // Причем программисту даже не нужно будет писать // имя метода toString для его вызова – // этот метод будет вызываться автоматически, если // попытаться передать объект в println, // вот так: System.out.println(someobject); // где someobject – объект класса MyClass. // Смотри пример ниже в main. @Override public String toString() { return “MyClass{” + “myA=” + myA + “, myB=” + myB + ‘}’; } } public class ToStringLesson { public static void main(String[] args) throws CloneNotSupportedException { MyClass someobject = new MyClass(10, 20); // Как можно увидеть, в консоли вывело ту самую // строку с полями объекта someobject. // При этом, как уже говорилось, нам не пришлось // вызывать метод toString вот так someobject.toString(); System.out.println(someobject); } }

Вывод:

Работа с элементами файловой системы в Java. Класс File

Fileкласс для работы с элементами файловой системы. С помощью различных методов этого класса можно получить информацию о переданном в конструктор объекта этого класса элементе файловой системы.

Например:

  • getPath()вернуть путь переданного элемента,  
  • getName()вернуть имя переданного элемента,
  • isDirectory()является ли директорией.

Пример программы:

import java.io.*; import java.util.*; class FileExample { public static void main(String[] args) { // Для примера выведем полный путь к файлу person.txt // в текущей директории с помощью метода getAbsolutePath. File file = new File(“person.txt”); System.out.println(file.getAbsolutePath()); // или например проверим является ли переданное // в конструктор директорией с помощью метода isDirectory. // Вернет true, так как InputOutputStreams // является директорией. File directory = new File( “C:/Users/User/Desktop/Java/JavaExamples/InputOutputStreams” ); System.out.println(directory.isDirectory()); } }

Вывод:

Как видим, вернулся абсолютный путь файла person.txt и то, что InputOutputStreams является директорией.

Также есть много других полезных методов для создания, удаления или переименования файла или директории, проверки готов ли файл для записи, получить размер файла.

Все эти методы, конечно, приводить не будем, так как они очень простые.

Далее разберем некоторые чуть более сложные и полезные.


Метод listFiles.

С помощью метода listFiles можем получить все файлы в директории и поместить их в массив File[]. Благодаря чему можем пройтись по всем файлам с помощью цикла и выполнить какие-то действия с каждым из них.

Пример программы:

import java.io.*; import java.util.*; class FileExample { public static void main(String[] args) { File directory = new File( “C:/Users/User/Desktop/Java/JavaExamples/InputOutputStreams”); System.out.println(directory.isDirectory()); // Получим все файлы в директории InputOutputStreams File[] files = directory.listFiles(); // Каждый элемент массива files — это один файл из InputOutputStreams. for (File f : files) { // Выведем имена каждого файла в директории InputOutputStreams System.out.println(f.getName()); } } }

Вывод:

В консоль вывелись имена всех файлов в папке InputOutputStreams.


Класс FileFilter.

FileFilter предназначен для проверки попадает ли объект File под некоторое условие.

Созданный объект FileFileter обычно передается в метод listFiles для фильтрации файлов в деректории.

Пример программы:

import java.io.*; import java.util.*; class FileExample{ public static void main(String[] args) { File directory = new File(“C:/Users/User/Desktop/Java/JavaExamples/InputOutputStreams”); // Нужно переопределить файл accept, который // будет применяться к каждому файлу, который будет // выбираться из директории с помощью listFiles FileFilter ff = new FileFilter() { // Анонимным классом переопределяем accept public boolean accept(File file) { // Если файл в директории заканчивается на .txt if (file.getName().endsWith(“.txt”)) { // Возвращаем true return true; } // Иначе false return false; } }; // Метод listFiles(FileFilter filter) отбирает не все // файлы данного каталога, а только те, которые // удовлетворяют определенному условию. // Параметр filter предназначен для // задания этого условия. // Добавляет в массив файлов только те, accept // которых вернул true File[] files = directory.listFiles(ff); for (File f: files) { System.out.println(f.getName()); } } }

Вывод:

Как видим, вывелись на консоль только имена файлов с форматом .txt.

Java Stream API

Streamдля работы с коллекциями.

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

Проще говоря пишем запрос к коллекции как к базе данных без ничего лишнего.

То есть например мы можем запросить из коллекции только четные числа простым запросом в формате цепочки соответствующих функций класса Stream.

Также позволяет производить операции над элементами коллекции в параллельных потоках.

Пример программы:

import java.util.*; import static java.util.stream.Collectors.toList; class SStream{ public static void main(String[] args) { List< Integer > numbers = Arrays.asList(3,2,2,3,7,3,5); // map нужен для преобразования элементов по какому-то правилу. Например // с помощью map умножаем каждый элемент коллекции сам на себя. // То есть i здесь это элемент коллекции и stream ниже проходит по каждому // элементу коллекции и совершает с ним операцию, заданную нами с помощью // лямбда-выражения. В нашем случае — умножение элемента i самого на себя. // В collect передаем метод, который будет преобразовывать в необходимую // коллекцию результат запроса. В нашем случае результирующая коллекция // будет List, так как метод toList(). // В итоге выводимая на консоль коллекция List будет содержать числа, // которые являются результатом лямбда-выражения. System.out.println(numbers.stream().map(i -> i*i).collect(toList())); // filter — фильтрует элементы в соответствии с условием. // Здесь запрашиваем только четные числа коллекции. System.out.println(numbers.stream().filter(i -> i%2 == 0).collect(toList())); // limit — оставляет только указанное количество элементов коллекции. System.out.println(numbers.stream().limit(4).collect(toList())); // forEach — применяем какое-то действие к каждому элементу. numbers.stream().forEach(System.out::println); // sorted — сортирует элементы. Также может принимать аргументом лямбда- // выражение, по которому сортирует. System.out.println(numbers.stream().sorted().collect(toList())); // parallel — для распараллеливания выполнения операций с элементами коллекции. // То есть благодаря добавлению parallel к коллекции лямбда-выражение может // выполняться намного быстрее — и насколько быстро, зависит от количества ядер // в компьютере. System.out.println(numbers.stream().parallel().map(i -> i*i).collect(toList())); } }

Вывод:

Лямбда выражения в Java

Что же такое лямбда выражения?

Всё просто. Это просто сокращенная форма реализации функц. интерфейса с помощью анонимного класса.

Реализовывать абстрактный метод функционального интерфейса с помощью анонимного класса не очень лаконично.

Поэтому лучше использовать лямбда выражения.

Пример программы:

import java.util.*; interface Adder{ public double pow(double a, double b); } class Lambda{ public static double func(Adder a){ return a.pow(2, 5); } public static void main(String[] args) { /* Вместо: System.out.println(func(new Adder() { public double pow(double a, double b){ return Math.pow(a, b); } })); */ // Проще сделать так: // Здесь a1, a2 это аргументы единственного // метода в функц. интерфейсе. После стрелочки // тело(реализация) функции единственного // метода в функц. интерфейсе. // То есть (a1, a2) это (double a, double b), // а {return Math.pow(a1, a2);} это собственно // реализация pow System.out.println(func((a1, a2)->{return Math.pow(a1, a2);})); // Еще проще через ссылку на метод(сокращенная // форма лямбда выражения вызывающего только // определенный метод) System.out.println(func(Math::pow)); } }

Вывод:

Функциональные интерфейсы в Java

В прошлом уроке мы видели интерфейс с одним единственным абстрактным методом.

Такой интерфейс называется функциональным.

Функциональный интерфейспредставляет одну абстрактную функцию, которая существует для ее переопределения анонимными классами или лямбда выражениями (позже разберем что это).

Открывает функциональное программирование. То есть функция используется как объект (ее можно хранить как объект, передать как объект аргументом в метод и т.д).

Пример программы:

import java.util.*; interface Adder{ public double pow(double a, double b); } //Разъясним на примерах что такое //функциональное программирование. class FuncInt{ public static void main(String[] args) { //В прошлом уроке мы не создавали ссылку //на однoразовый объект анонимного класса //в этом уроке создадим ее (adder). Adder adder = new Adder() { public double pow(double a, double b){ double result = a; for(int i=1; i < b; i++){ result *= a; } return result; } }; //Теперь созданный объект //не такой однoразовый как в прошлом уроке //так как мы создали ссылку adder и можем работать //с созданным объектом через нее. //Например вызовем метод pow через эту ссылку. System.out.println(adder.pow(3,6)); //И где же здесь функциональное программирование? //Спросите вы. В начале было упомянуто что //"функцию можно хранить как объект". //Что это значит? Это значит что мы сохранили //реализацию функции pow в объект, к которому мы //можем получить доступ через ссылку adder. //То есть мы фактически храним одну единственную //функцию в одном объекте. и мы можем пользоваться //этой функцией через этот объект. и эта //единственность функции достигается как раз //функциональным интерфейсом поскольку в нем может //быть всего одна функция. Спросите чем же так важна //эта единственность? На этот вопрос лучше всего //ответить аналогией. Мы же не храним //в переменной int два числа мы храним одно. //То есть например int a=5,b=59,c=91. //Каждая переменной хранится одно число и //также мы храним одну функцию в одном объекте. //В adder мы сохранили одну реализацию метода pow, //также можем создать adder1, в котором мы сохраним //другую реализацию метода pow. //Например ниже Adder adder1 = new Adder() { //другая реализация метода pow //с помощью библиотеки Math. public double pow(double a, double b){ //возвращаем a возведенное в степень b return Math.pow(a, b); } }; } }

Вывод:

Выше было упомянуто что функцию можно передать как обьект аргументом в метод.

Продемонстрируем это.

Пример программы:

import java.util.*; interface Adder{ public double pow(double a, double b); } class FuncInt1{ // Создадим какую-нибудь функцию, в которой будет // использоваться метод pow из функционального // интерфейса Adder. public static double func(Adder a){ return a.pow(2, 5) + 20; } public static void main(String[] args) { Adder adder = new Adder() { public double pow(double a, double b){ double result = a; for(int i = 1; i < b; i++){ result *= a; } return result; } }; Adder adder1 = new Adder() { public double pow(double a, double b){ return Math.pow(a, b); } }; // Очевидно, что мы можем передать функцию // в объекте adder или adder1 в метод func, // чтобы она там, в func, использовалась. System.out.println(func(adder)); // или System.out.println(func(adder1)); // То есть мы видим, что можем передать // в метод именно функцию, как например // числовую переменную или как сложный объект, // но мы передаем именно одну функцию. // И думаю, очевидно, почему программирование // называется функциональным: // Мы оперируем функциями. } }

Вывод:

Анонимные классы в Java

Анонимный класс одноразовая реализация интерфейса без создания полноценного класса.

То есть нам не нужно создавать много объектов, будет только один с данной реализацией.

Пример программы:

import java.util.*; interface Adder{ public double pow(double a, double b); } class AnonimousClass{ public static void main(String[] args) { // Как видим, мы передаем в метод println // объект на основе одноразовой реализации // интерфейса Adder и сразу пользуемся // этой реализацией, вызывая у этого объекта // только что реализованный pow // (то есть вызываем у реализованного // обьекта .pow(3,6)). System.out.println(new Adder() { public double pow(double a, double b){ double result = a; for(int i = 1; i < b; i++) result *= a; return result; } }.pow(3,6)); } }

Вывод:

Одноразовый он очевидно почему.

Потому что мы не создавали полноценный отдельный класс реализующий интерфейс, как мы делали это раньше. И мы даже не создавали ссылку для работы с созданным объектом, мы сразу вызвали у него .pow(3,6).

То есть вся эта реализация существовала для одной цели – вызова метода .pow(3,6) в методе println и всё. И больше нигде мы эту реализацию использовать не можем да и не должно быть нужно, она одноразовая.

Default методы в интерфейсах

В java 8 всё же появилась возможность добавлять реализации методов в интерфейс.

Нужно в основном для того чтобы у классов реализующих интерфейс не было слишком много одинаковых реализаций абстрактных методов интерфейса.

До java 8 приходилось делать так:

import java.util.*; interface Car { public void gas(); public void brake(); } class Sedan implements Car { @Override public void gas() { System.out.println(“Gas!”); } @Override public void brake() { System.out.println(“Stop!”); } } class Truck implements Car { // такая же как в прошлой реализации @Override public void gas() { System.out.println(“Gas!”); } // такая же как в прошлой реализации @Override public void brake() { System.out.println(“Stop!”); } } class PickUp implements Car { // такая же как в прошлой реализации @Override public void gas() { System.out.println(“Gas!”); } // такая же как в прошлой реализации @Override public void brake() { System.out.println(“Stop!”); } } // Как можно увидеть у всех реализаций // интерфейса Car одинаковые реализации gas и brake // что очевидно плохо class Ddefault { public static void main(String[] args) { Sedan sedan = new Sedan(); Truck truck = new Truck(); PickUp pickUp = new PickUp(); sedan.gas(); truck.gas(); pickUp.gas(); } }

Вывод:

Добавив дефолтные реализации gas и brake в интерфейс всем классам, которые реализуют Car больше не придется делать одинаковые реализации этих методов.

После java 8:                    

import java.util.*; // Определение интерфейса Car interface Car { // Дефолтные реализации методов. // Помечаются словом default public default void gas() { System.out.println(“Gas!”); } public default void brake() { System.out.println(“Stop!”); } } // Классы, реализующие интерфейс Car class Sedan implements Car { } class Truck implements Car { } class PickUp implements Car { } class Ddefault { public static void main(String[] args) { // Создание объектов классов Sedan sedan = new Sedan(); Truck truck = new Truck(); PickUp pickUp = new PickUp(); // Вызов метода gas() для каждого объекта sedan.gas(); truck.gas(); pickUp.gas(); } }

Вывод: