• 6.03

  • +7.50

  • ,

Уровень 28. Ответы на вопросы к собеседованию по теме уровня.

Всем привет!
Решил продолжить обучение на джавараше, несмотря на то, что работу по java я уже нашел.
Дисклеймер:
Этот блог я веду для самого себя, очень лениво, и, можно сказать, из под палки. Я буду очень рад, если он окажется для кого-то полезным, однако не стоит использовать его в качестве основного источника для поиска ответов на вопросы уровня.

1 Какие приоритеты нитей бывают?
Каждый поток в системе имеет свой приоритет. Приоритет – это некоторое число в объекте потока от 1 до 10, более высокое значение которого означает больший приоритет. Система в первую очередь выполняет потоки с большим приоритетом, а потоки с меньшим приоритетом получают процессорное время только тогда, когда их более привилегированные собратья простаивают.

Работать с приоритетами потока можно с помощью двух функций:

void setPriority(int priority) //устанавливает приоритет потока.

Возможные значения priority — MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY.

int getPriority() // получает приоритет потока.

Источник
Если приоритет не задан, то нить получает приоритет 5 — средний.

Приоритет нити не сильно влияет на ее работу, а носит скорее рекомендательный характер. Если есть несколько спящих нитей, которые нужно запустить, то Java-машина сначала запустит нить с более высоким приоритетом.

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

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

2 Можно ли остановить нить, снизив ее приоритет до 0?
Нет. Будет брошен IllegalArgumentException

3 Зачем нужен класс ThreadGroup?

Чтобы любая нить не могла останавливать и прерывать все нити подряд, было введено понятие «группы нитей». Нить может оказывать влияние только на другие нити, которые содержатся в той же группе, что и она. ThreadGroup – это класс, который управляет группами нитей. Такой подход позволяет защитить нити от нежелательного изменения.

Иногда приходится выполнять код, которому нельзя 100% доверять. Поэтому удобно поместить все его нити в отдельную группу и запретить им вмешиваться в работу основной группы нитей.

Иными словами для управления группами потоков

4 В какой группе нитей состоит main-thread?
main

5 Что такое паттерн ThreadPool
Паттерн ThreadPool общими словами — группа потоков, которые решают группы задач. Задачи организованы в очередь. Как только поток завершает работу над задачей, он запрашивает следующую задачу из очереди, до тех пор, пока все задачи в очереди не будут выполнены. После этого поток может завершиться, либо уснуть, до тех пор, пока в очереди не появятся новые задачи.

6 Зачем нужен класс ThreadPoolExecutor?
Для решения большого количества небольших задач группой потоков. Использование класса позволяет избегать расточительного использования ресурсов машины. Тк Создавать для каждой задачи свою нить бывает не очень рационально. Для каждой нити Java-машина выделяет довольно много ресурсов.
Другими словами – создание и уничтожение отработавшей нити может тратить больше ресурсов и времени, чем само выполняемое задание.
Java-разработчики придумали элегантное решение этой проблемы — ThreadPoolExecutor.
ThreadPoolExecutor – это класс, который имеет внутри две вещи:
А) Очередь задач, в которую можно добавлять задачи, по мере их появления в программе.
Б) Пул-нитей (группа нитей) – которые эти задачи исполняют.
При этом нити не уничтожаются после выполнения задания, а засыпают. Чтобы начать выполнять новое задание, как только оно появится.

7 Сколько способов создать нить вы знаете? (Thread, Runnable, Callable)
public class ThreadsTests {
    //Способ 1
    static class ThreadExampleRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    //Способ 2
    static class ThreadExampleFromThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    //Способ 3
    static class ThreadExampleFromCallable implements Callable{
        @Override
        public String call() throws Exception {
            return Thread.currentThread().getName();
        }
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new Thread(new ThreadExampleRunnable()).start(); //Способ 1
        new ThreadExampleFromThread().start(); //Способ 2

        //Способ 3
        ExecutorService service = Executors.newFixedThreadPool(5);
        Future<String> task = service.submit(new ThreadExampleFromCallable());
        System.out.println(task.get());

    }
}


8 Для чего используется класс Future?
Этот объект можно использовать, чтобы узнать, завершилось ли уже выполнение задачи, а также, чтобы получить результат ее выполнения.
boolean cancel(boolean mayInterrupt); // Останавливает задачу. 
boolean isCancelled(); //Возвращает true, если задача была остановлена.
boolean isDone(); //Возвращает true, если выполнение задачи завершено.
V get() throws InterruptedException, ExecutionException; //Возвращает результат вызова метода call или кидает исключение, если оно было.


9 В чем преимущества Callable над Runnable?
Используя Callable мы можем узнать завершилась ли задача, и узнать её результат, гораздо проще, чем при использовании Runnable

10 Можно ли отменить выполнение задачи, если использовать класс Future?
Можно, если задача лежит в очереди и ожидает своего исполнения, иначе не факт
  • ,

Моя история успеха

20.06.2015 — 09.01.2017

Думаю, пришло время и мне рассказать о своем пути в мир java.

Начну издалека.

Предупреждение, пост будет длинным.

Мне 25 лет. Я живу в Санкт-Петербурге. Приехал в этот город учиться из провинции. Учился на бизнес-аналитика. Сама учеба мне не приносила удовольствия. Я не понимал, что я буду делать, не видел практической выгоды в изучаемых мною предметах. Да и что лукавить, я просто страдал херней, как и большинство студентов. От сессии до сессии я вел беззаботный образ жизни, и вся моя учеба заключалась в закрытии нажитых учебных долгов.

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

Работу я нашел. Устроился в компанию, которая внедряла ERP системы 1С и Microsoft Navision. Устроился консультантом. Это тот чувак, который является промежуточным звеном между клиентом и разработчиком. Однако руководство приняло решение переделать меня на программиста, тк я знал Pascal. Так и порешили. Стал младшим разработчиком Microsoft Navision. Язык разработки — C/AL. Это практически тот же паскаль, кастомизированный под систему Navision. Т.е. писать на нем нельзя ничего, кроме всяких приблудов для этой системы. Первое время мне нравилось работать. Однако скоро я понял, что как программисту, мне в этой среде очень тесно и как-то не комфортно. Но учеба в магистратуре не позволяла учить что-то другое, да и что лукавить, я был слишком ленив для чего-то другого. Получив от магистратуры все, что я от нее хотел (время пожить в общаге и работу), я с ней кое как покончил.

Начало пути в мир java было положено тогда. Когда я освободился от оков универа, и принял решение изучить какой-нибудь язык OOП. Стал выбирать. С++ слишком сложно — писали на форумах, С# — не помню, что писали про него, Java — востребовано, не так сложно, как C++, есть много литературы, в том числе русскоязычной. Ну java так java (стоит сказать, что в универе я проходил один семестр джаву. Кое как получил зачет и решил, что никогда в жизни не свяжу свою жизнь с этим языком). Когда язык был выбран, не найти javarush было уже невозможно.

