Особенности Java 5. Часть 2

Поддержка функций с переменным числом аргументов

Предположим, вам необходимо вызывать метод с переменным числом аргументов. Такая возможность в Java 1.4 и более ранних версиях заключалась в передаче массива. Давайте рассмотрим простой пример:
    public static int max(int[] values)
    {
        int max = values[0];

        for(int aValue : values)
        {
            if (aValue > max) max = aValue;
        }

        return max;
    }
Мы можем вызывать данный метод передав ему массив с различным числом значений как это показано ниже:
        max(new int[] {1, 7, 2, 9, 8});
        max(new int[]{8, 12, 87, 23, 1, 3, 6, 9, 37});
Для того, чтобы вызвать метод, который принимает переменное число параметров, нам необходимо было объединять все параметры в массив. Хотя это и работает, но данный способ не очень элегантен. Введенная в Java 5 поддержка переменного числа аргументов делает все гораздо проще.

До того, как мы рассмотрим новый метод, давайте поговорим о том, как с этим работает C++. В C++ вы можете передавать неопределенное число аргументов методам при помощи принципа многоточия (…). Хотя данный принцип очень интересен, у него есть два значительных недостатка. Первый заключается в том, что код, который получает различные параметры в методе, не так и прост. Вам необходимо использовать специальный набор функций (va_list, va_args, и т.д.) для получения параметров из стека. Во-вторых, не существует гарантированного способа определить тип данных в стеке. В общем, мы не рекомендуем разработчикам использовать многоточие в C++ и вместо этого положиться на перегрузку операторов и конкатенацию.

Принцип поддержки функций с переменным числом аргументов в Java 5 не обладает данными недостатками. Он не только элегантен, но также и типизирован. Давайте возьмем метод max и модифицируем его таким образом, чтобы он использовал функции с переменным числом аргументов.
    public static int max(int... values)
Заметьте, что единственное изменение заключается в смене int[] на int…, и мы не изменили реализацию метода max. Если прошлая реализация данного метода будет работать после данного изменения с типом параметра, то в чем заключается новый синтаксис и как он связан с массивом?

Тип, за которым следует "…" является просто украшением синтаксиса – это всего-лишь массив. Тем не менее компилятор позволяет вам передать массив или дискретный набор значений для данного метода. К примеру, теперь мы можем вызывать модифицированный метод max следующим образом:
        max(new int[] {1, 7, 2, 9, 8});
        max(new int[]{8, 12, 87, 23, 1, 3, 6, 9, 37});

        max(1, 7, 2, 9, 8);
        max(8, 12, 87, 23, 1, 3, 6, 9, 37);
В нижних двух строчках меньше текста, чем в первых двух. Но что происходит когда вы передаете дискретные значения? Компилятор записывает их в массив. Потому код:
        max(1, 7);
будет скомпилирован в:
        max(new int[] {1, 7});
и, как вы видите, вот что может получиться:
   0:   iconst_2
   1:   newarray int
   3:   dup
   4:   iconst_0
   5:   iconst_1
   6:   iastore
   7:   dup
   8:   iconst_1
   9:   bipush  7
   11:  iastore
   12:  invokestatic    #2; //Метод max:([I)I
В указанном выше примере мы передавали массив значений типа int. А что случится, если мы передадим значения другого типа? Это возможно и давайте рассмотрим следующий пример:
    public static void print(Object... values)
    {
        for(Object obj : values)
        {
            System.out.printf("%s is of type %s\n", obj, obj.getClass().getName());
        }
    }
Указанный выше код получает неопределенное количество аргументов типа Object. Вы можете вызвать его с различными типами как это показано ниже:
print(1, "test", 'a', 2.1);
Результатом данного вызова будет:
1 is of type java.lang.Integer
test is of type java.lang.String
a is of type java.lang.Character
2.1 is of type java.lang.Double
Первая строка не удивляет если вы помните что тип будет Integer вместо int. Мы также использовали выражение printf что благотворно влияет на принцип поддержки функций с переменным числом аргументов.

Вас ничего не удерживает от того, чтобы у вас были только переменные аргументы в качестве параметров. То есть вы можете иметь как регулярные, так и переменные параметры. Тем не менее, переменные, в случае если таковые существуют, должны быть отслеживаемы. Другими словами, вы можете разместить любые регулярные параметры и затем распределить переменные, как это показано в следующем примере:
public static void print(String msg, Object... values)
Преимущества и недостатки:
  • Поддержка функций с переменным числом аргументов пригодна тогда, когда вам необходимо послать переменное число аргументов. Используйте ее в случае, если вам необходима гибкость и простота.
  • Вы потеряете немного времени при компиляции в случае, если ваши переменные аргументы являются типа Object, из-за отсутствия типизации. Тем не менее, если это какой-нибудь конкретный тип (как int…), то у вас будет наблюдаться типизация.
  • Если в вашем приложении вы ожидаете передачу только трех или четырех параметров в метод, то вам не стоит использовать поддержку переменного числа аргументов.
  • Если вы не будете осторожны, то у вас могут быть проблемы с перегрузкой метода, поскольку переменные аргументы могут увеличить вероятность конфликта параметров, когда методы будут перегружены.

