Приветствую уважаемых читателей и Java-программистов!
Cтатья посвящена подготовке к сдаче экзамена Oracle Java SE7 Professional с кодовым номером 1Z0-804. Про это на Хабре уже было написано множество постов (например здесь, здесь, тут, здесь, здесь, тут, тут, и вот тут), поэтому постараюсь не повторяться и дополнить заметками о том что наиболее часто встречалось, важными нюансами, которые на мой взгляд были пропущены или недостаточно хорошо освещены в указанных статьях, и вообще в общедоступной литературе (сразу отмечу, что материал не претендует на полноту, здесь я лишь старался обозначить каверзные вопросы с экзамена и лаконично изложить некоторые сложные вещи). Так же поделюсь своими соображениями насчет того, по каким материалам лучше готовиться. С первого раза экзамен сдать не получилось, поэтому начал сохранять для себя различные заметки, где записывал всё что мне казалось сложным или трудно-запоминаемым. Которыми теперь и решил с вами поделится. Заранее прошу проявить понимание, если вы вдруг заметите ошибку, недочёт или очепятку — пишите в комментарии.
Общая информация
Экзамен сдавал в Москве, стоит 150$, длится 2,5 часа, при себе иметь 2 документа. 90 вопросов, 65% проходной балл, т.е. неправильно ответить можно примерно на 30 вопросов (не уверен что за каждый вопрос дают одинаковое кол-во баллов). При необходимости — повторная пересдача только через 2 недели. Регистрация на экзамен на сайте Pearson VUE, там же выбирается тестинговый центр и дата, оплата по карте (как то раз платил в самом центре налом, но это выходило дороже). В этот раз ездил в ACET(Американский Центр Образования и Тестирования), м. Октябрьская. На сдаче тут всё серьёзно — вплоть до выворачивания карманов и запрета брать с собой любые вещи (телефон понятно, но вот например воду — как то слишком). Выходить из кабинета разрешается, но «под расписку», и экзамен при этом не останавливается. Результаты присылают минут через 30 на почту (или на аккаунт на Oracle Certview, где отображаются все экзамены и полученные сертификаты). Бумажный сертификат приходит в среднем в течение 1.5-2 мес (Московская обл.).
Рекомендации по подготовке
Ну конечно же программировать, программировать и снова программировать! И шутка это только отчасти. Поскольку Java я начал изучать недавно, и практического опыта было мало, то поначалу пытался подготовиться в основном по книжкам и различным тестам, но всё это быстро вылетает из головы, и обязательно нужна практика, чтобы всё уложилось и главное не забывалось. Для подготовки пользовался следующими материалами:
- Книжка Java SE 6 Programmer Practice Exams (Bert Bates, Kathy Sierra, 2011 г). По сути сборник тестов и рекомендаций. Правда она немного устаревшая и по 6 версии, но в остальном тесты и объяснения к ним довольно близки к реальным вопросам. Единственный минус — тут недостаёт следующих тем: NIO.2, JDBC и Concurrency (включая Executor-ы и Fork-Join Framework). Ошибок в ответах не обнаружено, что приятно.
- Тесты Enthuware. Стоят порядка 10$ за 1 экзамен, и скачивается соответствующая программка. Охватывает все темы экзамена, хотя некоторых теоретических вопросов, встретившихся на экзамене, там нет. Изредка встречаются ошибки, так что если не уверены в показанном ответе — обязательно проверяйте. В целом, этот тест оставил положительное впечатление, т.к. по характеру «уловок» многие вопросы были схожи с экзаменационными, есть разбивка по темам и самих вопросов очень много (полагаю порядка 300+).
- Официальный туториал от Oracle — Preparation for Java Programmer Language Certification Programmer Level II Exam. Это коварная штука, на которую я наткнулся изначально, будучи уверенным что уж тут должно быть всё и полностью… И вот провалил тест. А реально его можно использовать скорее как оглавление по темам и используемым в экзамене классам, да и то с оговорками. Во-первых, полнота информации и полнота и сложность примеров, в сравнении с экзаменом, оставляет желать лучшего. Во вторых, некоторые топики (например JDBC, NIO2) раздуты тут до лишней детальности, чего на экзамене я не встретил. В-третьих, тут нету теории паттернов (а Oracle эту тему как оказалось очень любит — разбирайтесь досконально). Ну и еще по мелочи, вроде некоторых моментов теории дизайна классов и потоков я тут тоже не видел (о том что нужно знать — напишу ниже).
- Различные приложения-тесты под Android. На мой взгляд наиболее полезными/удобными оказались SCJPv1 и SCJP Champ (хотя и устаревшими).
- Ну и конечно же различная литература по Java, кому какая больше нравится, я пользовался электронной Java 7 — The Complete Reference (Herbert Schildt), Философия Java (Брюс Эккель), официальной документацией и гуглом.
- В принципе в Интернетах еще есть много всяческих тестов, и дорогих, не очень, и вообще бесплатных, но поскольку гарантий на объективность тут никаких — было решено выбрать золотую середину и на ней остановиться.
Основные темы
Дизайн классов, конструкторы, доступ
- Одна из важных тем, по которой часто встречаются подвохи на экзамене. Уровень доступа к переменным и методам класса, перегрузки и переопределения, всевозможные конструкторы в конструкторе и т.п.
- Необходимо четко знать порядок инициализации различных частей класса, в т.ч. в случае наследования. Пример:
class SuperTest { static { System.out.print("1"); } { System.out.print("3"); } public SuperTest() { System.out.print("4"); } } public class Test extends SuperTest { static { System.out.print("2"); } { System.out.print("5"); } public Test() { System.out.print("6"); } public static void main(String[] args) { new Test(); } }
Данный фрагмент напечатает 123456. Если менять местами статические и динамические блоки инициализации, ничего не изменится. В случае наличия нескольких блоков одного типа, они выполняются последовательно.
- Встречаются различные «игры» с конструктором без аргументов (или конструктором по умолчанию), который создаётся Java автоматически, если не задан явно. Нужно помнить, что если в пустом классе создан конструктор_с_аргументами, то конструктор_по_умолчанию уже создаваться не будет, а это означает, что если создать наследника и не указать явно какой конструктор суперкласса вызывать, то Java будет пытаться вызываться конструктор_по_умолчанию (которого у нас нету), что вызовет ошибку компиляции.
- Нужно помнить важную особенность модификатора protected (разрешает доступ из того-же пакета и наследникам). Если наследник находится в другом пакете, он не имеет доступа к protected-члену через ссылку на объект суперкласса (base_class.var), но может обращаться к protected-полям суперкласса через цепочку наследования (super.var) либо через ссылку на объект этого же класса (extended_class.var, this.var).
- Часто любят забывать модификатор public при реализации интерфейса, или неверный тип исключения при переопределении метода, или отсутствие доступа.
- Встречались несколько вопросов, где присутствуют одинаковые названия переменных или методов. Самый простой случай — shadowing переменной у наследника, и использование переменной исходного класса и наследника в исходном и переопределяемом методе соответственно. Так же есть вопросы, где класс реализует два разных интерфейса с одноименными методами и переменными. И аналогичные примеры с одинаковыми названиями во вложенных классах. Проверяйте это всё самостоятельно на «живом коде», подмешивая в них различные this, super, ((ClassName)obj).var, разные исключения в одноименные методы и т.п.
- Из статического контекста нельзя обращаться к нестатическому, в т.ч. запрещены обращения к this и super.
Исключения
- Очень важно помнить базовую иерархию исключений. На нижеприведенной диаграмме красным отмечены т.н. unckecked exceptions (не требующие обработки), желтый — checked (требующие обработки). Собственно на картинке как раз показан тот необходимый минимум, который нужно знать «на зубок», т.к. каверзные вопросы встречаются довольно часто.
- Если исключение не обрабатывается в блоке catch (или блок catch отсутствует) и есть блок finally, то сперва выполнится блок finally, а затем сгенерируется исключение.
- Конструкция method() throws SomeCheckedException {… } не обязывает метод вызывать внутри исключение SomeCheckedException (checked-типа). Но при этом вызов этого метода воспринимается как требующий обработки (в try… catch или throws). Пример для класса Exception: mechod() throws Exception не обязывает вызывать (throw) его внутри метода (в т.ч. метод может быть пустой), но при это внешний вызов mechod() так же требует обработки исключения.
- Внутри try {...} catch (ExceptionClass e) {...} исключение ExceptionClass должно «вызываться», но только в случае если оно типа checked! Иначе будет ошибка компиляции: «exception is never thrown in body of corresponding try statement».
- Классы исключений в блоке catch, перечисляемые через вертикальную черту («или»), не должны лежать на одной ветви иерархии. К примеру, можно написать catch (ClassNotFoundException | IOException e), а вот catch(IOException | FileNotFoundException e) — не скомпилируется.
Методы и параметры
- Передача параметров по ссылке и по значению. Важно помнить, что в Java параметры в процедуры передаются по значению только для простых типов (int, float и т.п.) и для String. Это означает, что изменение значения параметра внутри процедуры не затронет переданную извне переменную. Для всех остальных — работает передача по ссылке (классов, массивов и др.). Т.е изменения внутри процедуры сохраняются в переданном извне объекте. Явно задать как передавать параметр — по ссылке или по значению — нельзя.
- При переопределении метода можно задать тип результата как подкласс результата у базового метода (в суперклассе), но не наоборот!
- При переопределении метода он:
a) может вызвать «более узкое» исключение (подкласс);
б) не может вызвать более широкое исключение (суперкласс);
в) может вообще не вызывать исключений.
Коллекции
Главное из того что нужно знать: синтаксис, иерархия классов и интерфейсов коллекций, работа с элементами, поиск/сортировка, работа методов equals() и hashCode(). Основные коллекции, которые встретились мне на экзамене: интерфейсы Set, Map, List, Deque, SortedMap, классы ArrayList, CopyOnWriteArrayList, ArrayDeque, TreeMap, TreeSet, HashMap, HashSet. Для поиска элемента в коллекции Java использует методы equals() и hashCode(). Для нового класса, по умолчанию, метод equals() выполняет сравнение объектов (т.е. указателей на них, т.к. наследуется от Object), поэтому если вы используете собственный класс элемента в коллекции, то необходимо реализовать этот метод, чтобы Java могла найти вставленный ранее элемент. Метод hashCode() необходим Java для поиска необходимого бакета c элементами (для хеш-коллекций). Соответственно, если метод не определен и бакет не найден, то и элемент не найден. Так же желательно знать всевозможные варианты синтаксиса создания коллекций:
// Нельзя:
List<> m = new ArrayList(); // error!
List<> m = new ArrayList<String>(); // error!
List<?> m = new ArrayList<?>(); // error!
List<String> m = new ArrayList<?>(); // error!
// Можно:
HashMap m = new LinkedHashMap<>();
HashMap<String, String> m = new LinkedHashMap();
HashMap<String, String> m = new LinkedHashMap<>();
HashMap<String, ?> m = new LinkedHashMap();
HashMap<?, ?> m = new LinkedHashMap<String, String>();
Теперь подробнее об особенностях коллекций. Для удобства запоминания иерархии нашел соответствующие визуальные диаграммы, и сделал небольшое резюме по специфике каждого класса:
- Set — Интерфейс. Все реализации выполняют хранение набора объектов. Дубликаты запрещаются, но в случае добавления дубликата — исключение не вызывается (если вставляется дубликат, метод add() возвращает false). Реализации:
- HashSet — Неупорядоченный набор. Разрешает вставлять null. Нет поддержки многопоточности.
- TreeSet — Упорядоченй HashSet. Нет поддержки многопоточности.
- Map — Интерфейс, реализации выполняют хранение пары <key,value>. Все реализации запрещают дубликаты:
- HashMap — Реализует хранение элементов в виде хеш-таблицы. Перезаписывает дубликаты новыми значениями. Метод put() возвращает null, если нет ключа key, и прежнее значение value, если ключ уже есть). Разрешает null для key и value, в случае дублирования key — замещает его новым value без вызова исключений. Нет поддержки многопоточности. Порядок обхода не гарантирован.
- Hashtable — null запрещены для key и value (в этом случае будет NullPointerException в обеих случаях). Потоко-безопасная. В остальном работа аналогична HashMap.
- TreeMap — аналог HashMap, только упорядоченная, т.е. при добавлении автоматически сортирует элементы по ключу (т.е. при обходе элементы будут отсортированы). Устроена по принципу красно-черного дерева.
- List — Интерфейс, реализации хранят списки объектов. Разрешает дубликаты и null. Реализации:
- ArrayList — Динамический массив. Нет поддержки многопоточности.
- Vector — Динамический массив. Потоко-безопасный.
- LinkedList — Двусвязный список. Нет поддержки многопоточности. Так же реализует интерфейс Deque.
- Queue — Реализации этого интерфейса построены по принципу очереди (FIFO) для хранения объектов. Null разрешены. Метод add() помещает в начало очереди.
- Deque — Реализации объединяют функциональность очереди (FIFO) и стека (LIFO). Метод add() аналогичен методу add() для Queue. Методы push() и addFirst() равносильны и помещают элемент в конец очереди (на вершину стека).
Дополнительную информацию по сравнению коллекций можно почитать тут:
- Java собеседование. Коллекции (Habrahabr)
- Производительность основных коллекций java. Сравнение с аналогами .Net
- Complete Java Collection tutorial for the beginner
Поиск и сортировка коллекций. Интерфейсы Comparable и Comparator
- Это тоже одна из «популярных» тем, на экзамене было ~5 вопросов про поиск/сортировку.
- Выучите в точности как работают методы Arrays.binarySearch и Collections.binarySearch, проверьте на примерах, т.к. в на мой взгляд документации написано маловато. Что возвращают, какие исключения вызывают, как работают с «сырыми» типами и т.п.
- Аналогично для методов Collections.sort и Arrays.sort.
- Интерфейс Comparable — используется для класса, чьи экземпляры объектов можно/нужно сравнивать. Все «обертки» простых типов — Char, Integer и т.д., а так же String — уже реализуют этот интерфейс. Использование:
- Для элемента коллекции необходимо реализовать метод public int compareTo(T obj);
- При создании коллекции указать новый класс элемента
TreeSet<ElementNewClass> myColl = new TreeSet<ElementNewClass>);
- Интерфейс Comparator — реализуется вне класса, объекты которого будут сравниваться. Основное отличие от Comparable:
1) можно создать несколько видов (классов) независимых сортировок;
2) используется если нужно отсортировать объекты чужого класса, в котором сравнение не реализовано.- Основной метод интерфейса: public int compare(T obj1, T obj2);
- Использование — при создании коллекции указать реализацию компаратора:
new TreeSet<String>(myComparator);
- Примеры использования:
-
Collections.sort(List<T> arg1, Comparator<? super T> arg2);
-
Arrays.sort(T[] arg1, Comparator<? super T> arg2);
-
- Методы sort(), reverse() и binarySearch() класса Collections НЕ работают с Set-ами (при этом исключение не вызывается)
Внутренние классы
- Outer class — класс верхнего уровня. Файл компилируется с именем Outside.class. Разрешенные модификаторы доступа: [default], public
- Inner classes — вложенные классы. Виды:
- Static nested class (или interface) — Определен в контексте верхнего класса, статический. Может обращаться к статическим членам внешнего класса. Экземпляры внутреннего и внешнего классов могут создаваться независимо (синтаксис создания: OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass();). Файл компилируется с именем: Outside$Inside.class. Разрешенные модификаторы доступа: ВСЕ.
- Member class — Определен в контексте верхнего класса, не статический. Экземпляр может быть создан только после создания экземпляра внешнего класса. Файл компилируется с именем: Outside$Inside.class. Разрешенные модификаторы доступа: ВСЕ.
- Local class — Определяется внутри блока кода, и виден только внутри этого блока. Может обращаться только к final-переменным внешнего класса и final-параметрам методов.Файл компилируется с именем: Outside$1$Inside.class. Разрешенный модификатор доступа: только [default].
- Anonymous class — Аналог local class, только безымянный. Файл компилируется с именем: Outside$1.class
Так же следует помнить:
- non-static inner класс не может иметь static-методы;
- non-static inner класс может иметь static-переменные (но только final!);
- static nested класс может иметь static и non-static переменные;
Работа со строками, регулярные выражения
- Помните, что объекты String — неизменяемы, StringBuilder — изменяемы, StringBuffer — тоже изменяемы и еще потоко-безопасны.
- StringBuilder и StringBuffer могут конкатенироваться через '+' только со String, и не могут друг с другом и другими простыми типами (в отличие от String, который может конкатенироваться с простыми типами).
- Тщательно выучите регулярные выражения, тут часто встречаются подвохи. Например, в строковом литерале для Pattern-a указывают только один обратный слеш ("s"), что вызывает ошибку компиляции.
- Помните про «жадность» квантификатора *, к примеру для строки «abcd bla-bla-bla abcd» выражение «a.*cd» будет соответствовать всей строке.
- Так же не забывайте про, что методы класса Matcher (find и group) начинают поиск следующего совпадения с позиции последней найденной группы, таким образом для «ababa» и выражения «aba» будет найдено только одно совпадение.
- Методы String.split(), String.replaceAll() и Scanner.useDelimeter() работают с регулярными выражениями.
- Помните основы форматирования строк: System.out.printf(«Hello java %03d!», 7); Была одна каверзная задачка про порядок параметров printf, рекомендую разобраться: System.out.printf(«1:%3$s 2:%2$s 3:%s»,1,2,3);
Потоки
Еще одна «излюбленная» тема, где нужно быть очень внимательным.
- Кратко о главном. synchronized method() {… } — блокирует для других потоков текущий и все остальные синхронизированные методы экземпляра объекта (вторую часть легко забыть). A если метод объявлен как static, то блокируются все synchronized-методы класса.
- И аналогично: syncronized (obj) {… } — блокирует:
1) все обращения других потоков к блоку внутри syncronized на данном объекте obj;
2) все другие блоки syncronized с тем-же объектом obj. - Встречаются неправильные формы реализации интерфейса Runnable, например private void run() {… }, public int run() {… }, или public void run(Runnable r) {… } и т.п.
- Нельзя указывать synchrnozed для методов интерфейса и абстрактного класса, а для методов enum — можно.
- После завершения потока, нельзя его снова запускать, т.е. Thread.start() вызовет IllegalThreadStateException.
- Помните отличия Runnable от Callable (последний возвращает значение и может вызывать исключение).
- Нужно знать теорию (определения):
1) deadlock — «мертвая» блокировка, тут, надеюсь, вопросов нет;
2) stravation — когда поток не может долго получить доступ к ресурсу из за того, что он долго занят другим потоком (более «жадным» или с более высоким приоритетом);
3) livelock — возникает в случае, если обменивающиеся или ожидающие друг друга потоки слишком заняты, чтобы эффективно продолжать работу. Т.е. они периодически освобождают друг для друга ресурсы на слишком короткое время, по аналогии как 2 человека в коридоре не могут разминуться, синхронно шагая то вправо, то влево;
4) race condition — ошибка проектирования многопоточной системы, при которой результат выполнения зависит от порядка выполнения потоков (т.е. непредсказуем).
Обёрточные типы и методы с переменным числом аргументов
- Widening — это автоматическое расширение Java типа к более широкому, например byte -> int
- Boxing — это автоматическое «оборачивание» примитивного типа Java в соответствующий ему объектный тип, например: long -> Long. Соответственно Long называется обёрточным (wrapper) типом для long.
- Unboxing — обратная операция: Long -> long
Существуют следующие правила для компилятора:
- Невозможен widening от одного оберточного типа к другому (например Byte -> Int) (IS-A fails);
- Запрещен widening, затем boxing (int не может стать Long);
- Но разрешается boxing, затем widening (int -> Integer -> Object);
- Методы с переменным числом аргументов можно сочетать с widening и boxing;
- В этом случае приоритет у компилятора будет следующий (по убыванию приоритета): widening, boxing, var-args;
- По отдельности boxing и var-args так же могут использоваться при перегрузке методов.
Нововведения 7 версии
- Fork-Join Framework. Нужно знать минимальные базовые принципы построения кода с его использованием. Встретилась пара вопросов с интерфейсами RecursiveTask/RecursiveAction (был аналог примера с рядом Фибоначчи из документации) и один теоретический вопрос на тему как разбить задачу на две.
- NIO2. Тут нужно хорошо знать следующее:
— маску поиска файлов PathMatcher («glob:*.jpg») и принцип обхода дерева файлов (Files.walkFileTree);
— класс Path (особенно методы getName, subpath, relativize);
— класс Files (методы copy, move + StandardCopyOption, newBufferedReader, newBufferedWriter, setAttribute, getAttribute);
— класс WatchService. - И обязательно разберитесь со всеми нюансами интерфейса Closeable и его использование в «try-with-resource»
Разное
- Выражение x instanceof Y — не скомпилируется, если x и Y принадлежат разной иерархии классов.
- Но x instanceof SomeInterface компилируется. Даже встретился вопрос, в котором объявлялся пустой интерфейс I1 и независимый класс C2, между которыми было соотношение C2 is-a I1.
- И еще instanceof не работает с обобщениями.
- Помните определения:
coupling — связанность. Это степень, в которой каждый программный модуль использует другие модули.
cohesion — связность. Это степень внутренней взаимосвязи между частями одного модуля. Или еще можно определить как меру сфокусированности класса на своих прямых задачах. - Почему-то встречается очень много вопросов на тему паттернов проектирования (может кто расскажет что в них такого необычайного?). Честно признаюсь, у меня здесь остались некоторые пробелы, т.к. нигде толком не нашел внятных материалов (википедия как то хромает). Вот тут более-менее написано про DAO. И вообще, считаю хороший программист должен сам придумывать паттерны, пригодные конкретно для его проекта, а не следовать везде подобным «рекомендациям». Короче singleton, DAO и factory на экзамене лучше знать на зубок.
- Нужно помнить основные аргументы утилит командной строки javac (-cp, -d, -source), java (-ea, -da, -cp) и утилиты jar.
- Что не встречалось на экзамене:
- Не было кода с использованием всевозможных Executors. Был только один вопрос по теории.
- Thread Pools. Вообще не было.
- Кода с использованием подклассов RowSet. Только теория.
Понятно, что если мне не попалась какая-то тема, это не означает что её в вопросах нет. Но может кому-то упростит и без того ёмкую подготовку. Подозреваю что Oracle периодически обновляет набор вопросов, чтобы подобные посты не делали жизнь слаще. Так что дерзайте пока свежее :)
Заключение
Конечно, осталось много материала про который я не написал (перечисления, работа с файлами, JDBC, Localizarion, ResourceBundle и вероятно что-то еще). По этим темам предлагаю разобраться самостоятельно, т.к. особых сложностей на мой взгляд они не представляют. И конечно всем удачи на экзамене!
Автор: dna1983