Присоединился к этому ресурсу 20 июня 2015 года. Начал учиться. Не знаю, как описать свое обучение здесь. Я не был выдающимся учеником. Я не занимался так регулярно, как самому хотелось бы. Я не всегда решал задачи сам. Я не всегда понимал почему у меня зачлась очередная задача. Я не всегда читал все доп. материалы к лекциям. Я часто бесился, когда валидатор испытывал мои нервы на прочность. У меня бывали большие перерывы в обучении (более месяца), иногда из-за напряг по работе, иногда из-за лени, которую не всегда удавалось побороть. Однако, я бы не нашел работу, если бы изучение java не изменило что-то внутри меня. Впервые в жизни я твердо решил довести это дело до конца. Внутри я говорил себе, что несмотря ни на что, я не брошу это дело. Это была первая цель в моей жизни, достижение которой требовало от меня больших усилий. И первый раз в жизни я говорил себе, что плевать на все, я достигну её. Не знаю, смог бы я также, если бы не было javarush. Этот ресурс позволяет видеть тебе путь до твоей цели. Он тебе как бы говорит: «Вот он, этот путь, 40 уровней и работа». Это очень помогает не бросить.

Купил себе unlimited подписку. Знания паскаля очень помогали в решении задач. И мне действительно нравилось решать их. Сидеть в ИДЕЕ, чувствовать себя крутым разработчиком с классным окружением. Прошел 20 уровней, и решил, что пора по собеседованиям. Я конечно надеялся получить предложение, но больше хотел попасть на собеседование для того, чтобы оценить приобретенные знания, и получить пинок и мотивацию для дальнейшего развития. Все так и произошло. Собеседование я конечно же не прошел, но к моему большому удивлению, все оказалось не так плохо, как я боялся. Однако пинок и дозу мотивации я получил. Я понял важность теории, и с 21 уровня стал активно читать доп. литературу. Ну как активно, с 21 уровня стали появляться вопросы на собеседования по теме уровня. Я и решил, что если отвечу на них, то этого будет достаточно, т.к. я ужасно ленивый. Вот с 21 уровня и стал постить ответы на вопросы по темам уровня.

Люди стали комментировать мои ответы. Был такой коммент: «Спасибо большое за топик. Продолжай вести блог — он для меня как точка опоры: вижу твой ответ и открываю книги, гуглю, пытаясь найти недостающее.» Это очень меня удивило. Я писал только лишь для себя, а оказалось, что кому-то это тоже полезно. Это придало дополнительной мотивации. Я уже чувствовал обязанность отвечать на все вопросы, и постить свои ответы.

Однако время шло, а я развивался только лишь в рамках Java core. Учил только то, что давал мне javarush. А на рынке труда были такие страшные слова, как maven, gradle, jdbc, tomcat, hibernate, spring и т.д. А я был как из анекдота: «Я знаю кун-фу, карате, таэквондо и очень много других страшных слов». Поэтому я решил, что пришла пора расширять кругозор. Стал гуглить, искать уроки по разным темам, начал с jdbc и там пошло. Когда-то меня дико бесило, что я не могу найти нормальный виджет погоды на андроид, еще тогда подумал, что напишу его сам. Это толкнуло меня изучать андроид. По андроиду есть классный ресурс startandroid, там из большого количества безобразно простых уроков, можно сложить что-то не такое-уж и простое. Виджет погоды я так и не написал, однако написал калькулятор, как бы банально это ни было. Во время разработки калькулятора изучил основы регулярных выражений и алгоритм обратной польской записи. Мне нравилось это дело. Но на рынке труда по джаве преобладали вакансии, связанные с Java EE. Поэтому метался между яварашем, java ee и андроидом. Написал небольшое веб приложение, одну логическую игрушку на андроиде. Когда-то я написал морской бой. Решил и его переписать, т.к. более-менее освоил ООП. Можно сказать, что я написал его с нуля, т.к. там был такой шлак, что я не мог понять, как я заставил ЭТО работать, и как ЭТО вообще работает.

Стал снова искать вакансии. Откликался на все подряд. Но меня никто не звал. Целых три месяца. Я не мог понять почему. Я искал инфу о том, как устроиться джуниором. И везде писали о том, как нужно вести себя на собеседованиях. Это конечно полезная информация, но как туда вообще попасть?! Это была действительно проверка на прочность. На каком-то форуме человек жаловался, что уже столько занимается, а его все никак не берут. Кто-то ответил ему: «Такова Java. Слишком высок порог вхождения.» Эти слова вселили какую-то гордость в меня. Да, я испытывал гордость, от того, что порог вхождения действительно высок, и я все равно не брошу. Я говорил себе, что мое дело малое, просто продолжать заниматься, чтобы не произошло. Это, наверное, самое сложное. Просто продолжать заниматься. Изо дня в день. Своим постом я хочу дать всем один совет. Занимайтесь. Верьте в себя и в свою мечту. Не позволяйте никому, и в первую очередь самому себе, бить по вашей решительности. Вы должны не просто верить, что все получится. Вы должны быть уверены в том, что это произойдет, потому что вы не бросите это дело, и будете бить в одну точку, пока не пробьетесь к своей цели.

Почему меня никуда не звали, я так и не понял. Но спустя 3 месяца меня пригласили на 4 собеседования. На три вакансии я откликался. А одна компания позвала меня сама. Это был Яндекс. Я так и не понял, чего они меня позвали. Хотя, это было бы классным завершением моего поста. Но в Яндекс я так и не прошел. На собеседованиях в основном гоняли по теории. Мне очень помогли мои личные проекты, перечисленные выше, которые позволяли менять тему разговора от теории к практике. Так что это тоже как бы совет, это может вам помочь.

Из 4 собеседований, яндекс меня отшил. Другая фирма оказалась шарашкиной конторой. Третья и четвертая обещали перезвонить.

Перезвонили из четвертой, поздравили с успешным прохождением первого этапа собеседования. Выслали тестовое задание.
Описывать его подробно не буду. Но если коротко, необходимо было написать веб приложение, через которое мне нужно было взаимодействовать с интерфейсом мобильного приложения, через стороннюю программу, локально запущенную на компе. Т.е. веб приложение посылало команды стороннему приложению, сторонее приложение посылало команду телефону по wifi, и все это отражалось в веб интерфейсе. Когда я получил тестовое задание, я не знал львиную долю тех технологий, которые нужно было использовать. У меня была неделя. Это был кодерский марафон. Настолько интенсивного кодинга у меня не было никогда. На протяжении недели не существовало в мире ничего кроме меня, и этого проекта.

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

С 9 января сего года я работаю автоматизатором тестирования мобильных приложений. Работой сейчас я очень доволен. Много плюшек, в числе которых бесплатное питание весь день, график с 12, гамак в кабинете и корпоративный английский. Иными словами, занимайтесь. Вы делаете это, потому что верите, что оно того стоит. И оно действительно того стоит.
  • ,

