Сообщений 43    Оценка 755 [+0/-1]         Оценить  
Система Orphus

Крадущийся Тигр

Что нас ждет в Java 1.5

Автор: Ноздреватых Ростислав aka Blazkowicz
The RSDN Group

Источник: RSDN Magazine #2-2004
Опубликовано: 27.07.2002
Исправлено: 10.12.2016
Версия текста: 1.0
Введение
Эволюция синтаксиса
Шаблоны классов (Generics)
Автобоксинг (Autoboxing/Unboxing)
Метаданные (Metadata)
Типобезопасные перечисления (Typesafe Enums)
Функции с переменным количеством параметров (varargs)
Улучшения цикл for
Статический импорт (Static Import)
Изменения в ядре
Оснастка (Instrumentation)
Совместный доступ к классам (Class Data Sharing)
Monitoring и Management
Вместо заключения
Полезные ссылки

Введение

Выход проекта под кодовым именем Tiger на сегодняшний день одно из самых ожидаемых событий в мире Java. Новая версия Java 2 Standard Edition это не просто ещё одна единичка в номере версии, это ряд революционных новшеств, которых, наверное, не было со времен появления Java 2. В этой статье мы рассмотрим основные из них.

ПРИМЕЧАНИЕ

Номер версии 1.5 заменен на 5.0. И теперь Tiger носит гордое название J2SE 5.0.

Эволюция синтаксиса

Шаблоны классов (Generics)

Компилятор с поддержкой шаблонов классов был доступен на сайте Sun ещё даже до выхода альфа-версии Java 1.5. Программисты С++ пользуются шаблонами давно, программисты С# сравнительно недавно (пока доступны только в альфа-версии – прим.ред.), и вот, наконец, это счастье стало доступно и сообществу Java. Что же нам дадут шаблоны? Во-первых, это более строгая типизация. Классический пример использования шаблонов – это типизированные коллекции. Раньше при извлечении объектов из коллекции приходилось постоянно прибегать к приведению типов, либо создавать свою типизированную обертку. Теперь можно добиться того же результата, не прибегая к дополнительному кодированию. Вот такой код позволяет создать коллекцию, в которой можно хранить только строковые значения.

ArrayList<String> stringsList = new ArrayList<String>();

Все попытки использовать такую коллекцию для хранения других объектов будут пресекаться ещё на этапе компиляции. Как создать шаблон класса? Это не трудно:

        class Property<Type>
{
  Type value;
  Type get()
  {
    returnthis.value;
  }
  void set(Type newValue)
  {
    this.value = newValue;
  }
}

Но это ещё не всё. Существуют так называемые ограничения. Например, мы хотим, чтобы класс Property использовался только для числовых значений. Тогда объявление класса будет выглядеть следующим образом.

        class Property<Type extends Number>{...}

Ещё пример. Добавим в наш класс метод.

        void process(List<Data> list){}

И попробуем скомпилировать следующий код.

Property<Number> sp = new Property<Number>();
sp.process(new ArrayList<Integer>());

Несмотря на то, что класс Integer наследуется от Number, этот код не будет компилироваться. Чтобы решить проблему, придется немного изменить семантику метода process, используя так называемую маску (wildcard).

        void process(List<? extends Data> list){}

Теперь в наш метод можно передавать любой список, который содержит экземпляры класса, расширяющего тип, с которым был создан класс Property. То есть теперь такой код будет нормально компилироваться:

Property<Number> sp = new Property<Number>();
sp.process(new ArrayList<Integer>());
sp.process(new ArrayList<Double>());
sp.process(new ArrayList<Number>());

Возможна и обратная ситуация, когда мы хотим передавать в метод (или возвращать) экземпляры суперкласса. Для этого ключевое слово extends необходимо заменить на super. Объявление:

        void process(List<? super Data> list){}

Использование:

Property<Integer> sp = new Property<Integer>();
sp.process(new ArrayList<Integer>());
sp.process(new ArrayList<Number>());

А можно и вообще изменить метод так:

        void process(List<?> list){}

И тогда в него можно передавать список любых объектов.

