Структура Класса

    Класс состоит из:

1. Модификатора (public,final) 

2. class ключевое слово;

3. Имя класса всегда с большой буквы;

4. Тело класса всегда пишется в {. 


    Тело класса состоит полей и методов.  

ПОЛЕ:

1. Начинается с  модификатора (public, statiс, final);

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

3. Имя класса всегда с маленькой буквы;

4.  Установка значения по умолчанию (необязательно).  

МЕТОД:

1. Начинается с  модификатора (public, statiс, final);

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

3. Имя метода main всегда с маленькой буквы;

4. Список аргументов метода указывается через запятую в скобках. Каждый аргумент состоит из:

    a) модификатора:
    b) имя типа данных обозначается с большой буквы ити с маленькой, если является примитивным значением:
    c) имя аргумента.

5. Список имен классов вызыывакмых исключений черех запятую полсе ключевого слова throws:

6. Тело метола. 
    



Как я провожу собеседования

Собеседование

  1. Общие технические знания
  2. Java core
  3. Java collections
  4. Алгоритмы
  5. JMM/Concurency
  6. JVM/GC
  7. SQL/NOSQL
  8. Spring

Общие технические знания
Обычно все начинается с вопросов о текущем используемом стеке и об известных библиотеках/фреймворках.
Я могу поспрашивать почему используется то или иное решение.
Задам вопрос про DevOps и спрошу - а принимал ли ты участие в организации поставок.
Спрошу что-нибудь из аббревиатуры SOLID
Спрошу про паттерны проектирования, может быть, про их типизацию, попрошу в деталях рассказать о парочке наиболее интересных из тех, о которых ты вспомнишь

Java core
Где-то обязательно будет вопрос про неизменяемость строк.
Основное путешествие начнется с equals и hashCode:
  • Я спрошу про контракт между ними
  • Спрошу про контракт на equals
  • Спрошу про тип данных, который возвращает hashCode
  • Спрошу про число байтов в int, о том, сколько может быть уникальных хэшкодов, может быть, попрошу представить это в десятичном виде
  • Где-то выше мелькнет слово коллизия - задел под хэшмапу
  • Спрошу как хэшкод считается по умолчанию, попробую подвести, что можно считать рандомом
  • Если был ответ «адрес в памяти» - спрошу про изменяется ли адрес объекта, спрошу про размер адреса, может быть, поговорим о выравнивании адреса и о том, сколько надо байт, чтобы адресовать кучи разных размеров (в том числе, про сжатые ссылки)
  • Могу спросить где храниться хэшкод 

Java collections
Спрошу про типы коллекций, чем сет отличается от листа, что такое мапа
Для всех реализаций коллекций буду спрашивать сложность доступа по индексу, вставки и удаления
Начну с листов: каким пользовался, почему пользовался линкедлистом (или не пользовался), чем линкедлист хуже, почему вставка в середину не лучше чем в аррэйлист, по какой коллекции проще итерироваться (как коллекция лежит в памяти), сравнить по потреблению памяти линкедлист и аррэйлист
Блок вопросов про хэшмапам: как считается индекс в массиве, как разрешаются коллизии, худший случай по получению значения по ключу, как можно оптимизировать худший случай (про трансформацию списка в дерево). Чем отличается конкарент хэшмапа от обычной.
Тримапы: чем красно-черное дерево отличается от просто бинарного дерева, какие еще бинарные деревья бывают, какая может быть максимальная разница между самым длинным и самым коротким путем от корня до листа, почему такая разница

Алгоритмы
Заход с того: а как отсортировать, например, лист.
Спрошу про то, какие типы сортировок используются в java, какие сортировки не хуже nlogn знаешь еще. Спрошу про то, как работает timsort/quicksort, про худший случай quicksort, как его оптимизировать, про его устойчивость. Если знаешь другие сортировки - спрошу про них

JMM/Concurency
Спрошу, что дает критическая секция. Что будет, если написать synchronized перед методом. Перед статическим методом. Зачем нужны wait, notify, notifyAll. Спрошу про порядок засыпания и пробуждения потоков и кто этот порядок может гарантировать. Может быть, попрошу спроектировать исполнение задачи в средпуле.
Спрошу, что дает volatile. Что за кэш? Через какие физические устройства происходит чтение из оперативной памяти - пока не доберемся до кэшей процессора. Спрошу про happens-before относительно volatile и какие еще бывают. 

Чем хороши атомики, кто больше потребляет ресурсов: атомики, критическая секция или volatile. 

TBD

Сравнение объектов: практика

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

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

Ах, эти строки... Один из наиболее часто используемых типов, вызывающих при этом немало проблем. В принципе, о них есть отдельная статья. А здесь я коснусь вопросов сравнения.
Разумеется, строки можно сравнивать с помощью equals. Более того, их НУЖНО сравнивать через equals. Однако, есть тонкости, которые стоит знать.
Прежде всего, одинаковые строки на самом деле являются единственным объектом. В чем легко убедиться, выполнив следующий код:
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
Результатом будет "the same". Что означает, что ссылки на строки равны. Это сделано на уровне компилятора, очевидно, для экономии памяти. Компилятор создает ОДИН экземпляр строки, и присваивает str1и str2 ссылку на этот экземпляр1.
Однако, это относится только к строкам, объявленным как литералы, в коде. Если скомпоновать строку из кусков, ссылка на нее будет другой. Подтверждение – данный пример:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
Результатом будет "not the same". Также можно создать новый объект с помощью копирующего конструктора:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
Результатом также будет "not the same".
Таким образом, иногда строки можно сравнивать и через сравнение ссылок. Но на это лучше не полагаться.
Я хотел бы затронуть один весьма любопытный метод, который позволяет получить так называемое каноническое представление строки – String.intern. Поговорим о нем поподробнее.

Метод String.intern