Уровень 26. Ответы на вопросы к собеседованию по теме уровня. Часть 2. Вопросы 6-9, 11-12

6. Что такое канкаренси?

Concurrency – это библиотека классов в Java, в которой собрали специальные классы, оптимизированные для работы из нескольких нитей. Эти классы собраны в пакете java.util.concurrent. Их можно схематично поделить по функциональному признаку следующим образом:


Concurrent Collections — набор коллекций, более эффективно работающие в многопоточной среде нежели стандартные универсальные коллекции из java.util пакета. Вместо базового враппера Collections.synchronizedList с блокированием доступа ко всей коллекции используются блокировки по сегментам данных или же оптимизируется работа для параллельного чтения данных по wait-free алгоритмам.

Queues — неблокирующие и блокирующие очереди с поддержкой многопоточности. Неблокирующие очереди заточены на скорость и работу без блокирования потоков. Блокирующие очереди используются, когда нужно «притормозить» потоки «Producer» или «Consumer», если не выполнены какие-либо условия, например, очередь пуста или перепонена, или же нет свободного «Consumer»'a.

Synchronizers — вспомогательные утилиты для синхронизации потоков. Представляют собой мощное оружие в «параллельных» вычислениях.

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

Locks — представляет собой альтернативные и более гибкие механизмы синхронизации потоков по сравнению с базовыми synchronized, wait, notify, notifyAll.

Atomics — классы с поддержкой атомарных операций над примитивами и ссылками.

Источник:
habrahabr.ru/company/luxoft/blog/157273/

7. Какие классы из «канкаренси» ты знаешь?

Ответ на этот вопрос отлично изложен в этой статье. Смыла перепечатывать всю её сюда я не вижу, поэтому приведу описания только тех классов, с которыми имел честь вскользь ознакомиться.

ConcurrentHashMap<K, V> — В отличие от Hashtable и блоков synhronized на HashMap, данные представлены в виде сегментов, разбитых по hash'ам ключей. В результате, для доступа к данным лочится по сегментам, а не по одному объекту. В дополнение, итераторы представляют данные на определенный срез времени и не кидают ConcurrentModificationException.

AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray — Что если в классе нужно синхронизировать доступ к одной простой переменной типа int? Можно использовать конструкции с synchronized, а при использовании атомарных операций set/get, подойдет также и volatile. Но можно поступить еще лучше, использовав новые классы Atomic*. За счет использования CAS, операции с этими классами работают быстрее, чем если синхронизироваться через synchronized/volatile. Плюс существуют методы для атомарного добавления на заданную величину, а также инкремент/декремент.

8. Как устроен класс ConcurrentHashMap?

К моменту появления ConcurrentHashMap Java-разработчики нуждались в следующей реализации хэш-карты:
  • Потокобезопасность
  • Отсутствие блокировок всей таблицы на время доступа к ней
  • Желательно, чтобы отсутствовали блокировки таблицы при выполнении операции чтения
Основные идеи реализации ConcurrentHashMap следующие:

1. Элементы карты

В отличие от элементов HashMap, Entry в ConcurrentHashMap объявлены как volatile. Это важная особенность, также связанная с изменениями в JMM.

static final class HashEntry<K, V> {
    final K key;
    final int hash;
    volatile V value;
    final HashEntry<K, V> next;

    HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
        this .key = key;
        this .hash = hash;
        this .next = next;
        this .value = value;
     }

    @SuppressWarnings("unchecked")
    static final <K, V> HashEntry<K, V>[] newArray(int i) {
        return new HashEntry[i];
    }
}


2. Хэш-функция

В ConcurrentHashMap также используется улучшенная функция хэширования.
Напомню, какой она была в HashMap из JDK 1.2:
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

Версия из ConcurrentHashMap JDK 1.5:
private static int hash(int h) {
    h += (h << 15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h << 3);
    h ^= (h >>> 6);
    h += (h << 2) + (h << 14);
    return h ^ (h >>> 16);
}

В чём необходимость усложнения хэш-функции? Таблицы в хэш-карте имеют длину, определяемую степенью двойки. Для хэш-кодов, двоичные представления которых не различаются в младшей и старшей позиции, мы будем иметь коллизии. Усложнение хэш-функции как раз решает данную проблему, уменьшая вероятность коллизий в карте.

3. Сегменты

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

4. ConcurrencyLevel

Данный параметр влияет на использование картой памяти и количество сегментов в карте.
Количество сегментов будет выбрано как ближайшая степень двойки, большая чем concurrencyLevel. Занижение concurrencyLevel ведёт к тому, что более вероятны блокировки потоками сегментов карты при записи. Завышение показателя ведёт к неэффективному использованию памяти.

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

Итого

Итак, основные преимущества и особенности реализации ConcurrentHashMap:
  • Карта имеет схожий с hashmap интерфейс взаимодействия
  • Операции чтения не требуют блокировок и выполняются параллельно
  • Операции записи зачастую также могут выполняться параллельно без блокировок
  • При создании указывается требуемый concurrencyLevel, определяемый по статистике чтения и записи
  • Элементы карты имеют значение value, объявленное как volatile

Источник: habrahabr.ru/post/132884/

9. Что такое класс Lock?

Для управления доступом к общему ресурсу в качестве альтернативы оператору synchronized мы можем использовать блокировки. Функциональность блокировок заключена в пакете java.util.concurrent.locks.

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

Классы блокировок реализуют интерфейс Lock, который определяет следующие методы:

  • void lock(): ожидает, пока не будет получена блокировка
  • boolean tryLock(): пытается получить блокировку, если блокировка получена, то возвращает true. Если блокировка не получена, то возвращает false. В отличие от метода lock() не ожидает получения блокировки, если она недоступна
  • void unlock(): снимает блокировку
  • Condition newCondition(): возвращает объект Condition, который связан с текущей блокировкой

Организация блокировки в общем случае довольно проста: для получения блокировки вызывается метод lock(), а после окончания работы с общими ресурсами вызывается метод unlock(), который снимает блокировку.

Объект Condition позволяет управлять блокировкой.

Как правило, для работы с блокировками используется класс ReentrantLock из пакета java.util.concurrent.locks. Данный класс реализует интерфейс Lock.

Рассмотрим использование Java Lock API на примере небольшой программы:
И так, пусть у нас есть класс Resource с парочкой потокобезопасных методов и методов, где потокобезопасность не требуется.
public class Resource {
 
    public void doSomething(){
        // пусть здесь происходит работа с базой данных 
    }
     
    public void doLogging(){
        // потокобезопасность для логгирования нам не требуется
    }
}

А теперь берем класс, который реализует интерфейс Runnable и использует методы класса Resource.
public class SynchronizedLockExample implements Runnable{
 
    // экземпляр класса Resource для работы с методами
    private Resource resource;
     
    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }
     
    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}

