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

Уровень 23

1. final и другие ключевые слова java

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

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

- Я расскажу тебе сегодня про несколько ключевых слов в Java. Но начну с самого интересного – ключевого слова final. Если перевести его с английского, то получится что-то вроде финальный или окончательный.

Ключевое слово final можно добавлять при объявлении переменной, метода и класса.

- А зачем нужен этот final?

- Все довольно просто. Если мы пометили переменную словом final, то она становится неизменной:

 
final int i = 5;
i++;
//ошибка компиляции – нельзя менять значение переменной i

- Ясно.

- Если мы пометили метод словом final, то этот метод запрещено переопределять в классах-наследниках:

 
class Cat
{
 public final String getName()
 {
  return "cat";
 }
}

class Tiger extends Cat
{
 public String getName() //ошибка компиляции – нельзя переопределять метод getName()
 {
  return "tiger";
 }
}

- Ясно. А зачем может понадобиться запрещать переопределение метода?

- Например, программист написал в этом методе много важного кода и хочет, чтобы все наследники его класса гарантированно имели заданное поведение.

И наконец – третье.

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

 
public final class Cat
{
 public String getName()
 {
  return "cat";
 }
}

class Tiger extends Cat //ошибка компиляции – нельзя наследоваться от класса Cat
{
 public String getName()
 {
  return "tiger";
 }
}

- А зачем запрещать наследование классов?

- Ты должен понять, что запрет наследования идет не из вредности, а ради безопасности и целостности кода. Если наследование класса не запрещено, то подразумевается, что оно разрешено. И код проектировщика класса будет нормально работать и с объектами его класса и с объектами класса-наследника.

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

- Класс String, например, объявлен как final, как и все примитивные типы: Integer, Boolean, Double, Character,…

- Ага, понимаю. Класс String сделан immutable и если бы вдруг появились изменяемые строки, то много чего перестало бы работать.

- Ну, почти. Скажем так, все работало бы почти по-старому, но иногда возникали бы ошибки, которые было бы очень сложно найти и понять. Поэтому, в некоторых случаях наследование классов или методов не грех и запретить – меньше потом ошибок вылавливать.

- А где еще можно писать final?

- final можно писать перед переменными-аргументами функции и перед переменными в методе. Вот пример:

 
public void setName(final String name)
{
 final String displayName = "Mr."+name;
  …
 this.name = displayName;
}

- А какой в этом смысл?

- Ну, смыслов – два. Во-первых, мы объявляем переменную final – если хотим сообщить другим разработчикам, что это значение – определенная константа, а не просто переменная.

Например, мы хотим рассчитать НДС от цены:

 
public int getPriceNDS()
{
 final int NDS = 20;
 return this.price * NDS / 200;
}

И во-вторых, если мы будем писать локальные или анонимные внутренние классы, то такие переменные нам понадобятся. Я расскажу о таких классах в ближайшее время. Но не сегодня.

- Ок, пока вроде ничего сложного.

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

- Как раз хотел спросить этот момент. А нет способа сделать объект неизменным?

- Нет, только если ты напишешь immutable класс.

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

Этот код скомпилируетсяЭтот код не скомпилируется
class Home
{
 private final int width = 200;
 private final int height = 100;

 public Home()
 {
 }
}
class Home
{
 private final int width;
 private final int height;


 public Home()
 {
 }
}

Но, вместе с тем, Java разрешает перенести инициализацию final-переменных класса в конструктор.

Этот код скомпилируетсяЭтот код не скомпилируется
class Home
{
 private final int width = 200;
 private final int height;

 public Home()
 {
  height = 100;
 }
}
class Home
{
 private final int width;
 private final int height;

 public Home()
 {
  height = 100;
 }
}

Более того, в разных конструкторах final-переменные можно инициализировать разными значениями. Это очень удобно:

Этот код скомпилируется
class Home
{
 private final int width;
 private final int height;

 public Home()
 {
  height = 100;
  width = 200;

 }

 public Home(int width)
 {
  this.height = 300;
  this.width = width;

 }

 public Home(int width, int height)
 {
  this.height = height;
  this.width = width;

 }
}

- Действительно интересная тема, и абсолютно все понятно, спасибо, Билаабо!

2. Задачи на final,...

- Билаабо принес Амиго задачи:

Задачи
1. Запретите наследование