И в заключении один небольшой пример: шаблон с несколькими типами. Например, java.util.Map. Объявление:

        public
        interface Map<K,V>{}

И использование:

Map<Integer, String> map = new HashMap<Integer, String >();
ПРИМЕЧАНИЕ

Немного о том как реализованы шаблоны в первой бета-версии. Шаблоны в Java являются чем-то средним между шаблонами C++ и шаблонами C#. Они всего лишь директивы прекомпиляции которые не оказывают никакого влияния на результирующий байт код. То есть, если вы создали коллекцию типа String, то это не значит что нет совершенно никакой возможности поместить в коллекцию объект другого типа. Просто компилятор постарается предотвратить все попытки это сделать.

Автобоксинг (Autoboxing/Unboxing)

Автобоксинг позволит уменьшить количество малозначащего кода в Java-приложениях. Эта новая возможность практически стирает разницу между базовыми типами (int, byte,float и пр.) и их обертками (java.lang.Integer, java.lang.Float...). Теперь для того чтобы хранить базовые типы в коллекциях не надо будет создавать дополнительные объекты:

Map<Integer, Double> map = new HashMap<Integer, Double>();
int i = 10;
map.put(i, 2d);
map.put(2, 3d);

А достать значение из коллекции можно и как базовый тип, и как класс:

        double d1 = map.get(i);
Double d2 = map.get(i);

Кроме этого, числовые операции теперь можно производить, не доставая из обертки значение:

Integer b = 10;
b++;

Возникает вопрос, разве теперь нет никакой разницы, что использовать? Разница все-таки есть. Например, нельзя вызывать методы у базовых типов, нельзя использовать базовые типы в шаблонах.

ПРЕДУПРЕЖДЕНИЕ

Следующий код компилироваться не будет:

int i = 0;

i.toString();

List<int> list = new List<int>();

Есть небольшой нюанс в unboxing'е - обработка null значений. В текущей реализации автобоксинга мы получим NullPointerException:

        try
{
  Integer object = null;
  int i = object;
}
catch(NullPointerException e)
{
  System.out.println("null reference unboxing");
}

Не все считают такую реализацию правильной. Почему бы вместо вызова исключения не присваивать переменной значение по умолчанию? Оба варианта имеют как достоинства, так и недостатки. Возможно, споры по этому вопросу утихнут ещё не скоро.

Ещё одну интересную особенность можно продемонстрировать таким примером.

        public
        class AutoBoxingReferenceCheck
{
  publicstaticvoid main(String[] args)
  {
    int a = 1;
    Integer b = a;
    Integer c = a;
    System.out.println("a == b: " + (a == b));
    System.out.println("a == c: " + (a == c));
    System.out.println("b == c: " + (b == c));
  }
}

Результат:

a == b: true
a == c: true
b == c: false

При сравнении boxed-переменной с исходным значением проверка производится по значению. При сравнении же двух boxed-переменных происходит сравнение их адресов. Как говорится, граждане, будьте бдительны.

ПРИМЕЧАНИЕ

В новых версиях Java результат третьего сравнения может оказаться вообще не предсказуем. Например, если будет создан пул для объектов-оберток, и обертки не будут каждый раз при боксинге создаваться заново. Надо так же отметить, что использовать автобоксинг надо обдуманно и аккуратно, так как постоянное создание оберток, и изъятие значений из них может сказаться на производительности. Тут на лицо недостатки неявных преобразований. Один и тот же кусок кода может быть плохим и хорошим, но определить это будет не возможно, без знания типов переменных, которые в нем используются.

Метаданные (Metadata)

Метаданные – это дополнительная информация, которая может быть добавлена к классам, интерфейсам, полям и методам. Так называемые "данные о данных". Метаданные доступны как на этапе компиляции, так и во время исполнения через механизм рефлексии. Данные, которые мы можем добавить к классам, интерфейсам, полям и методам, называются аннотациями. Объявление аннотации выглядит так:

@Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation
{
  int memberA() default -1; 
  int memberB();
  // int memberC(int i); недопустимый член аннотации - метод с аргументами
// int memberD = 10; недопустимый член аннотации - поле
// void memberE(); недопустимый член аннотации – возвращаемое значение void
}

