Данная статья посвящена новой разработке компании SUN – JDK 5.0
Мы рассмотрим те особенности, которые разработчики посчитали ключевыми. Большинство (можно сказать даже, что практически вся) представленной информации читатель может найти на официальном сайте компании. Автор просто решил представить данные особенности на свой лад и снабдить некоторыми примерами.
Итак, какие особенности можно назвать революционными:
1. Generics – переводу в одно слово это понятие поддается с трудом. Можно дать приблизительно такую трактовку: «обобщенные характеристики всего класса».
В двух словах это выглядит так: раньше все, что программист помещал в классы Collection (Vector, List, Stack) имело тип Object. Если Вы получали элемент из Collection (например, типа String), то Вам необходимо было делать обязательно так:
1 2 |
Vector v = …; String str = (String)v.get(0); |
Или если Вы хотели просмотреть всю коллекцию с помощью итератора, то раньше Вы делали так:
1 2 3 4 5 |
Collection c =…; for (Iterator i = c.iterator(); i.hasNext(); ) { System.out.println( ((String)i.next()).length()); } |
Заметьте, что тут есть два момента. Во-первых, необходимо привести тип элемента из коллекции к нужному виду. И, во-вторых, надо быть уверенным, что он действительно String. НИКАКИХ гарантий, что там окажется именно String, Вам не давалось. В любой момент в коллекцию можно было положить любой другой тип данных. И соответственно дальше все было делом либо доверия – там нет ничего кроме String, либо надо было убеждаться, что тип именно String.
Теперь у Вас может быть такая уверенность – Вы можете на этапе компиляции указать, какой тип Вы хотите хранить в коллекции. И уже на этапе компиляции Вам скажут, что Вы делаете что-то не то.
1 2 3 4 5 |
Collection c = …; for (Iterator i = c.iterator(); i.hasNext(); ) System.out.println(i.next().length()); } |
Чтобы было нечто более конкретное, напишем полноценный пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import java.util.*; public class Test { public static void main(String[] args) { Vector v = new Vector<String>(); v.add("12"); v.add("123"); v.add("1234"); //v.add(new Integer(1)); printCollection(v); } public static void printCollection(Collection<String> c) { for(Iterator<String> i= c.iterator(); i.hasNext(); ) { System.out.println(i.next().length()); } } } |
При внимательном просмотре текста Вы увидите, что, кроме того, что объявлена переменная Vector, объявление снабжено указанием типа данных, которые могут быть использованы в данной коллекции. Но если Вы удалите комментарий в строке 12 (//v.add(new Integer(1));), то программа уже на этапе компиляции выдаст ошибки. И если Вам действительно важно, чтобы в Вашей коллекции не было ничего кроме String, Вы можете быть уверены, что так оно и будет – компилятор следит за этим.
Давайте попробуем собрать такой же пример, но уже без указания класса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import java.util.*; public class Test1 { public static void main(String[] args) { Vector v = new Vector(); v.add("12"); v.add("123"); v.add("1234"); //v.add(new Integer(1)); printCollection(v); } public static void printCollection(Collection c) { for(Iterator i= c.iterator(); i.hasNext(); ) { System.out.println( ((String)i.next()).length()); } } } |
Программа соберется, но Вы можете увидеть предупреждения компилятора
1 2 |
Note: Test1.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. |
Давайте сделаем как просит компилятор и соберем еще раз с указанными ключом.
1 |
javac -Xlint:unchecked Test.java |
Мы увидим достаточно большое сообщение
1 2 3 4 5 6 7 8 |
Test1.java:8: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.Vector v.add("12"); ^ Test1.java:9: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.Vector v.add("123"); ^ Test1.java:10: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.Vector v.add("1234"); |
Компилятор нас предупреждает, что мы используем небезопасную операцию над коллекцией. Если Вы удалите комментарий, то наверняка программа соберется (опять же с предупреждениями), но при запуске вылетит по ошибке.
На первый взгляд может показаться, что такое нововведение излишне утяжеляет язык, но на взгляд автора это хорошая идея. Во-первых, код становиться более надежным. Во-вторых, Вам никто не запрещает использовать коллекции так же, как и прежде без указания конкретного типа.
2. The For-Each Loop – улучшенный вариант цикла прохождения по коллекции.
Возьмем наш первый пример, в котором мы распечатали длины всех строк из коллекции. Нам интересен цикл
1 2 3 |
for(Iterator i= c.iterator(); i.hasNext(); ) { System.out.println( ((String)i.next()).length()); } |
Теперь есть более упрощенный вариант такого цикла
1 2 3 |
for(String s : c) { System.out.println(s.length()); } |
В полном варианте наш пример будет выглядеть теперь вот так
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import java.util.*; public class Test { public static void main(String[] args) { Vector v = new Vector(); v.add("12"); v.add("123"); v.add("1234"); printCollection(v); } public static void printCollection(Collection c) { for(String s : c) { System.out.println(s.length()); } } } |
Также можно поступить и с обычным массивом – распечатаем значения массива
1 2 3 |
int[] a = … for (int i : a) System.out.println(i); |
3. Autoboxing/Unboxing – автоматическая упаковка в объект.
Давайте просто внимательно разберем пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.*; public class Test { public static void main(String[] args) { Vector v = new Vector(); v.add(1); v.add(2); v.add(3); for(Integer i : v) { int assa = i; System.out.println(assa+100); } } } |
Заметьте, что в коллекцию можно вставлять объект типа Integer. И вообще в коллекцию можно было вставлять только наследников от Object. Т.е. раньше надо было писать
1 2 3 |
v.add(new Integer(1)); v.add(new Integer(2)); v.add(new Integer(3)); |
А также получение числа из объекта Integer тоже надо было делать приведением типа к Integer и вызовом метода intValue(). В цикле это происходит автоматически. Вот в принципе и весь смысл этого нововведения. Но удобно очень 🙂
4. Enums – перечисления.
Раньше для объявления констант использовалась такая форма:
1 2 3 4 5 6 |
public static final int SHAPE_BOX = 0; public static final int SHAPE_OVAL = 1; public static final int SHAPE_RECT = 2; public static final int MALE = 0; public static final int FEMALE = 1; |
Такое объявление имеет ряд недостатков, наиглавнейший из которых – не уникальность. Вам никто не мешает использовать одни и те же значения для разных констант. И теперь эта проблема решена.
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 |
public class Test { public enum SHAPE { BOX, OVAL, RECT } public enum SEX { MALE, FEMALE } private SHAPE shape; private SEX sex; public Test(SHAPE sh, SEX sx) { shape = sh; sex = sx; } public String toString() { return shape.toString()+":"+sex.toString(); } public static void main(String[] args) { Test t = new Test( SHAPE.BOX, SEX.FEMALE); System.out.println(t); } } |
Как видите теперь Вашему запутанному коду не удастся обмануть компилятор без Вашего соизволения 🙂
Казалось мелочь, но кроме того, что [b]enum[/b] может хранить константы, они могут иметь методы!!!
Мне очень понравился пример из оригинального текста, который показывает пример планет.
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 |
public enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7), PLUTO (1.27e+22, 1.137e6); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; } private double mass() { return mass; } private double radius() { return radius; } // universal gravitational constant (m3 kg-1 s-2) public static final double G = 6.67300E-11; double surfaceGravity() { return G * mass / (radius * radius); } double surfaceWeight(double otherMass) { return otherMass * surfaceGravity(); } } |
Теперь Вы можете использовать не только константы, но также делать для этих констант необходимые вычисления. Разве не замечательно.
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { double earthWeight = Double.parseDouble(args[0]); double mass = earthWeight/EARTH.surfaceGravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceWeight(mass)); } |
Я специально ждал того момента, когда пытливый читатель обратит внимание на последнюю строку. Увидели?
Да, JAVA теперь имеет полноценный printf, который испокон веков был в языке Си/С++. Теперь он есть и в JAVA.
Но, как пытливый читатель заметил, такая форма printf подразумевает, что количество аргументов может быть переменным. Именно так.
5. Переменное количество аргументов в методе
В принципе здесь особо много говорить не стоит – проще посмотреть на пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Test { public static void main(String[] args) { String par1 = new String("1"); String par2 = new String("2"); String par3 = new String("3"); showParam(par1, par2, par3); } public static void showParam(Object... args) { for(Object o : args) { System.out.println(o.toString()); } } } |
6. Импорт статических переменных/констант
По большому счету это просто более удобный метод использования констант. Частенько бывает, что только ради какой-то константы приходится делать import ко всему классу. А бывает, что лениво перечислить классы и import вытаскивает полный пакет. Выглядит это не очень красиво. И потому теперь вы можете импортировать статическую и больше ничего. Насколько это удобно – даже не знаю что сказать.
Выглядит это так:
1 2 3 4 5 6 7 |
package ansa.constants; public class GlobalConstant { public static final double PI = 3.14159; …. } |
И теперь в другом пакете Выможете написать таким образом:
1 |
import static ansa.constants.GlobalConstant.PI; |
Вы можете использовать также все константы, которые описаны в Вашем классе. Вот как это выглядит:
1 |
import static ansa.constants.GlobalConstant.*; |
7. Аннотация (метаданные)
Идея аннотации (мета-данных) предложена для того, чтобы в процессе работы программы Вы бы могли получить информацию не только о методах, классах и параметрах, но и какие-то дополнительные значения или параметры, которые могут дать дополнительную характеристику. Объем стаьи не позволяет растекаться «по древу», поэтому приведу просто пример, к которому будут даны объяснения.
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 |
import java.lang.annotation.*; import java.lang.reflect.*; @Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface debug { String comment(); } @debug(comment="Comment for MetaTest class") public class MetaTest { @debug(comment="Comment for member production") public boolean production=true; @debug(comment="Comment for testMethod") public void testMethod() { } public static void main(String[] args) { MetaTest mt = new MetaTest(); try { Annotation[] ant1 = mt.getClass().getAnnotations(); for (Annotation a : ant1) { System.out.println(a); } System.out.println("-----------------------"); Annotation[] ant2 = mt.getClass().getMethod("testMethod").getAnnotations(); for (Annotation a : ant2) { System.out.println(a); } System.out.println("-----------------------"); Annotation[] ant3 = mt.getClass().getField("production").getAnnotations(); for (Annotation a : ant3) { System.out.println(a); } } catch(NoSuchMethodException e) { System.out.println(e); } catch(NoSuchFieldException e) { System.out.println(e); } } } |
Давайте потихонечку разберемся с нашей программой. Первая непонятка это строка, которая начинается с @Retention(…
Разработчики JDK 5.0 предложили следующий способ введения мета-данных:
Программист описывает структуру тех мета-данных, которые он будет использовать. Я ввел структуру, которая содержит всего одно поле – comment
Полная запись мета-данных выглядит так:
1 2 3 4 |
@interface debug { String comment(); } |
После введения мета-данных программист может использовать эти мета-данные где ему интересно. Понятно, что таких структур будет не одна, а много. Мы для примера взяли одну. Везде, где встречается @debug (кроме описания) мы видим, что в скобках указана пара «параметр=значение».
Очень важно отметить, что перед описанием структуры @debug стоит структура @Retention. Именно отсутствие этой записи заставило мучаться несколько часов автора статьи – все собиралось, но аннотация исчезала во время запуска. Этот параметр указывает, как следует обходиться с теми мета-данными, которые описаны после нее. Мы использовали параметр java.lang.annotation.RetentionPolicy.RUNTIME. Это значение говорит о том, что информация из мета-данных будет сохранена даже при запуске приложения. Другие значения (CLASS, SOURCE) говорят, что эта информация не будет доступна во время выполнения (смотрите JavaAPI for JDK 5.0).
А дальше все очень просто – мы использовали данную структуру для хранения информации для самого класса, параметра и метода.
В методе main мы по очереди попросили вернуть аннотации у самого класса, потом у метода и поля. Если Вы немного поиграете с данной программой, то поймете достаточно много.
Вот в принципе и все наиболее важные новинки, которые ждут Вас в JDK 5.0. На взгляд автора их достаточно много
Все замечания и пожелания автор принимает с удовольствием.