А теперь перепишем приведенную выше программу с использованием Lock API вместо ключевого слова synchronized.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
 
    private Resource resource;
    private Lock lock;
     
    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }
     
    @Override
    public void run() {
        try {
            // лочим на 10 секунд
            if(lock.tryLock(10, TimeUnit.SECONDS)){
            resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //убираем лок
            lock.unlock();
        }
        // Для логгирования не требуется потокобезопасность
        resource.doLogging();
    }
 
}

Как видно из программы, мы используем метод tryLock(), чтобы убедиться в том, что поток ждет только определенное время. Если он не получает блокировку на объект, то просто логгирует и выходит.

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

Источники:
metanit.com/java/tutorial/8.9.php
prologistic.com.ua/java-lock-teoriya-i-primer-ispol-zovaniya-concurrency-lock.html

11. Что такое mutex?

Мютекс – это специальный объект для синхронизации нитей/процессов. Он может принимать два состояния – занят и свободен. Если упростить, то мютекс – это boolean-переменная, которая принимает два значения: занят(true) и свободен(false).

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

Мютекс прикреплен к каждому объекту в Java. Прямой доступ к мютексу есть только у Java-машины. От программиста он скрыт.

12. Что такое монитор?

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

В Java монитор реализован с помощью ключевого слова synchronized.

Когда мы пишем блок synchronized, то компилятор Java заменяет его тремя кусками кода:
  1. В начале блока synchronized добавляется код, который отмечает мютекс как занятый.
  2. В конце блока synchronized добавляется код, который отмечает мютекс как свободный.
  3. Перед блоком synchronized добавляется код, который смотрит, если мютекс занят – то нить должна ждать его освобождения.


Часть 1.
  • ,

Уровень 26. Ответы на вопросы к собеседованию по теме уровня. Часть 1. Вопросы 1-5, 10.

Конспект получился довольно громоздким, поэтому разделил его на две части. Во второй части собраны ответы на вопросы касательно канкаренси и многопоточности. В первой части остальные.
Написание далось довольно-таки тяжело. Многого все равно не понимаю, поэтому как всегда, комментарии, замечания, дополнения — приветствуются)


1. Как пользоваться интерфейсом Comparable?

В интерфейсе Comparable объявлен всего один метод compareTo(Object obj), предназначенный для реализации упорядочивания объектов класса. Его удобно использовать при сортировке упорядоченных списков или массивов объектов.

Данный метод сравнивает вызываемый объект с obj. В отличие от метода equals, который возвращает true или false, compareTo возвращает:
  • 0, если значения равны;
  • Отрицательное значение, если вызываемый объект меньше параметра;
  • Положительное значение, если вызываемый объект больше параметра.

Прежде всего он удобен для сортировки упорядоченных списков (java.util.List) и массивов объектов. Если список/массив содержит элементы, реализующие этот интерфейс, то они могут быть отсортированы автоматически методами java.util.Collections.sort(List)/Arrays.sort(Object[]).

С интерфейсом Comparable связано понятие натурального упорядочивания, потому как он устанавливает натуральный порядок следования экземпляров любого класса, реализующего этот интерфейс. Иначе говоря, порядок (x, y) соответствует выполнению условия x.compareTo(y) <= 0.

Правила реализации Comparable, а вернее, его метода compareTo(Object) следующие (x и y – экземпляры класса, реализующего Comparable):
  • x.compareTo(y) возвращает -1 или 1, если x должен находиться, соответственно, раньше или позже y. Если метод возвращает 0, то порядки (x, y) и (y, x) эквивалентны.
  • Если sign(a) – функция, возвращающая -1,0,1 для а, соответственно, меньше 0, равного 0 и больше 0, то должно выполняться равенство sign(x.compareTo(y))==-sign(y.compareTo(x)). Что логично: если x идет раньше y, то y должен идти позже x, и наоборот.
  • Если x.compareTo(y) > 0 и y.compareTo(z) > 0, то x.compareTo(z) > 0 – соотношение транзитивности неравенств.
  • Если x.compareTo(y) == 0, то sign(x.compare(z)) == sign(y.compareTo(z)), для любых z.
  • Вызов x.compareTo(null) должен бросать исключение NullPointerException. В этом есть расхождение с логикой реализации equals (напомню, x.equals(null) возвращает false).
  • Если y по своему типу не может быть сравнен с x, то вызов x.compareTo(y) должен бросать исключение ClassCastException.
  • (x.compareTo(y) == 0) == x.equals(y), т.е. вызов x.compareTo(y) должен возвращать 0 тогда и только тогда, когда x.equals(y) возвращает true. Это правило непротиворечивости, и его очень важно учитывать.

Источники:
echuprina.blogspot.ru/2012/02/comparable-comparator.html
www.skipy.ru/technics/objCompTh.html#comparable

2. Как пользоваться интерфейсом Comparator?

В интерфейсе Comparator объявлено два метода compare(Object obj1, Object obj2) и equals(Object obj).
При использовании интерфейса Comparator, логика сравнения пары объектов не прячется внутрь класса/объекта, а реализуется в отдельном классе.

Метод compare(x,y) в точности соответствует по своей сути вызову x.compareTo(y). Точно так же должны выполняться все правила, что и правила для реализации метода compareTo(Object) интерфейса Comparable.
Comparator может использоваться в любом месте, где нужна сортировка. При этом, во-первых, появляется необходимая гибкость – возможность реализации нескольких правил сортировки. А во-вторых, сортируемые объекты могут не реализовывать интерфейс Comparable. В случае, если они его все-таки реализуют, Comparator имеет приоритет.

Интерфейс Comparator определяет еще и метод equals(Object), как это ни парадоксально. Этот метод служит для сравнения самих экземпляров интерфейса Comparator и должен возвращать true только в том случае, если сравниваемые объекты обеспечивают одинаковый порядок сортировки. Однако всегда безопасно оставлять исходную реализацию Object.equals(Object) нетронутой

Источник:
echuprina.blogspot.ru/2012/02/comparable-comparator.html
www.skipy.ru/technics/objCompTh.html#comparable

3. Какие методы есть у класса Collections?

public static <T> boolean addAll(Collection<? super T> c, T… elements)
Метод добавляет элементы массива elements в коллекцию Collection<? super T> c. Элементы могут быть указаны по одиночке, либо как массив. Когда элементы указанны по отдельности данный метод предоставляет возможность удобно добавить все элементы в имеющуюся коллекцию:
Collections.addAll(flavors, "Peaches 'n Plutonium", "Rocky Racoon");


public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)


Оба метода ищут в списке переданном в параметре объект переданный в параметре используя алгоритм двоичного поиска. Возвращают индекс элемента, если такой элемент в списке есть, иначе индекс первого элемента списка большего key, если все элементы меньше key, возвращает list.size().

Перед использованием данных методов списки должны быть отсортированы. В первом случае отсортированы по возрастанию в «естественном» порядке следования элементов списка (такой же, как при использовании Collections.sort(list)). Во втором случае список должен быть отсортирован по возрастанию в порядке следования, который обеспечивает переданный компаратор (такой же порядок, как при использовании Collections.sort(list, c)[здесь «с» — компаратор из параметров описываемого метода])