Статический импорт (Static import)

Выражение import позволяет вам подсказать компилятору о том, на какой класс вы ссылаетесь в вашем коде. Это предоставит вам удобство использования короткого названия для классов (как JButton) вместо длинного, полноценного названия (как javax.swing.JButton). Помните, что import не говорит компилятору расположения класса (это делает classpath). Оно всего лишь говорит компилятору, какой класс вы хотите использовать.

Конечно, если классы во множественных пакетах/пространствах имен конфликтуют, то мы получим ошибку. В этом случае вы можете вернуться к использованию полноценного имени класса.

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

Static import - это принцип в Java 5, который, к сожалению, ухудшает его. Давайте рассмотрим следующий код:
package com.agiledeveloper.com;

public class Test
{
    public static void main(String[] args)
    {
        System.out.println("Math.abs(-2) = " + Math.abs(-2));
        System.out.println("Math.ceil(3.9) = " + Math.ceil(3.9));
    }
}
Изучив следующий код, вы можете сразу понять, что вызываются методы abs() и ceil(), принадлежащие классу Math. Тем не менее, необходимо было писать Math. для обоих вызовов. Допустим, вам необходимо вызывать функцию abs() десяток раз в одном коде. Ведь в таком случае вы можете аргументировать необходимость в сокращении вызывая abs() вместо Math.abs(). Статическое импортирование позволяет вам это выполнить:
package com.agiledeveloper.com;

import static java.lang.Math.abs;
import static java.lang.Math.ceil;
import static java.lang.Runtime.*;

public class Test
{
    public static void main(String[] args)
    {
        System.out.println("Math.abs(-2) = " + abs(-2));
        System.out.println("Math.ceil(3.9) = " + ceil(3.9));

        System.out.printf("Free memory is = %d", getRuntime().freeMemory());
    }
}
В указанном выше коде мы вызываем abs(), ceil() и getRuntime() таким образом, как будто они являются статическими для класса Test(а они таковыми не являются). Статическое импортирование позволяет нам сделать вид, что мы внесли данные методы в текущее пространство имен.

Хотя есть некоторое неудобство в определении корней метода при статическом импортировании, у данной техники есть и преимущества. К примеру, в EasyMock 2.0, статическое импортирование используется для уменьшения объема кода. Используя Mock, вы можете написать следующий код:
SomeInterface mock = (SomeInterface) createMock(SomeInterface.class);
expectCall(mock.foo()).andReturn(5);
replay(mock);
Подчеркнутые методы являются частью класса EasyMock , и могут быть использованы в указанном коде, если мы добавим:

import static org.easymock.EasyMock.*;

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

Enum, annotation, и generics

Принципы перечисления (enum), аннотации (annotation) и обобщения (generics) заслуживают отдельного упоминания. Enum является очень интересной возможностью в Java 5.

Другие возможности

В данной статье мы сконцентрировались на возможностях языка в Java 5. Java 5 обладает довольно интересными возможностями, и далее мы приведем список некоторых из них:
  • StringBuilder
    • StringBuffer снижает затраты на создание объектов, но обладает некоторыми издержками синхронизации.
    • StringBuilder избавляет от этих издержек.
  • Различия между клиентом и сервером при сборе мусора, более адаптированный сбор.
  • Улучшенный ввод / вывод изображений для большей производительности и лучшего использования памяти.
  • Меньшее время на загрузку и необходимый объем диска используя разделенный архив (shared archive).
  • Улучшения в расстановке приоритетов потоков .
  • Возможность получить суммарную трассу для потока или для всех потоков.
  • UncoughtExceptionHandler по отношению к потокам.
  • Улучшенные отчеты об ошибках и исключениях.
  • System.nanoTime() со степенью разбиения в наносекунды для временных измерений.
  • ProcessBuilder
    • Легче чем Runtime.exec() для старта процесса.
  • Formatter и Formattable предоставляют возможность форматировать вывод в виде printf.
  • Сканнер (Scanner) для более удобного преобразования к примитивным типам – основан на Regex.
  • java.lang.instrument позволяет улучшить двоичный код во время рабочего цикла.
  • Collections Framework обладает интерфейсами и реализациями Queue, BlockingQueue и ConcurrentMap. Некоторые классы были модифицированы для использования новых интерфейсов.
  • Интерфейс Reflection (API) поддерживает аннотацию (annotation) и перечисление (enum). Класс был обобщен.
  • System.getenv() теперь рекомендуется!

Вывод

В данной статье, которая является второй частью, рассматривающей возможности Java 5, мы обсудили преимущества поддержки функций с переменным числом аргументов, и увидели способ сокращения кода для более легкого усвоения. Это всего лишь замена синтаксиса, где вы можете передать методу массив дискретных значений. Мы также рассмотрели статическое импортирование, которое рекомендуется использовать в редких случаях.