Начнем с того, что класс String поддерживает пул строк. В этот пул добавляются все строковые литералы, определенные в классах, и не только они. Так вот, метод intern позволяет получить из этого пула строку, которая равна имеющейся (той, у которой вызывается метод intern) с точки зрения equals. Если такой строки в пуле не существует, то туда помещается имеющаяся, и возвращается ссылка на нее. Таким образом, если даже ссылки на две равных строки разные (как в двух примерах выше), то вызовы у этих строк intern вернут ссылку на один и тот же объект:
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
Результатом выполнения этого фрагмента кода будет "the same".
Я не могу сказать точно, зачем это сделано так. Метод intern – native, а в дебри С-кода мне, честно сказать, не хочется. Скорее всего это сделано для оптимизации потребления памяти и производительности. В любом случае, стоит знать об этой особенности реализации.
Переходим к следующей части.

Сравнение вещественных примитивов

Для начала я хочу задать вопрос. Очень простой. Чему равна следующая сумма – 0.3f + 0.4f? Чему? 0.7f? Проверим:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
Как результат? Нравится? Мне тоже. Для тех, кто не выполнил этот фрагмент, скажу – результат будет...
f1==f2: false
Почему это происходит?.. Выполним еще один тест:
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("f1="+(double)f1);
System.out.println("f2="+(double)f2);
System.out.println("f3="+(double)f3);
System.out.println("f4="+(double)f4);
Обратите внимание на приведение к double. Это сделано для того, чтобы вывести побольше знаков после запятой. Результат:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
Собственно говоря, результат прогнозируемый. Представление дробной части осуществляется с помощью конечного ряда 2-n, а потому о точном представлении произвольно взятого числа говорить не приходится. Как видно из примера, точность представления float – 7 знаков после запятой.
Строго говоря, в представлении float на мантиссу отведено 24 бита. Таким образом минимальное по модулю число, которое можно представить с помощью float (без учета степени, ибо мы говорим о точности) – это 2-24≈6*10-8. Именно с таким шагом реально идут значения в представлении float. А поскольку есть квантование – есть и погрешность.
Отсюда вывод: числа в представлении float можно сравнивать только с определенной точностью. Я бы рекомендовал округлять их до 6-го знака после запятой (10-6), либо, что предпочтительнее, проверял быабсолютное значение2 разности между ними:
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("|f3-f4|<1e-6: "+( Math.abs(f3-f4) < 1e-6 ));
В этом случае результат вселяет надежду:
|f3-f4|<1e-6: true
Разумеется, точно та же картина и с типом double. С единственной разницей, что там на мантиссу отведено 53 бита, следовательно, точность представления – 2-53≈10-16. Да, величина квантования куда меньше, но она есть. И может сыграть злую шутку.
Кстати, в тестовой библиотеке JUnit в методах сравнения вещественных чисел точность указывается в явном виде. Т.е. метод сравнения содержит три параметра – число, чему оно должно быть равно и точность сравнения.
Еще кстати, хочу упомянуть о тонкости, связаной с записью чисел в научном формате, с указанием степени. Вопрос. Как записать 10-6? Практика показывает, что более 80% отвечают – 10e-6. Между тем, правильный ответ – 1e-6! А 10e-6 – это 10-5! Мы наступили на эти грабли в одном из проектов, довольно неожиданно. Ошибку искали очень долго, на константы смотрели раз 20. И ни у кого не возникло ни тени сомнения в их правильности, пока однажды, в большой степени случайно, константу 10e-3 не вывели на печать и не обнаружили у нее после запятой два знака вместо ожидавшихся трех. А потому – будьте бдительны!
Движемся дальше.

+0.0 и -0.0

В представлении вещественных чисел старший бит является знаковым. А что будет, если все остальные биты равны 0? В отличие от целых, где в такой ситуации получается отрицательное число, находящееся на нижней границе диапазона представления, вещественное число только со старшим битом, выставленным в 1, тоже обозначает 0, только со знаком минус. Таким образом, у нас есть два нуля – +0.0 и -0.0.
Возникает логичный вопрос – считать ли эти числа равными? Виртуальная машина считает именно так. Однако, это два разных числа, ибо в результате операций с ними получаются разные значения:
float f1 = 0.0f/1.0f;
float f2 = 0.0f/-1.0f;
System.out.println("f1="+f1);
System.out.println("f2="+f2);
System.out.println("f1==f2: "+(f1==f2));
float f3 = 1.0f / f1;
float f4 = 1.0f / f2;
System.out.println("f3="+f3);
System.out.println("f4="+f4);
... и результат:
f1=0.0
f2=-0.0
f1==f2: true
f3=Infinity
f4=-Infinity
Таким образом, в некоторых случаях есть смысл расценивать +0.0 и -0.0 как два разных числа. А если у нас есть два объекта, в одном из которых поле равно +0.0, а в другом -0.0 – эти объекты точно так же можно расценивать как неравные. Возникает вопрос – а как понять, что числа неравны, если их прямое сравнение виртуальной машиной дает true?
Ответ таков. Несмотря на то, что виртуальнай машина считает эти числа равными, представления у них все-таки отличаются. Поэтому – единственное, что можно сделать, это сравнить представления. А для того, чтобы его получить, существуют методы int Float.floatToIntBits(float) и long Double.doubleToLongBits(double), которые возвращают битовое представление в виде int и longсоответственно (продолжение предыдущего примера):
int i1 = Float.floatToIntBits(f1);
int i2 = Float.floatToIntBits(f2);
System.out.println("i1 (+0.0):"+ Integer.toBinaryString(i1));
System.out.println("i2 (-0.0):"+ Integer.toBinaryString(i2));
System.out.println("i1==i2: "+(i1 == i2));
Результатом будет
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
Таким образом, если у вас +0.0 и -0.0 – разные числа, то сравнивать вещественные переменные следует через их битовое представление.
С +0.0 и -0.0 вроде как разобрались. -0.0, однако, является не единственным сюрпризом. Есть еще такое явление как...

