Вы достигли нового уровня

Уровень 21

1. Приоритет операторов

- Привет, Билаабо!

- Билаабо рад приветствовать друга!

Билаабо сегодня расскажет о приоритете операторов. Но сначала он расскажет о самих операторах.

- А что это такое – эти операторы?

На самом деле ты уже знаком с ними, просто, возможно, не знал, что это – операторы.

Вот, например, есть выражение c = a + b;

Тут есть два оператора: оператор сложения и оператор присваивания.

- Т.е. операторы – это просто математические знаки? Как умножение, деление и сложение?

- Ага, так и есть. Хотя есть и отличия.

Я не буду давать определение операторов: от этого ты умнее не станешь. Лучше посмотрим их на деле. Операторы можно разбить на несколько групп, и мы сейчас их рассмотрим.

1) Группа «математические операторы»

ОбозначениеПримерНазваниеОписание (что делает)
+ a + b Оператор сложения Складывает два числа.
- c - d Оператор вычитания Вычитает второе число из первого.
* a * t Оператор умножения Умножает два числа.
/ a / b Оператор деления Делит первое число на второе.
% c % d Остаток от деления Вычисляет остаток от деления первого числа на второе.
- -a Одинарный минус Меняет знак переменой наобратный.
Плюс на минус, минус на плюс.
+ +a Одинарный плюс Ничего не меняет. Добавлен вместе с одинарным минусом для красоты.

- Это я и со школы знаю – мне залили школьный курс вместе с прошивкой.

А что это еще за процент такой волшебный?

- Это «остаток от деления». Если 11 делить нацело на 5, то получим 2 и остаток от деления – 1. Вот этот 1 и можно получить, если записать 11 % 5;

При делении целых чисел в Java результат тоже будет целым числом. Остаток от деления просто отбрасывается. Если разделить 8 на 5, то получим 1.

ВыражениеОтвет 
19 / 10 1 Если делить нацело 19 на 10, получим 1 и 9 в остатке.
19 % 10 9 Если делить нацело 19 на 10, получим 1 и 9 в остатке.
2 / 5 0 Если делить нацело 2 на 5, получим 0 и 2 в остатке.
16 % 2 0 Если делить нацело 16 на 2, получим 8 и 0 в остатке.

- А зачем вообще нужен этот остаток от деления?

- Допустим тебе нужно проверить – четное число а или нет, тогда это можно записать так

 
if (a % 2 == 0)

А если нужно проверить, что b – нечетное, тогда это можно записать так

 
if (b % 2 == 1)

Или, например, делится ли d нацело на 3:

 
if (d % 3 == 0)

- Интересно, я это запомню.

2) Группа «операторы сравнения»

ОбозначениеПримерНазваниеОписание (что делает)
< a < b Меньше Проверяет, что a меньше b.
<= c <= d Меньше или равно Проверяет, что c меньше или равно d.
> a > b Больше Проверяет, что a больше b.
>= c >= d Больше или равно Проверяет, что c больше или равно d.
== i == j Равно Проверяет, что i равно j.
!= a != 0 Неравно Проверяет, что a неравно нулю.

- Я этим всем уже пользовался.

- И какое же главное отличие этих операторов от математических операторов?

- Если мы складываем два числа, то получаем число, а если сравниваем два числа – получаем true или false.

- Верно. Результатом сравнения будет «логическое значение» или попросту одно значение из пары (true, false), которые, как ты знаешь, являются значениями типа boolean.

Иногда еще говорят, что результатом сравнения будет «логическое значение», подразумевая, что будет значение типа boolean.

- Да, тут все понятно, я это уже знаю.

3) Группа «логические операторы»

ОбозначениеПримерНазваниеОписание (что делает)
&& a && b AND – «и» Результат выражения true, только когда и a и b равны true.
|| c || d OR – «или» Результат выражения true, если a или b равно true. Оба или хотя бы один.
! !a NOT – «не» Результат выражения true, только если a равно false.

- Логические операторы можно применять только к переменным или выражениям логического типа.

ПримерОписание
boolean a = true;
boolean b = true;

if (a && b)
Условие if истинно (true), если оба значения истинны.
Т.е. если a и b равны true, то результат – true.
boolean a = true;
boolean b = false;

if (a || b)
Условие if истинно (true), если хотя бы одно значение - истина.
Т.е. если a или b равны true, то результат – true.
boolean b = false;

if (!b)
Условие if истинно (true), если b - не истина.
Т.е. если b равно false, то результат – true.
int a=2, b=3, c=4;

if (a<b && a<c)
эквивалентно
if ((a<b) && (a<c))
Если a меньше b и a меньше c, то результат выражения – истина.
a,b,c – целые числа, но результат сравнения целых чисел – это уже логическое значение (true, false) а значит мы можем использовать логические операторы.

- Все это я и так знаю.

- Да? Тогда продолжим.

4) Группа – «побитовые операторы»

ОбозначениеПримерНазваниеОписание (что делает)
& a & b AND – «и» Побитовое «И»
| c | d OR – «или» Побитовое «ИЛИ»
~ ~a NOT – «не» Побитовое «НЕ»
^ a^b XOR – «исключающее или» Побитовое «ИСКЛЮЧАЮЩЕЕ ИЛИ»

- Побитовые операторы выполняю операцию над целыми числами побитово.

- Это как?

- Каждое число представляется в виде набора бит, а затем вычисляется результат по такой схеме:

Если первый бит в обоих числах равен 1, то первый бит результата будет 1.

Если второй бит в обоих числах равен 1, то второй бит результата будет 1. И так далее

- Это верно для всех побитовых операторов?

- Тут все намного проще. Бит может принимать только два значения – 0 и 1, так?