public static <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)

Преамбула:
Механизм дженериков в языке обеспечивает проверку типов во время компиляции. Обычно этого и достаточно, однако бывают случаи, когда все-таки нет. К примеру мы нашу коллекцию передаем в код библиотеки, куда-нибудь на сторону, нам неизвестную, и нам очень сильно хочется, чтоб код этой «third-party library» не вставил в нашу коллекцию элемент неправильного типа. Это вот возможная проблема номер 1.
Возможная проблема номер 2 следующая. Предположим наша программа выдает нам ClassCastException, который оповещает нас о том, что в коллекцию был вставлен элемент неправильного типа. К сожалению данное исключение может вылететь в любой момент, после того, как неправильный элемент был вставлен, и обычно предоставляет нам совсем немного или вообще ноль информации об источнике проблемы.
Конец преамбулы.

Используя метод метод checkedCollection мы можем избавить себя от проблемы один и два, т.к. этот метод создает коллекцию проверяемую на этапе выполнения.
Решение проблемы номер два, с помощью данного метода:
К примеру мы имеем вот это, и у нас вываливается ClassCastException.
Collection<String> c = new HashSet<String>();

Код выше можно временно заменить на:
Collection<String> c = Collections.checkedCollection(
         new HashSet<String>(), String.class);

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

Родственные на мой взгляд методы:
public static <E> List<E> checkedList(List<E> list,Class<E> type)
public static <K,V> Map<K,V> checkedMap(Map<K,V> m, Class<K> keyType,Class<V> valueType)
public static <E> Set<E> checkedSet(Set<E> s,Class<E> type)
public static <K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K,V> m,Class<K> keyType,Class<V> valueType)
public static <E> SortedSet<E> checkedSortedSet(SortedSet<E> s,Class<E> type)


public static <T> void copy(List<? super T> dest,List<? extends T> src)
Метод копирует элементы src в dest. индексы у копированных элементов будут совпадать.

public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
public static <T> T min(Collection<? extends T> coll,Comparator<? super T> comp)
public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)


методы возвращают минимальный\максимальный элемент в коллекции с точки зрения «естественного порядка»(интерфейс Comparable) либо порядка переданного компаратора.

public static boolean disjoint(Collection<?> c1,Collection<?> c2)
Возвращает true если у коллекций нет одинаковых элементов.

<T> List <T> emptyList(), <K,V> Map <K,V> emptyMap(),
<T> Set <T> emptySet()
– возвращают пустой список, карту отображения
и множество соответственно;

<T> void fill(List<? super T> list, T obj) – заполняет список заданным элементом;

int frequency(Collection<?> c, Object o) – возвращает количество вхождений в коллекцию заданного элемента;

<T> List <T> nCopies(int n, T o) – возвращает список из n заданных элементов;

<T> boolean replaceAll(List<T> list, T oldVal, T newVal) – заменяет все заданные элементы новыми;

void reverse(List<?> list) – “переворачивает” список;

void rotate(List<?> list, int distance) – сдвигает список циклически на заданное число элементов;

void shuffle(List<?> list) – перетасовывает элементы списка;

<T> Set <T> singleton(T o), singletonList(T o), singletonMap(K key, V value) – создают множество, список и карту отображения, состоящие из одного элемента;

<T extends Comparable<? super T>> void sort(List<T> list),
<T> void sort(List<T> list, Comparator<? super T> c)
– сортировка списка, естественным порядком и используя Comparator соответственно;

void swap(List<?> list, int i, int j) – меняет местами элементы списка стоящие на заданных позициях.

Источники:
crypto.pp.ua/2010/06/klass-collections-v-java/
docs.oracle.com/javase/7/docs/api/java/util/Collections.html

4. Какие методы есть у класса Arrays?

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

public static <T> List<T> asList(T… a)
формирует список на основе массива. Массив при этом используется для внутреннего представления списка. Таким образом сохраняется связь между списком и исходным массивом:

изменения в массиве отразятся на списке:
String[] a = { "foo", "bar", "baz"};
List<String> list = Arrays.asList(a);
System.out.println(list); // [foo, bar, baz]

a[0] = "aaa";
System.out.println(list); // [aaa, bar, baz]

изменения в списке отразятся на массиве:
String[] a = { "foo", "bar", "baz"};
List<String> list = Arrays.asList(a);
System.out.println(list); // [foo, bar, baz]

list.set(0, "bbb");
System.out.println(Arrays.toString(a)); // [bbb, bar, baz]

Если массив содержит объекты, очевидно, и массив и список будут ссылаться на одни и те же экземпляры:
Object[] a = { new Object(), new Object(), new Object()};
List<Object> list = Arrays.asList(a);
System.out.println(a[0] == list.get(0)); // true


int binarySearch(параметры) – перегруженный метод организации бинарного поиска значения в массивах примитивных и объектных типов. Возвращает позицию первого совпадения;

void fill(параметры) – перегруженный метод для заполнения массивов значениями различных типов и примитивами;

void sort(параметры) – перегруженный метод сортировки массива или его части с использованием интерфейса Comparator и без него;

static <T> T[] copyOf(T[] original, int newLength) –заполняет массив определенной длины, отбрасывая элементы или заполняя null при необходимости;

static <T> T[] copyOfRange(T[] original, int from, int to) – копирует заданную область массива в новый массив;

<T> List<T> asList(T… a) – метод, копирующий элементы массива в объект типа List<T>.

Источник:
crypto.pp.ua/2010/06/klass-arrays-v-java/

5. Как называется сортировка, которая используется при вызове Collections.sort()?

Из документации:
Реализация является адаптированным вариантом сортировки списка для Python Тима Петерса (TimSort). Данная реализация сбрасывает список в массив, сортирует массив, затем проходит по списку и перезагружает каждый элемент списка из соответствующего элемента массива. Это позволяет избежать сложности n*n log(n), которая возникла бы при попытки отсортировать связный список напрямую

Из вики:
Timsort — гибридный алгоритм сортировки, сочетающий сортировку вставками и сортировку слиянием, опубликованный в 2002 году Тимом Петерсом. В настоящее время Timsort является стандартным алгоритмом сортировки в Python, OpenJDK 7 и реализован в Android JDK 1.5. Основная идея алгоритма в том, что в реальном мире сортируемые массивы данных часто содержат в себе упорядоченные подмассивы. На таких данных Timsort существенно быстрее многих алгоритмов сортировки.

10. Что такое итератор?


Представленный в релизе JDK 1.2 языка Java интерфейс java.util.Iterator обеспечивает итерацию контейнерных классов. Каждый Iterator реализует методы next() и hasNext() и дополнительно может поддерживать метод remove(). Итераторы создаются соответствующими контейнерными классами, как правило методом iterator().