Значение NaN

NaN расшифровывается как Not-a-Number. Это значение появляется в результате некорректных математических операций, скажем, деления 0.0 на 0.0, бесконечности на бесконечность и т.п.
Особенностью этого значения является то, что оно не равно самому себе. Т.е.:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
... даст в результате...
x=NaN
x==x: false
Чем это может обернуться при сравнении объектов? Если поле объекта будет равно NaN, то сравнение даст false, т.е. объекты гарантированно будут считаться неравными. Хотя по логике вещей мы можем хотеть как раз обратного.
Добиться нужного результата можно, используя метод Float.isNaN(float). Он возвращает true, если аргумент – NaN. На сравнение битовых представлений я бы в этом случае не полагался, т.к. оно не стандартизовано.
Пожалуй, о примитивах хватит. Перейдем теперь к тонкостям, появившимся в Java с версии 5.0. И первый момент, которого я бы хотел коснуться –

Java 5.0. Производящие методы и сравнение через '=='

В проектировании есть шаблон, называемый производящий метод. Иногда его использование гораздо более выгодно, нежели использование конструктора. Приведу пример. Думаю, все хорошо знаю объектную оболочку Boolean. Этот класс неизменяемый, способен содержать всего два значения. Т.е., фактически, для любых нужд хватит всего-навсего двух экземпляров. И если их создать заранее, а потом просто возвращать, то это будет намного быстрее, чем использование конструктора. Такой метод у Boolean есть: valueOf(boolean). Появился он в версии 1.4.
Подобные же производящие методы были введены с версии 5.0 и в классах ByteCharacterShortInteger и Long. При загрузке этих классов создаются массивы их экземпляров, соответствующие определенным диапазонам значений примитивов. Диапазоны эти следующие:
Byte:-128..127(фактически – все значения)
Character:0..127
Short:-128..127
Integer:-128..127
Long:-128..127
Означает это, что при использовании метода valueOf(...) при попадании аргумента в указанный диапазон всегда будет возвращаться один и тот же объект. Возможно, это и дает какое-то увеличение скорости. Но при этом появляются проблемы такого характера, что докопаться до сути бывает довольно сложно. Читайте об этом дальше.
Теоретически производящий метод valueOf добавлен и в классы Float и Double. В их описании сказано, что если не нужен новый экземпляр, то лучше пользоваться этим методом, т.к. он может дать прибавку в скорости и т.д. и т.п. Однако в текущей (Java 5.0) реализации в этом методе создается новый экземпляр, т.е. прибавки в скорости его использование не даст гарантированно. Более того, мне сложно представить, как можно ускорить этот метод, ибо ввиду непрерывности значений кеш там не организуешь. Разве что для целых чисел. В смысле, без дробной части.

Java 5.0. Autoboxing/Unboxing: '==''>=' и '<=' для объектных оболочек.

Подозреваю, что производящие методы и кеш экземпляров были добавлены в оболочки для целочисленных примитивов ради оптимизации операций autoboxing/unboxing. Напомню, что это такое. Если в операции должен участвовать объект, а участвует примитив, то этот примитив автоматически оборачивается в объектную оболочку. Это autoboxing. И наоборот – если в операции должен участвовать примитив, то можно подставить туда объектную оболочку, и значение будет автоматически из нее развернуто. Это unboxing.
Естественно, за такое удобство надо платить. Операции автоматического преобразования несколько замедляют скорость работы приложения. Однако к текущей теме это не относится, потому оставим этот вопрос.
Все хорошо до тех пор, пока мы имеем дело с операциями, однозначно относящимися к примитивам либо к оболочкам. А что будет с операцией '=='? Допустим, у нас есть два объекта Integer, с одинаковым значением внутри. Как они будут сравниваться?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
Результат:
i1==i2: false
Кто бы сомневался... Сравниваются они как объекты. А если так:
Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
Результат:
i1==i2: true
Вот это уже интереснее! При autoboxing-е возвращаются одинаковые объекты!
Вот тут и находится ловушка. Однажды обнаружив, что возвращаются одинаковые объекты, мы начнем экспериментировать, чтобы проверить, всегда ли это так. И сколько мы проверим значений? Одно? Десять? Сто? Скорее всего ограничимся сотней в каждую сторону вокруг нуля. И везде получим равенство. Казалось бы, все хорошо. Однако, посмотрите чуть назад, вот сюда. Догадались, в чем подвох?..
Да, экземпляры объектных оболочек при autoboxing-е создаются с помощью производящих методов. Что хорошо иллюстрируется следующим тестом:
public class AutoboxingTest {

    private static final int numbers[] = new int[]{-129,-128,127,128};

    public static void main(String[] args) {
        for (int number : numbers) {
            Integer i1 = number;
            Integer i2 = number;
            System.out.println("number=" + number + ": " + (i1 == i2));
        }
    }
}
Результат будет таков:
number=-129: false
number=-128: true
number=127: true
number=128: false
Для попадающих в диапазон кеширования значений возвращаются одинаковые объекты, для находящихся вне него – разные. А следовательно, если где-то в приложении будут сравниваться оболочки вместо примитивов – есть шанс получить самую страшную ошибку: плавающую. Потому как тестировать код, скорее всего, тоже будут на ограниченом диапазоне значений, в котором эта ошибка не проявится. А в реальной работе она то будет проявляться, то исчезать, в зависимости от результатов каких-то вычислений. Проще сойти с ума, чем найти такую ошибку. А потому – я бы советовал избегать autoboxing-а где только можно.
И это не всё. Вспомним математику, не далее чем 5-го класса. Пусть выполняются неравенства A>=B и А<=B. Что можно сказать об отношении A и B? Только одно – они равны. Согласны? Думаю, да. Запускаем тест:
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1>=i2: "+(i1>=i2));
System.out.println("i1<=i2: "+(i1<=i2));
System.out.println("i1==i2: "+(i1==i2));
Результат:
i1>=i2: true
i1<=i2: true
i1==i2: false
И вот это для меня – самая большая странность. Я вообще не понимаю, зачем было вводить в язык эту возможность, если она вносит такие противоречия. В общем, повторю еще раз – если есть возможность обойтись без autoboxing/unboxing, то стоит эту возможность использовать на полную катушку.
Последняя тема, которой я хотел бы коснуться, это...

