Сортировка объектов
Показанный ранее пример Arrays.sort() работает только для массивов примитивных типов данных, которые имеют порядок:
- естественный;
- числовой;
- символьный в таблице ASCII (двоичное число, представляющее символ).
У объектов может не быть естественного порядка сортировки, поэтому вам нужно предоставить другой объект, который может определять порядок ваших объектов. Такой объект называется компаратором – это интерфейс.
Вот первый класс для объектов, которые мы хотим отсортировать:
private static class Employee{ public String name; public int employeeId; public Employee(String name, int employeeId){ this.name = name; this.employeeId = employeeId; } }
Класс Employee – это простая модель сотрудника, у которого есть имя и идентификатор. Вы можете отсортировать массив объектов Employee по имени или по идентификатору сотрудника.
Вот первый пример сортировки массива объектов Employee по их имени с помощью метода Arrays.sort():
Employee[] employeeArray = new Employee; employeeArray = new Employee("Xander", 1); employeeArray = new Employee("John" , 3); employeeArray = new Employee("Anna" , 2); java.util.Arrays.sort(employeeArray, new Comparator() { @Override public int compare(Employee e1, Employee e2) { return e1.name.compareTo(e2.name); } }); for(int i=0; i < employeeArray.length; i++) { System.out.println(employeeArray.name); }
- Сначала объявляется массив.
- Три объекта Employee создаются и вставляются в массив.
- Метод Arrays.sort() вызывается для сортировки массива. В качестве параметра передаем массив employee и реализацию Comparator, которая может определять порядок объектов Employee. Это создает анонимную реализацию интерфейса Comparator.
В примере важно уловить реализацию метода compare() анонимной внутренней реализации интерфейса Comparator. Этот метод возвращает:
- положительное число, если первый объект «больше»(позже в порядке сортировки), чем второй объект;
- 0 – они «равны»(в порядке сортировки);
- отрицательное число, если первый объект «меньше» (ранее в порядке сортировки), чем второй объект.
В приведенном выше примере мы просто вызываем метод String.compare(), который выполняет для нас сравнение (сравнивает имена сотрудников).
После сортировки массива мы перебираем его и выводим имена сотрудников. Вывод:
Anna John Xander
Обратите внимание, как порядок был изменен по сравнению с порядком, в котором они были первоначально вставлены в массив. Сортировка объектов Employee по их идентификатору сотрудника на основании предыдущего примера с измененной реализацией метода compare() анонимной реализации интерфейса Comparator:
Сортировка объектов Employee по их идентификатору сотрудника на основании предыдущего примера с измененной реализацией метода compare() анонимной реализации интерфейса Comparator:
Employee[] employeeArray = new Employee; employeeArray = new Employee("Xander", 1); employeeArray = new Employee("John" , 3); employeeArray = new Employee("Anna" , 2); java.util.Arrays.sort(employeeArray, new Comparator() { @Override public int compare(Employee e1, Employee e2) { return e1.employeeId - e2.employeeId; } }); for(int i=0; i < employeeArray.length; i++) { System.out.println(employeeArray.name); }
Вывод:
Xander Anna John
Чтобы сравнить объекты Employee в массиве сначала по их имени, а если оно совпадает, то по их идентификатору сотрудника, реализация compare():
java.util.Arrays.sort(employeeArray, new Comparator() { @Override public int compare(Employee e1, Employee e2) { int nameDiff = e1.name.compareTo(e2.name); if(nameDiff != 0) { return nameDiff; } return e1.employeeId - e2.employeeId; } });
Example 1: How To Compare Two Set Values In Java
package com.javaprogramto.java8.compare.set; import java.util.HashSet; import java.util.Set; public class CompareTwoSetExample { public static void main(String[] args) { Set<String> set1 = new HashSet<>(); set1.add("Hello"); set1.add("Java Developer"); set1.add("Welcome"); set1.add("To"); set1.add("JavaProgramTo.com"); Set<String> set2 = new HashSet<>(); set2.add("Hello"); set2.add("Java Developer"); set2.add("Welcome"); set2.add("To"); set2.add("JavaProgramTo.com"); boolean isEquals = set1.equals(set2); System.out.println("Is set 1 and set 2 equal ? : "+isEquals); } }
Output:
Is set 1 and set 2 equal ? : true
In the above program, created two sets of objects and added the same values to it. After that invoked equals() method on set1 with argument set2.
As we know that values in both sets are the same so it returned true.
Методы класса String
У класса очень много методов: одних только конструкторов у него 18 штук! Поэтому ниже мы приведем только самые основные из них:
Методы | Описание |
---|---|
Возвращает количество символов в строке | |
Проверяет, что строка == пустая строка | |
Проверяет, что в строке — только whitespace-символы: пробел, tab, enter и т.п. | |
Возвращает символ, который стоит на index-позиции в строке. | |
Возвращает массив символов (копию), из которых состоит строка | |
Преобразует строку в набор байт и возвращает массив байт. | |
Разделяет строку на несколько подстрок. | |
Склеивает вместе несколько подстрок | |
Помещает строку в пул . |
Больше о конструкторах вы можете узнать из статьи Зачем нужен конструктор?
Давайте напишем программу, которая преобразовывает путь к файлу из Unix Style в Windows Style. Unix в качестве разделителя директорий использует символ , Windows — символ .
Решение 1 — использование массива char’ов
Код | Примечания |
---|---|
Создание объекта Scanner Чтение строки с консоли Преобразование строки в массив символов Цикл по символам Если символ равен , заменить его на . Не забываем про экранирование. Создаем новую строку на основе массива символов. Выводим строку на экран. |
Решение 2 — использование методов и :
Код | Примечания |
---|---|
Создание объекта Scanner Чтение строки с консоли Преобразование строки в массив строк. В качестве разделителя используется символ (дополнительные два слеша – это следствие двойного экранирования). Объединяем все строки из массива строк, в качестве разделителя используется символ (мы видим его экранированным). Выводим строку на экран. |
Решение 3 — использование метода :
Код | Примечания |
---|---|
Создание объекта Scanner Чтение строки с консоли Просто заменяем один символ на второй (второй — экранирован) Выводим строку на экран. |
Устройство класса String
Сегодня мы поговорим о классе . Класс String — самый популярный класс в Java после типа int. Он используется абсолютно везде. У него есть куча полезных методов, которые лучше знать, чем не знать.
Класс — единственный класс, кроме примитивных типов, литералы которого можно использовать в ; компилятор по-особому обрабатывает сложение строк и объектов; объекты по-особому хранятся в памяти. В общем, класс — это очень специфический класс.
Также у класса есть куча классов-сателлитов, цель которых — еще больше упростить работу со строками в Java. Когда вы изучите все это, вам действительно станет значительно проще делать многие вещи. Ну а начнем мы с самого сердца этой экосистемы — с устройства класса .
Массив символов
А устроен класс на самом деле очень просто: внутри него находится массив символов (char), который хранит все символы строки. Вот так, например, хранится слово «Привет»:
Важно!
На самом деле все немного не так. Т.к. класс очень важен, в нем используется очень много оптимизаций, и данные хранятся внутри не в виде массива символов, а просто в виде массива байтов.
Сравнение с помощью == и equals в Java
Приведем пример программного кода:
Java
String s1 = new String(«vscode.ru»);
String s2 = new String(«vscode.ru»);
System.out.println(s1 == s2); //каким здесь будет результат сравнения?
System.out.println(s1.equals(s2)); //а здесь?
1 |
Strings1=newString(«vscode.ru»); Strings2=newString(«vscode.ru»); System.out.println(s1==s2);//каким здесь будет результат сравнения? System.out.println(s1.equals(s2));//а здесь? |
Но, в чем же разница между этими двумя записями? Какое отличие между оператором сравнения == и методом equals?
Все очень просто. Метод equals в Java при сравнении проверяет и сопоставляет само содержимое объектов (их значения) и на основе этого делает заключение равны они (true) или нет (false).
Оператор == (в случае с примитивными типами данных) сравнивает значения переменных и возвращает результат, НО в случае со ссылочными типами данных (объекты, массивы и т.д.) сравнивает ссылки на объекты в памяти компьютера, и на основании равенства или неравенства ссылок возвращает результат (true или false). Вот в чём отличие метода equals и оператора ==.
Вы можете почитать подробную статью про оба вида типов данных и их различия в соответствующей статье.
Метод equals — это метод класса Object. Каждый объект неявно унаследован от класса Object и они могут вызывать метод equals.
Возвращаясь к примеру, приведенному в начале подраздела, можно сделать вывод о том, каким будет результат операции сравнения в обоих случаях:
Java
System.out.println(s1 == s2); //возвращено false. Сравниваются ссылки, а они различны
System.out.println(s1.equals(s2)); //возвращено true, поскольку значения объектов равны (они идентичны)
1 |
System.out.println(s1==s2);//возвращено false. Сравниваются ссылки, а они различны System.out.println(s1.equals(s2));//возвращено true, поскольку значения объектов равны (они идентичны) |
Стоит понимать, что вместо класса String, мог быть объявлен любой другой ссылочный тип данных (Object, ArrayList<>, ваш пользовательский класс и т.д.).
Принцип неизменности класса String.
Чтобы понять неизменность класса String, сначала посмотрите, какие переменные-члены находятся в классе String. В JDK1.8 переменные-члены String в основном имеют следующее:
Прежде всего, вы можете видеть, что класс String использует модификатор final, указывая на то, что класс String не наследуется. Затем мы в основном сосредотачиваемся на значении переменной-члене класса String. Это значение имеет тип char [], поэтому объект String фактически инкапсулируется с этим массивом символов. Если посмотреть на модификатор значения, используется private, а метод setter не предоставляется. Следовательно, значение не может быть изменено за пределами класса String, а значение также изменяется с помощью final. Тогда значение не может быть изменено внутри класса String, но приведенное выше является окончательным. В содержании измененных переменных ссылочного типа упоминалось, что это может только гарантировать, что значение не может указывать на другие объекты, но состояние объекта, на который указывает значение, может быть изменено.Посмотрев на исходный код класса String, мы можем обнаружить, что класс String является неизменным.Ключ состоит в том, что инженеры компании SUN очень осторожны, чтобы не перемещать элементы в массиве символов во всех стоящих за ним методах String. Таким образом, ключ к неизменности класса String заключается в базовой реализации, а не только в финале.
Что такое Java Stream API
Это новый инструмент языка Java, который позволяет использовать функциональный стиль при работе с разными структурами данных.
Для начала стриму нужен источник, из которого он будет получать объекты. Чаще всего это коллекции, но не всегда. Например, можно взять в качестве источника генератор, у которого заданы правила создания объектов.
Данные в стриме обрабатываются на промежуточных операциях. Например: мы можем отфильтровать данные, пропустить несколько элементов, ограничить выборку, выполнить сортировку. Затем выполняется терминальная операция. Она поглощает данные и выдает результат.
Порядок выполнения операторов
Когда в выражении несколько логических операторов, результат вычисляется с учётом их приоритета. Если нет логических скобок, то операции выполняются в таком порядке:
- ! (NOT)
- & (AND)
- ^ (XOR)
- | (OR)
- && (условный AND)
- || (условный OR)
Если одинаковые операции стоят по соседству, то раньше выполняется та, что левее.
Первый пример
Вычислим true ^ true & false:
- Выбираем самый приоритетный оператор (если таких больше одного — тот, что левее). У нас самый приоритетный & (он здесь такой один).
- Смотрим, что слева и справа от него: это true и false соответственно.
- Вычисляем выражение true & false — получаем false.
- В исходном выражении заменяем true & false результатом его вычисления (false) — и получаем: true ^ false.
- Вычислив это выражение, получаем результат true.
Или короче:
- true ^ true & false
- true ^ false
- true
Второй пример
Заменим & на &&:
Теперь самый приоритетный оператор в выражении это ^ — и порядок вычислений будет уже другой:
- true ^ true && false
- false && false
- false
Эффективная сортировка
Пирамидальная сортировка (англ. Heapsort, «Сортировка кучей»)
Пирамидальная сортировка является методом сортировки, который интерпретирует элементы в массиве, как почти полное бинарное дерево.
Она берет элементы массива и вставляет их в пирамиду.
После построения пирамиды, из нее по очереди удаляются наибольшие элементы и вставляются в конец массива, где и находятся в отсортированном виде.
Общее время сортировки расчитывается по O(N logN) для N элементов.
package com.topjavatutorial; public class ExampleHeapSort { public static void main(String[] args) { // TODO Auto-generated method stub int[] numbers = { 12, 2, 15, 56, 23, 78, 45, 34, 16, 91, 53, 27 }; heapsort(numbers); for (int h = 0; h < numbers.length; h++) System.out.print(numbers+ " "); } // sort num to num public static void heapsort(int[] a) { for (int i = a.length / 2 - 1; i >= 0; i--) // convert the array to a heap shiftDown(a, i, a.length); for (int i = a.length - 1; i > 0; i--) { swap(a, 0, i); /* deleteMax */ shiftDown(a, 0, i); } } // end heapSort private static void shiftDown(int[] a, int i, int n) { int child; int tmp; for (tmp = a; leftChild(i) < n; i = child) { child = leftChild(i); if (child != n - 1 && (a < a)) child++; if (tmp < a) a = a; else break; } a = tmp; } private static int leftChild(int i) { return 2 * i + 1; } // swap numbers public static void swap(int[] numbers, int i, int j) { int temp = numbers; numbers = numbers; numbers = temp; } } // end class ExampleHeapSort
Вывод: 2 12 15 16 23 27 34 45 53 56 78 91
Сортировка слиянием
Сортировка слияние один из наиболее популярных алгоритмов в Java так как использует наименьшее количество сравнений.
Сортировка слиянием используется в стандартных Java библиотеках для сортировки generic.
Идеей сортировки слиянием является то, что происходит слияние двух отсортированных списков.
Сортировка слиянием занимает O(nlogn).
Высокоуровневое представление о сортировке слиянием:
Start merge sort sort first half (recursive) sort second half(recursive) merge sorted halves into one sorted list End sort
Сортировка слиянием в Java:
package com.topjavatutorial; public class ExampleMergeSort { public static void main(String[] args) { // TODO Auto-generated method stub int[] num = { 3,6,1,7,2,8,10,4,9,5}; int n = num.length; mergeSort(num, 0, n - 1); for (int h = 0; h < n; h++) System.out.print(num+ " "); } /* * Internal method that makes recursive calls to sort the data * elements is the array of elements to be sorted * low is the left most position of the array * high is the right most position of the array */ public static void mergeSort(int[] elements, int low, int high) { if (low < high) { // list contains at least 2 elements int mid = (low + high) / 2; mergeSort(elements, low, mid); // recursion : sort first half mergeSort(elements, mid + 1, high); // recursion : sort second half merge(elements, low, mid, high); // merge both sorted halves } } /* * Merge sorted array of elements from low to mid and mid+1 * low is the left most position of the subset of elements * high is the right most position of the subset of elements */ private static void merge(int[] subset, int low, int mid, int high) { int n = high-low+1; int[] Temp = new int; int i = low, j = mid + 1; int k = 0; while (i <= mid || j <= high) { if (i > mid) Temp = subset; else if (j > high) Temp = subset; else if (subset < subset) Temp = subset; else Temp = subset; } for (j = 0; j < n; j++) subset = Temp; } // end merge }
Вывод: 1 2 3 4 5 6 7 8 9 10
Быстрая сортировка
Быстрая сортировка это алгоритм быстрой сортировки. Его среднее время O(N logN), наихудшее O(N²).
package com.topjavatutorial; public class ExampleQuickSort { public static void main(String[] args) { // TODO Auto-generated method stub int[] numbers = {3,6,1,7,2,8,10,4,9,5}; int n = numbers.length; quicksort(numbers, 0, n-1); for (int h = 0; h < n; h++) System.out.print(numbers+ " "); } // Quick sort algorithm public static void quicksort(int[] numbers, int low, int high) { if (low < high) { int dp = partition(numbers, low, high); quicksort(numbers, low, dp-1); quicksort(numbers, dp+1, high); } } // partition numbers to numbers using numbers as the pivot private static int partition(int[] numbers, int low, int high) { int pivot = numbers; int i = low; for (int j = low + 1; j <= high; j++) if (numbers < pivot) { ++i; swap(numbers, i, j); } //end for swap(numbers, low, i); return i; } // Exchange list and list values private static void swap(int[] list, int i, int j) { int temp = list; list = list; list = temp; } }
Вывод: 1 2 3 4 5 6 7 8 9 10
Сравнение примитивных персонажей
Мы можем сравнивать примитивные символы, используя метод compare () или используя реляционные символы. Операторы например, операторы <,> или =.
Использование метода compare ()
Метод compare () принадлежит к классу Character и сравнивает два символа численно. Ниже приведен синтаксис метода compare ().
public static int compare(char x, char y);
Параметры: этот метод принимает два символа, которые необходимо сравнить.
Возвращаемое значение: этот метод возвращает одно из следующих значений в результате сравнения.
- 0, если оба символа равны
- отрицательное значение (число меньше 0), если 1-й символ меньше второго символа, т.е. x <y
- положительное значение (число больше 0), если 1-й символ больше второго, т. е. x> y
Пример:
В приведенном ниже примере показано, как сравнить два символа в Java с помощью метода compare (). Здесь символ f меньше символа r. следовательно, метод возвращает отрицательное значение.
public class CompareChar { public static void main(String[] args) { char a = 'f'; char b = 'r'; int x = Character.compare(a, b); if(x>0) System.out.println(a + " is greater than " + b); else if(x<0) System.out.println(a + " is lesser than " + b); else System.out.println(a + " and " + b + " are equal"); } }
f is lesser than r
Использование операторов отношения
Мы можем использовать реляционные операторы например, <,> или = для сравнения символов в Java. Но мы можем использовать это для сравнения только примитивных символов. В приведенном ниже примере показано, как использовать реляционные операторы для сравнения двух символов в Java. Это самый простой метод, поскольку он не требует никаких классов или методов.
public class CompareChar { public static void main(String[] args) { char a = 's'; char b = 'g'; if(a<b) System.out.println(a + " is lesser than " + b); else if(a>b) System.out.println(a + " is greater than " + b); else System.out.println(a + " and " + b + " are equal"); } }
s is greater than g
Ниже приведен еще один пример, в котором сравниваются два символа, значение которых одинаково.
public class CompareChar { public static void main(String[] args) { char a = 's'; char b = 's'; if(a<b) System.out.println(a + " is lesser than " + b); else if(a>b) System.out.println(a + " is greater than " + b); else System.out.println("Both characters are equal"); } }
Both characters are equal
Исключения (Exception)
В мире программирования возникновение ошибок и непредвиденных ситуаций при выполнении программы называют исключением. Они могут возникать в результате неправильных действий пользователя, отсутствии необходимого ресурса на диске или потери соединения с сервером по сети. Причинами исключений при выполнении программы также могут быть ошибки программирования или неправильное использование API. Ваша программа должна чётко знать, как поступать в такой ситуации. Для этого в Java предусмотрен механизм исключений.
При возникновении ошибки в процессе выполнения программы JVM создаёт объект нужного типа из иерархии исключений Java — множества возможных исключительных ситуаций, унаследованных от общего «предка» — класса Throwable. Исключение можно также создать вручную с помощью оператора throw. При этом выполнение основного кода программы прерывается, а обработчик исключений JVM пытается найти способ обработать исключение.
Блоки кода, для которых предусмотрена обработка исключений в Java, создаются с помощью конструкций try{}catch, try{}catch{}finally, try{}finally{}.
При возбуждении исключения в блоке try обработчик исключения ищется в следующем за ним блоке catch. Если в catch есть обработчик этого типа исключения, управление переходит к нему. Если нет, то JVM ищет обработчик этого типа исключения в цепочке вызовов методов до тех пор, пока не будет найден подходящий catch.
После выполнения блока catch управление передаётся в необязательный блок finally. Если подходящий блок catch не найден, JVM останавливает выполнение программы и выводит стек вызовов методов (stack trace), выполнив перед этим код блока finally при его наличии.
Материал по этому разделу можно найти в книгах:
- «Java. Библиотека профессионала. Том 1. Основы» — глава 7;
- «Java 8. Полное руководство» — глава 10.