Последний основной выпуск платформы Java был 1.2. На самом деле он был настолько значительным, что язык стал известен как Java 2. Более поздние выпуски Java добавили ряд новых функций и исправлений, но не за что слишком волноваться. Java 5 (JDK 1.5), однако, еще одна важная персона!
В этой статье я познакомлю вас с самыми захватывающими новыми дополнениями к языку. Я покажу вам, почему Java более надежна, чем когда-либо, и во многих случаях даже проще в использовании. Давайте начнем с рассмотрения отличной новой функции под названием Autoboxing.
Autoboxing
Автобокс — это языковая функция, которая значительно облегчает жизнь программиста, когда дело доходит до работы с примитивными типами-оболочками. Рассмотрим этот фрагмент кода:
int num = 23;
Integer numObject = new Integer(num);
Примитивные объекты-обертки, такие как используемый здесь класс Integer
Следовательно, вы должны были «обернуть» ваш примитивный тип соответствующим примитивным объектом-оберткой, как показано выше.
Забудь все это! Java 5 позволяет вам делать следующее:
int num = 23;
Integer numObject = num;
Сначала это кажется немного странным: мы создали объект типа Integer и присвоили ему примитивный тип! Как это может быть? Примитив — это примитив, а объект — это объект, и они никогда не встретятся, верно? Независимо от этого, дополнительный шаг обертывания примитива больше не требуется. Он был «автоматически упакован» от вашего имени.
Распаковка использует тот же процесс в обратном порядке. Изучите следующий фрагмент:
Boolean canMove = new Boolean(true);
if(canMove){
System.out.println("This code is legal in Java 5!");
}
Что здесь такого? Посмотрите внимательно: для оператора if требуется логическое примитивное значение, но ему был присвоен объект Boolean-обертки. Нет проблем! Java 5 автоматически «распакует» это для вас.
Имейте в виду, что компилятор по-прежнему создает отсутствующий код-обертку, поэтому вы ничего не получите в плане производительности. Считайте эту функцию удобной для программиста, а не для повышения производительности.
Лично мне очень нравится эта новая языковая функция. Моя единственная проблема в том, что он стирает грань между объектным и примитивным типами (мне всегда нравился тот факт, что различие между ними было очень черно-белым). Я не должен жаловаться, хотя. Язык C # от Microsoft на самом деле продвигает идею дальше — вы даже можете вызывать методы непосредственно на примитивах!
Вар-арг
В Java спецификации метода в значительной степени заложены в камень. Например, метод, который объявляет два параметра определенного типа, должен быть вызван с этими параметрами. Если вы предоставите слишком много, слишком мало или неправильно введенные параметры, вы получите ошибку времени компиляции. Конечно, мы используем перегрузку методов, чтобы иметь дело со случаями, когда нам нужно объявить переменное число или тип параметров.
Тем не менее, время от времени имеет смысл или более удобно иметь один метод для обработки любого количества аргументов, которые выдается ему, как это разрешено в таких языках, как JavaScript и ColdFusion. Многие программисты ранее моделировали такое поведение в Java, чтобы создать массив параметров, а затем передать этот массив в качестве аргумента нужному методу.
В Java 5 этот довольно грязный подход больше не нужен. Рассмотрим спецификацию этого метода:
public void myMethod(Object ... args)
Обратите внимание на три периода рядом с типом параметра? Вот как мы сообщаем методу, что ему разрешено принимать различное количество аргументов. Тип должен быть Object
Учитывая это, все следующие вызовы методов абсолютно законны:
myMethod(23, 34, 78);
myMethod("Hello", "Goodbye");
myMethod(123);
Это имеет смысл, за исключением одного. Я сказал, что аргументы должны быть типа Object
Это автобокс в действии — аргументы передаются как типы Object
компилятор заботится о примитивном переносе от нашего имени.
Как и следовало ожидать, внутри тела метода параметры обрабатываются как массив типа Object
В следующем примере кода показано, сколько кода нам может понадобиться для достижения того же результата до Java 5.
int num1 = 23;
int num2 = 28;
int num3 = 98;
Integer objNum1 = new Integer(num1);
Integer objNum2 = new Integer(num2);
Integer objNum3 = new Integer(num3);
Integer[] params = {objNum1, objNum1, objNum3}
myMethod(params);
Здесь мы делаем все примитивное завертывание себя. Затем мы создаем параметр на основе массива. Наконец, мы делаем вызов метода. Теперь это намного больше работы!
Это довольно приятная новая функция, но я не могу не подчеркнуть, что ею нельзя злоупотреблять. Он не предназначен в качестве замены для правильного объектно-ориентированного проектирования и, по сути, был широко представлен, чтобы сделать возможной еще одну новую функцию — метод printf()
Давайте обсудим это дальше.
Метод printf
Вот удобное дополнение к классам java.io.PrintStream
java.io.PrintWriter
printf()
Первым аргументом printf()
Остальные аргументы называются «спецификаторами формата». Благодаря функции var-args вы можете иметь столько спецификаторов формата, сколько пожелаете. Это проще объяснить с помощью простого примера:
Calendar c = Calendar.getInstance();
System.out.printf("Hi %2s, the current month is: %1tB", cal, "Andy");
Давайте разберемся с этим, поскольку здесь происходит довольно много вещей. Давайте рассмотрим первый спецификатор формата в строке формата — это бит, который читает, %2s
Символ %
Цифра, которая следует, является индексом аргумента. В этом случае это 2
"Andy"
Следующий спецификатор формата — %1tB
Из приведенного выше объяснения вы узнаете, что это еще один спецификатор формата, на этот раз ссылающийся на первый аргумент (часть %1
В этом случае мы используем t
Буква B
t
В результате получится строка «Привет, Энди, текущий месяц — октябрь».
Для программистов на Си хорошая новость заключается в том, что Sun решила сделать мини-язык за всем этим мгновенно узнаваемым (хотя и не на 100% совместимым). Если вам интересно, как вы должны выяснить, что делают все эти причудливые спецификаторы формата — и их много, — javadcoc
java.util.Formatter
Как вы, вероятно, видите, без функции var-args printf()
Это редкий пример правильного использования var-args.
Сканеры
В прошлом многим начинающим программистам без нужды давали неверное представление о Java. Отчасти это было связано с трудностями работы с системной консолью — областью, где начинаются многие Java-образования.
Чтобы читать из стандартного ввода, сначала нужно было написать код обработки исключений. Как только это будет сделано, вы должны обернуть InputStreamReader
BufferedReader
System.in
Наконец, вы должны преобразовать ввод в тип, который может быть использован вашей программой, скажем, через Integer.parseInt()
Этот процесс был определенно не для слабонервных!
Передай привет моему новому другу, java.util.Scanner
Этот класс значительно упрощает чтение ввода из различных источников символов (фактически, всего, что реализует java.lang.Readable
Вот как вы можете получить себе сканер:
Scanner keyboard = Scanner.create(System.in);
Ввод читается как набор «токенов». На консоли после нажатия клавиши ввода вы можете использовать метод nextSomething()
Класс Scanner
nextSomething()
String
BigInteger
BigDecimal
Рассмотрим следующий код:
Scanner keyboard = Scanner.create(System.in);
System.out.println("Please enter your name:");
String name = keyboard.nextString();
System.out.println("Welcome " + name + " Please enter your age:");
int age = keyboard.nextInt();
Здесь я работаю только с одним токеном, который я ожидаю предоставить, но вы должны знать, что существует соответствующая серия логических возвращающих hasNextSomething()
При желании вы можете перехватить исключение InputMismatchException
nextSomething()
В сканерах есть нечто большее, например, поддержка локалей и регулярных выражений. Взгляните на javaDocs и подружитесь с этим удобным маленьким классом.
Статический импорт
Новый оператор import static — еще одно замечательное дополнение к языку. Как программисты на Java, вы можете написать код, подобный следующему, более одного раза:
PrintStream o = System.out;
o.print("stuff");
o.print("more stuff");
Ссылочная переменная o
System.out
В Java 5 вы можете импортировать статические члены класса и ссылаться на них без обычного префикса имени класса. Таким образом, теперь вы можете сделать что-то вроде этого:
import static java.lang.System.out;
// other code here.
out.print("stuff");
out.print("more stuff");
После того, как статический член импортирован, вы можете использовать его в своем коде без префикса класса System
Вот еще лучший пример из мира графического интерфейса Swing.
// without static import.
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// with static import.
import static javax.swing.WindowConstants.*;
// other code
setDefaultCloseOperation(EXIT_ON_CLOSE);
Во втором примере добавление статического импорта значительно улучшает читаемость кода и сокращает объем требуемого кода. Я использовал *
Перечисляемые типы
Давайте начнем с этого краткого обзора того, как мы использовали для моделирования перечисляемых типов:
class FizzyDrink {
static final int PEPSI = 1;
static final int COKE = 2;
static final int SCHWEPPES = 3
}
В чем здесь проблема? Теперь у нас есть 3 типа FizzyDrink
FizzyDrink.PEPSI
FizzyDrink.COKE
FizzyDrink.SCHWEPPES
Ну не совсем. На самом деле у нас просто есть 3 значения типа int
Посмотрите на следующий код:
int todaysFizzyDrink = 237; // ouch, this shouldn't happen!
Этот подход небезопасен по типу, что означает, что компилятор не может заставить пользователей нашего класса использовать одно из допустимых значений, которые мы определили. Кроме того, поскольку константы компилируются в каждом классе, который их использует, этот подход может иногда приводить к незначительным ошибкам в коде — вы должны помнить, что нужно отслеживать и перекомпилировать каждый другой зависимый класс каждый раз, когда вам нужно изменить класс, содержащий ваши константы!
Вот новый способ:
enum FizzyDrink{ pepsi, coke, schweppes }
Обратите внимание на использование ключевого слова enum
class
enum
Вот пример использования этого enum
FizzyDrink todaysFizzyDrink = FizzyDrink.pepsi;
Теперь у нас действительно есть тип FizzyDrink
Фактически, если бы мы напечатали значение todaysFizzyDrink
pepsi
Вот действительно крутая часть: перекомпиляция зависимого класса не требуется, и Java даже предупредит вас, когда вы измените перечислимый тип, но в другом месте останется код, который использует старые перечисляемые значения!
Это новое дополнение — мой личный фаворит, поскольку оно значительно повышает надежность программного обеспечения Java, но в то же время очень простое в использовании. Конечно, перечисления в этой статье — это больше, чем я могу описать, но они действительно очень просты.
Дженерики
Обобщение типов на сегодняшний день является наиболее радикальным улучшением основного языка Java. В двух словах, дженерики позволяют программистам Java передавать типы в качестве аргументов классам так же, как значения передаются в методы.
Дженерики в основном предназначены для использования со структурой коллекций, поэтому я продолжу их очень краткое обсуждение в этой области. Взгляните на следующий пример до и после того, как осуществляется доступ к элементам в LinkedList
String custName = (String) myList.getFirst(); // without generics
String custName = myList.getFirst(); // with generics
В первом примере мы должны были выполнить обычное приведение, потому что текущие классы коллекции работают с объектами. Программист должен выяснить тип и выполнить соответствующее преобразование.
Во втором примере нет актерского состава. Так как же метод getFirst()
String
Простой ответ заключается в том, что когда был создан экземпляр LinkedList
Давайте посмотрим, как это было сделано:
LinkedList<String> myList = new LinkedList<String>();
Вы увидите, что два набора угловых скобок содержат «тип», который мы передаем классу при его создании. Этот список теперь будет работать только со строками — он просто не будет компилироваться, если будут предприняты какие-либо попытки добавить объекты неправильного типа. Давайте посмотрим на пример реализации LinkedList
public class LinkedList <Element>{
boolean add(Element o) {
// code omitted
}
Element getFirst(){
// code omitted
}
}
Ключом ко всему этому является объявление <Element>
Этот довольно странный синтаксис на самом деле служит для тех же целей, что и скобки, для параметров метода, за исключением того, что мы передаем типы, а не значения. Кстати, настоящий класс LinkedList
<E>
<Element>
Во-первых, легко предположить, что генерики просто избавляют нас от необходимости выполнять приведения — это действительно хорошая функция, но это не главное. До появления дженериков некоторый ошибочный код мог добавить, скажем, целое число в коллекцию, которая должна была содержать строки. К сожалению, это останется незамеченным компилятором и вместо этого появится много месяцев или лет спустя в виде почти невозможной для поиска ошибки!
Классы коллекций теперь точно знают типы, с которыми они имеют дело, поэтому обнаружение ошибок перенесено на более раннюю точку жизненного цикла разработки: время компиляции.
Мое единственное беспокойство в связи с универсальной функцией заключается в том, что она добавляет еще один уровень сложности для начинающего программиста (а эти угловые скобки просто безобразны!), Но Java для них все лучше и сильнее.
Улучшения JVM
Хотя это и не так «в вашем лице», как функции, о которых я уже говорил, некоторые из лучших вещей, которые могут произойти в Java 5, — это усовершенствования работы JVM.
Класс обмена данными обеспечит значительное повышение производительности. По сути, большая часть библиотеки времени выполнения теперь отображается в памяти как образ памяти, а не загружается из серии файлов классов.
Кроме того, большая часть библиотек времени выполнения теперь будет совместно использоваться несколькими экземплярами JVM — это очень поможет, когда несколько программ Java запускаются одновременно.
Резюме
Java 5 действительно является серьезным шагом вперед, и, как таковой, есть чертовски много, что нужно принять. Некоторые из новых функций могут быть немного хитрыми, чтобы разобраться, пока вы не увидите их в действии, так что не надо будьте стеснительными — перейдите на java.sun.com и получите себе копию. Это, конечно, не полный список всех новых функций, поэтому обязательно изучите и, самое главное, весело проведите время!