Java 5.0. сравнение элементов перечислений (тип enum)

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



Оригинал: http://www.skipy.ru/technics/objCompPr.html

Сравнение объектов: теория

Думаю, нет смысла задаваться вопросом – а зачем нужно сравнение вообще и в программировании в частности? Ответа на этот вопрос нет. Так сложилось, что мы постоянно что-то сравниваем. Собственно, идентификация любого объекта есть его сравнение с эталоном. Другой вид сравнения – сравнение объектов между собой. Например, для упорядочивания по определенным критериям. В этой статье мы рассмотрим сравнения обоих типов, где какое сравнение применяется, их тонкости и подводные камни. А именно – речь пойдет о следующих темах:
Вступление закончилось, перейдем к делу.

Сравнение примитивов

Сравнение примитивных типов вопросов не вызывает. Будь то целочисленные (bytecharshortintlong) или вещественные (floatdouble) типы – сравнивать мы их умеем еще со школьной скамьи. С типом booleanдело обстоит иначе. Переменные этого типа можно проверить на равенство. Но кто из них больше – эта операция не определена. С объектными же их оболочками ситуация следующая: в версиях до 5.0 они не были сравнимы (comparable, см. ниже). В версии 5.0 java.lang.Boolean реализует интерфейс java.lang.Comparable, причем таким образом, что false<true.
Есть, однако, нюанс, связаный со сравнением вещественных чисел. Ввиду ограниченной точности представления (и не только) возможны различные неожиданые эффекты. Об этом – во второй части: Сравнение объектов: практика -> Сравнение вещественных примитивов.
Следующая тема –

'==' против 'equals(Object)'

Пусть у нас есть две переменные. Скажем, типа int. Сравнение с помощью оператора '==' есть сравнение значений этих переменных, т.е., фактически, сравнение содержимого соответствующих переменным областей памяти.
Пусть теперь у нас переменные не примитивного типа, а объектного. Что это означает? Означает это, что в соответствующих этим переменным областях памяти хранятся ссылки на объекты. Вопрос: что означает сравнение этих переменных с помощью все того же оператора '=='?
А означает это ровно то же. Сравнение содержимого соответствующих областей памяти. Т.е. эта операция вернет true тогда и только тогда, когда содержимое совпадает, что означает, что переменные указывают на ОДИН И ТОТ ЖЕ объект.
Собственно говоря, это правильно. Сравнение объектов – операция скорее логическая, ибо объект – это сложное образование, которое не всегда можно разобрать и сравнить "в лоб". А потому – операция сравнения содержимого отделена от операции сравнения ссылок. И если вторую можно реализовать на уровне виртуальной машины, то первая должна быть реализована непосредственно разработчиком, т.к. только он может сформулировать правила сравнения. И делается это с помощью переопределения метода equals(Object) класса java.lang.Object.
Реализация этого метода должна подчиняться нескольким правилам:
  • Для любого x!=null вызов x.equals(x) должен вернуть true. Что, в общем-то, логично, ибо объект всегда равен самому себе. Это правило рефлексивности.
  • Для любых x!=null и y!=null вызов x.equals(y) должен вернуть true тогда и только тогда, когда вызов y.equals(x) возвращает true. Иначе говоря, если А==В, то В==А. Опять-таки совершенно логично. Этоправило симметрии.
  • Для любых x, y и z, не равных null, таких, что x.equals(y)==true и y.equals(z)==true, выполняется также и x.equals(z)==true. Иначе говоря, если А==В и В==С, то А==С. Это правило транзитивности.
  • Для любых x и y, не равных null, многократные вызовы x.equals(y) согласованно возвращают true либо false, в случае если объекты не были измнены между вызовами. Это правило непротиворечивости.
  • Для любого x!=null вызов x.equals(null) должен вернуть false.
Строго говоря, последнее правило противоречит правилу симметрии. Если взять x!=null и y==null, то, согласно этому правилу, вызов x.equals(y) вернет false, тогда как вызов y.equals(x) (симметричный) приведет к исключению NullPointerException, а, следовательно, о симметричном результате речи не идет. Однако, многие классы из базовой библиотеки полагаются именно на описаное выше поведение, потому это правило учитывать необходимо.
В остальном – реализация оставлена на усмотрение разработчика. Единственный момент – есть нюансы, связаные со сравнением родительских и дочерних объектов. Эти нюансы будут рассмотрены ниже.
Движемся дальше.

Коллекции java.util.Map и метод hashCode