Метод next() переводит итератор на следующее значение и возвращает указываемое значение итератору. При первоначальном создании итератор указывает на специальное значение, находящееся перед первым элементом, поэтому первый элемент можно получить только после первого вызова next(). Для определения момента, когда все элементы в контейнере были перебраны, используется тестовый метод hasNext(). Следующий пример демонстрирует простое использование итераторов:
Iterator iter = list.iterator();
//Iterator<MyType> iter = list.iterator(); в J2SE 5.0
while (iter.hasNext())
    System.out.println(iter.next());

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

Кроме того, для java.util.List существует java.util.ListIterator со схожим API, но позволяющем прямую и обратную итерации, обеспечивая определение текущего индекса в списке и переход к элементу по его позиции.

Источник:
ru.wikipedia.org/wiki/%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80#Java

Часть 2.
  • ,

Уровень 25. Ответы на вопросы к собеседованию по теме уровня.

1. Назовите все состояния объекта Thread?
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED


2. В какие состояния может перейти нить, при входе в блок synchronized?
RUNNABLE
BLOCKED

В RUNNABLE, если блок кода, помеченный synchronized, не занят другой нитью. Иначе наша нить получит состояние BLOCKED и будет ждать освобождения объекта-мютекса.

3. В какое состояние перейдет нить, при вызове метода wait()?
Вызов этого метода переводит нить в состояние WAITING.
Метод wait() можно вызвать только внутри блока synchronized у объекта-мютекса, который был «залочен (заблокирован)» текущей нитью, в противном случае метод выкинет исключение IllegalMonitorStateException.
Object monitor = getMonitor();
synchronized(monitor)
{
 …
 monitor.wait();
 …
}

При вызове метода wait(), текущая нить снимает блокировку с объекта monitor, и переходит в состояние WAITING, ожидая вызова метода monitor.notify() или monitor.notifyAll() другой нитью. Как только это произойдет, нить проснется и если монитор не был занят, то захватит его и продолжит работу.
Если монитор окажется занят другой нитью, текущая нить перейдет в состояние BLOCKED.

4. В какое состояние перейдет нить, при вызове метода wait(500)?
Вызов этого метода переводит нить в состояние TIMED_WAITING.
По аналогии с методом wait(), wait(timeout) можно вызвать только внутри блока synchronized у объекта-мютекса, который был «залочен (заблокирован)» текущей нитью.
Object monitor = getMonitor();
synchronized(monitor)
{
 …
 monitor.wait(500);
 …
}

При вызове метода wait(), текущая нить снимает блокировку с объекта monitor, и засыпает на 500 миллисекунд. Объект monitor может быть захвачен другой нитью.
Через 500 миллисекунд нить проснется и если monitor не был занят, то захватит его и продолжит работу.
Если монитор окажется занят другой нитью, текущая нить перейдет в состояние BLOCKED.

5. В какое состояние перейдет нить, при вызове метода notify()?
Object monitor = getMonitor();
synchronized(monitor)
{
 …
 monitor.wait();
 …
}

После monitor.wait(), нить перейдет в состояние WAITING. Метод notify(), вызванный другой нитью у объекта monitor переведет нить из состояния WAITING в состояние RUNNABLE, если объект monitor не будет захвачен другой нитью, иначе в состояние BLOCKED.

6. В какое состояние перейдет нить, при вызове метода notifyAll()?
notifyAll() «пробудет» все нити. Одна из всех «спящих» (WAITING) нитей перейдет в состояние RUNNABLE, захватит монитор используемого объекта и продолжит свою работу. Остальные окажутся в состоянии BLOCKED. Как только первая «проснувшаяся» нить отпустит монитор, который все остальные ожидают, её участь повторит следующая нить (произвольная нить из состояния BLOCKED перейдет в состояние RUNNABLE). Это будет продолжаться до тех пор, пока все «пробужденные» нити не покинут состояния BLOCKED.

7. Три нити в блоке synchronized вызвали wait() у объекта-мютекса. В какое состояние перейдут эти нити, если четвертая нить вызовет notifyAll()?
Две из них перейдут в состояние BLOCKED, одна в состояние RUNNABLE

8. Чем отличается join(500) от wait(500)?
Несмотря на то, что и join(500) и wait(500) переведут текущую нить в состояние TIMED_WAITING, между ними существенные различия:

join(500) вызывается у нити, wait(500) вызывается внутри синхронизированного блока у объекта, по которому данный блок синхронизирован.

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

Через 500 миллисекунд в обоих случаях нити продолжат работу.

9. Чем отличается wait(500) от sleep(500)?
sleep(500) вызывается у нити, wait(500) вызывается внутри синхронизированного блока у объекта, по которому данный блок синхронизирован.

При вызове sleep(500) текущая нить будет ожидать 500 милисекунд, затем продолжит свою работу.
При вызове wait(500) текущая нить снимет блокировку с синхронизированного объекта, и засыпает на 500 миллисекунд.

10. В какое состояние перейдет нить при вызове метода yield()?
При вызове метода yield() – текущая нить «пропускает свой ход» и java сразу переключается на выполнение следующей нити. Нить из состояния running переходит в состояние ready. Состояния running & ready – это подсостояния состояния RUNNABLE.

PS Комментарии, дополнения, исправления, замечания — приветствуются =)
  • ,

Уровень 24. Ответы на вопросы к собеседованию по теме уровня.

1. Во что компилируются анонимные внутренние классы?
Анонимные внутренние классы компилируются в файлы внешнийКласс$n.class. На месте внешнего класса, соответственно, название обрамляющего класса, внутри которого описывается анонимный внутренний класс. На месте n число от 1 до количества анонимных классов.

2. Можно ли наследовать внутренние классы?
Наследовать внутренние классы от других — можно.
Наследование от внутреннего класса получается чуть сложнее, чем обычное, так как конструктор внутреннего класса связывается со ссылкой на окружающий внешний объект. Проблема состоит в том, что «скрытая» ссылка на объект объемлющего внешнего класса должна быть инициализирована, а в производном классе больше не существует объемлющего объекта по умолчанию. Для явного указания объемлющего внешнего объекта применяется специальный синтаксис:

//: innerclasses/InheritInner.java
// Наследование от внутреннего класса.
 
class WithInner {
  class Inner {}
}
 
public class InheritInner extends WithInner.Inner {
  //! InheritInner() {} // He компилируется 
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
}

Здесь класс InheritInner расширяет только внутренний класс, а не внешний. Но когда дело доходит до создания конструктора, предлагаемый по умолчанию конструктор не подходит, и вы не можете просто передать ссылку на внешний объект. Необходимо включить в тело конструктора выражение
ссылкаНаОбъемлющийКласс.super();

в теле конструктора. Оно обеспечит недостающую ссылку, и программа откомпилируется.

3. Можно ли наследовать анонимные внутренние классы?
Описывая анонимный класс мы уже наследуемся от какого-то класса или реализуем какой-либо интерфейс. К анонимным классам напрямую нельзя применить слова extends или implements, но ведь никто не мешает заранее подготовиться и расширить нужный интерфейс, который будем реализовывать с помощью анонимного класса. Пример в коде ниже.
import java.awt.event.WindowListener;