- Да.

- Тогда представь, что 1 – это true, а 0 – это false. Тогда операции над отдельными битами будут практически такими же, как и логические операции:

Логическое выражениеБитовое выражение
true && true == true 1&1 == 1
true && false == false 1&0 ==0
true || true == true 1|1 ==1
true || false == true 1|0 ==1
false || false = false 0|0 ==0
!false == true ~0 ==1
!true == false ~1 == 0

- О! Так все же очень просто.

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

- Да, я помню: первый бит одного числа взаимодействует с первым битом второго, и результат записывается также в первый бит. Для остальных битов – аналогично.

- Верно. Есть еще вопросы?

- А что это за XOR еще такой – «исключающее или»?

- Тут тоже все просто: когда значение разные – истина (true, 1), когда одинаковые – (false, 0)

Логическое выражениеБитовое выражение
true XOR true == false 1 ^ 1 ==0
false XOR false == false 0 ^ 0 ==0
true XOR false == true 1 ^ 0 ==1
false XOR true == true 0 ^ 1 ==1

Вот тебе еще пара примеров побитовых операций:

ПримерЧисла в битовом видеОтвет в битовом видеОтвет
5 & 3 00000101 & 00000011 00000001 1
7 & 2 00000111 & 00000010 00000010 2
5 | 9 00000101 | 00001001 00001101 13
5 ^ 9 00000101 ^ 00001001 00001100 12
~9 ~00001001 11110110 246

- Спасибо, Билаабо. Буду знать.

- Есть еще одна группа побитовых операторов – операторы сдвига:

5) Группа «операторы сдвига»

ОбозначениеПримерНазваниеОписание (что делает)
>> a>>b сдвиг вправо Сдвигает биты числа a, на b разрядов вправо.
<< c<<d сдвиг влево Сдвигает биты числа c, на d разрядов влево.
>>> a>>>2 сдвиг вправо с заполнением нулем Сдвигает биты числа a, на 2 разряда вправо.

- Это что еще за уличная магия?

- На самом деле тут все просто. Вот смотри:

ПримерЧисла в битовом видеОтвет в битовом видеОтвет
10>>1 00001010>>1 00000101 5
10>>2 00001010>>2 00000010 2
10<<1 00001010<<1 00010100 20
10<<2 00001010<<2 00101000 40

Сдвига разрядов числа на 1 влево – то же самое, что умножить число на 2. На два разряда равно умножению на 4, на три разряда – умножению на 8 и так далее.

Сдвиг вправо – соответственно, деление на 2,4,8,16,… и так далее.

- А в чем отличие операторов «>>>» и «>>»?

- Они отличаются при работе с отрицательными числами. Дело в том, что знак отрицательного числа хранится в крайнем левом бите. И при сдвиге вправо число перестает быть отрицательным. Поэтому и придумали два различных оператора, вот смотри:

- На самом деле тут все просто. Вот смотри:

ВыражениеРезультатОписание
10001010>>1 11000101 Отрицательное число остается отрицательным.
Новые разряды заполняются 1, для отрицательных чисел.
10001010>>2 11100010
10001010>>3 11110001
10001010>>>1 01000101 Отрицательное число перестает быть отрицательным. Новые разряды заполняются 0, для отрицательных чисел.
10001010>>>2 00100010
10001010>>>3 00010001

Сдвиг не циклический. Биты, которые оказались за краем числа слева или справа, просто отбрасываются.

6) Группа «операторы присваивания»

- Присваивание я уже знаю. А почему ты говоришь операторы?

- Потому что их несколько ☺

ОператорЧто он значит
a += b; a = a + b;
a -= b; a = a - b;
a *= b; a = a * b;
a %= b; a = a % b;
a |= b; a = a | b;
a &= b; a = a & b;

Думаю, логика тебе ясна.

7) Группа «операторы инкремента и декремента»

ЗаписьПримерОписание
++ a++;
++b;
Увеличивает число на 1.
-- d--;
--i;
Уменьшает число/переменную на 1.

- А в чем отличие, когда два минуса спереди или сзади?

- А отличие есть. Хоть и не очень большое. Если переменная с таким оператором участвует в каком-нибудь выражении или присваивании, то тут возникают различия. Давай я лучше покажу тебе на примере:

ПримерЧто происходит на самом делеОписание
int a = 3;
int b = ++a;
int a = 3;
a=a+1;
int b = a;
а сначала увеличивается на 1, затем участвует в выражении.
int a = 3;
int b = a++;
int a = 3;
int b = a;
a=a+1;
а сначала участвует в выражении, затем увеличивается на 1.
int a = 3;
return a++;
int a = 3;
int result = a;
a=a+1;
return result;
функция вернет 3, но значение a будет увеличено на 1.
int x = 5;
x = ++x + ++x;
int x = 5;
int a=x+1;//первое слагаемое 6
x = a;
int b=x+1;//второе слагаемое 7
x = b;
x = a + b;
тут будет ответ 13. Сначала x увеличится на 1, и это значение подставится на место первого слагаемого, а потом х увеличится еще раз на 1.

- Ничего себе! Вот это круто!

- Рад, что тебе понравилось. Но, если нет никакого выражения или присваивания, то никаких отличий нет:

Просто «x++» эквивалентно x=x+1.

Просто «++x» тоже эквивалентно x=x+1.

- Буду знать, спасибо, Билаабо.

8) Группа «тернарный оператор»

- В этом операторе участвуют не одна переменная/выражение и не две, а сразу три:

Вот как он записываетсяА вот чему он эквивалентен:
a ? b : c; if (a)
 b
else
 c
int min = a if (a<b)
 min = a;
else
 min = b;
return a!=null ? a.length : 0; if (a!=null)
 return a.length;