Пожалуй, чаще всего со сравнением объектов приходится сталкиваться при использовании коллекций. В особенности, если коллекция подразумевает уникальность элементов. К примеру, в java.util.Map каждому ключу сопоставлено значение, а, следовательно, ключи должны быть уникальны. Для поиска объекта по ключу их необходимо сравнивать. Вопрос: как это сделать эффективнее всего?
Простой перебор всех ключей – занятие весьма и весьма дорогостоящее. Между тем, процесс поиска объекта можно ускорить, и довольно хорошо. Представьте себе, что каждому объекту сопоставлено некоторое число. Неважно, какое именно, важно, что оно одинаковое для равных объектов. Организуем множество объектов в виде набора подмножеств. Пусть их для примера будет 10. Тогда в первое подмножество попадут элементы, у которых остаток от деления сопоставленного им числа на 10 равен 0, во второе – с остатком 1, в третье – с остатком 2 и т.д. Таким образом, для поиска объекта нужно будет определить остаток от деления и перебрать элементы подмножества, которых, в среднем, в 10 раз меньше.
Число, про которое я говорю, называется хеш-код. И возвращается оно для каждого объекта через вызов функции hashCode(). Есть правило, что при переопределении метода equals(Object) необходимо переопределять и hashCode(). Потому как его использование в коллекциях основывается на предположении, о котором я говорил выше – для любых равных объектов (не идентичных, двух ссылок на один объект, а равных, с точки зрения equals(Object)) хеш-коды должны быть равны. Между тем, по умолчанию реализация в качестве хеш-кода выдает адрес объекта в памяти. Почему так? Сейчас объясню.
При использовании 10 подмножеств скорость поиска теоретически может вырасти в 10 раз. Почему теоретически? Потому что это произойдет только в том случае, если хеш-коды распределены равномерно. Иначе говоря, если в половине случаев метод hashCode() будет возвращать 0, то где-то половина объектов попадет в одно подмножество. Что сильно снижает эффективность поиска. Именно потому реализация по умолчанию выдает адрес области памяти – во-первых, это обеспечивает для равных объектов (в реализации по умолчанию – равных ссылок) равные значения хеш-кодов, во-вторых – это обеспечивает более-менее равномерное распределение значения.
При реализации hashCode используется несколько простых правил. Прежде всего, при вычислении хеш-кода следует использовать те же поля, которые сравниваются в equals. Это, во-первых, даст равенство хеш-кодов для равных обектов, во-вторых, распределено полученное значение будет точно так же, как и исходные данные. Теоретически, можно сделать так, чтобы хеш-код всегда был равен 0, и это будет абсолютно легальная реализация. Другое дело, что ее ценность будет равна тому же самому нулю.
Далее. Несмотря на то, что хеш-коды равных объектов должны быть равны, обратное неверно! Два неравных объекта могут иметь равные хеш-коды. Решающее значение имеет не уникальность, а скорость вычисления, потому как это приходится делать очень часто. Потому, в некоторых случаях имеет смысл посчитать хеш-код заранее и просто выдавать его по запросу. Прежде всего это стоит делать тогда, когда вычисление трудоемко, а объект неизменен.
Простейшая реализация hashCode и equals может выглядеть где-то так (все несущественные методы опущены):
public class PhoneBookEntry{

    private String ownerName;
    private int countryCode;
    private int areaCode;
    private int number;

    public boolean equals(Object obj){
        if (!(obj instanceof PhoneBookEntry))
            return false;
        PhoneBookEntry entry = (PhoneBookEntry)obj;
        return ownerName.equals(entry.ownerName) &&
               countryCode == entry.countryCode &&
               areaCode == entry.areaCode &&
               number == entry.number;
    }

    public int hashCode(){
        int hash = 37;
        hash = hash*17 + ownerName.hashCode();
        hash = hash*17 + countryCode;
        hash = hash*17 + areaCode;
        hash = hash*17 + number;
        return hash;
    }

}
Умножение на 17 применено для лучшего распределения значений. Иначе, например, в случае использования простой суммы, значения могут быть сосредоточены в очень небольшом диапазоне. При отсутствии в этом примере строки так и было бы – значения хеш-кода были бы в диапазоне 0-1001000, за очень редким исключением, при том, что диапазон значений int больше почти в 5000 раз.
На тонкостях реализации я останавливаться не буду, они очень хорошо описаны в книге Джошуа Блоха Java. Эффективное программирование, статья 8. Там же есть и рецепты по написанию хорошего метода hashCode().
Описаный мною выше способ ускорения поиска путем разбиения на подмножества – на самом деле есть реализация интерфейса java.util.Map – java.util.HashMap. Как следует из названия, эта реализация построена на использовании хеш-кода. И потому, если вы хотите реализовать объект, который собираетесь потом использовать в качестве ключа в HashMap – вам необходимо переопределить методы hashCode() и equals(Object). О том, что произойдет, если этого не сделать, читайте ниже.
Перейдем к следующему разделу.

Набор уникальных объектов – коллекции java.util.Set

В Java имеется интерфейс, определяющий коллекцию, содержащую уникальные элементы. Это интерфейс java.util.Set. Основная его реализация – java.util.HashSet. Хочу сказать пару слов об этой реализации. В ее основе лежит уже упомянутая выше коллекция HashMap. Элементы HashSet на самом деле являются ключами в HashMap, следовательно, к ним предъявляются те же требования, что и к ключам, а именно – переопределение hashCode() и equals(Object).
Особо хочу оговорить (и обратить ваше внимание!) вот какой момент: вышесказанное относится к реализациям java.util.Set, но не его наследника java.util.SortedSet. В какой-то степени это нарушение принципов ООП, ибо наследника всегда можно рассматривать как объект родительского класса, во всяком случае, можно ожидать соответствующего родителю поведения. Между тем, в данном случае это не совсем так. Подробнее я коснусь этого вопроса в разделе, посвященом упорядоченным коллекциям.
Однако, простое сравнение объектов – это не весь класс задач. Очень часто нам приходится не устанавливать идентичность объектов, а упорядочивать их. Т.е. от двоичной логики (равен/не равен) мы переходим к троичной – меньше/равен/больше. Об этом и пойдет речь.

Упорядочивание – интерфейс java.lang.Comparable

