Java

Часть 1, Часть 2, Часть 3, Часть 4

Данные статьи помогут легко и быстро разобраться в концепциях и программировании на Java. Даже при нулевых знаниях в Java трудностей в освоении этих материалов не возникнет. А опытные Java-разработчики смогут освежить свои знания.

Неизменяемость

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

  • Примеры неизменяемых классов: String, все классы-оболочки и enum.
  • Неизменяемые классы всегда потокобезопасны.

Как создать неизменяемый класс:

1. Убедитесь, что класс нельзя переопределить, — объявите класс final. (прим. ред. Класс, объявленный с модификатором final, не может иметь подклассов и исключает наследование.)

2. Все поля должны быть закрытыми.

3. Не добавляйте методов, которые могут изменять состояние объекта. Никаких сеттеров.

4. Используйте клонирование или защитное копирование.

  • BigDecimal не относится к неизменяемым классам, т.к. не является конечным.

String, StringBuffer и StringBuilder

  • String — это неизменяемый класс. И объекты String изменять нельзя. При присвоении нового значения в стеке каждый раз создается новый объект String, и указатель обращается к новому объекту.
  • Пул строк (String intern pool) — это специальная область памяти в Java Heap. Если строка уже создана или присутствует в пуле, то будет возвращаться именно ее ссылка. Новый объект не создается, и ссылка на него не возвращается.

Вопрос: Почему String относится к неизменяемым?

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

2. Кеширование хеш-кода: Если строка не является неизменяемой, то можно изменить ее хеш-код. А это не годится для кеширования.

3. Безопасность: String активно используется в качестве параметра для многих Java классов (сетевые подключения, открытие файлов и т.д.). В изменяемой строке возникают угрозы безопасности, т.к. ее значения могут перехватить другие участки кода.

Сравнение строк:

String a = “abcd”;

String b = “abcd”;

System.out.println(a == b); // True

System.out.println(a.equals(b)); // True

String c = new String(“abcd”);

String d = new String(“abcd”);

System.out.println(c == d); // False

System.out.println(c.equals(d)); // True

== проверяет участки памяти; equals() проверяет значения. Использование конструктора приводит к образованию лишнего объекта. Поэтому для создания одной строки применяются двойные кавычки.

  • В String имеются следующие методы: concat(), trim(), substring() и replace().
  • StringBuffer и StringBuilder — изменяемы.
  • StringBuilder добавлен в версии 1.5, не синхронизирован.
  • Метод intern() — это ссылочное значение String, т.е. адрес.

Поэтому S1.intern() == S2.intern() только при истинности S1.equals(S2).

Сериализация

  • Сериализация — это процесс сохранения состояния объекта. Сам объект запоминается в виде последовательности байтов, а затем заново «воссоздается» из этих байтов.
  • Выполняется через реализацию интерфейса Serializable. Это маркерный интерфейс.
  • Поля, отмеченные как переходные, не доступны для сериализации.
  • serialVersionUID — добавляет номер версии. Так можно проверить, что сериализированный объект не был изменен после десериализации.
  • В сериализации задействованы методы writeObject() и readObject(). Их можно переопределять и перенастраивать.
  • При экстернализации (т.е. в интерфейсе Externalizable) используются методы readExternal() и writeExternal().

Comparator и Comparable

  • Интерфейс Comparable сравнивает текущий объект с другим объектом через метод CompareTo().
  • Интерфейс Comparator сравнивает два разных объекта. Использует метод Compare().

Синтаксис метода:

CompareTo(Object obj)
Compare(Object obj1, Object obj2)
  • Comparator более управляемый.
  • Collections.sort(list) для Comparable и Collections.sort(list, new comparatorObject()) для Comparator.
  • Comparable реализуется классом для сравнения собственного объекта с другими.
class HDTV implements Comparable<HDTV> {

private int size;

private String brand;

public HDTV(int size, String brand) {

this.size = size;

this.brand = brand;

}

// .. getters & setters

@Override

public int compareTo(HDTV tv) {

if (this.getSize() > tv.getSize())

return 1;

else if (this.getSize() < tv.getSize())

return -1;

else

return 0;

}}

public class Main {

public static void main(String[] args) {

HDTV tv1 = new HDTV(55, “Samsung”);

HDTV tv2 = new HDTV(60, “Sony”);

if (tv1.compareTo(tv2) > 0) {

System.out.println(tv1.getBrand() + “ is better.”);

} else {

System.out.println(tv2.getBrand() + “ is better.”);

}

}}