else
 return 0;

- Так это же очень удобная штука.

- Ага. И компактно и код читабельный. Пользуйся на здоровье.

9) Группа «остальное»

- Сколько музыку не разбирай, а папку «разное» создать придется.

- Да, каждый, кто хоть раз это делал, полностью с тобой согласен.

- Так вот, есть еще три оператора, о которых я хотел тебе рассказать:

ЗаписьПримерОписание
() (a+b)*c Скобки повышают приоритет выполнения.
Сначала выполняется то, что в скобках.
[] c[i] = c[i+1]; Получение элемента массива по индексу.
. int n = a.length; «оператор точка» – получение переменных и методов у объекта.

И, наконец, приоритет операторов и порядок их выполнения:

ОператорыПримеры
Высший приоритет (сначала выполняются операторы с меньшим номером)
()
[]
.
(a+b)
c[i] = c[i]+1
++
--
~
!
+
-
i++; ++i;
--j; a--;
~c
!f
return +a;
return -a;
*
/
%
a * b
c / d
a % b
+
-
a+b
c-d
String s = "count"+"35 ";
>>
<<
>>>
a>>3
b<<2
c>>>3
<
<=
>
>=
a < b
a <= b
c > b
c >= b
==
!=
a == 3
a != 0
& a & 7
^ a ^ b
| a | b
&& (a<b) && (a<c)
|| (b!=0) || (c!=0)
? : = a>0 ? a : -a;
=
*=, /=, %=
-=, +=
<<=. >>=, >>>=
&=, ^=. |=
Низший приоритет (выполняются в самом конце)

2. Задачи на приоритет операторов

- Привет, Амиго! Билаабо нашел для тебя задачи:

Задачи
1. Определяем адрес сети

1) Даны IP-адрес и маска подсети, необходимо вычислить адрес сети - метод getNetAddress. Используйте операцию поразрядной конъюнкции (логическое И).

Пример:
IP-адрес:             11000000  10101000  00000001  00000010 (192.168.1.2)
Маска подсети:   11111111    11111111   11111110    00000000 (255.255.254.0)
Адрес сети:         11000000  10101000  00000000  00000000 (192.168.0.0)
2) Реализовать метод print, который выведет в консоль данные в двоичном коде
3) Метод main не участвует в тестировании
2. Сравниваем модификаторы

Реализовать логику метода isAllModifiersContainSpecificModifier, который проверяет,
содержит ли переданный параметр allModifiers значение конкретного модификатора specificModifier
3. Все гениальное - просто!

Упростить. Переменные не переименовывать, комментарии не оставлять.

3. Все методы класса Object, метод toString()

- Привет, Амиго!

- Привет!

- Сегодня мы будем изучать класс Object.

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

- А зачем ему методы? Разве кто-то создает его объекты?

- Посмотри на ситуацию с такой стороны – методы, которые есть у Object’а, есть вообще у всех классов. Т.е. разработчики Java отобрали несколько методов, которые, по их мнению, должны быть у всех классов и добавили их в класс Object.

А в сочетании с полиморфизмом – возможностью переопределять методы класса Object в классах-наследниках – это порождает очень мощный инструмент.

Давай посмотрим, что же это за методы:

МетодОписание
public String toString() Возвращает строковое представление объекта.
public native int hashCode()
public boolean equals(Object obj)
Пара методов, которые используются для сравнения объектов.
public final native Class getClass() Возвращает специальный объект, который описывает текущий класс.
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout)
public final void wait(long timeout, intnanos)
public final void wait()
Методы для контроля доступа к объекту из различных нитей. Управление синхронизацией нитей.
protected void finalize() Метод позволяет «освободить» родные не-Java ресурсы: закрыть файлы, потоки и т.д.
protected native Object clone() Метод позволяет клонировать объект: создает дубликат объекта.

Методы можно разбить на 6 групп. С некоторыми ты уже знаком, а с остальными мы познакомимся в ближайших лекциях.

- Что-то не вижу тут ничего полезного.

- Амиго! Если бы эти методы были не важны, их бы не добавили абсолютно всем объектам! Так что советую тебе более внимательно разобраться – что это такое и зачем они нужны. Если тебе кажется, что они не важны, значит, ты что-то не понял или понял не правильно.

- Ок. Я буду внимательно слушать.

- Начнем с метода toString();

Этот метод позволяет получить текстовое описание любого объекта. Реализация его в классе Object очень простая:

 
return getClass().getName() + "@" + Integer.toHexString(hashCode());

getClass() и hashCode() – это тоже методы класса Object.

Вот стандартный результат вызова такого метода

 
java.lang.Object@12F456

- И в чем же польза такого описания?

- Из такого описания можно узнать класс объекта, у которого вызвали данный метод. А также можно различать объекты – разным объектам соответствуют разные цифры, идущие после символа @.

Но ценность данного метода в другом. Данный метод можно переопределить в любом классе и возвращать более нужное или более детальное описание объекта.

Но и это еще не все. Благодаря тому, что для каждого объекта можно получить его текстовое представление, в Java можно было реализовать поддержку «сложения» строк с объектами.

Вот смотри:

КодЧто происходит на самом деле
int age = 18;
System.out.println("Age is " + age);
String s = String.valueOf(18);
String result = "Age is" + s;
System.out.println(result);
Student st = new Student("Vasya");
System.out.println("Student is " + st);
Student st = new Student("Vasya");
String result = "Student is" +
   st.toString();
System.out.println(result);
Car car = new Porsche();
System.out.println("My car is " + car);
Car car = new Porsche();
String result = "My car is" +
   car.toString();
System.out.println(result);

- Да, я постоянно этим пользуюсь. Особенно когда пишу программу или ищу в ней ошибки. Полезный метод.

