Особенности Java 5
Возможности Java 5
Несколько интересных возможностей были добавлены к языку программирования Java в версии 1.5, также называемой Java 5. Этими возможностями являются auto-boxing, foreach, enums, varargs, annotation, static imports, и generics. Большинство из данных возможностей может считаться прогрессом. Они улучшают производительность разработчиков путем сокращения многословного синтаксиса и делая код более интуитивным. Следующий рисунок демонстрирует наше мнение о пригодности данных функций:Свойства в левой части (foreach, varargs, auto-boxing, enum) хороши. Annotation очень полезно, но мы также должны понять где и когда его использовать. Static import предоставляет только несущественную выгоду (и может привести к плохому коду).
Auto-boxing для объектных оболочек
Java обладает двумя классами: objects и primitives. Вы не можете смешивать их в вашем коде. Но что, если вы хотите написать обобщенный программный интерфейс программирования, который сможет принять параметры любого типа. Это выполняется в Reflection API. Давайте напишем простую имитацию метода invoke(), принадлежащего Reflection:package com.agiledeveloper;В main() мы вызываем invoke() с foo1 , а затем с foo2. Для того, чтобы послать любое число параметров, invoke() принимает массив объектов. Не существует препятствий для отсылки объекта A или объекта B данному методу. Тем не менее, давайте добавим другой метод:
class A {}
class B {}
public class Test
{
public static void foo1(A obj)
{
System.out.println("foo вызван с" + obj);
}
public static void foo2(A obj1, B obj2)
{
System.out.println("foo2 вызван с " + obj1 + " и " + obj2);
}
// слабая имитация программного интерфейса для демонстрации общей идеи
public static void invoke(String method, Object[] args)
{
if (method.equals("foo1"))
{
foo1((A) args[0]);
}
if (method.equals("foo2"))
{
foo2((A) args[0], (B) args[1]);
}
}
public static void main(String[] args)
{
invoke("foo1", new Object[]{new A()});
invoke("foo2", new Object[]{new A(), new B()});
}
}
public static void foo3(int value)Как же вызвать данный метод используя метод invoke()? invoke() ожидает массив объектов в то время, как foo3() ожидает значение типа int. В предыдущих версиях Java вам пришлось бы обернуть int в объект и послать его методу invoke() . Стандартным объектом для упаковывания int является Integer. Аналогично, для Double есть double, Character для char, и так далее. В то время как Integer.class представляет собой класс Integer, Integer.TYPE представляет собой примитивный тип int , которому данный класс служит оболочкой.
{
System.out.println("foo3 вызван с" + value);
}
Такой подход к ручной обертке и распаковке имеет несколько недостатков:
- Он приводит к нагромождению кода
- Он требует больше работы от разработчиков
- Выглядит не натурально
- Пугает новичков программистов
public static void invoke(String method, Object[] args)А вызов будет выглядеть следующим образом:
{
if (method.equals("foo1"))
{
foo1((A) args[0]);
}
if (method.equals("foo2"))
{
foo2((A) args[0], (B) args[1]);
}
if (method.equals("foo3"))
{
foo3(((Integer) (args[0])).intValue());
}
}
invoke("foo3", new Object[]{new Integer(3)}); // до Java 1.4
Заметьте, как вы помещаете (оборачиваете) значение 3 в объекте Integer . Аналогично, в вызове к foo3() в invoke(), вам необходимо распаковать данное значение.
В Java 5, функции auto-boxing и auto-unboxing назначены для разрешения данного излишкаi. Хотя все это проходит за занавесом, на уровне исходного кода, нам не нужно этого выполнять. Вот модифицированный код:
invoke("foo3", new Object[] {3});
Указанный выше код при компиляции переводится таким образом, что значение 3 автоматически упаковывается в объект Integer.
Давайте изучим другой пример – мы добавим метод foo4() как это показано ниже :
public static Integer foo4(Integer v)Теперь вот как его можно вызвать используя новую функцию Java 5 auto-boxing/unboxing:
{
System.out.println("foo4 вызван с " + v.intValue());
return new Integer(5);
}
int someValue = foo4(4);Как вы видите, данный код выглядит более натурально и занимает меньше места. Доводы за и против:
System.out.println("Результат вызова foo4 " + someValue);
- Поймите, что оборачивание и распаковка происходит за занавесом. Посмотрите на сгенерированный код:
92: iconst_4
Этот код кажется обманывающим - у него есть проблемы с производительностью, особенно если вы вызываете методы со значительными вычислительными нуждами.
93: invokestatic #31;
//Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
96: invokestatic #33;
//Method foo4:(Ljava/lang/Integer;)Ljava/lang/Integer;
99: invokevirtual #23;
//Method java/lang/Integer.intValue:()I
102: istore_1 - Если объект Integer равен null, его назначение int в результате вызовет выброс исключения – NullPointerException.
- Поймите, что означает == . Для объектов вы сравниваете идентичность, а для примитивных типов вы сравниваете значение. В случае с auto-unboxing происходит сравнение, основанное на значениях.
- Используйте его только когда производительность не важна.
foreach
Циклы - это структура управления, которая существовала со времен создания языков программирования. Старый добрый синтаксис циклов, который вы чаще всего используете:for(int i = 0; i < arr.length; i++)Хотя данная структура проста, нам приходится использовать индекс i даже если в нем нет необходимости.
{
... = arr[i] ...
}
Но что, если вы хотите пройтись по набору, как ArrayList? Вы сделаете что-то наподобие этого:
for(Iterator iter = lst.iterator(); iter.hasNext(); )
{
System.out.println(iter.next());
}
Не настолько элегантно, не правда ли? В выражении for, у вас нет ничего после последней ;. Вам необходимо вызвать метод next() в пределах тела цикла для того, чтобы перейти к следующему элементу и в то же время получить элемент для обработки.
Введенный цикл foreach в Java 5 упрощает процесс циклов. Давайте рассмотрим пример того, как вы можете использовать новый и старый способы организации циклов:
package com.agiledeveloper;foreach был тактично создан для избежания введения других ключевых слов:
import java.util.ArrayList;
import java.util.Iterator;
public class Test
{
public static void main(String[] args)
{
String[] messages = {"Привет", "Здравствуйте", "Спасибо"};
for (int i = 0; i < messages.length; i++)
{
System.out.println(messages[i]);
}
for (String msg : messages)
{
System.out.println(msg);
}
ArrayList lst = new ArrayList();
lst.add(1);
lst.add(4.1);
lst.add("тест");
for (Iterator iter = lst.iterator(); iter.hasNext();)
{
System.out.println(iter.next());
}
for (Object o : lst)
{
System.out.println(o);
}
ArrayList<Integer> values = new ArrayList<Integer>();
values.add(1);
values.add(2);
int total = 0;
for (int val : values)
{
total += val;
}
System.out.println("Всего : " + total);
}
}
for (String msg : messages)
Вместо придумывания нового ключевого слова “foreach” разработчики решили использовать старый добрый “for”. Также, “in” может быть использовано в существующем коде для полей, переменных или методов. Но было решено использовать : . В пределах цикла foreach, msg представляет собой элемент String в пределах массива String[].
Как вы могли бы заметить, организация циклов значительно упрощена, более элегантна (за исключением : ), и легка. Но что на самом деле происходит? Давайте опять изучим код.
for (String msg : messages)порождает следующий код :
{
System.out.println(msg);
}
51: aload_1Для массива foreach просто преобразовывается в старый for. Давайте посмотрим что происходит с:
52: astore_2
53: aload_2
54: arraylength
55: istore_3
56: iconst_0
57: istore 4
59: iload 4
61: iload_3
62: if_icmpge 85
65: aload_2
66: iload 4
68: aaload
69: astore 5
71: getstatic #6;
//Field java/lang/System.out:Ljava/io/PrintStream;
74: aload 5
76: invokevirtual #7;
//Method java/io/PrintStream.println:(Ljava/lang/String;)V
79: iinc 4, 1
82: goto 59
for (Object o : lst)где lst является ArrayList. Вот байтовый код :
{
System.out.println(o);
}
157: aload_2
158: invokevirtual #22;
//Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
161: astore_3
162: aload_3
163: invokeinterface #18, 1;
//InterfaceMethod java/util/Iterator.hasNext:()Z
168: ifeq 190
171: aload_3
172: invokeinterface #19, 1;
//InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
177: astore 4
179: getstatic #6;
//Field java/lang/System.out:Ljava/io/PrintStream;
182: aload 4
184: invokevirtual #20;
//Method java/io/PrintStream.println:(Ljava/lang/Object;)V
187: goto 162
Как вы уже могли заметить, в данном случае foreach преобразуется в for , циклирующий через Iterator.
Итак, foreach просто является синтаксисом, который преобразуется в традиционный цикл во время компиляции.
В указанном выше примере мы использовали foreach к String[] и к ArrayList. А что, если вы хотите использовать foreach на вашем собственном классе? Вы можете так сделать, но ваш класс должен реализовывать интерфейс Iterable. Это все очень просто - для того, чтобы foreach работал, вы должны возвратить Iterator. Конечно, вы знаете что Collection предоставляет итератор. Тем не менее, для того, чтобы использовать foreach на вашем собственном классе не будет выгодно, если вам пришлось бы использовать громоздкий интерфейс Collection. Интерфейс Iterable имеет всего лишь один метод :
/** Реализация данного интерфейса позволяет объекту становиться целью выражения "foreach".Давайте попробуем следующее :
*/
public interface Iterable<T> {
/**
* Возвращает итератор через набор элементов типа T.
*
* @return an Iterator.
*/
Iterator<T> iterator();
}
//Wheel.javaТеперь вы можете использовать foreach для объекта Car, как это показано ниже :
package com.agiledeveloper;
public class Wheel
{
}
//Car.java
package com.agiledeveloper;
import java.util.Iterator;
import java.util.ArrayList;
public class Car implements Iterable<Wheel>
{
ArrayList<Wheel> wheels = new ArrayList<Wheel>();
public Car()
{
for(int i = 0; i < 4; i++)
{
wheels.add(new Wheel());
}
}
public Iterator<Wheel> iterator()
{
return wheels.iterator();
}
}
Car aCar = new Car();Доводы за и против :
for(Wheel aWheel : aCar)
{
// aWheel ...
}
- foreach выглядит проще, элегантнее и легче.
- Но вы не можете его использовать в любое время. Если вам нужен доступ к индексу в наборе (для установки или изменения значений) или же вам необходим доступ к итератору (к примеру, для удаления элемента), то в этом случае вы не можете использовать foreach.
- Пользователи вашего класса не смогут использовать foreach с вашим объектом если вы не реализуете интерфес Iterable.