public class TestExtendAnonym {
    private interface MyInterface extends Runnable, WindowListener {
    }

    Runnable r = new MyInterface() {
    ...
    //Пример того как реализовать 2 и более интерфейса в анонимном классе
    };
}

Наследоваться от анонимного класса нельзя.

4. Можно ли переопределять внутренние классы?
Переопределение внутреннего класса, как если бы он был еще одним методом внешнего класса, фактически не имеет никакого эффекта:
//: innerclasses/BigEgg.java
// Внутренний класс нельзя переопределить 
// подобно обычному методу,
import static net.mindview.util.Print.*;
 
class Egg {
  private Yolk y;
  protected class Yolk {
    public Yolk() { print("Egg.Yolk()"); }
  }
  public Egg() {
    print("New Egg()");
    y = new Yolk();
  }
}	
 
public class BigEgg extends Egg {
  public class Yolk {
    public Yolk() { print("BigEgg.Yolk()"); }
  }
  public static void main(String[] args) {
    new BigEgg();
  }
}

Вывод:
New Egg()
Egg.Yolk()

Конструктор по умолчанию автоматически синтезируется компилятором, а в нем вызывается конструктор по умолчанию из базового класса. Можно подумать, что при создании объекта BigEgg должен использоваться «переопределенный» класс Yolk, но это отнюдь не так, как видно из результата работы программы.

Этот пример просто показывает, что при наследовании от внешнего класса ничего особенного с внутренними классами не происходит. Два внутренних класса — совершенно отдельные составляющие, с независимыми пространствами имен.
Иными словами нельзя.

5. Какие ограничения есть у локальных классов?
Вначале вспомним что такое локальный класс. Это класс, описанный в блоке кода, то есть, по-простому — между кавычек {}. Наиболее часто эти кавычки являются телом метода. Но могут они быть и просто блоком, статическим блоком, телом if-ов, циклов и т.д.
Локальный класс наделён особенностями внутренних классов, но имеет отличительные черты, а именно:
  1. он имеет доступ только к финальным полям и аргументам обрамляющего метода, а также ко всем полям обрамляющего класса, в том числе приватным и статическим;
  2. локальный класс виден и может создаваться только в блоке, в котором описан;
  3. у локального класса не ставиться модификатор доступа;
  4. не может иметь статических полей, методов, классов (за исключением финальных);
  5. локальный класс, объявленный в статическом блоке может обращаться только к статическим полям внешнего класса.

Но! Начиная с Java8 мы можем обращаться в локальных классах к не финальным локальным переменным, если они не были изменены до момента инициализации класса. Также теперь стало возможным обращение к не финальным параметрам метода.

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

7. Можно ли создать объект внутреннего класса, если у внешнего класса только private конструктор?
Имея подобный код:
public class PrivateConst {
    private PrivateConst() {}
    public class InnerClass{
        public void f(){
            System.out.println("hello");
        }
   }
}

Напрямую, в другом классе (вне обрамляющего), конечно, создать объект InnerClass следующим способом не получится:
PrivateConst.InnerClass priv = new PrivateConst().new InnerClass();

Но! Что если у нас есть метод, возвращающий экземпляр
PrivateConst:public class PrivateConst {
    private static PrivateConst instance;
    private PrivateConst() {}

    public static PrivateConst getInstance(){
        instance = new PrivateConst();
        return instance;
    }

    public class InnerClass{}
}

В этом случае приватный конструктор нам не помеха для создания объекта InnerClass. Так же мы без проблем сможем создавать его в методах и в других внутренних классах, принадлежащих PrivateConst. Ответ — можно, если каким-либо способом нам удастся получить объект обрамляющего класса.

8. Можно ли объявлять внутренние классы private?
Да, можно.

PS Обоснования так и не нашел, но на философии java встречались подобные примеры.
Плюс IDE не ругается.
Буду признателен за обоснование, но предположу, что в этом плане внутренний класс ничем не отличается от обычного класса.

9. Можно ли объявлять анонимные внутренние классы private?
Аналогично (в плане не нашел обоснования). Можно объявить private переменную от типа которой наследуется наш анонимный класс.

10. Сколько у класса максимально может быть внутренних классов?
Сколь угодно много. Ограничение особенности ОС и длинны имени файлов.
  • ,

Уровень 23. Ответы на вопросы к собеседованию по теме уровня.

1. Какие бывают внутренние классы?
Вложенные классы делятся на два вида: статические и не статические.
  • Вложенные классы, объявленные как статические называются вложенными статическими (static nested classes).
  • Вложенные не статические классы называются внутренними (inner classes).

2. Во что компилируется анонимный внутренний класс?
Во внутренний не статический класс

3. Зачем использовать ключевое слово final при создании анонимных классов?
Если определяется анонимный внутренний класс и ему нужно при этом использовать объекты, определенные вне этого внутреннего класса, компилятор требует, чтобы переданные на них ссылки объявлялись неизменными (final). Без такого объявления вы получите сообщение об ошибке при компиляции программы.

4. Как правильно создать объект внутреннего класса?
Внутренние (не статические) классы, как переменные и методы связаны с объектом внешнего класса. Внутренние классы так же имеют прямой доступ к полям внешнего класса. Такие классы не могут содержать в себе статические методы и поля. Внутренние классы не могут существовать без экземпляра внешнего. Для создания объекта:
Outer outer = new Outer();
Innter inner = outer.new Inner();


5. Как правильно создать объект вложенного класса?
Синтаксис создания объекта вложенного класса:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();


6. Можно ли создавать статические методы/переменные во внутреннем классе?
Статические методы/переменные объявлять во внутреннем классе (не вложенном) нельзя.
Внутренние (не статические) классы, как переменные и методы связаны с объектом внешнего класса. Такие классы не могут содержать в себе статические методы и поля.

7. Назовите три любых внутренних класса?
  1. private static class Holder —вложенный класс HashMap из java.util.
  2. В интерфейсе Map есть interface Entry<K,V>, который опять же в HashMap и реализуется в другом вложенном классе static class Entry<K,V> implements Map.Entry<K,V>.
  3. private static class IntegerCache в классе Integer .

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