4. Методы equals & hashCode: зачем, где используются, как работают

- Теперь я расскажу о не менее полезных методах equals(Object o) & hashCode().

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

КодПояснение
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i не равно j
Переменные указывают на различные объекты.
Хотя объекты содержат одинаковые данные;
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i равно j
Переменные содержат ссылку на один и тот же объект.

- Да, я это помню.

- Есть также стандартное решение этой ситуации – метод equals.

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

- И как он это делает?

- Тут все аналогично методу toString().

У класса Object есть своя реализация метода equals, которая просто сравнивает ссылки:

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

- М-да. С чем боролись, на то и напоролись.

- Не вешай нос. Тут все тоже очень хитро.

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

- А можно пример такого метода?

- Конечно. Допустим, у нас есть класс, описывающий математические дроби, тогда он выглядел бы так (для ясности, я переведу английские названия на русский язык):

Пример
class Дробь
{
 private int числитель;
 private int знаменатель;

 Дробь(int числитель, int знаменатель)
 {
  this.числитель = числитель;
  this.знаменатель = знаменатель;
 }

 public boolean equals(Object obj)
 {
  if (obj==null)
   return false;

  if (obj.getClass() != this.getClass() )
   return false;

  Дробь other = (Дробь) obj;
  return this.числитель* other.знаменатель == this.знаменатель * other.числитель;
 }
}
Пример вызова:
Дробь one = new Дробь(2,3);
Дробь two = new Дробь(4,6);
System.out.println(one.equals(two));
Результат вызова будет true.
дробь 2/3 равна дроби 4/6

Для большей ясности я использовала русские названия. Так можно делать только в обучающих целях.

Теперь разберем пример.

Мы переопределили метод equals, и теперь для объектов класса Дробь у него будет своя реализация.

В этом методе есть несколько проверок:

1) Если переданный для сравнения объект – null, то объекты не равны. Объект, у которого вызвали метод equals ведь точно не null.

2) Проверка на сравнение классов. Если объекты разных классов, то мы не будем пробовать их сравнить, а сразу говорим, что это различные объекты – return false.

3) Со второго класса школы все помнят, что дробь 2/3 равно дроби 4/6. А как это проверить?

2/3 == 4/6
Умножим обе части на оба делителя (6 и 3), получим:
6 * 2 == 4 * 3
12 == 12
Общее правило:
Если
a / b == c / d
То
a * d == c * b

- Поэтому в третьей части метода equals мы преобразуем переданный объект к типу Дробь и сравниваем дроби.

- Понятно. Если бы мы просто сравнивали числитель с числителем и знаменатель со знаменателем, то дробь 2/3 не была бы равной 4/6.

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

- Да, но это только половина дела. Есть еще второй метод – hashCode()

- С методом equals все понятно, а зачем нужен hashCode()?

- Метод hashCode нужен для быстрого сравнения.

У метода equals есть большой минус – он слишком медленно работает. Допустим, у тебя есть множество(Set) из миллиона элементов, и нам нужно проверить, содержит ли оно определенный объект или нет. Как это сделать?

- Можно в цикле пройтись по всем элементам и сравнить нужный объект с каждым объектом множества. Пока не найдем нужный.

- А если его там нет? Мы выполним миллион сравнений, чтобы узнать, что там нет этого объекта? Не многовато ли?

- Да, даже мне понятно, что слишком много сравнений. А что, есть другой способ?

- Да, для этого и используется hashCode().

Метод hashCode() для каждого объекта возвращает определенное число. Какое именно – это тоже решает разработчик класса, как и в случае с методом equals.

Давай рассмотрим ситуацию на примере:

Представь, что у тебя есть миллион 10-тизначных чисел. Тогда в качестве hashCode для каждого числа можно выбрать остаток от его деления на 100.

Пример:

ЧислоНаш hashCode
1234567890 90
9876554321 21
9876554221 21
9886554121 21

- Да, с этим понятно. И что нам делать с этим hashCode-числом?

- Вместо того чтобы сравнивать числа, мы будем сравнивать их hashCode. Так быстрее.

И только если hashCode-ы равны, сравнивать уже посредством equals.

- Да, так быстрее. Но нам все равно придется сделать миллион сравнений, только уже более коротких чисел, а для тех чисел, чьи hashCode совпадают, опять вызвать equals.

- Нет, можно обойтись гораздо меньшим числом.

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

Представь, что ты студент, и ищешь своего друга, которого знаешь в лицо и про которого известно, что он живет в 17 общаге. Тогда ты просто проходишься по всем общежитиям универа и в каждом общежитии спрашиваешь «это 17 общага?». Если нет, то ты отбрасываешь всех из этой общаги и переходишь к следующей. Если «да», то начинаешь ходить по всем комнатам и искать друга.

В данном примере номер общаги – 17 – это и есть hashCode.

Разработчик, который реализует функцию hashCode, должен знать следующие вещи:

А) у двух разных объектов может быть одинаковый hashCode (разные люди могут жить в одной общаге)

Б) у одинаковых объектов (с точки зрения equals) должен быть одинаковый hashCode.

В) хеш-коды должны быть выбраны таким образом, чтобы не было большого количества различных объектов с одинаковыми hashCode. Это сведет все их преимущество на нет (Ты пришел в 17 общагу, а там живет пол универа. Облом-с).

И теперь самое важное. Если ты переопределяешь метод equals, обязательно нужно переопределить метод hashCode(), с учетом трех вышеописанных правил.

Все дело в том, что коллекции в Java перед тем как сравнить объекты с помощью equals всегда ищут/сравнивают их с помощью метода hashCode(). И если у одинаковых объектов будут разные hashCode, то объекты будут считаться разными - до сравнения с помощью equals просто не дойдет.