Попробуем разобрать этот код по деталям:

Теперь пример использования:

        import java.lang.annotation.*;
import java.lang.annotation.Annotation;
//Объявляем аннотацию
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{
  String value() default"The Class";
}

//Назначаем аннотацию классу. Значение члена аннотации берется по умолчанию.
@MyAnnotation() 
publicclass MetadataTest
{
  //Назначаем аннотацию полю
  @MyAnnotation(value = "The Field") 
  publicfinal String TEST_FIELD = "The Method";
  //Назначаем аннотацию методу. Значение берется из final поля.
  @MyAnnotation(value = TEST_FIELD)
  publicvoid testMethod(){}

  publicstaticvoid main(String[] args) 
  throws Exception
  {
    MetadataTest metadata = new MetadataTest();
    Annotation[] classAnnotations;
    Annotation[] methodAnnotations;
    Annotation[] fieldAnnotations;
    //Читаем аннотации класса
    classAnnotations = metadata.getClass().getAnnotations();
    showAnnotations(classAnnotations);
    //Читаем аннотации метода
    methodAnnotations = 
      metadata.getClass().getMethod("testMethod").getAnnotations();
    showAnnotations(methodAnnotations);
    //Читаем аннотации поля
    fieldAnnotations = 
      metadata.getClass().getField("TEST_FIELD").getAnnotations();
    showAnnotations(fieldAnnotations);  
   }
  publicstaticvoid showAnnotations(Annotation[] annotations)
  {
    for (int i = 0; i < annotations.length; i++)
    {
      // Не явно вызываемый метод toString() 
// выдаёт имя аннотации и значения всех членов
      System.out.println(annotations[i]);
      if(annotations[i] instanceof MyAnnotation)
      {
        //А так мы можем получить значение интересующего нас члена аннотации
        System.out.println("My annotation value is: " 
          + ((MyAnnotation) annotations[i]).value());
      }
    }
  }
}

Интересен факт, что класс java.lang.Package тоже реализует интерфейс java.lang.reflect.AnnotatedElement. Но в текущей реализации метод getAnnotations() всегда возвращает пустой массив, хотя в спецификации заявлено иначе. Скорее всего, в окончательной версии это будет исправлено.

Типобезопасные перечисления (Typesafe Enums)

До появления поддержки перечислений в Java их пытались заменить двумя способами. Небезопасный заключался в создании полей, обычно типа int. А безопасный выглядел, как класс, который имел несколько статических полей с экземплярами этого же класса и private-конструктором (для того чтобы нельзя было создавать другие экземпляры). Оба способа имеют свои недостатки. При использовании констант базовых типов в методы можно передать некорректные аргументы. Подобные ошибки выявляются только во время исполнения, либо не выявляются никогда (если вдруг значения констант совпадут). При использовании второго способа для определения значения нельзя использовать конструкцию switch(...) { case... }.

Все эти проблемы исчезают с появлением перечислений. Создать перечисление несложно.

        enum MyTypes { myInt, myDouble, myByte }

Так же как и использовать:

MyTypes type = MyTypes.myInt;
switch(type)
{
  case MyTypes.myInt: 
    System.out.print("This is really my Int");
}

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

Функции с переменным количеством параметров (varargs)

Это новое свойство языка позволит отказаться от объявлений массивов для передачи в метод нескольких однотипных аргументов. Код, который раньше выглядел так:

method.invoke(null, new String[]{ "A", "B", "C" });

Теперь будет выглядеть так:

method.invoke(null, "A", "B", "C");

оставляя процесс создания массива на совести платформы.

Функции с переменным количеством параметров объявляются с помощью троеточия (...) следующим образом:

        void call(Object... args){}

А обращение к этим параметрам происходит как к обычному массиву.

Теперь про ограничения. Псевдоаргумент args должен быть последним аргументом в объявлении метода. Кроме этого, недопустима перегрузка следующего вида:

        void call(Object... args){}
void call(Object o, Object... args){}