Подобно тому, как equals(Object) предназначен для унификации процедуры сравнения по содержимому, точно так же хочется иметь и метод, который бы унифицировал процесс упорядочивания по все тому же содержимому. Но, поскольку все объекты можно сравнить, но далеко не все объекты можно логически упорядочить, этот метод имеет смысл вынести в отдельный интерфейс, который можно было бы реализовать в случае необходимости. Именно так и появился интерфейс java.lang.Comparable.
Прежде всего он удобен для сортировки упорядоченных списков (java.util.List) и массивов объектов. Если список/массив содержит элементы, реализующие этот интерфейс, то они могут быть отсортированы автоматически методами java.util.Collections.sort(List)/Arrays.sort(Object[]).
С интерфейсом Comparable связано понятие натурального упорядочивания, потому как он устанавливает натуральный порядок следования экземпляров любого класса, реализующего этот интерфейс. Иначе говоря, порядок (xy) соответствует выполнению условия x.compareTo(y) <= 0.
Правила реализации Comparable, а вернее, его метода compareTo(Object) следующие (x и y – экземпляры класса, реализующего Comparable):
  • x.compareTo(y) возвращает -1 или 1, если x должен находиться, соответственно, раньше или позже y. Если метод возвращает 0, то порядки (xy) и (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. Это правило непротиворечивости, и его очень важно учитывать. О том, что будет, если этого не делать, я расскажу ниже. Хотя, теоретически это правило из разряда "очень желательно".
Реализация Comparable для приведенного выше класса PhoneBookEntry может быть такой (упорядочение по имени, потом по коду страны, потом по коду города, потом по номеру):
public class PhoneBookEntry implements Comparable{

    private String ownerName;
    private int countryCode;
    private int areaCode;
    private int number;

    public boolean equals(Object obj){
        if (!(obj instanceof PhoneBookEntry))
            return false;
        PhoneBookEntry entry = (PhoneBookEntry)obj;
        return ownerName.equals(entry.ownerName) &&
               countryCode == entry.countryCode &&
               areaCode == entry.areaCode &&
               number == entry.number;
    }

    public int hashCode(){
        int hash = 37;
        hash = hash*17 + ownerName.hashCode();
        hash = hash*17 + countryCode;
        hash = hash*17 + areaCode;
        hash = hash*17 + number;
        return hash;
    }

    public int compareTo(Object obj){
        PhoneBookEntry entry = (PhoneBookEntry)obj;
        int result = ownerName.compareTo(entry.ownerName);
        if (result != 0) return result;
        result = countryCode – entry.countryCode;
        if (result != 0) return (int)(result/Math.abs(result));
        result = areaCode – entry.areaCode;
        if (result != 0) return (int)(result/Math.abs(result));
        result = number – entry.number;
        return (result != 0) ? (int)(result/Math.abs(result)) : 0;
    }

}
Кроме автоматической сортировки списков и массивов интерфейс Comparable используется в упорядоченных коллекциях. О них и поговорим.

Упорядоченные коллекции – java.util.SortedSet и java.util.SortedMap

Упорядочение этих коллекций означает только то, что итератор по элементам SortedSet или по ключам SortedMap будет возвращать элементы в порядке, определенном их реализацией интерфейса Comparable. Казалось бы, все просто. Внешне – да.
Однако тут зарыта огромная собака для тех, кто не любит читать документацию. Дело в том, что согласно ей (документации, не собаке!) натуральный порядок элементов SortedSet либо ключей SortedMap не должен находиться в противоречии с реализацией метода equals (который, напомню, вместе с hashCode должен быть переопределен, если вы хотите использовать объект в качестве ключа или помещать его в Set). Иначе говоря, обязано выполняться последнее из правил реализации Comparable. Как я и обещал, возможные проблемы в случае невыполнения этого правила я опишу ниже.
Еще один момент, который знать нелишне. Реализация SortedMapTreeMap, в отличие от HashMap не использует hashCode(). Их внутренняя структура основана на красно-черном дереве. Соответственно, по сравнению с постоянным временем поиска HashMap в случае TreeMap время поиска зависит от количества элементов (log(n)). Во всяком случае, это верно для TreeMap/TreeSet, потому как TreeSet реализован на основе TreeMap.
Пойдем дальше. Может возникнуть вопрос – а что делать, если необходимо сортировать одни и те же объекты по-разному? Comparable предполагает единственный порядок сравнения. Между тем, в случае сPhoneBookEntry может быть необходимо отсортировать объекты сначала по коду страны и коду города, а уж потом по имени и номеру. И что делать?
Как говорил Гость с Юга из фильма "Чародеи" – "Выход есть! Есть выход!" Надо использовать ...

"Внешнее" сравнение – интерфейс java.util.Comparator

Термин "внешнее", на самом деле, – мой. Я называю это сравнение внешним, потому что его логика находится вне сравниваемых объектов. А суть его проста: нужно реализовать интерфейс java.util.Comparator. Его метод compare(x,y) в точности соответствует по своей сути вызову x.compareTo(y). Точно так же должны выполняться все правила, о которых я упоминал выше.
Comparator может использоваться в любом месте, где нужна сортировка. При этом, во-первых, появляется необходимая гибкость – возможность реализации нескольких правил сортировки. А во-вторых, сортируемые объекты могут не реализовывать интерфейс Comparable. В случае, если они его все-таки реализуют, Comparator имеет приоритет.
Реализация Comparator для приведенного выше случая сортировки (код страны -> код города -> имя -> номер) выглядит так:
public class PhoneBookEntryComparator implements Comparator{

    public int compare(Object obj1, Object obj2){
        PhoneBookEntry entry1 = (PhoneBookEntry)obj1;
        PhoneBookEntry entry2 = (PhoneBookEntry)obj2;
        int result = entry1.getCountryCode() – entry2.getCountryCode();
        if (result != 0) return (int)(result/Math.abs(result));
        result = entry1.getAreaCode() – entry2.getAreaCode();
        if (result != 0) return (int)(result/Math.abs(result));
        result = entry1.getOwnerName().compareTo(entry2.getOwnerName());
        if (result != 0) return (int)(result/Math.abs(result));
        result = entry1.getNumber() – entry2.getNumber();
        return (result != 0) ? (int)(result/Math.abs(result)) : 0;
    }

}
Замечание 1. В реализации используются методы getXXX, которые возвращают значения соответствующих полей класса и просто были опущены для краткости.
Замечание 2. Интерфейс Comparator определяет еще и метод equals(Object), как это ни парадоксально. Этот метод служит для сравнения самих экземпляров интерфейса Comparator и должен возвращать trueтолько в том случае, если сравниваемые объекты обеспечивают одинаковый порядок сортировки. Однако всегда безопасно оставлять исходную реализацию Object.equals(Object) нетронутой, именно потому метод не был реализован.
Замечание 3. В Java 5.0 Comparator имеет параметризуемый тип. Потому, в этом случае реализация будет несколько проще:
public class PhoneBookEntryComparator implements Comparator<PhoneBookEntry>{

    public int compare(PhoneBookEntry entry1, PhoneBookEntry entry2){
        // приведения типа не нужны, в остальном реализация та же
    }

}
Следующий вопрос, который я хотел бы рассмотреть –

Сравнение, упорядочивание и наследование

Пусть у нас есть класс "точка" – всего лишь две координаты. Его код будет таким:
public class Point{

    private int x;
    private int y;

    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }

    public boolean equals(Object obj){
        if (!(obj instanceof Point)) // !!!
            return false;
        Point p = (Point)obj;
        return x==p.x && y==p.y;
    }

    public int hashCode(){
        return (17+x)*37+y;
    }

    public int getX(){
        return x;
    }

    public int getY(){
        return y;
    }

}
Второй класс, который мы определим – "цветная точка":
public class ColoredPoint extends Point{