В нашем примере с Дробью, если бы мы взяли hashCode равный числителю, то дроби 2/3 и 4/6 имели бы разные hashCode. Дроби – одинаковые, equals говорит, что они одинаковые, но hashCode говорит, что они разные. И если перед сравнением с помощью equals сравнивать по hashCode, то получим что объекты разные и до equals просто не дойдём.

Пример:

 
HashSet<Дробь>set = new HashSet<Дробь>();
set.add(new Дробь(2,3));

System.out.println( set.contains(new Дробь(4,6)) );
Если метод hashCode() будет возвращать числитель дроби, то результат будет false.
Объект new Дробь(4,6) не будет найден в коллекции.

- А как правильно реализовать hashCode для дроби?

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

Вариант 1: hashCode равен целой части от деления.

Для дроби 7/5 и 6/5 это будет 1.

Для дроби 4/5 и 3/5 это будет 0.

Но этот вариант плохо годится для сравнения дробей, которые заведомо меньше 1. Целая часть (hashCode) всегда будет 0.

Вариант 2: hashCode равен целой части от деления знаменателя на числитель.

Этот вариант подойдет для случая, когда значение дроби меньше 1. Если дробь меньше 1, значит перевернутая дробь больше 1. А если мы переворачиваем все дроби – это никак не скажется на их сравнении.

Итоговый вариант будет совмещать в себе оба решения:

 
public int hashCode()
{
 return числитель/знаменатель + знаменатель/числитель;
}

Проверяем для дробей 2/3 и 4/6. У них должны быть равные hashCode:

 Дробь 2/3Дробь 4/6
числитель / знаменатель 2 / 3 == 0 4 / 6 == 0
знаменатель / числитель 3 / 2 == 1 6 / 4 == 1
числитель / знаменатель
+
знаменатель / числитель
0 + 1 == 1 0 + 1 == 1

На этом – все.

- Спасибо, Элли, было действительно интересно.

5. Задачи на equals & hashCode

- Привет, Амиго!

- Привет, Диего!

- Старый добрый Диего подготовил для тебя несколько задач:

Задачи
1. Equals and HashCode

В классе Solution исправить пару методов equals/hashCode в соответствии с правилами реализации этих методов.
Метод main не участвует в тестировании.
2. Исправить ошибку

Сравнение объектов Solution не работает должным образом. Найти ошибку и исправить.
Метод main не участвует в тестировании.
3. Ошибка в equals/hashCode

Исправьте ошибки реализаций методов equals и hashCode для класса Solution

6. Знакомство с методами wait, notify, notifyAll

- Привет, Амиго!

- Привет, Риша!

- Я познакомлю тебя с методами wait, notify, notifyAll класса Object.

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

- Хорошо.

- Эти методы были придуманы как часть механизма синхронизации нитей.

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

- Я помню, это делается с помощью ключевого слова synchronized.

- Правильно. Обычно такой код выглядит примерно так:

 
public void print()
{
 Object monitor = getMonitor();
 synchronized(monitor)
 {
  System.out.println("text");
 }
}

Помнишь, как это работает?

- Ага. Если две нити одновременно вызовут метод print(), то одна из них войдет в блок, помеченный synchronized, и заблокирует monitor, поэтому вторая нить будет ждать, пока монитор не освободится.

- Правильно. Как только нить входит в блок, помеченный synchronized, то объект-монитор помечается как занятый, и другие нити будут вынуждены ждать освобождения объекта-монитора. Один и тот же объект-монитор может использоваться в различных частях программы.

- Кстати, почему – монитор?

- Монитором принято называть объект, который хранит состояние занят/свободен.

Вот тут и вступают в дело методы wait и notify.

Собственно, методов как таковых всего два. Остальные – это лишь модификации этих методов.

Теперь разберемся, что же такое метод wait и зачем он нужен.

Иногда в программе может оказаться такая ситуация, что нить вошла в блок кода synchronized, заблокировала монитор и не может работать дальше, т.к. каких-то данных еще не хватает: например, файл который она должна обработать еще не загрузился или что-нибудь в таком духе.

Мы же можем просто подождать, когда файл скачается. Можно просто в цикле проверять – если файл еще не скачался – спать, например, секунду и опять проверять и т.д.

Примерно так:

 
while(!file.isDownloaded())
{
 Thread.sleep(1000);
}
processFile(file);

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

Для решения этой проблемы и был придуман метод wait(). Вызов этого метода приводит к тому, что нить освобождает монитор и «становится на паузу».

Метод wait можно вызвать у объекта-монитора и только тогда, когда это монитор занят – т.е. внутри блока synchronized. При этом нить временно прекращает работу, а монитор освобождается, чтобы им могли воспользоваться другие нити.

Часто встречаются ситуации, когда в блок synchronized зашла нить, вызвала там wait, освободила монитор. Затем туда вошла вторая нить и тоже стала на паузу, затем третья и так далее.

- А как же нить снимется с паузы?

- Для этого есть второй метод – notify.

Методы notify/notifyAll можно вызвать у объекта-монитора и только, когда этот монитор занят – т.е. внутри блока synchronized. Метод notifyAll снимает с паузы все нити, которые стали на паузу с помощью данного объекта-монитора.

Метод notify «размораживает» одну случайную нить, метод notifyAll – все «замороженные» нити данного монитора.

- Очень интересно, спасибо Риша.

- Рад, что тебе нравится, Амиго!

Есть еще модификации метода wait():

Метод wait()Пояснение
void wait(long timeout) Нить «замерзает», но через переданное количество миллисекунд автоматически «размораживается».
void wait(long timeout, int nanos) Нить «замерзает», но через переданное количество миллисекунд и наносекунд автоматически «размораживается».