То есть не то чтобы совсем недопустима, создать такие методы можно, и даже можно вызвать первый метод без аргументов, но вызов метода с несколькими аргументами станет невозможным, так как оба метода будут подходящими для вызова. Интересно, для чего тогда нужно было вводить ограничение на то, что аргумент переменной длины должен быть последним. Надеемся, эта проблема в будущем разрешится, может быть даже к выходу окончательной версии Java 1.5.

Улучшения цикл for

Теперь цикл for позволит перебирать элементы в коллекциях и массивах без создания индексов и итераторов. Выглядит это следующим образом:

        for(String f : iterable)
{
  System.out.println(f);
}

Где iterable – это коллекция, реализующая интерфейс java.lang.Iterable, либо массив.

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

Заявлено, что статический импорт создан, для того, чтобы избавиться от антипаттерна, когда интерфейс реализуется (а то и производится наследование от класса) только чтобы использовать объявленные в нем константы без указания имени интерфейса. Честно говоря, сомнительное удовольствие – использовать константу направо и налево без указания класса, которому она принадлежит. Но факт остаётся фактом, теперь мы можем делать это без всяческих ухищрений. Для этого нужно её импортировать:

        import
        static java.awt.BorderLayout.CENTER;
publicclass StaticImport
{
  publicstaticvoid main(String[] args)
  {
    System.out.println(CENTER);
  }
}

Импортировать можно не только поля, но и вообще любые статические члены класса. Кроме этого можно просто импортировать все статические члены класса. Для этого нужно вместо имени написать символ "*":

        import
        static java.awt.BorderLayout.*;

Изменения в ядре

Оснастка (Instrumentation)

Оснастка – это специальный API, который позволяет во время исполнения менять байт-код методов. Ранее подобная возможность появилась в JPDA (Java Platform Debugger Architecture) для Java 1.4. Она позволяла в режиме отладки вносить исправления в код методов, не перезапуская Java-машину. До этого подобные трюки выполнялись с помощью специальных загрузчиков классов. Судя по всему, HotSwap, который был частью JPDA, теперь стал доступен и во время исполнения. Большим плюсом этой технологии является то, что изменение кода происходит без потери экземпляров класса. В этом можно легко убедиться, слегка модифицировав пример, который приведен в статье Кельвина Остина (Calvin Austin) J2SE 1.5 in a Nutshell, добавив одно поле в модифицируемый класс. К минусам технологии относится то, что модифицировать можно только реализацию методов. Любые изменения в структуре класса (добавление/удаление полей и методов, реализация разных интерфейсов) недопустимы и вызывают UnsupportedOperationException. Может быть, такая возможность появится в будущих версиях JVM.

Совместный доступ к классам (Class Data Sharing)

CDS позволяет уменьшить потребление памяти и скорость запуска при работе нескольких экземпляров Java-машины. При инсталляции JRE создаёт специальный образ памяти, из которого можно быстро подгружать необходимые классы. За счет этого и происходит экономия ресурсов. Это должно быть особенно заметно при запуске нескольких небольших Java-процессов. CDS имеет большой потенциал. Серьёзных сдвигов в этом направлении можно ожидать в следующих версиях JRE.

Monitoring и Management

Теперь Java-машина предоставляет ряд управляемых бинов (MBeans), с помощью которых можно по большей части наблюдать, а местами даже управлять, состоянием Java-машины и операционной системы.

Вместо заключения

Кроме всего перечисленного, новая JRE содержит ряд приятных изменений, перечисления которых займут не одну страницу. Снова появился доступ к переменным окружения, появились утилиты, облегчающие разработку многопоточных систем, добавлены новые методы в классы коллекций, появилась поддержка формата BMP в Image I/O, улучшенный GC, конфигурации в формате XML, форматированный консольный ввод/вывод, дополнительные математические операции, ряд дополнительных методов для internet-приложений и многое другое.

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

Ряд утверждений в этой статье может быть оспорен, так как многое основано на исследованиях автора и довольно скромном количестве статей на данный момент. Кроме этого, к окончательной версии и спецификация, и реализация JRE может быть изменена.

Полезные ссылки


Эта статья опубликована в журнале RSDN Magazine #2-2004. Информацию о журнале можно найти здесь
    Сообщений 43    Оценка 755 [+0/-1]         Оценить