Полная версия
Java за неделю. Вводный курс
При разработке простого приложения имя пакета можно не указывать, и среда NetBeans автоматически создаст безымянный «пакет по умолчанию». Но лучше сразу привыкать к использованию именованных пакетов.
Каждый пакет формирует отдельное пространство имен. Это важно для крупных профессиональных разработок, когда один и тот же пакет может быть включен в состав различных независимых проектов. Благодаря разделению классов по пакетам, разработчики застрахованы от случайных конфликтов имен.
В первой строке листинга 2.1 мы указали, что создаем пакет hello_java и работаем в его пространстве имен. Допускается создание подпакетов (вложенных пакетов). В таком случае имя пакета и подпакета разделяется точкой:
package main_pack.sub_pack;
Глубина вложенности пакетов формально не ограничена. Физическая структура файлов и папок на диске компьютера должна соответствовать структуре вложенных пакетов проекта.
Чтобы воспользоваться в программе классами из стороннего пакета, его нужно импортировать при помощи инструкции import. После нее указывают имя пакета и, через точку, имя импортируемого класса или звездочку *, если импортируются все публичные классы пакета:
import mypack.MyClass;
import nextpack.*;
Использование звездочки не увеличит размер приложения, потому что компилятор все равно включит в него только нужные классы. Но если пакет содержит несколько сотен или тысяч классов, то время компиляции может заметно возрасти.
Теперь разверните в окне просмотра проекта (рис. 2.6) папку «Библиотеки». По умолчанию там находится главный системный пакет JDK, который содержит предоставленные разработчиком классы для работы с системой. Этот пакет всегда подключен на уровне среды разработки, поэтому в явном импорте классов SDK нет нужды.
Теперь мы можем сказать, что означает строка из листинга 2.1:
System.out.println («Hello Java»);
В этой строке мы последовательно обращаемся к встроенному классу System, его полю out и методу println (String). Компилятор преобразует эту строку в байт—код, который заставит виртуальную Java—машину вывести в окно терминала строку текста.
При разработке собственных приложений вы можете подключать к проекту библиотеки сторонних разработчиков. Например, чтобы работать с последовательными портами компьютера, можно воспользоваться библиотекой JSSC, а для работы с базами данных MS Access пригодится библиотека UCanAccess.
Глава 3. Переменные и операторы
Вы получили общее представление о языке Java. Теперь настало время перейти к более конкретным понятиям. В этой главе будет рассказано о переменных, типах данных и операторах.
3.1 Переменные и типы данных
Может показаться, что в программировании нет ничего проще, чем переменная. Какие могут быть сложности? Тем не менее, для начинающих программистов сложности есть. Неправильное понимание того, как устроен мир переменных и данных, может привести к появлению трудно локализуемых ошибок.
Переменная представляет собой указатель на физическую область памяти, в которой хранятся данные. При помощи указателя мы можем записывать значения в память и считывать их оттуда. Иными словами, переменная – это имя фрагмента памяти компьютера. Размер этого фрагмента зависит от того, какие данные мы собираемся хранить.
Разрабатывая или запуская программу, мы не знаем заранее, по каким физическим адресам будут находиться данные в конкретном компьютере. Более того, в компиляторах современных языков принимаются специальные меры для дополнительного сокрытия информации о физическом размещении данных. Это делается для того, чтобы злоумышленнику было труднее получить доступ к критически важным данным, анализируя содержимое оперативной памяти компьютера.
До первого обращения к переменной ее надо объявить. При объявлении переменной указывают ее тип и имя. Это важно, потому что компилятор должен заранее знать, какой объем памяти выделить для хранения переменной, и как истолковывать данные, прочитанные из памяти.
Типы данных в языке Java можно разделить на две основные категории: примитивные (простые) и ссылочные. Они различаются по способу размещения данных в памяти.
Данные примитивного типа хранятся непосредственно в той ячейке памяти, которая ассоциирована с именем переменной. Обращаясь к переменной по имени, мы тем самым, обращаемся к данным в памяти. Если вы сравниваете две переменных, то сравниваются данные, которые с ними связаны. Если вы присваиваете одной переменной примитивного типа значение другой переменной примитивного типа, то происходит копирование данных.
В случае использования ссылочного типа в ячейке памяти, которая ассоциирована с именем переменной, хранится адрес данных, т.е. ссылка на данные, а не сами данные.
Необходимость в ссылочном типе данных можно продемонстрировать с помощью простого примера. Допустим, вы объявили строковую переменную с начальным значением «Java». Под это значение выделяется место в памяти. В процессе работы программы этой переменной присваивается новое значение «Hello, World!». Очевидно, что это совершенно другой объем данных, который не поместится в ранее отведенном фрагменте памяти.
Иными словами, ссылочные типы предназначены для работы с динамически создаваемыми и уничтожаемыми сущностями, объем которых невозможно предсказать заранее.
В таком случае программа размещает новые данные в другом фрагменте памяти. Новый адрес этих данных записывается в ячейку, которая ассоциирована с переменной ссылочного типа. Если на старые данные больше не ссылается ни одна переменная, то они превращаются в мусор (garbage) и удаляются из памяти при помощи специального сборщика мусора (garbage collector). В языке Java сборка мусора выполняется автоматически.
При проверке ссылочных переменных на равенство сравниваются не сами данные, а их адреса, хранящиеся в ссылочных переменных. Если вы присваиваете одной ссылочной переменной значение другой ссылочной переменной, при этом копируется адрес данных, а не сами данные.
3.1.1 Примитивные типы данных
В языке Java заявлено восемь примитивных типов данных. Первые четыре используются для хранения целых чисел.
byte – однобайтное целое – предназначен для хранения целых чисел в диапазоне от -128 до 127 и занимает один байт в памяти.
short – короткое целое – занимает два байта в памяти и применяется для хранения чисел в диапазоне от -32768 до32767.
int – целое – занимает 4 байта в памяти и применяется для хранения чисел в диапазоне от -231 (-2147483648) до 231—1 (2147483647). Это стандартный тип данных для работы с целыми числами.
При работе с числовыми данными старайтесь использовать тип int. Это связано с особенностями автоматического приведения типов, а также с тем, что целочисленные литералы (например, 10 или 123) в коде программы обрабатываются компилятором, как тип int. Приведение типов мы обсудим далее в этой главе.
long – длинное целое – занимает 8 байтов в памяти и хранит числа в диапазоне от -263 до 263—1. На практике настолько большие числа встречаются редко. Чтобы определить длинное целое число, следует добавить суффикс «L» в конце, например 5201225834L.
В дополнение к целочисленным типам, имеется два типа данных для хранения чисел с плавающей точкой.
float – с плавающей точкой – занимает 4 байта в памяти и может хранить числа в диапазоне от -3,4×1038 до 3,4×1038 с дискретностью 3,4×10—38. Такая точность представления соответствует 7 знакам после запятой. Если вы попытаетесь сохранить в типе float число 1,234567891 (10 знаков), оно будет округлено до 1,234568 (7 знаков).
Что такое дискретность? Вы не можете задать значение типа float с произвольной точностью. Ведь количество байт памяти для хранения этого числа ограничено. Если мы начнем перечислять подряд, начиная с ноля, числа с плавающей точкой, то они будут следовать с некоторым шагом (дискретностью) в младших разрядах: 0; 3,4×10—38; 6,8×10—38 и т. д. Величину дискретности можно условно назвать погрешностью представления числа. Для достижения более высокой точности применяется тип double.
double – с плавающей точкой, двойной точности – занимает 8 байтов в памяти и может хранить числа в диапазоне от -1,7×10308 до 1,7×10308 с дискретностью 1,7×10—308. Если вы не скованы ограничениями объема памяти, используйте тип double вместо float, как более точный.
По умолчанию, как только вы использовали десятичную точку в программе на языке Java, этому значению присваивается тип double. Если вы хотите, чтобы это число было истолковано именно как float, добавьте суффикс «F» в конце числа.
Кроме шести перечисленных типов, Java располагает двумя специфическими типами данных.
char – символ – занимает 2 байта и применяется для хранения одиночного символа Unicode, например «A», "@», «$» и т. д.
boolean – логический – это особый тип данных, который может хранить только два фиксированных значения: true (истина) и false (ложь). Размер занятой памяти зависит от реализации Java—машины. Этот тип данных широко используется в условных операторах и операторах цикла, которые мы рассмотрим позже.
Все остальные типы данных, включая пользовательские типы, являются ссылочными.
3.1.2 Объявление и инициализация переменных
При объявлении переменной указывается тип переменной и ее имя. Переменная может быть объявлена в любом месте программы, главное – до первого использования.
boolean fileSaved;
Если объявляется несколько переменных одного типа, то их можно перечислить через запятую.
int userNum, userAge, userWeight;
Одновременно с объявлением переменной ей можно присвоить значение. Эта процедура называется инициализацией.
int start=10, end=100;
Допускается динамическая инициализация переменной, когда ей присваивается значение, полученное вычислением из значений других переменных. Исходные переменные должны быть объявлены и инициализированы ранее.
int start=5, end=10;
int sum=a+b;
В этом примере переменная sum инициализирована значением 15.
Обратите внимание, что в момент динамической инициализации не возникает связь между переменными. Например, если после инициализации изменится значение переменных start и end, это никак не повлияет на значение sum.
3.1.3 Доступность переменных
Доступность, или область видимости переменных – это важный аспект программирования. Если кратко, переменная доступна внутри блока, определенного парой фигурных скобок, внутри которого она объявлена. Например, если переменная объявлена внутри цикла, то она будет доступна только внутри этого цикла. Снаружи цикла может быть объявлена переменная с таким же именем, но фактически это будет совершенно другая переменная.
Допустим, в некой фирме работает Иванов, он выполняет свои задачи в пределах штата фирмы. В соседнем офисе тоже работает Иванов, но это другой человек, который делает другую работу. Директор первой фирмы не может отдавать распоряжения Иванову из второй фирмы. Для него второй Иванов недоступен.
Если переменная доступна только внутри некого метода (функции), то она называется локальной. Если переменная задана на уровне класса, она называется глобальной. Глобальные переменные обычно доступны любому из методов, входящих в класс. При использовании глобальных переменных необходимо соблюдать осторожность. Если внутри одного из методов случайно изменить значение глобальной переменной, другие методы будут получать неправильное значение. Это приведет к появлению трудно локализуемой логической ошибки, на которую не реагирует компилятор.
3.1.4 Ввод и считывание данных
Переменным можно присваивать значения, введенные извне. Давайте немного отвлечемся от абстрактных рассуждений и запустим две простых программы, которые запрашивают данные у пользователя и обрабатывают их. К этому моменту вы должны уметь создавать проекты в среде NetBeans IDE, поэтому я привожу только исходный код примеров.
Программа из листинга 3.1 поддерживает консольный ввод – пользователь читает запросы программы и вводит данные в окне системного монитора среды NetBeans. В программе из листинга 3.2 задействованы модальные окна с привычным графическим оформлением. Вы увидите, насколько просты эти программы. Не волнуйтесь, если что-то непонятно. Пока просто привыкайте к новым терминам. По мере чтения этой книги придет полное понимание.
Листинг 3.1 Чтение консольного ввода, вывод в консоль
import java.util.Scanner;
public class Listing3_1 {
public static void main (String [] args) {
// Создаем объект input класса Scanner
Scanner input = new Scanner(System.in);
// Переменная для хранения имени пользователя
String name;
// Переменная для хранения отчества пользователя
String surName;
// Переменная для хранения даты рождения пользователя
int yearBorn;
// Переменная для хранения текущего года
int yearNow;
// Выводим запрос данных
System.out.print («Ваше имя:»);
// Считываем имя (строка)
name = input.nextLine ();
System.out.print («Ваше отчество:»);
// Считываем отчество (строка)
surName = input.nextLine ();
System.out.print («Какой сейчас год?»);
// Считываем текущий год (целое число)
yearNow = input.nextInt ();
System.out.print («В каком году вы родились?»);
// Считываем год рождения (целое число)
yearBorn = input.nextInt ();
System.out.println («Здравствуйте, "+name+" "+surName+»!»);
System.out.println («Ваш возраст: "+ (yearNow-yearBorn) +».»);
}
}
В первой строке этой программы мы импортируем класс Scanner, который входит в состав системного пакета java. util. Затем мы создаем новый объект класса Scanner и назначаем ему идентификатор (имя) input. После этого приступаем к получению данных от пользователя. Выводим в консоль текстовый запрос и считываем ответ. Обратите внимание, что текстовые ответы мы считываем при помощи метода nextLine (), а целочисленные при помощи метода nextInt (). В противном случае возникнет ошибка несоответствия типа данных. Ведь мы объявили переменные yearNow и yearBorn как целые числа.
Отдельно разберем строку
System.out.println («Ваш возраст: "+ (yearNow-yearBorn) +».»);
В этой строке происходит арифметическое вычисление возраста пользователя, формирование строки вывода и вывод в консоль. Выражение (yearNow-yearBorn) обязательно должно быть в круглых скобках, потому что сначала должно быть вычислено его значение, а затем результат вычисления будет преобразован из числа в строку (автоматическое приведение типов).
Наберите или скачайте исходный код программы и запустите проект на выполнение. Введите ответы на вопросы. В окно терминала должно быть выведено что-то наподобие этого:
run:
Ваше имя: Иван
Ваше отчество: Петрович
Какой сейчас год? 2018
В каком году вы родились? 1988
Здравствуйте, Иван Петрович!
Ваш возраст: 30.
СБОРКА УСПЕШНО ЗАВЕРШЕНА (общее время: 22 секунды)
На компьютере с ОС Windows вместо символов кириллицы вы можете увидеть квадратики. В этом случае необходимо настроить кодировку проекта. В окне просмотра содержимого проекта щелкните правой кнопкой мыши на названии проекта и выберите пункт Свойства контекстного меню. В открывшемся окне найдите поле «Кодировка» и выберите в списке кодировку windows—1251. Нажмите OK.
Вторая программа имеет графический интерфейс, основанный на модальных окнах. Это специальные окна, которые содержат сообщение или поле ввода. Чтобы программа продолжила выполнение, пользователь обязательно должен отреагировать на появление окна – ввести данные или прочитать сообщение и закрыть.
Листинг 3.2 Ввод и вывод данных в модальных окнах
// импортируем класс JOptionPane из библиотеки Swing
import javax.swing.JOptionPane;
public class Listing3_2 {
public static void main (String [] args) {
// Объявление числовых переменных
int yearNow, yearBorn, userAge;
// Объявление строковой переменной
String userData;
// Выводим окно запроса текущей даты
userData = JOptionPane.showInputDialog («Какой сейчас год?»);
// Преобразуем строку в число в явном виде
yearNow = Integer.parseInt (userData);
// Выводим окно запроса года рождения
userData = JOptionPane.showInputDialog («В каком году вы родились?»);
// Преобразуем строку в число в явном виде
yearBorn = Integer.parseInt (userData);
// Вычисляем возраст
userAge = yearNow – yearBorn;
// Выводим окно сообщения с результатом
JOptionPane.showMessageDialog (null, «Ваш возраст: " + userAge);
}
}
В первой строке программы мы импортируем класс JOptionPane из библиотеки Swing. Библиотека Swing содержит набор классов для разработки приложений с графическим интерфейсом. Это очень емкая и мощная библиотека, входящая в пакет поставки SDK. Вы будете постоянно использовать ее при разработке приложений с графическим интерфейсом. Класс JOptionPane предназначен для создания стандартных модальных (диалоговых) окон. Для вывода окна с запросом данных применяется метод showInputDialog (), а для вывода сообщения – метод showMessageDialog ().
Любые значения, возвращаемые методом showInputDialog () являются строковыми данными. Чтобы выполнить над ними арифметические действия, необходимо в явном виде преобразовать строки в числа. Мы делаем это при помощи метода parseInt () системного класса Integer:
yearNow = Integer.parseInt (userData);
Программа завершается вычислением возраста пользователя и выводом результата.
Запустите проект на выполнение. Вы должны поочередно увидеть три диалоговых окна (рис. 3.1).
Рис.3.1 Диалоговые окна запроса и вывода данных
Если все работает правильно, нажмите клавишу F11 или выберите пункт меню Выполнить | Собрать проект. Будет создан исполняемый файл приложения. Его можно запустить на любом компьютере, где установлена Java-машина. Оформление окон приложения – цветовая схема, форма кнопок – может различаться в зависимости от операционной системы и реализации Java-машины.
По умолчанию файл проекта находится в папке Документы | NetBeansProjects. Внутри папки с именем проекта найдите папку dist. В этой папке находится готовый распространяемый файл приложения с расширением jar.
3.2 Приведение типов
Иногда возникает ситуация, когда в одном выражении присутствуют разные типы данных. Будем считать, что это осознанное действие, а не ошибка программиста, ибо такие ошибки чрезвычайно коварны. Несоответствие типов в выражении не всегда влечет за собой ошибку компиляции, но программа может вести себя не так, как ожидалось. Изменение типа данных в процессе выполнения программы называется приведением типа. Реализация приведения типов зависит от конкретного языка. Некоторые языки допускают большие вольности в приведении типов. За это их резко критикуют профессиональные программисты.
Про особенности приведения типов в разных языках программирования можно написать отдельную брошюру. Но в нашем вводном курсе мы ограничимся изложением основных принципов.
Приведение типов разделяется на явное (указанное программистом в коде) и неявное (автоматическое).
При явном приведении типов перед значением или выражением в скобках указывается новый тип, например:
double x = 15.7;
y = (int) 15.7;
В этом примере число с плавающей точкой приводится к типу «целое», при этом просто отбрасывается дробная часть. Результатом приведения будет усеченное значение 15, а не округленное 16.
Обратное преобразование из целого в число с плавающей точкой тоже выполняется, но это происходит автоматически. Запомните простое правило: если в выражении участвуют операнды разных типов, то результат приводится к тому типу, который занимает больше места в памяти. Поэтому важно, чтобы тип переменной, которой вы хотите присвоить результат вычислений, совпадал с типом результата. Вот простой пример приведения типов:
byte a = 2;
а = (byte) (a*5);
В этом примере целочисленный литерал 5 трактуется, как значение типа int, поэтому результат умножения будет расширен до типа int. Но переменная объявлена, как byte, поэтому возникнет конфликт выделения памяти и ошибка компиляции.
Чтобы избежать ошибки, мы в явном виде приводим результат умножения к типу byte. При этом из 32 байт остаются только младшие 8, а остальные отбрасываются. Это опасная потеря информации. Может получиться так, что при маленьких исходных значениях результат будет верным. Но стоит разрядности результата умножения превысить 8 битов, и после приведения типов вы получите неправильный результат вычислений. Такая блуждающая ошибка зависит от сочетания факторов и трудно поддается локализации в коде.
Автоматическое приведение типов часто применяется при суммировании строки и числа. В этом случае число автоматически преобразуется в строку и выполняется обычная конкатенация (слияние) строк. Например:
int yearNow = 2018;
System.out.println («Текущий год " + yearNow);
В окно терминала будет выведена строка «Текущий год: 2018».
Обратное преобразование из строки в число автоматически не выполняется. Необходимо воспользоваться специальными методами, такими как Integer.parseInt (), Double.parseInt () и т. п. в зависимости от нужного типа. В листинге 3.2 вы уже встречали преобразование из строки в число.
3.3 Основные операторы
Основные операторы языка Java можно разделить на четыре группы: арифметические, логические, битовые и операторы сравнения.
По количеству обязательных операндов в выражении операторы разделяются на унарные (один операнд), бинарные (два операнда) и тернарные (три операнда).
3.3.1 Арифметические операторы
К арифметическим операторам относятся сложение (+), вычитание (-), умножение (*), деление (/), вычисление остатка (%), инкремент (++) и декремент (– -).
Допустим, мы задали значения x=18 и y=4. Тогда результаты использования операторов будут выглядеть так:
Сложение: x + y = 22
Вычитание: x – y = 14
Умножение: x*y = 72
Пока ничего необычного, но дальше будет немного сложнее.
Деление: 18 / 4 = 4
Неожиданно, не так ли? В языке Java результат деления одного целого числа на другое целое число будет целочисленным, остаток отбрасывается без округления. Получить результат деления с дробной частью можно двумя способами: объявить один или оба операнда как число с плавающей точкой или использовать явное приведение.
18 / 4.0 = 4.50
(double) 18/4 = 4.50
Вычисление остатка: 18%4 = 2. При делении 18/4 нацело мы получаем частное 4 (4*4=16) и остаток 2 (18—16=2). Иными словами, остаток – это побочный продукт целочисленного деления.
Инкремент: оператор постфиксного инкремента x++ сперва возвращает исходное значение переменной, затем увеличивает его на единицу. Оператор префиксного инкремента ++x сперва увеличивает значение переменной на 1, затем возвращает новое значение.
Строка с постфиксным инкрементом
System.out.print (x++);
равнозначна последовательности команд
System.out.print (x);
x = x +1;
Строка с префиксным инкрементом
System.out.print (++x);
равнозначна последовательности команд
x = x +1;
System.out.print (x);
Декремент: оператор постфиксного декремента сперва возвращает исходное значение переменной, затем уменьшает его на единицу. Оператор префиксного декремента сперва уменьшает значение переменной на 1, затем возвращает новое значение.
Строка с постфиксным декрементом
System.out.print (x – -);
равнозначна последовательности команд
System.out.print (x);
x = x – 1;
Строка с префиксным декрементом
System.out.print (– – x);
равнозначна последовательности команд
x = x – 1;
System.out.print (x);
3.3.2 Логические операторы
Логические операторы предназначены для использования с логическими операндами и создания условий для логических операторов.