Это, как еще говорят, wait с таймаутом. Метод работает как обычный wait, но если указанное время прошло, а нить никто не снял с паузы – она активируется сама.

7. Метод clone, интерфейс Cloneable

- Привет, Амиго!

- Привет, Ким.

- Я тебе расскажу про метод clone().

Цель этого метода – клонировать объект – т.е. создать его клон/копию/дубликат.

Если его вызвать, то Java-машина создаст и вернет дубликат объекта, у которого вызвали этот метод.

Клонирование объекта в классе Object реализовано очень примитивно – при клонировании создается всего один новый объект: просто создается еще один объект и его полям присваиваются значения полей объекта-образца. Если копируемый объект содержит ссылки на другие объекты, то ссылки будут скопированы, дубликаты тех объектов не создаются.

- Гм. Не густо.

- Дело в том, что Java-машина не знает, какие объекты можно клонировать, а какие нет. Файлы, например, клонировать нельзя. Как и поток System.in.

Поэтому вопрос о полноценном клонировании был отдан на откуп разработчикам классов. Тут все было сделано по аналогии с методом equals. Даже есть свой аналог hashCode – это интерфейс Cloneable.

Интерфейс Cloneable – это так называемый интерфейс-маркер, который не содержит никаких методов. Он используется, чтобы маркировать (помечать) некоторые классы.

Если разработчик класса считает, что объекты класса можно клонировать, он помечает класс этим интерфейсом (наследует класс от Cloneable).

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

При вызове метода clone(), Java проверяет, был ли у объекта интерфейс Cloneable. Если да - клонирует объект методом clone(), если нет - выкидывает исключение CloneNotSupportedException.

- Т.е. мы должны или переопределить метод clone и написать его новую реализацию или унаследовать класс от Cloneable?

- Да, но переопределять метод все же придется. Метод clone() объявлен как protected, так что он доступен для вызова только классам из его пакета (java.lang.*) или классам-наследникам.

- Я немного запутался, так что же надо сделать, чтобы клонировать объект?.

- Смотри, если ты хочешь воспользоваться «клонированием по умолчанию», которое реализовано в классе Object, тебе нужно:

а) Добавить интерфейс Cloneable своему классу

б) Переопределить метод clone и вызвать в нем базовую реализацию:

 
class Point implements Cloneable
{
 int x;
 int y;

 public void clone()
 {
  return super.clone();
 }
}

Или ты можешь написать реализацию метода clone полностью сам:

 
class Point
{
 int x;
 int y;

 public void clone()
 {
  Point point = new Point();
  point.x = this.x;
  point.y = this.y;
  return point;
 }
}

- Интересный метод, буду пользоваться. Иногда…

8. Задачи на клонирование

- Привет, Амиго!

Я тут нашел журнал за 2014 год, представляешь?

И там набор старых-престарых задач на клонирование. Вот смотри:

Задачи
1. Глубокое клонирование карты

Клонируйтие объект класса Solution используя глубокое клонирование.
Данные в карте users также должны клонироваться.
2. Клонирование

Класс Plant не должен реализовывать интерфейс Cloneable
Реализуйте механизм глубокого клонирования для Tree.
3. Запретить клонирование

Разрешите клонировать класс А
Запретите клонировать класс B
Разрешите клонировать класс C

9. Метод finalize, интерфейс closeable, try-with-resources(java7)

- Привет, Амиго!

Я тут решила с тобой еще раз обсудить метод finalize().

Если ты помнишь, то finalize() – это специальный метод, который вызывается у объекта перед тем, как сборщик мусора его уничтожит.

Основная цель этого метода – освободить используемые внешние не-Java ресурсы: закрыть файлы, потоки ввода-вывода и т.п.

К сожалению, этот метод не оправдывает возложенных на него надежд. Java-машина может отложить уничтожение объекта, как и вызов метода finalize на сколько угодно. Более того, она вообще не гарантирует, что этот метод будет вызван. В куче ситуаций ради «оптимизации» он не вызывается.

Приведу тебе две цитаты:

 
У Джошуа Блоха хорошо написано об этом методе: link
Краткая выдержка:
1. finalize() можно использовать только в двух случаях:
1.1. Проверка/подчистка ресурсов с логированием.
1.2. При работе с нативным кодом, который не критичен к утечке ресурсов.
2. finalize() замедляет работу GC по очистке объекта в 430 раз
3. finalize() может быть не вызван
Если я на собеседовании скажу, что finalize - это вредный и опасный костыль, который своим существованием сбивает с толку, то буду прав?

- М-да, обрадовала ты меня, Элли.

- На замену метода finalize в Java 7 появился новая конструкция. Называется она – try-with-resources. Это не совсем замена finalize – скорее альтернативный подход.

- Как try-catch, только с ресурсами?

- Почти как try-catch. Дело в том, что в отличие от метода finalize(), блок finally из конструкции try-catch-finally вызывается всегда. Этим и пользовались программисты, когда нужно было гарантированно освободить ресурсы, закрыть потоки и т.д.

Пример:

 
InputStream is = null;
try
{
 is = new FileInputStream("c:/file.txt");
 is.read(…)
}
finally
{
 if (is!=null)
 is.close();
}

Независимо от того, нормально ли отработал блок try, или там возникло исключение, блок finally вызовется всегда, и там можно будет освободить занятые ресурсы.

Поэтому в Java 7 этот подход решили сделать официальным, и вот что из этого вышло:

 
try(InputStream is = new FileInputStream("c:/file.txt"))
{
 is.read(…)
}

Это специальная конструкция try, называемая try-with-resources (так же как и второй for для коллекций называется foreach).