Запретите наследование от класса Listener.
2. Запретите переопределение

Запретите переопределение метода onMouseDown.
3. Запретите создание экземпляров класса

Запретите создание экземпляров класса Listener.

3. Вложенные классы

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

- Привет, Элли!

- Сегодня будет очень интересная тема. Я сегодня расскажу о внутренних классах.

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

Объекты внутренних классов при этом вложены в объекты внешних классов и могут обращаться к переменным внешних классов.

Пример
public class Car
{
 int height = 160;
 ArrayList<Door> doors = new ArrayList<Door>();

 public Car
 {
  doors.add(new Door());
  doors.add(new Door());
  doors.add(new Door());
  doors.add(new Door());
 }

class Door()
 {
  public int getDoorHeight()
  {
   return (int)(height * 0.80);
  }
 }
}

Обрати внимание, у класса Door(дверь) есть метод, который возвращает высоту двери – getDoorHeight, этот метод использует переменную height объекта Car.

Объект типа Door не может существовать отдельно от объекта типа Car – ведь он использует его переменные. Компилятор незаметно добавляет в конструктор и в класс Door ссылку на объект внешнего класса Car, чтобы методы внутреннего класса Door могли обращаться к переменным и вызвать методы внешнего класса Car.

- Вложенные объекты. Мне все понятно. Судя по картинке тут все элементарно.

- Так и есть. Лишь пара нюансов.

Во внутреннем классе Door имеется ссылка на объект класса Car, поэтому:

1) Нельзя создать объект Door внутри статического метода в классе Car: негде взять ссылку на объект типа Car, который неявно передается в конструктор типа Door.

ПравильноНеправильно
public class Car
{
 public static Door createDoor()
 {
  Car car = new Car();
  return car.new Door();
 }

 public class Door
 {
  int width, height;
 }
}
public class Car
{
 public static Door createDoor()
 {
  return new Door();
 }

 public class Door
 {
  int width, height;
 }
}

2) Класс Door не может содержать статические переменные и методы.

ПравильноНеправильно
public class Car
{
 public int count;
 public int getCount()
 {
  return count;
 }

 public class Door
 {
  int width, height;
 }
}
public class Car
{

 public class Door
 {
  public static int count;
  int width, height;

  public static int getCount()
  {
   return count;
  }
 }
}

- А если мне нужна общая переменная для всех объектов Door?

Ты всегда можешь объявить ее просто в классе Car – это и будет общая переменная для всех объектов Door, вложенных в объект Car.

3) Обрати внимание, что если внутренний класс объявлен как public, то его объекты можно создавать вне внешнего класса, но объект внешнего класса при этом обязан присутствовать:

 
Car car = new Car();
Car.Door door = car.new Door();
Car.Door door = new Car().newDoor();

4) И еще одно замечание – чуть не забыла.

Т.к. у нас два вложенных объекта, то в методах внутреннего объекта доступно две ссылки this:

 
public class Car
{
 int width, height;

 public class Door
 {
  int width, height;

  public void setHeight(int height)
  {
   this.height = height;
  }

 public int getHeight()
 {
  if (height!=0)
   return this.height;
  else
   return (int)(Car.this.height * 0.8);
 }
}

Я специально объявила в классах одинаковые переменные.

Чтобы обратиться к переменной внешнего класса, когда она скрыта, или к this внешнего класса, достаточно написать «имя класса» точка this:

Как получить доступ к this внешнего (или любого) класса
Car.this
Car.Door.this
Car.Door.InnerClass2.InnerClass3.this

- Т.е. если мы внутри метода внутреннего класса пишем this, то этот this относится к внутреннему классу?

- Да. Именно так.

Как тебе внутренние классы, Амиго?

- Очень интересно. Я бы не сказал, что очень сложно.

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

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

Спасибо за отличный урок, Элли.

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

4. Задачи по внутренним классам

- Привет, Амиго! Готов заниматься?

- Да, давай уже сюда свои задачи по внутренним классам, Диего:

Задачи
1. Inner

Реализовать метод getTwoSolutions, который должен возвращать массив из 2-х экземпляров класса Solution.
Для каждого экземпляра класса Solution инициализировать поле innerClasses двумя значениями.
Инициализация всех данных должна происходить только в методе getTwoSolutions.
2. Inner 2

В классе SuperUser метод getDescription должен учитывать страну и город, т.е. возвращать результат аналогичный следующему:
My name is George. I'm from the USA, Seattle.
Используйте возможности иннер класса.
2. Inner 3

Внутри класса Solution:
1) реализуйте private class TaskDataProvider используя Task и DbMock, цель которого - обновить поле tasks.
2) реализуйте private class NameDataProvider используя String и DbMock, цель которого - обновить поле names.