    private Color color;

    public ColoredPoint(int x, int y, Color color){
        super(x,y);
        this.color = color;
    }

    public Color gerColor(){
        return color;
    }

    public int hashCode(){
        return super.hashCode()*37 + color.hashCode();
    }

    public boolean equals(Object obj){
        if (!super.equals(obj))
            return false;
        if (!(obj instanceof ColoredPoint))  // !!!
            return false;
        ColoredPoint cp = (ColoredPoint)obj;
        return color.equals(cp.color);
    }

}
Пока вроде все логично. Мы можем сравнивать объекты только одного класса, потому в строках с комментарием в конце "// !!!" стоит проверка на принадлежность объекта к определенному типу.
Вот тут, собственно, и находятся грабли. Большие и увесистые. Вспомним про правило симметрии: если x.equals(y), то y.equals(x). Посмотрим на следующий код:
public class Tester{

    public static void main(String args[]){
        Point p = new Point(0,0);
        ColoredPoint cp = new ColoredPoint(0,0,Color.red);
        System.out.println("p.equals(cp): "+p.equals(cp));
        System.out.println("сp.equals(p): "+сp.equals(p));
    }

}
Каков будет результат:
p.equals(cp): true
cp.equals(p): false
Вопрос. Почему?
Все просто. Именно так и должно быть. Потому как цветная точка безусловно является точкой. И проверку на принадлежность к типу Point объект класса ColoredPoint пройдет. А поскольку координаты равны, объекты будут считаться равными.
Обратное же неверно – точка не является цветной точкой, потому проверку на принадлежность к типу ColoredPoint объект класса Point не пройдет. Со всеми вытекающими последствиями.
Печально то, что эта проблема фундаментальна. Она не имеет корректного решения. Можно в сравнении ограничиться, к примеру, только объектами данного класса, исключив наследников – сравнивать придется не черезinstanceof, а с помощью this.getClass().equals(obj.getClass()). Однако это можно расценивать как нарушение основного принципа наследования – любой объект дочернего класса может по праву считаться объектом родительского класса.
Точно та же картина и с реализацией compareTo, с одним исключением – сравнение cp.compareTo(p) будет бросать исключение ClassCastException, т.е. объект класса Point нельзя привести к типу ColoredPoint.
Вообще, честно говоря, с проблемой этой приходится сталкиваться не так часто. Если вообще приходится. Мне не довелось ни разу. Однако знать о ней необходимо, чтобы при реализации проявлять осторожность. Можно, к примеру, вместо наследования создать класс, который содержит внутри себя точку и цвет, т.е., применить композицию. В некоторых случаях это может дать преимущества. Советую на эту тему почитать уже упомянутую книгу Блоха Java. Эффективное программирование, статьи 7 и 14.
Вот мы и подошли к последнему разделу. Тут мы поговорим о том, что будет, если делать не так, как положено.

Несовместимость в реализациях equalshashCodecompareTo и её последствия

Начнем с простого. Что будет, если переопределить equals, но не переопределить hashCode? Будем расуждать логически. Пусть у нас есть два равных с точки зрения equals объекта. Если не переопределить hashCode, то вероятность того, что эти два объекта попадут в одно подмножество в HashSet, – эта вероятность крайне низка. Ненулевая, но все же низкая. А это значит, что два равных с вашей точки зрения объекта попадут вHashSet с очень большой вероятностью. Если же эти объекты использовать в качестве ключей в HashMap (используя один объект как ключ, положить значение, используя другой – искать его), то значение, проассоциированное с ключом, найдено не будет. Опять-таки, с большой вероятностью. И это хуже всего. Если бы вероятность была 100% – ошибку искать было бы намного проще.
Все вышесказаное верно и для случая, когда hashCode переопределен, но неверно – так, что для двух объектов, равных с точки зрения equalshashCode возвращает разные значения.
Пойдем дальше. Обратная ситуация: переопределен hashCode, но не переопределен equals. Тут ситуация проще. В том плане что, поскольку именно возвращаемое значение equals является критерием равенства в неупорядоченных коллекциях, – два равных по содержимому объекта гарантированно не будут признаны равными ни в HashSet, ни при поиске по ключу в HashMap. А, следовательно, ошибку найти проще.
Перейдем теперь к реализации compareTo. Пусть equals у двух объектов возвращает true, а compareTo – не 0. Положим оба объекта в SortedSet (в TreeSet для простоты). Поскольку сравнение в SortedSet идет ТОЛЬКО с использованием compareTo, оба объекта благополучно окажутся в коллекции. А теперь переложим все элементы в HashSet. Тут критерием равенства будет equals, а потому – во вторую коллекцию попадет только один элемент. Что вызывает вполне понятное недоумение у людей, не знакомых с предметом.
Рассмотрим обратную ситуацию – compareTo возвращает 0, а equals – false. Положим объекты в HashSet. Они все окажутся в коллекции, спасибо методу equals. Переложим их в TreeSet, с помощью метода addAll. Увы, во второй коллекции окажется только один объект.
Кстати говоря, ошибки в реализации compareTo встречаются довольно часто. Обусловлено это желанием отсортировать объекты по определенному признаку, во-первых, и незнанием правил реализации, во-вторых. Если же вам действительно надо отсортировать объекты только по некоторым из полей, использующихся в equals, – могу посоветовать положить их в List и использовать Collections.sort(List, Comparator).