Обрати внимание – после try следуют круглые скобки, где объявляются переменные и создаются объекты. Эти объекты можно использовать внутри блока try, обозначенного скобками {}. Когда выполнение команд блока try закончится, независимо от того – нормально оно закончилось или было исключение, для объекта, созданного внутри круглых скобок (), будет вызван метод close();

- Как интересно. Такая запись гораздо компактнее, чем предыдущая. Понять бы ее еще.

- Все не так сложно, как ты думаешь.

- А я могу указывать в круглых скобках объекты своих классов?

- Да, конечно, иначе от этих скобок было бы мало пользы.

- А если мне нужно вызвать другой метод при выходе из блока try, где мне его указать?

- Тут все немного тоньше. В Java 7 появился такой интерфейс:

 
public interface AutoCloseable
{
 void close() throws Exception;
}

Ты можешь унаследовать свой класс от такого интерфейса. И тогда его объекты можно будет использовать внутри try-with-resources. Только объекты такого типа можно использовать внутри круглых скобок try-with-resources для «автоматического закрытия».

- Т.е. мне нужно будет переопределить метод close и написать в нем код по «очистке» моего объекта, а указать другой метод нельзя?

- Ага. Зато можно указывать несколько объектов, разделив их точкой с запятой:

 
try(
InputStream is = new FileInputStream("c:/file.txt");
OutputStreamos = new FileOutputStream("c:/output.txt")
)
{
 is.read(…)
 os.write(…);
}

- Уже лучше, но не так круто, как я надеялся.

- Все не так плохо, ты привыкнешь. Со временем.

10. Задачи на освобождение занятых ресурсов

- Привет, Амиго!

Я думал, ты уже разобрался с finalize.

- Я тоже так думал. А теперь еще и этот try-with-resources.

Охренеть. Я так Java никогда не выучу. Только кажется, что все понял, а тут на тебе!

- Не волнуйся ты так – маслопровод лопнет, лучше вот порешай задачи пока:

Задачи
1. Рефакторинг

Отрефакторите метод writeZipEntriesToFile в соответствии с java7 try-with-resources.
Допускаются только текстовые коментарии.
2. Освобождаем ресурсы

Реализуйте метод finalize, подумайте, что именно в нем должно быть.
Отрефакторите метод getUsers в соответствии с java7 try-with-resources.
Допускаются только текстовые коментарии.
3. Нюансы Exceptions

Классы семейства Utilizator должны утилизировать ресурсы не влияя на работу программы, т.е. программа должна отрабатывать одинаково с любым из Utilizator-ов.
На данный момент это не выполняется из-за неправильных реализаций утилизаторов. Исправьте реализацию утилизаторов.
Метод main не участвует в тестировании.
UtilizatorUtil не менять.
Стек трейс не выводить.

11. Метод getClass(), объект класс, знакомство с Reflection

- Привет, Амиго!

А теперь самое интересное. Мы познакомимся с классом Class и немного с Reflection.

Как ты уже, наверное, успел понять, в Java все является объектом. А что нужно для объекта? Что есть у каждого объекта и определяет саму его суть?

- Класс объекта!

- Правильно! Молодец. У каждого объекта есть класс. Но вернемся к объектам. Некоторые объекты полностью содержат какую-то сущность, другие же просто помогают ей управлять.

Ко вторым можно отнести FileOutputStream или Thread. Когда ты создаешь объект Thread, новая нить не создается. Ее создает Java-машина после вызова метода start(). Этот объект просто помогает управлять процессом.

Так же и FileOutputStream: файл хранится на диске и его хранением и доступом к нему управляет ОС. Но мы можем взаимодействовать с ним посредством объектов типа File, при опять-таки помощи Java-машины.

- Да, я это понял уже.

- Так вот, для взаимодействия с классами есть специальный класс и называется он - Class.

- Не трудно было догадаться.

- Ага. Каждый раз, когда Java-машина загружает в память новый класс, она создает объект типа Class, посредством которого можно получить некоторую информацию о загруженном классе.

К каждому классу и объекту привязан такой «объект класса».

ПримерОписание
Class clazz = Integer.class; Получение «объект класса» у класса Integer.
Class clazz = int.class; Получение «объект класса» у класса int.
Class clazz = "123".getClass(); Получение «объект класса» у объекта типа String.
Class clazz = new Object().getClass(); Получение «объект класса» у объекта типа Object.

- Ух ты, как интересно.

А почему ты пишешь clazz, а не class?

- А ты помнишь, что слово class – это ключевое слово в Java и использовать его для именования переменных нельзя?

- Да, я это знаю, знаю. Только забыл.

- Ты где-нибудь уже использовал объект Class?

- Да, мы использовали его, когда писали свою реализацию метода equals.

- Да, можно сравнить – одинаковые ли у объектов классы, если воспользоваться методом getClass().

- А что можно делать с этим объектом?

- Ну, много чего:

КодОписание
Class s = int.class;
String name = s.getName();
Получить имя класса.
Class s = Class.forName("java.lang.String"); Получить класс по имени.
Object o1 = String.valueOf(1);
Object o2 = 123 + "T";
o1.getClass() == o2.getClass();
Сравнить классы у объектов.

- Интересно, но не так круто, как я думал.

- Хочешь, чтобы было круто? Есть еще Reflection. Reflection – это очень круто.

- А что такое Reflection?

- Reflection – это способность класса получить информацию о самом себе. В Java есть специальные классы: Field – поле, Method – метод, по аналогии с Class для классов. Т.к. объект типа Class дает возможность получить информацию о классе, то объект типа Field–получить информацию о «поле класса», а Method–о «методе класса». И вот что с ними можно делать:

КодОписание
Class[] interfaces = List.class.getInterfaces(); Получаем список «объектов класса» для интерфейсов класса List
Class parent = String.class.getSuperclass(); Получаем «объект класса» родительского класса для класса String
Method[] methods = List.class.getMethods(); Получаем список методов, которые есть у класса List
String s = String.class.newInstance(); Создаем новый объект класса String
String s = String.class.newInstance();
Method m = String.class.getMethod("length");
int length = (int) m.invoke(s)
Получаем метод length у класса String, вызываем его у строки s

- Вау! Вот это реально круто!

- Рад, что тебе понравилось.

12. Учимся гуглить. Я показываю, как я ищу решение определенной задачи

- Привет, Амиго!

Я могу научить тебя тому, что лучше всего умею – как делать меньше ненужной работы.

- Гм. Начало интересное.

- Запомни, всего знать невозможно. Да и не нужно. Но если ты можешь быстро найти нужную тебе информацию, то ты на коне.

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

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

Все, что ты хочешь написать, кто-то уже когда-то написал. Ну, может не все, но процентов 90-95 точно.

- Ничего себе.

- Я хочу, чтобы ты крепко запомнил две вещи:

1. Программирование появилось более 50 лет назад. Языку Java скоро 20 лет.

99% кода, который тебе нужен, уже написан.

2. Прежде, чем что-то писать с нуля, поищи в интернете – скорее всего кому-то это понадобилось раньше, и проблема уже решена.

Поэтому мы будем учиться гуглить – искать в интернете. Гуглить – это от слова Google.

Яндекс тоже хорош, но только для рунета. Программирование же активно развивается в первую очередь на западе, так что Google – наше все.

Я буду давать тебе задания – найти что-то в гугле, а ты должен будешь учиться это искать.

Но в этот раз начнем просто с примеров.

Что хотим найтиЗапрос в GoogleПримечание
Как в Java проверить, есть файл или нет? java file exists По первой же ссылке находим ответ.
Найденный ответ:
File f = new File(filePathString);
if(f.exists())
{ /* do something */ }
Как в java скачать файл из интернета? java file download По первой же ссылке находим пример.
Найденный ответ:
URL website = new URL("http://www.website.com/information.asp");
ReadableByteChannel rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream("information.html");
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
Сколько будет $100 в рублях? 100 dollar in RUB Для ответа даже не нужно переходить по ссылке!
Найденный ответ:
3 270.21812 российских рубля
Как узнать, какая именно версия JDK установлена? how to get jdk version Вторая ссылка.
Найденный ответ:
C:\>java -version
java version "1.6.0_18"
Java(TM) SE Runtime Environment (build 1.6.0_18-b07)
Java HotSpot(TM) Client VM (build 16.0-b13, mixed mode, sharing)

Не ленись. Зайди в Google, вбей указанные запросы и найди ответы.

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

- Ого. Обещаю не лениться.

- Опытный разработчик может найти в интернете готовый ответ или подсказку на 99.99% всех возникающих проблем.

- Ничего себе! Буду всегда внимательно тебя слушать!

13. Профессор дает доп. материал

- Привет, Амиго! Вот ссылка на дополнительный материал по сегодняшнему уровню.

Ссылка на дополнительный материал

14. Хулио

- Привет, Амиго!.

Я вот что придумал. Когда я пересплю со страшненькой, чтобы перед собой не было так стыдно, называю это "сексуальная благотворительность".

- Да ты, я посмотрю, меценат....

Оригинал видео на YouTube

15. Вопросы к собеседованию по этой теме

- Чуть не забыл. Вот тебе несколько вопросов к собеседованию, рассмотренных в текущем уровне:

 Вопросы к собеседованиям
1 Перечислите методы класса Object
2 Зачем нужны методы equals & hashCode?
3 Что будет, если переопределить equals, но не переопределить hashCode?
4 Зачем нужны методы wait, notify, notifyAll?
5 Как правильно клонировать объект?
6 Зачем нужен метод finalize() и как он работает?
7 В чем отличие final, finally, finalize?
8 Что такое try-with-resources?
9 Чем отличаются методы wait(1000) и sleep(1000)?
10 В чем отличие i++ и ++i ?

16. Большая задача

- Привет, Амиго!

- Здравия желаю, Капитан Бобров!

- Теперь будем учиться писать большие проекты. В связи с этим, появился новый тип заданий – "большая задача". Это одна большая задача, разбитая на много маленьких. Решая очередную "маленькую задачу" ты будешь дописывать новый код к нынешнему коду, а не писать что-то с нуля. Будущее федерации в твоих руках.

- Есть, сэр!

- Цель первых пяти "больших задач" научиться писать большие и сложные проекты. Сначала описание "маленьких задач" довольно детальное, иногда даже чересчур. Затем оно будет становиться все более общим, а размер задач расти. Сначала будут небольшие кусочки "моего" кода. Потом большие. И наконец – целые фреймворки (библиотеки).

- Я готов, сэр!

- Я постарался сделать описание задач максимально однозначным. Но если что-то не получается:

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

б) попробуй решить несколькими разными способами.

в) попроси помощи/напиши нам - это же новые задачи и мы с радостью их "отполируем".

Вот тебе первое задание:

Сегодня мы напишем небольшую игру под названием "Ипподром".

Когда я говорю мы – я имею в виду тебя. Я же буду работать наставником.

- А где же условие?

- Какое условие, рядовой? Ты что, до сих пор в учебке? Это же секретная военная программа. Запускай Intellij IDEA – там будет первая часть этого задания. Каждое следующее задание будет доступно только после того, как ты успешно выполнишь предыдущее.Приступай немедленно.

- Есть, приступать немедленно!