Как вы наверное знаете, в методах обобщенного класса можно использовать параметр типа, а следовательно, они становятся обобщенными относительно параметра типа.
Но можно объявить обобщенный метод, в котором непосредственно используется один или несколько параметров типа. Более того, можно объявить обобщенный метод, входящий в необобщенный класс.
Начнем рассмотрение обобщенных методов с конкретного примера. В приведенной ниже программе объявляется необобщенный класс GenMethDemo, а в нем — статический обобщенный метод isIn().
Этот метод определяет, является ли объект членом массива. Его можно применять к любому типу объектов и массивов, при условии, что массив содержит объекты, совместимые с типом искомого объекта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// Продемонстрировать простой обобщенный метод class GenMethDemo { // определить, содержится ли объект в массиве static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y) { for(int i = 0; i < y.length; i++) { if(x.equals(y[i])) { return true; } } return false; } public static void main(String args[]) { // применить метод isIn() для целых чисел Integer nums[] = {1, 2, 3, 4, 5, 6, 7}; if(isIn(2, nums)) { System.out.println("Число 2 содержится в массиве nums"); } if(!isIn(8, nums)) { System.out.println("Число 8 отсутствует в массиве nums"); } System.out.println(); // применить метод isIn() для символьных строк String strs[] = {"один", "два", "три", "четыре", "пять", "шесть", "семь"}; if(isIn("три", strs)) { System.out.println("три содержится в массиве strs"); } if(!isIn("девять", strs)) { System.out.println("девять отсутствует в массиве strs"); } // Не скомпилируется! Типы должны быть совместимы // if(isIn("пять", nums)) { // System.out.println("пять содержится в массиве strs"); // } } } |
Ниже приведен результат выполнения данной программы:
1 2 3 4 5 6 7 8 |
pro-java.ru@admin:~$ javac genericmethod.java pro-java.ru@admin:~$ java GenMethDemo Число 2 содержится в массиве nums Число 8 отсутствует в массиве nums три содержится в массиве strs девять отсутствует в массиве strs pro-java.ru@admin:~$ |
Рассмотрим метод isIn() подробнее. Прежде всего обратите внимание на объявление этого метода в следующей строке кода:
1 2 |
static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y) { |
Параметр типа объявляется до типа, возвращаемого методом. Обратите так-же внимание на то, что тип Т расширяет обобщенный тип Comparable<T>, где Comparable — это интерфейс, объявляемый в пакете java.lang.
В классе, реализующем интерфейс Comparable, определяются объекты, которые моrут быть упорядочены.
Следовательно, указание интерфейса Comparable в качестве верхней границы гарантирует, что метод isIn() вполне применим к объектам, которые можно сравнивать.
Интерфейс Comparable является обобщенным, а параметр его типа обозначает тип сравниваемых объектов. Обратите далее внимание на то, что тип V ограничен с верху типом Т.
Это означает, что тип V должен быть тем же типом, что и Т, или же типом его подкласса. Такая взаимосвязь подразумевает, что метод isIn() может быть вызван только с совместимыми аргументами.
И наконец, обратите внимание на то, что метод isIn() объявлен как статический, что позволяет вызывать его независимо ни от какого объекта.
Следует, однако, иметь в виду, что обобщенные методы могут быть как статическими, так и нестатическими. Никаких ограничений на этот счет не существует.
А теперь обратите внимание на то, что метод isIn() вызывается из метода main() с нормальным синтаксисом вызовов, не требуя указывать аргументы типа.
Дело в том, что типы аргументов различаются автоматически, а типы Т и V соответственно подстраиваются.
Например, в первом вызове этого метода:
1 |
if(isIn(2, nums)) |
первый аргумент относится к типу Integer (благодаря автоупаковке), поэтому вместо типа Т подставляется тип Integer.
Второй аргумент также относитсяк типу Integer, который подставляется вместо типа V. Во втором вызове данного метода оба аргумента относятся к типу String, который и подставляется вместо типов Т и V.
Для вызовов большинства обобщенных методов, как правило, достаточно и выведения типов, но если требуется, то аргументы типа можно указать явно.
В качестве примера ниже показано, как должен выглядеть первый вызов метода isIn(), если явно указаны оба аргумента типа:
1 |
GenMethDemo.<Integer, Integer>isIn(2, nums) |
Очевидно, что явное указание аргументов типа в данном случае не дает никаких преимуществ.
Более того, выведение типов в отношении методов было усовершенствовано в версии JDК 8. Таким образом, указывать аргументы типа явным образом требуется лишь в крайне редких случаях.
А теперь обратите внимание на приведенный ниже закомментированный кодиз рассматриваемой здесь программы:
1 2 3 |
// if(isIn("пять", nums)) { // System.out.println("пять содержится в массиве strs"); // } |
Если убрать комментарии из этих строк кода, а затем попытаться скомпилировать данную программу, то возникнет ошибка.
Дело в том, что параметр типа V ограничивается типом Т в выраженииe extends из объявления параметра типа V. Это означает, что параметр типа V должен иметь тип Т или же тип его подкласса.
В данном же случае первый аргумент относится к типу String, а следовательно, второй аргумент также должен относиться к типу String, хотя на самом деле он относится к типу Integer который не является производным от класса String.
В итоге во время компиляции возникает ошибка несоответствия типов. Такая способность обеспечивать типовую безопасность является одним из самых главных преимуществ обобщенных методов.
Синтаксис, использованный для создания метода isIn(), можно обобщить. Ниже приведена общая синтаксическая форма обобщенного метода:
1 2 |
<список_параметров_типа> возвращаемый_тип имя_метода (список_параметров) { // ... |
В любом случае список_параметров_типа обозначает разделяемый запятыми список параметров типа.
Обратите внимание на то, что в объявлении обобщенного метода список параметров типа предшествует возвращаемому типу.