Бывают случаи, когда не стоит изменять класс и делать его сопоставимым.

class SizeComparator implements Comparator<HDTV> {

@Override

public int compare(HDTV tv1, HDTV tv2) {

int tv1Size = tv1.getSize();

int tv2Size = tv2.getSize();

if (tv1Size > tv2Size) {

return 1;

} else if (tv1Size < tv2Size) {

return -1;

} else {

return 0;

}

}}

public class Main {

public static void main(String[] args) {

HDTV tv1 = new HDTV(55, “Samsung”);

HDTV tv2 = new HDTV(60, “Sony”);

HDTV tv3 = new HDTV(42, “Panasonic”);

ArrayList<HDTV> al = new ArrayList<HDTV>();

al.add(tv1);

al.add(tv2);

al.add(tv3);

Collections.sort(al, new SizeComparator());

for (HDTV a : al) {

System.out.println(a.getBrand());

}

}}

Коллекции

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

  • Коллекция представляет собой интерфейс для расширения списков, множеств и очередей.
  • Класс Collections содержит статические служебные методы.
  • Для работы со структурами данных, объем которых известен заранее, лучше брать Array. Он работает быстрее, чем ArrayList или Vector. Массивы не могут «разрастаться» как списки.
  • ArrayList и Vector — это специальные структуры данных, которые используют Array и ряд полезных методов: add()remove() и т.д. Поэтому такой массив может разрастаться и сокращаться при необходимости.
  • ArrayList поддерживает индексированный поиск с методами indexOf() и lastIndexOf().
  • Vector синхронизируется и является потокобезопасным. Тем не менее, для синхронизации лучше брать ArrayList и код ниже:
List mylist = Collections.synchronizedList(mylist); 
// Single lock for the entire list
  • Интерфейс Iterator используется для циклического прохода по всей коллекции в прямом направлении.
  • ListIterator расширяет Iterator и поддерживает двунаправленное прохождение.

Классы Collection являются так называемыми «fail first», то есть при изменении значения одного потока в процессе движения по другому, выбрасывается ConcurentModificationException. Это исключение распространяется и на SynchronizedList с SynchronizedMap, т.к. они являются условно потокобезопасными. Это означает, что потокобезопасными считаются лишь отдельные операции, а не весь процесс. Поэтому смело берите блок Synchronize, ConcurentHashMap, или CopyOnWriteArrayList. ConcurentHaspMap, CopyOnWriteArrayList и CopyOnWriteArraySet считаются потокобезопасными и доступными для синхронизации.

  • HashMap работает по принципу хеширования (Hashing).
  • Простейшая форма хеширования — это некий способ присвоения уникального кода для любой переменной или объекта после применения формул или алгоритмов к их свойствам.
  • В HashMap присутствует внутренний класс Entry для хранения ключа и преобразованного значения.

Хеш-значение вычисляется с помощью хеш-кода ключа и вызова его метода hashCode(). Это значение требуется для расчета индекса массива при хранении объекта Entry. Разработчики JDK логично предположили, что на практике могут проявляться плохо написанные функции hashCode(), которые будут возвращать слишком большие или малые значения хеш-кода. Поэтому была придумана еще одна hash() функция. Теперь хеш-код объекта, передаваемый в эту функцию, выводит значение только в пределах индекса массива.

  • MapReduce содержит два ключевых компонента — Map и Reduce. Функция Map применяется к набору входных значений для вычисления пары «ключ/значение». Reduce берет эти значения и применяет к ним еще одну функцию.

Рекомендуется использовать Collections.EMPTY_LIST/EMPTY_SET вместо null. Пример:

List testList = Collections. EMPTY_LIST;
  • А лучше всего в качестве ключей в Map брать неизменяемые объекты.

Arrays.asList(T… a) возвращает java.util.Arrays.ArrayList, а не java.util.ArrayList. Это представление исходного массива Array.

Сравнение ArrayList

Collection<String> listOne = new ArrayList(Arrays.asList(“a”,”b”, “c”,”g”));

Collection<String> listTwo = new ArrayList(Arrays.asList(“a”,”b”,”d”, “e”));

List<String> sourceList = new ArrayList<String>(listOne);

List<String> destinationList = new ArrayList<String>(listTwo);