Оригинал: http://www.skipy.ru/technics/objCompTh.html

Особенности применения модификаторов в Java

Модификаторы доступа

Для начала разберемся с модификаторами доступа. Их всего четыре:
  • private члены класса доступны только внутри класса 
  • package-private или default (по умолчанию) члены класса видны внутри пакета
  • protected члены класса доступны внутри пакета и в классах-наследниках
  • public члены класса доступны всем

Во время наследования возможно изменение модификаторов доступа в сторону БОЛЬШЕЙ видимости.
Модификатор доступа у конструкторов, методов и полей может быть любой, а вот с классами и их блоками не так все просто. Класс может быть только либо public, либо default, причем в одном файле может находиться только один public класс. У блока может быть только один модификатор – default.

Модификаторы static, abstract и final

Static

  • Применяется к внутренним классам, методам, переменным и логическим блокам 
  • Статические переменные инициализируются во время загрузки класса 
  • Статические переменные едины для всех объектов класса (одинаковая ссылка) 
  • Статические методы имеют доступ только к статическим переменным 
  • К статическим методам и переменным можно обращаться через имя класса 
  • Статические блоки выполняются во время загрузки класса 
  • Не static методы не могут быть переопределены как static 
  • Локальные переменные не могут быть объявлены как static 
  • Абстрактные методы не могут быть static
  • Static поля не сериализуются (только при реализации интерфейса Serializable)
  • Только static переменные класса могут быть переданы в конструктор с параметрами, вызывающийся через слово super(//параметр//) или this(//параметр//)

Abstract

  • Применяется только для методов и классов 
  • У абстрактных методов нет тела метода 
  • Является противоположностью final: final класс не может наследоваться, abstract класс обязан наследоваться 
  • Класс должен быть объявлен как abstract если: 
  1. он содержит хотя бы один абстрактный метод 
  2. он не предоставляет реализацию наследуемых абстрактных методов 
  3. он не предоставляет реализацию методов интерфейса, реализацию которого он объявил 
  4. необходимо запретить создание экземпляров класса

Final

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

Модификаторы strictfp, transient, volatile, synchronized, native

Strictfp

  • Применяется для методов и классов 
  • Обеспечивает выполнение операций над числами типа float и double (с плавающей запятой) по стандарту IEEE 754

Transient

  • Применяется только для переменных уровня класса (локальные переменные не могут быть объявлены как transient) 
  • Transientпеременные могут не быть final или static. 
  • Transientпеременные не сериализуются

Volatile

  • Используется только с переменными 
  • Может использоваться со static переменными 
  • Не используется с final переменными - Значение переменной, объявленной как volatile, измененное одним потоком, асинхронно меняется и для других потоков 
  • Применяется в многопоточных приложениях

Synchronized

  • Применяется только к методам или частям методов 
  • Используется для контроля доступа к важным частями кода в многопоточных программах

Native

  • Используется только для методов 
  • Обозначает, что метод написан на другом языке программирования 
  • Классы в Java используют много native методов для повышения производительности и доступа к аппаратным средствам 
  • Можно предавать/возвращать Java объекты из native методов 
  • Сигнатура метода должна заканчиваться “;”, фигурные скобки вызовут ошибку компиляции

Особенности в интерфейсах

  • Методы всегда public и abstract, даже если это не объявлено
  • Методы не могут быть static, final, strictfp, native, private, protected 
  • Переменные только public static final, даже если это не объявлено 
  • Переменные не могут быть strictfp, native, private, protected 
  • Может только наследовать (extends) другой интерфейс, но не реализовывать интерфейс или класс (implements).
Соберем все модификаторы вместе:

Класс
Внутренний класс
Переменная
Метод
Конструктор
Логический блок
public
Да
Да (кроме локальных и анонимных классов)
Да
Да
Да
Нет
protected
Нет
Да (кроме локальных и анонимных классов)
Да
Да
Да
Нет
default
Да
Да 
Да (и для локальной переменной)
Да
Да
Да
private
Нет
Да (кроме локальных и анонимных классов)
Да
Да
Да
Нет
final
Да
Да (кроме анонимных классов)
Да (и для локальной переменной)
Да
Нет
Нет
abstract
Да
Да (кроме анонимных классов)
Нет
Да
Нет
Нет
static
Нет
Да (кроме локальных и анонимных классов)
Да
Да
Нет
Да
native
Нет
Нет
Нет
Да
Нет
Нет
transient
Нет
Нет
Да
Нет
Нет
Нет
synchronized
Нет
Нет
Нет
Да
Нет
Да (только как часть метода)
volatile
Нет
Нет
Да
Нет
Нет
Нет
strictfp
Да
Да
Нет
Да
Нет
Нет





Оригинал: http://www.quizful.net/post/features-of-the-application-of-modifiers-in-java