5. Внутренние статические классы

- Итак, тема номер два – вложенные классы.

Перед объявлением внутреннего класса мы можем поставить ключевое слово – static и тогда внутренний класс станет вложенным.

Давай разберемся, что же значит слово static рядом с объявлением вложенного класса. Как ты думаешь?

- Если переменная объявлена статической, то она существует в единственном экземпляре, а если вложенный класс – статический, то можно создать всего один объект такого класса?

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

- Ага. Обычные методы втихаря хранят ссылку на объект, а статические – нет. То же и со статическими классами, я прав, Элли?

- Абсолютно. Твоя догадливость очень похвальна. Вложенные статические классы не имеют скрытых ссылок на объекты внешнего класса, в котором они объявлены.

 
class Zoo
{
 private static int count = 7;
 private int mouseCount = 1;

 public static int getAnimalCount()
 {
  return count;
 }

 public int getMouseCount()
 {
  return mouseCount;
 }

 public static class Mouse
 {
  public Mouse()
  {
  }
   public int getTotalCount()
  {
   return count + mouseCount; //ошибка компиляции.
  }
 }

}

Давай посмотрим внимательно на этот пример.

К каким переменным может обращаться статический метод getAnimalCount?

- Только к статическим. Это же статический метод.

К каким переменным может обращаться метод getMouseCount?

- И к статическим, и к нестатическим. Он скрытно хранит ссылку(this) на объект типа Zoo.

- Верно. Так вот, статический вложенный класс Mouse, как и статический метод, может обращаться к статическим переменным класса Zoo, но не может обращаться к нестатическим.

Мы можем спокойно создавать объекты класса Mouse, даже когда нет ни одного созданного объекта класса Zoo. Вот как можно это сделать:

 
class Home
{
 public static void main(String[] args)
 {
  Zoo.Mouse mouse = new Zoo.Mouse();
 }
}

Фактически класс Mouse – это самый обычный класс. Из-за того, что он объявлен внутри класса Zoo, у него есть две особенности.

1) При создании объектов вложенного класса (как класс Mouse) вне внешнего класса-родителя, надо еще указывать через точку и имя внешнего класса.

Например так: Zoo.Mouse.

2) Класс Zoo.Mouse и его объекты имеют доступ к private static переменным и методам класса Zoo ( класс Mouse ведь тоже объявлен внутри класса Zoo).

На этом на сегодня все.

- Т.е. просто дополнительное имя и все?

Да.

- Это еще проще, чем казалось на первый взгляд.

6. Задачи по внутренним статическим классам

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

- Да, задачи, знаю, знаю. Кстати, Диего, у тебя получились отличные задачи.

- Спасибо, что наконец-то заметил. Держи еще:

Задачи
1. Как выбрать нужное?

В методе main присвойте объекту Object obj экземпляр класса TEST
Константу TEST и класс TEST менять нельзя.
2. Рефакторинг

Отрефакторите класс Solution: вынесите все константы в public вложенный(nested) класс Constants.
Запретите наследоваться от Constants.

7. Внутренние анонимные классы, примеры

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

- Так здоровались уже, Элли!

- Так, не спорь с тетей. В 31 веке принято здороваться опять, если не видел человека более получаса. Так что не возникай!

Так вот, новая интересная тема – размножение роботов!

- О_О.

- Шучу, новая тема – анонимные вложенные классы.

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

Пример внутреннего класса, унаследованного от Thread
class Tiger extends Cat
{

 public void tigerRun()
 {
  .....
 }

public void startTiger()
 {
  TigerThread thread = new TigerThread();
  thread.start();
 }

 class TigerThread extends Thread
 {
  public void run()
  {
   tigerRun();
  }
 }
}

Давай разберем этот пример:

Нам нужен класс, унаследованный от Thread, чтобы переопределить у него метод run.

Для этого внутри класса Tiger мы объявили внутренней класс TigerThread, который унаследовали от Thread и у которого переопределили метод run.

