Особенности 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});Для того, чтобы вызвать метод, который принимает переменное число параметров, нам необходимо было объединять все параметры в массив. Хотя это и работает, но данный способ не очень элегантен. Введенная в Java 5 поддержка переменного числа аргументов делает все гораздо проще.
max(new int[]{8, 12, 87, 23, 1, 3, 6, 9, 37});
До того, как мы рассмотрим новый метод, давайте поговорим о том, как с этим работает 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В указанном выше примере мы передавали массив значений типа int. А что случится, если мы передадим значения другого типа? Это возможно и давайте рассмотрим следующий пример:
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
public static void print(Object... values)Указанный выше код получает неопределенное количество аргументов типа Object. Вы можете вызвать его с различными типами как это показано ниже:
{
for(Object obj : values)
{
System.out.printf("%s is of type %s\n", obj, obj.getClass().getName());
}
}
print(1, "test", 'a', 2.1);Результатом данного вызова будет:
1 is of type java.lang.IntegerПервая строка не удивляет если вы помните что тип будет Integer вместо int. Мы также использовали выражение printf что благотворно влияет на принцип поддержки функций с переменным числом аргументов.
test is of type java.lang.String
a is of type java.lang.Character
2.1 is of type java.lang.Double
Вас ничего не удерживает от того, чтобы у вас были только переменные аргументы в качестве параметров. То есть вы можете иметь как регулярные, так и переменные параметры. Тем не менее, переменные, в случае если таковые существуют, должны быть отслеживаемы. Другими словами, вы можете разместить любые регулярные параметры и затем распределить переменные, как это показано в следующем примере:
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;Изучив следующий код, вы можете сразу понять, что вызываются методы abs() и ceil(), принадлежащие классу Math. Тем не менее, необходимо было писать Math. для обоих вызовов. Допустим, вам необходимо вызывать функцию abs() десяток раз в одном коде. Ведь в таком случае вы можете аргументировать необходимость в сокращении вызывая abs() вместо Math.abs(). Статическое импортирование позволяет вам это выполнить:
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));
}
}
package com.agiledeveloper.com;В указанном выше коде мы вызываем abs(), ceil() и getRuntime() таким образом, как будто они являются статическими для класса Test(а они таковыми не являются). Статическое импортирование позволяет нам сделать вид, что мы внесли данные методы в текущее пространство имен.
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());
}
}
Хотя есть некоторое неудобство в определении корней метода при статическом импортировании, у данной техники есть и преимущества. К примеру, в EasyMock 2.0, статическое импортирование используется для уменьшения объема кода. Используя Mock, вы можете написать следующий код:
SomeInterface mock = (SomeInterface) createMock(SomeInterface.class);Подчеркнутые методы являются частью класса EasyMock , и могут быть использованы в указанном коде, если мы добавим:
expectCall(mock.foo()).andReturn(5);
replay(mock);
…
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() теперь рекомендуется!