class Tiger extends Cat
{

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

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

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


9. Чем отличаются анонимные классы, созданные на основе интерфейса и на основе класса?
Анонимный класс согласно JLS 15.9.5 представляют собой выражение, в котором объявление нового класса и его инициализация совмещены:

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

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

10. Можно ли создать анонимный статический вложенный класс?
Нет, статической становится только переменная, но не класс.
  • ,

Уровень 22. Ответы на вопросы к собеседованию по теме уровня.

1. Как правильно сравнить две строки в Java?
Метод equals проверяет – совпадают ли строки.
boolean equals (Object o)
String s = "cat";
boolean test1 = s.equals("cat");//true
boolean test2 = s.equals("Cat");//false
boolean test3 = s.equals("c"+"a"+"t");//true


2. Как правильно сравнить две строки в Java игнорируя регистр букв?
Метод equalsIgnoreCase – совпадают ли строки, игнорируя регистр букв.
boolean equalsIgnoreCase (String str)
String s = "cat";
boolean test1 = s.equalsIgnoreCase("cat");//true
boolean test2 = s.equalsIgnoreCase("Cat");//true
boolean test3 = s.equalsIgnoreCase("cAT");//true


3. Как отсортировать список строк в алфавитном порядке?
Используя метод Collections.sort().
ArrayList<String> list = new ArrayList<>();
list.add("zas");
list.add("fas");
list.add("sd");
list.add("asdg");
Collections.sort(list);


4. В какой кодировке хранятся строки в Java?
Строки в java хранятся в Unicode.

5. Как преобразовать строку в кодировку Windows-1251?
String utf8 = "text";
byte[] bytes1251 = utf8.getBytes("windows-1251");
String win1251 = new String(bytes1251,"windows-1251");


6. Как разбить строку на отдельные слова?
а) String[] split(String regex)

б) StringTokenizer:
String s = "Good news everyone!";

StringTokenizer tokenizer = 
   new StringTokenizer(s,"ne");
while (tokenizer.hasMoreTokens())
{
 String token = tokenizer.nextToken();
 System.out.println(token);
}


7. Как развернуть строку задом наперед?
String s = "Bender";
StringBuilder s2 = new StringBuilder(s);
s2.reverse(); //будет "redneB";


8. Что происходит, когда мы пишем «A»+«b»+«C»?
Примерно следующее:
new StringBuilder().append("A").append("b").append("C").toString();


9. Что такое mutable и immutable типы?
Объекты, которые после их создания изменить нельзя, называются неизменяемыми или immutable.
Объекты, которые после создания можно изменить, называются изменяемыми или mutable.

10. Что дает типу String то, что его сделали immutable?
а) Безопасность.
String широко используется, как параметр для многих классов Java, в частности для открытия сетевых соединений, подключений к БД, открытию файлов и пр. И если бы строка изменялась, то мы могли получить доступ к объекту (файлу например), на который мы имеем право, затем изменить строку с именем (случайно или намеренно) и получить доступ уже к другому файлу.

Так же String используется в механизме загрузки файлов, и это — фундаментальный аспект. И если бы строка изменялась, то запрос на загрузку «java.io.Writer» мог бы быть изменён на «DiskErasingWriter».

б) Hashcode
Из-за того, что строка не изменяется, она кэширует свой хэшкод и не вычисляет его каждый раз, когда мы его вызываем, что делает строку очень быстрой как ключ для hashmap.

в) Многопоточность
immutable делает экземпляры строк потокобезопасными.
  • ,

Уровень 21. Ответы на вопросы к собеседованию по теме уровня.

1. Перечислите методы класса Object
equals()
hashCode()
toString()
getClass()
notify()
notifyAll()
wait()
wait(long timeOut)
wait(long timeOut, int nanos)

2. Зачем нужны методы equals & hashCode?
используются для сравнения объектов.
Цель метода equals – определить идентичны ли объекты внутри, сравнив внутреннее содержание объектов.
Тк equals работает медленно, сначала сравниваются хэш коды объектов, и, в том случае, если хэш коды равны, идет проверка по equals

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

4. Зачем нужны методы wait, notify, notifyAll?
Иногда в программе может оказаться такая ситуация, что нить вошла в блок кода synchronized, заблокировала монитор и не может работать дальше, т.к. каких-то данных еще не хватает: например, файл который она должна обработать еще не загрузился или что-нибудь в таком духе. Для решения этой проблемы и был придуман метод wait(). Вызов этого метода приводит к тому, что нить освобождает монитор и «становится на паузу».
Для снятия с паузы используются методы notify, notifyAll. Метод notify «размораживает» одну случайную нить, метод notifyAll – все «замороженные» нити данного монитора.

5. Как правильно клонировать объект?
Два типа клонирования.
Для клонирования объекта по умолчанию нужно:
а) Добавить интерфейс Cloneable своему классу
б) Переопределить метод clone и вызвать в нем базовую реализацию:

class Point implements Cloneable
{
 int x;
 int y;

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


Или можно написать реализацию метода clone самому:
class Point 
{
 int x;
 int y;

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


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

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

7. В чем отличие final, finally, finalize?
  • final — модификатор
  • Поля не могут быть изменены, методы переопределены
  • Классы нельзя наследовать
  • Этот модификатор применяется только к классам, методам и переменным (также и к локальным переменным)
  • Аргументы методов, обозначенные как final, предназначены только для чтения, при попытке изменения будет ошибка компиляции
  • Переменные final не инициализируются по умолчанию, им необходимо явно присвоить значение при объявлении или в конструкторе, иначе – ошибка компиляции
  • Если final переменная содержит ссылку на объект, объект может быть изменен, но переменная всегда будет ссылаться на тот же самый объект
  • Также это справедливо и для массивов, потому что массивы являются объектами, – массив может быть изменен, а переменная всегда будет ссылаться на тот же самый массив
  • Если класс объявлен final и abstract (взаимоисключающие понятия), произойдет ошибка компиляции
  • Так как final класс не может наследоваться, его методы никогда не могут быть переопределены

finally — блок в связке try-catch-finally, код в котором выполнится независимо от того вылетело ли исключение в блоке try или нет. Используется для освобождения ресурсов…

finalize- метод в классе Object см 6.

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

9. Чем отличаются методы wait(1000) и sleep(1000)?
sleep() приостанавливает поток на указанное. состояние меняется на TIMED_WAITING, по истечению — RUNNABLE

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

10. В чем отличие i++ и ++i ?
++i, i сначала увеличивается на 1, затем участвует в выражении.
i++, i сначала участвует в выражении, затем увеличивается на 1.
  • ,

Морской бой и Swing

Всем доброго времени суток

Будучи еще школьником и изучая программирование у репетитора (Pascal), как и все, я захотел написать игрушку. С репетитором написал консольную игру Быки и Коровы, но раздутое чсв требовало чего-нибудь посерьезнее. Выбор остановился на морском бое. Стал писать его на паскале. В планах была консольная игра, в которой компьютер просто сообщает игроку адрес ячейки, по которой он стреляет, а игрок, в свою очередь, сообщает компьютеру адрес своей ячейки. Все тогда застопорилось на том, что не смог научить компьютер «добивать» раненные кораблики, а дописывать игру, в которой компьютер рандомно стреляет по полю не хотелось.

Прошло 9 лет, открыл для себя javarush, на 21-ом уровне проснулось чсв и потребовало морской бой.
Основными целями были:
  • Наличие интерфейса
  • Адекватная стрельба компьютера (он должен добивать раненые корабли)

Первую цель достиг с помощью библиотеки Swing
А вторую с помощью библиотеки КостыльВелосипедИВераВБога

Всем заинтересовавшимся предлагаю опробовать сего франкенштейна на GitHub

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

Всем спасибо!