Для нашего удобства в классе Tiger мы объявили два метода (аналоги методов runи startкласса Thread) –методы tigerRun&startTiger.

В методе startTiger мы создаем объект типа TigerThread и вызываем у него метод start().

При этом Java-машина создаст новую нить, и эта нить начнет работу с вызова метода run, класса TigerThread.

А этот метод в свою очередь вызовет наш метод run – метод tigerRun.

- С нитями я уже дело имел, так что вроде не очень сложно.

А обязательно называть методы tigerRun и startTiger?

- Нет, можно было назвать run и start, но я хотела дополнительно показать, что мы не наследуемся от Thread, к тому же ты мог сильнее запутаться в моем пояснении.

- Ок. Тогда все вроде понятно. Только при вызове метода startTiger второй раз мы создадим еще один класс Thread и запустим его. Не получиться ли что у нас «один тигр будет бегать в двух различных нитях»?

- Ну ты и глазастый. Согласна, это не хорошо. Тогда давай перепишем код так:

Код
class Tiger extends Cat
{

 public void tigerRun()
 {
  .....
 }

public void startTiger()
 {
  thread.start();
 }

private TigerThread thread = new TigerThread();

 private class TigerThread extends Thread
 {
  public void run()
  {
   tigerRun();
  }
 }
}

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

- Правильно, запустил второй раз, тигра – получи Exception.

- Я уже лучше тебя вижу все ошибки, Элли!

Да, ты молодец. Тогда перейдём к анонимным внутренним классам.

Обрати внимание на несколько аспектов вышеописанного кода:

1) Мы унаследовались от класса Thread, но фактически не дописали туда никакого кода. Нам скорее пришлось унаследоваться, а не «мы унаследовались с целью расширить класс Thread»

2) Будет создан всего один объект класса TigerThread.

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

Помнишь, как я рассказывала про появление конструкторов?

До изобретенияПосле изобретения
TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 }
}
Thread thread = new Thread()
{
 public void run()
 {
  tigerRun();
 }
};

- Вижу, что код стал компактнее, но не совсем понимаю, что произошло.

- Мы можем объединить в одном месте три вещи:

А) объявление класса-наследника

Б) переопределение метода

В) объявление переменной

Г) создание объекта класса-наследника.

Фактически мы объединяем вместе две операции – объявление класса-наследника и создание его объекта:

Без анонимного классаС использованием анонимного класса
Cat tiger = new Tiger();

class Tiger extends Cat
{
}
Cat tiger = new Сat()
{
};

Еще раз разбираем синтаксис:

Объявление переменной типа Thread
Thread thread = new Thread();
Объявление переменной типа «анонимный класс-наследник Thread»
Thread thread = new Thread()
{

};

Обрати внимание – мы не просто объявляем новый класс – мы создаем переменную – в конце ставится точка с запятой!

- А если мы хотим переопределить метод run, то нужно писать так:

Объявление переменной типа Thread
Thread thread = new Thread()
{
 public void run()
  {
   System.out.println("new run-method");
  }
};

- Быстро схватываешь – молодец!

- Спасибо. А если мне нужны еще методы, которых нет у класса Thread?

- Ты можешь их дописать.

Это же полноценный внутренний класс, хоть и анонимный:

КодОписание
Thread thread = new Thread()
{
  public void run()
  {
   printHi();
  }

  public void printHi()
  {
   System.out.println("Hi!");
  }
}
;
Красным цветом отмечен код создания переменной.

Зеленым – создания объекта.

Синим – код анонимного класса-наследника.

- Полноценный внутренний класс?

Т.е. я могу и переменные внешнего класса использовать?

- Да, конечно можешь.

- А в конструктор я могу что-то передавать?

- Да, но только параметры конструктора базового класса:

КлассОбъект анонимного внутреннего класса
class Cat
{
 int x, y;
 Cat(int x, int y)
 {
  this.x = x;
  thix.y = y;
 }
}
Cat cat = new Cat(3,4)
{
  public void print()
  {
   System.out.println(x+" "+y);
  }
};

Мы не можем добавить свои параметры в чужой конструктор. Зато можем использовать переменные внешнего класса – это достаточно сильно компенсирует этот недостаток.

- А если мне все-таки очень нужно добавить в конструктор еще параметры?

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

- Действительно, я чуть не забыл об этом.

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

- Нет. Будет именно анонимный внутренний класс. Смотри примеры:

С анонимным классомБез анонимного класса
Thread thread = new Thread()
{
  public void run()
  {
   tigerRun();
  }
};
TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 }
}
static Thread thread = new Thread()
{
  public void run()
  {
   tigerRun();
  }
};
static TigerThread thread = new TigerThread();

private class TigerThread extends Thread
{
 public void run()
 {
  tigerRun();
 }
}

- Ясно. Статической становится только переменная, но не класс.

- Ага.

На самом деле, в процессе компиляции Компилятор создает внутренние классы для всех анонимных внутренних классов. Такие классы обычно получают имена «1», «2», «3», и т.д.

8. Задачи по анонимным классам

- Кто хочет задачи по анонимным внутренним классам?

- Кто же еще, конечно я. Давай их уже. Хочу немного попрограммировать:

Задачи
1. Напряги извилины!

Метод printName должен выводить свое собственное имя, т.е. "sout"
Сделайте минимум изменений.
2. Повторяем threads

Сделать так, чтобы в методе someActions вызывались только методы класса Solution.
Ожидаемый вывод в методе main:
Amigo: Mmmmm, beef
Amigo: knock knock
Amigo: Zzzzzzz...1 sec
3. Анонимность иногда так приятна!

1. В пакете vo создайте public классы User, Location, Server, Subject, Subscription, которые наследуются от NamedItem
2. В классе Solution для каждого класса создайте свой метод, который возвращает список экземпляров класса.
Например, для класса User это будет - public ListgetUsers()
Для класса Location это будет - public ListgetLocations()
3. Внутри каждого такого метода создайте анонимный класс от AbstractDbSelectExecutor и вызовите его нужный метод.

Подсказка: тело метода должно начинаться так: returnnewAbstractDbSelectExecutor

4. Пример вывода для User и Location:
Id=5, name='User-5', description=Got by executing 'select * from USER'
Id=1, name='Location-1', description=Got by executing 'select * from LOCATION'

5. Проанализируйте пример вывода и сформируйте правильный query для всех классов.
6. Классы не должны содержать закоментированного кода.

9. Учимся гуглить. Как узнать, есть ли у объекта определенный метод? getName, getDisplayName, getPublicName

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

Продолжаем наши уроки – учимся гуглить.

Вот тебе несколько заданий:

 Вопрос
1 Как отсортировать массив чисел?
2 Как отсортировать список строк в обратном алфавитном порядке?
3 Как отправить email из программы Java?
4 Как определить, содержит ли переданный объект определенный метод?
5 В чем отличие TreeMap и HashMap?
6 Зачем нужно писать в таком коде ArrayList<?>
7 Как узнать максимальное значение int?
8 Как узнать минимальное значение byte?
9 Как преобразовать число в 16-тиричную строку (123->"7B")?
10 Как преобразовать число в двоичную строку (123->"1111011")?

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

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

Вот тебе дополнительный материал по теме.

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

11. Хулио

- Привет, дружище!

Элли сегодня подходит и спрашивает: - Дорогой, как я тебе?

А, я что-то на своей волне был, вот и отвечаю:

Немножко надоела... А так - ничего!

- Ну ты даешь!

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

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

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

 Вопросы к собеседованиям
1 Какие бывают внутренние классы?
2 Во что компилируется анонимный внутренний класс?
3 Зачем использовать ключевое слово final при создании анонимных классов?
4 Как правильно создать объект внутреннего класса?
5 Как правильно создать объект вложенного класса?
6 Можно ли создавать статические методы/переменные во внутреннем классе?
7 Назовите три любых внутренних класса?
8 Как внутренние классы решают проблему множественного наследования в Java?
9 Чем отличаются анонимные классы, созданные на основе интерфейса и на основе класса?
10 Можно ли создать анонимный статический вложенный класс?

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

- Здорово, Амиго!

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

- Готов к новой полностью секретной миссии?

- Готов, сэр!

- Тогда вот тебе инструкции к первому секретному заданию:

«А теперь напишем игру "змейка"».

- Есть, написать игру «змейка»!

- Да не кричи ты. Говорят же тебе – полностью секретное задание.

Для его выполнения будешь работать с агентом IntellijIDEA. Он введет тебя в курс дела.

Все дальнейшие инструкции также через него.

- Разрешите приступить, сэр?

- Приступайте.