sourceList.removeAll( listTwo ); // Result: [c, g]

destinationList.removeAll( listOne ); // Result: [d, e]

Различия интерфейсов ITERATOR и ENUMERATION: В Iterator есть дополнительный метод remove().К тому же, он позволяет вызывающему оператору удалять элементы из основной коллекции при итерации по определенной семантике.

Вопрос: Как сделать reverse объектов TreeMap?

С помощью Collections.reverseOrder()

Map tree = new TreeMap(Collections.reverseOrder());

Вопрос: Можно ли добавлять гетерогенные объекты в TreeMap?

Нет. Упорядоченные коллекции не допускают включения гетерогенных элементов, т.к. их нельзя сравнить. На деле ничего страшного нет — сопоставимость реализована в их классах.

Вопрос: Есть ли разница между int[] x и int x[]?

Между ними нет разницы. Оба способа отлично подходят для объявления массива.

Иерархия затрат памяти:

ArrayList < LinkedList < HashTable < HashMap < HashSet

  • Contains() использует линейный поиск.
  • HashMap разрешает один нулевой (null) ключ и несколько нулевых значений. В HashTable, наоборот, нулевые значения запрещены.
  • LinkedHashMap отлично предотвращает конфликты, например, в кэше LRU.
  • ConcurentHashMap — лучше HashTable.
  • Сортируйте HashMap по ключам и значениям.

Guava

Guava — это одна из основных Java-библиотек от Google с добавленными классами и интерфейсами коллекций.

  1. Добавлены MultiSet и UniqueList. В обычном списке (List) элементы упорядочены, повторяющиеся значения разрешены. В UniqueList список упорядочен, однако повторяющиеся значения запрещены. Обычный набор данных (Set) — повторяющиеся значения запрещены, элементы не упорядочены. В MultiSet элементы также не упорядочены, но повторы разрешены.
  2. Добавлены ImmutableList, ImmutableSet, ImmutableSortedSet и ImmutableMap.
  3. Еще добавлены ImmutableMultiSet, HashMultiSet, LinkedHashMultiSet, TreeMultiSet, EnumMultiSet и MultiMap.
  4. В MultiMap поддерживаются отношения «ключ-значение» и «многие-ко-многим». В стандартном Java присутствует только отношение «один-ко-многим».

История версий Java

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

  • 1.4: Добавлены системы входа/выхода (I/O), масштабируемость. Улучшена производительность.
  • 1.5: Добавлены обобщения (generic) — коллекции, Iterator и т.д. Также добавлены циклы, автоупаковка/распаковка, аннотации, перечисления (enum) и статический импорт.
  • 1.6: Полная поддержка Windows Vista. Более быстродействующая система. Улучшения по части компиляции. Добавлена XML-обработка веб-сервисов.
  • 1.7: Добавлен объект String в операторе switch, несколько исключений в одном блоке catch и т.д.
  • 1.8:

Лямбда-выражения: Это метод без объявления, который возвращает имя и значение. Позволяет не тратить время на объявление и написание отдельного метода для содержащего класса. Пример: модификатор доступа.

Функции рассматриваются как аргумент метода, а код — как некие данные.

MathOperation addition = (int a, int b) -> a + b;

addition(a,b);

Интерфейсы даты и времени: Текущее время представлено классом Clock. Это абстрактный класс, поэтому экземпляры класса в нем не создаются. Статический метод systemUTC() возвращает текущее время.

import javax.time.Clock;

Clock clock = Clock.systemUTC();

Clock clock = Clock.systemDefaultZone();

ZoneId zone = ZoneId.of(“Europe/Berlin”);
// ZoneId zone = ZoneId.systemDefault(); you can also use this

Clock clock = Clock.system(zone);
 
import javax.time.LocalDate;

LocalDate date = LocalDate.now();

Стрим (stream)— это «одноразовый» объект. После прохождения объекта повторить это действие невозможно. По мере прохождения стримы можно фильтровать или выполнять операции Map/Reduce. Пример последовательного стрима:

List <Person> people = list.getStream.collect(Collectors.toList());

Using a parallel stream:

List <Person> people = list.getStream.parallel().collect(Collectors.toList());

Перевод статьи Madhu Pathy : A Beginner’s Guide to Java: Part 2 of 4

Предыдущая статьяРешение алгоритмических проблем: Поиск повторяющихся элементов в массиве
Следующая статьяКак я создал свою первую видеоигру