Основы Java

Автор: | 28.01.2018

Платформа Java
Сравнение C++ и Java
Пакеты классов
Структура класса
Абстрактные классы
Абстрактные методы
Статические члены класса
Внутренние классы
Интерфейсы
Сетевые приложения

Платформа Java

Программная платформа Java – ряд программных продуктов и спецификаций, которые совместно представляют систему для разработки кросс-платформенного программного обеспечения.

На языке Java все приложения – это текстовые файлы, имеющие расширение .java. Эти файлы компилируются с помощью компилятора javac.exe в файлы с расширением .class в так называемый байткод. Программа может состоять из множества классов, размещенных в различных файлах. Для облегчения размещения больших программ, часть файлов вида .class могут быть упакованы вместе в так называемый .jar файл (сокращение от Java Archive). Обычно jar-файл используется для запуска приложения вне среды разработки.

Файлы .class (или .jar) не содержат машинный код, исполняемый непосредственно процессором, для этого используется так называемая Java Virtual Machine (сокращенно Java VM, JVM)

JVM исполняет (рис.5.1) файлы .class или .jar, эмулируя инструкции, написанные для JVM, путем интерпретирования или использования just-in-time компилятора (JIT), такого, как HotSpot от Sun microsystems. В наши дни JIT компиляция используется в большинстве JVM в целях достижения большей скорости. Существуют также ahead-of-time компиляторы, позволяющие разработчикам приложений перекомпилировать файлы классов в родной для конкретной платформы код.

Поскольку существует реализация JVM для многих операционных систем, один и тот же файл.class может быть запущен на Microsoft Windows, UNIX, Mac OS, Linux и других.

Так как виртуальные машины Java доступны для многих аппаратных и программных платформ, Java может рассматриваться и как связующее программное обеспечение, и как самостоятельная платформа. Использование одного байт-кода для многих платформ позволяет описать Java как «скомпилировано однажды, запускается везде» (compile once, run anywhere). JVM может также использоваться для выполнения программ, написанных на других языках программирования (Ada, JavaScript, Python, Ruby и Scala). Например, исходный код на языке Ada может быть откомпилирован в байт-код Java, который затем может выполниться с помощью JVM.

Платформа Java распространяется в двух вариантах – JRE (Java Runtime Environment) и JDK (Java Development Kit).

JRE – это минимальная реализация виртуальной машины, необходимая для исполнения Java-приложений. Если пользователь хочет только запускать программы, это именно то, что ему нужно.

JDK (называют еще SDK – Software Development Kit) – базовое средство разработки приложений. JRE включен в состав JDK.

Сравнение C++ и Java

Java представляет собой новую точку отсчета в программном обеспечении. Разработчики языка взяли за основу С++, затем методично удалили из него то, что не являются абсолютно необходимым, чаще мешает программисту, чем облегчают его задачу. В то же время в языке Java полностью сохранен «дух» программирования на С++, опытным С++ программистам потребуется одна-две недели на освоение самого языка, а огромный объем программного обеспечения, уже созданного с использованием С++, может быть адаптирован под новый язык относительно легко.

Отметим основные особенности Java:

    1. Одно из основных преимуществ языка Java – независимость от платформы, на которой выполняются программы: один и тот же код можно запускать под управлением операционных систем Windows, Solaris, Linux, Machintosh и др. Это действительно необходимо, когда программы загружаются через Интернет для последующего выполнения под управлением разных операционных систем.
    2. Отдельные файлы классов Java (файлы .class или .jar) позволяют более эффективно использовать память, нежели большие библиотеки классов (типа MFC), потому что файлы класса могут быть связаны загрузчиком на основании управляемого запроса.
    3. Встроенная поддержка многопоточности снабжает программистов Java мощным инструментом для улучшения интерактивной работы графических приложений. Если наше приложение должно выполнять мультипликацию и музыку игры при прокрутке страницы и загрузку текстового файла с сервера, то многопоточность – это способ быстро реализовать поставленную задачу.
    4. В Java отсутствуют определённые низкоуровневые конструкции, такие как указатели, также Java имеет очень простую модель памяти, где каждый объект расположен в куче и все переменные объектного типа являются ссылками. Декларация массива также резервирует только ссылку на массив, а не место под реальный объект. Содержимое строк и массивов доступно только по индексам.
    5. Исчезла необходимость явно управлять памятью вызовами функции free или операторами delete, поскольку в систему встроен автоматический сборщик мусора. Он освобождает память, на которую больше нет ссылок.
    6. Полностью исключена препроцессорная обработка. Операция включения в программу файлов-заголовков с описаниями классов (include) заменена на операцию import, которая читает подготовленные бинарные файлы с описанием классов.
    7. Java – полностью объектно-ориентированный язык, даже в большей степени, чем C++. Функции и процедуры, не привязанные к контексту какого-либо объекта, больше не присутствуют в системе. В ситуации, когда функция логически не привязана к определенному экземпляру класса, она может быть создана как метод самого класса (так называемая статическая функция). В этом смысле Java чисто объектно-ориентированная система.
    8. Исключено множественное наследование. Оно заменено новым понятием – интерфейсом, Интерфейс дает программисту почти все, что тот может получить от множественного наследования, избегая при этом сложностей, возникающих при управлении иерархиями классов.
    9. Опыт использования перегруженных операторов в С++ показывает, что они имеют смысл в довольно ограниченном наборе ситуаций. С другой стороны, злоупотребление этим свойством может сделать программу абсолютно непонятной. Единственное «встроенное» в язык Java исключение – возможность использования оператора «+» для склеивания строк.

Пакеты классов

Java package (пакет Java) – механизм, позволяющий организовать Java классы в пространстве имен. Java пакеты могут содержаться в сжатом виде в JAR файлах. Обычно в пакеты объединяют классы одной и той же категории, либо предоставляющие сходную функциональность. Основные пакеты:

java.lang – базовая функциональность языка и основные типы;

java.math – математические операции;

java.net – операции с сетями, сокетами, DNS-запросами;

java.awt – иерархия основных пакетов для GUI;

Структура класса

Как и в С++ класс определяет новый тип данных, который можно использовать для создания объектов этого класса.

Классы образуются в Java при помощи ключевого слова class и наследуются (от не более чем одного класса) при помощи указания ключевого слова extends, а также могут реализовывать (implement) один или несколько интерфейсов. Синтаксис объявления класса:

модификаторы class имя_создаваемого_класса extends имя­_cупер_класса implements имя_интерфейса1, имя_интерфейса2, …, имя_интерфейсаN { тело класса}

Модификаторы класса:

  • public – доступны для использования или расширения любым объектом вне зависимости от пакета. Public классы должны храниться в файлах с именем: <имя_класса>.java. Для остальных классов такое соответствие рекомендуемое.
  • friendly – модификатор класса по умолчанию (если модификатор не определен явно для класса). Такой класс доступен (public) только для объектов находящихся в том же самом пакете, вне пакета, он выступает как private.

  • abstract – класс, для которого нельзя создавать объекты. Такие классы используются для производных классов. Подробнее см. ниже.
  • final – класс не может иметь подклассов. Обычно такие классы разрабатываются и используются для организации определенного стандарта реализации.

Абстрактные классы

Абстрактным называется класс, на основе которого не могут создаваться объекты. При этом наследники класса могут быть не абстрактными, на их основе можно создавать объекты. Рассмотрим пример использования абстрактного класса:

abstract class A {
int p1;
A() {
p1 = 1;
}
void print() {
System.out.println(p1);
}
}

class B extends A {
}

public class Main {
public static void main(String[] args) {
A ob1;
ob1 = new A(); // ошибка 
B ob2 = new B(); // будет вызван конструктор из A
ob2.print();
}
}

Абстрактные методы

Абстрактным называется метод, который не имеет реализации в данном классе. То есть определение у абстрактного метода отсутствует, одно лишь объявление. Перед именем метода указывается при этом модификатор abstract.

Какой смысл в создании метода без реализации? Ведь его нельзя будет использовать. Для объектов того класса, где метод описан – нет, но вот если унаследовать класс и в потомках переопределить метод, задав там его описание, то для объектов классов потомков метод можно будет вызывать.

Чтобы исключить возможность использования абстрактного метода, в Java введено следующее требование: класс, имеющий хоть один абстрактный метод, обязан быть абстрактным классом.

Когда же уместно использовать абстрактные методы и классы? Ниже рассмотрены два примера. Слева рассмотрен пример иерархии классов домашних животных, где нет ни абстрактных классов, ни абстрактных методов. Справа рассмотрен пример с участием абстрактного класса и абстрактного метода.

Пример 1 Пример 2
class Pet {
String name;
int age;
boolean hungry;
void voice() {
}
void food() {
hungry = false;
}
}

class Snake extends Pet {
double length;
void voice() {
System.out.println("Шшш-ш-ш");
}
}

class Dog extends Pet {
void voice() {
System.out.println("Гав-гав");
}
}

class PatrolDog extends Dog {
void voice() {
System.out.println("Ррр-р-р");
}
}

class Cat extends Pet {
void voice() {
System.out.println("Мяу-мяу");
}
}

class Fish extends Pet {
}

public class Main {
public static void main(String[] args) {
Pet zorka = new Pet();
zorka.food();
Fish nemo = new Fish();
nemo.voice();
}
}
abstract class Pet {
String name;
int age;
boolean hungry;
abstract void voice();
void food() {
hungry = false;
}
}

class Snake extends Pet {
double length;
void voice() {
System.out.println("Шшш-ш-ш");
}
}

class Dog extends Pet {
void voice() {
System.out.println("Гав-гав");
}
}

class PatrolDog extends Dog {
void voice() {
System.out.println("Ррр-р-р");
}
}

class Cat extends Pet {
void voice() {
System.out.println("Мяу-мяу");
}
}

class Fish extends Pet {
void voice() {
}
}

public class Main {
public static void main(String[] args) {
// ошибка:
Pet zorka = new Pet();
Fish nemo = new Fish();
nemo.voice();
}
}


Чем отличается виртуальный метод в С++ от абстрактного в Java? Виртуальный метод может предоставлять реализации в базовом классе, а абстрактный метод только лишь декларируется в базовом классе.

Зачем писать в абстрактном классе абстрактный метод, если мы в классе потомке пишем его еще раз (реализацию)? В абстрактном классе вы диктуете условия, мол, каждый объект данного типа должен иметь этот метод, мол, не забудьте реализовать его.

Статические члены класса

Модификатор static может использоваться переменной или методом или блоком кода. Пример:

class StaticDemo
{
static int x = 0;
StaticDemo()
{
x++;
}
}

Переменная x является статической. Статические переменную или метод также называют переменной (методом) класса, так как она принадлежит непосредственно классу, а не объекту этого класса. Это означает, что есть только одна переменная х, независимо от того, сколько объектов класса StaticDemo могло бы существовать. К статическим переменным (методам) можно обращаться как через имя любого объекта класса, так и через имя класса.

Пример:

StaticDemo d1 = new StaticDemo();
StaticDemo d2 = new StaticDemo();
d1.x = 100;
d2.x = 200;
testVariable = d1.x;

В вышеупомянутом коде, если бы мы не знали, что х является статической переменной, мы могли бы подумать, что testVariable=100. На самом деле testVariable=200, поскольку d1.x и d2.x обращаются к одной и той же (статической) переменной. Лучший способ обратиться к статической переменной – через имя класса. Следующий код идентичен коду выше:

StaticDemo d1 = new StaticDemo();
StaticDemo d2 = new StaticDemo();
StaticDemo.x = 100;
StaticDemo.x = 200;
testVariable = StaticDemo.x;

Некоторые правила относительно статических методов и переменных:

  1. Методы, объявленные как static, могут работать только с переменными и методами, объявленными как static. При этом нестатические методы могут работать как с обычными, так и со статическими переменными.
  2. В статических методах нельзя ссылаться на this (ссылка на объект, который вызвал метод).
  3. Статический метод не может быть переопределен, чтобы быть нестатическим.

Внутренние классы

Внутренний, или вложенный класс (англ. inner class) – класс, целиком определённый внутри другого класса. В Java существуют 4 типа внутренних классов:

Статические внутренние классы. Определяются внутри основного класса и обозначаются ключевым словом static. Не имеют доступа к членам внешнего класса за исключением статических. Может содержать статические поля, методы и классы, в отличие от других типов внутренних классов. Пример:

class OuterClass
{
   public OuterClass() {}
   private int outerField;
   static int staticOuterField;
   static class InnerClass
   {
      int getOuterField()
      {
         return OuterClass.this.outerField; // код ошибочен
      }
      int getStaticOuterField()
      {
         return OuterClass.staticOuterField; // код корректен
      }
   }
}

Внутренние классы. Определяются внутри основного класса. В отличие от статических внутренних классов, имеют доступ к членам внешнего класса. Не могут содержать (но могут наследовать) определение статических полей, методов и классов (кроме констант). Пример:

 class OuterClass{
   public OuterClass(){}
   private int outerField;
   class InnerClass{
         int getOuterField(){
           return OuterClass.this.outerField; // код корректен
         }
    };
};

Локальные классы. Определяются внутри методов основного класса. Могут быть использованы только внутри этих методов. Имеют доступ к членам внешнего класса. Имеют доступ, как к локальным переменным, так и к параметрам метода при одном условии – переменные и параметры, используемые локальным классом, должны быть задекларированы final. Не могут содержать (но могут наследовать) определение статических полей, методов и классов (кроме констант). Пример: 

class OuterClass
{
public OuterClass(){}
private int outerField;
InnerClass inner; // код ошибочен
void methodWithLocalClass (final int parameter)
{
InnerClass innerInsideMehod; // код корректен
int notFinal = 0;
class InnerClass
{
int getOuterField()
{
return OuterClass.this.outerField; // код корректен
}
notFinal++; // код ошибочен
int getParameter()
{
return parameter; // код корректен         }
};
}
};


Анонимные (безымянные) классы. Определяются внутри методов основного класса. Могут быть использованы только внутри этих методов. В отличие от локальных классов, анонимные классы не имеют названия. Главное требование к анонимному классу – он должен наследовать существующий класс или реализовывать существующий интерфейс. Не могут содержать (но могут наследовать) определение статических полей, методов и классов (кроме констант).

Интерфейсы

Интерфейс – это конструкция, в рамках которой могут описываться только абстрактные публичные (abstract public) методы и статические константные свойства (final static).  Так же, как и на основе абстрактных классов, на основе интерфейсов нельзя порождать объекты.

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

interface Instruments {
final static String key = "До мажор";
public void play();
}

class Drum implements Instruments {
public void play() {
System.out.println("бум бац бац бум бац бац");
}
}

class Guitar implements Instruments {
public void play() {
System.out.println("до ми соль до ре до");
}
}

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

Отличия абстрактного класса и интерфейса:

  1. В абстрактном классе можно написать реализацию метода, в интерфейсе – нет.
  2. Абстрактный класс может иметь конструктор, а интерфейс нет.
  3. Класс может иметь родителем только один класс (абстрактный или нет), но несколько интерфейсов.

Сетевые приложения

Введение

Cетевые соединения основаны на трех основных понятиях: клиент, сервер и протокол.

Сервер постоянно находится в состоянии ожидания, он прослушивает (listen) сеть, ожидая запросов от клиентов. Клиент связывается с сервером и посылает ему запрос (request) с описанием услуги, например, имя нужного файла. Сервер обрабатывает запрос и отправляет ответ (response), в нашем примере, файл, или сообщение о невозможности оказать услугу.

Каждый компьютер или другое устройство, подключенное к объединению сетей Internet, так называемый хост (Host), получает уникальный номер — четырехбайтовое целое число, называемое IP-адресом (IP-address).

Запросы клиента и ответы сервера формируются по строгим правилам, совокупность которых образует протокол интернет связи.

В современных глобальных сетях принят стандарт из четырех уровней протоколов, называемый стеком протоколов TCP/IP. Название TCP/IP происходит от двух наиважнейших протоколов семейства, которые были разработаны и описаны первыми в этом стандарте.

Порты

IP адреса не достаточно для уникальной идентификации сервера, так как многие сервера могут существовать на одной машине. Каждая IP машина также содержит порты, и когда вы устанавливаете клиента или сервер, вы должны выбрать порт, через который и клиент, и сервер согласны соединиться.  Порт – это не физическое расположение в машине, а программная абстракция.

Сокеты

Слово Socket в переводе на русский язык означает «гнездо». Это название образовалось по аналогии с гнёздами (разъемами) на аппаратуре, с которыми стыкуются разъемы. Сокет представляет собой программную конструкцию (объект), которая определяет конечную точку соединения.

Сокеты позволяют приложениям рассматривать сетевые подключения как файлы, и программа может читать из сокета или писать в сокет, как она делает это с файлом. Как и файл сокет имеет адрес – это комбинация IP-адреса и порта. Например,  64.104.137.58:80, где 64.104.137.58 – IP-адрес и 80 – порт.

Тестирование программ без сети

Использование IP-адреса 127.0.0.1 позволяет устанавливать соединение и передавать информацию для программ-серверов, работающих на том же компьютере, что и программа-клиент. Этому адресу однозначно сопоставляется мнемоническое имя компьютера «localhost».

Обычно localhost используется для тестирования интернет проектов на одном компьютере (без подключения к сети). Это удобно, если вы пишите программы, которые еще не достаточно стабильны и не могут быть выложены в сеть.

Многопоточность

Процесс — независимо работающая программа. Каждый процесс имеет хотя бы один выполняющийся поток. Тот поток, с которого начинается выполнение программы, называется главным. В языке Java, после создания процесса, выполнение главного потока начинается с метода main(). Затем, по мере необходимости, в заданных программистом местах, и при выполнении заданных им же условий, запускаются другие, побочные потоки.

В многопоточной программе главный поток должен заканчивать выполнение последним. Если он заканчивается прежде, чем завершится побочный поток, то исполнительная система Java может зависнуть.

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

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

Управляемые событиями UI-инструментарии, например, AWT и Swing, содержат поток, который обрабатывает такие события пользовательского интерфейса, как нажатие клавиш и щелчки мышью.

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

Потоки ввода-вывода

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

Понятие потока также позволяет отвлечься от особенностей конкретных устройств ввода/вывода. Считается, что в программу идет входной поток (InputStream) символов Unicode или просто байтов. Из программы выводится выходной поток (OutputStream). При этом неважно, куда направлен поток: на консоль, на принтер, в файл или в сеть, методы write  и print  ничего об этом не знают.

Операции ввода/вывода по сравнению с операциями в оперативной памяти выполняются очень медленно. Для компенсации в оперативной памяти выделяется некоторая промежуточная область — буфер, в которой постепенно накапливается информация. Когда буфер заполнен, его содержимое быстро переносится процессором, буфер очищается и снова заполняется информацией.

Управление потоками

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

Управление потоками обеспечивается планированием последовательности из пяти возможных состояний потоков:

    1. Новый (New). После создания экземпляра потока, он находится в состоянии “Новый” до тех пор, пока не вызван метод start(). В этом состоянии, он еще не считается живым.
    2. Работоспособный (Runnable). Поток переходит в состояние “Работоспособный”, когда вызывается метод start(). Поток может перейти в это состояние также из состояния “Работающий” или из состояния “Блокирован”. Когда поток находится в этом состоянии, он считается живым.
    3. Работающий (Running). Поток переходит из состояния “Работоспособный” в состояние “Работающий”, когда Планировщик потоков выбирает его из runnable pool как работающий в данный момент.
    4. Ожидающий (Waiting) / Заблокированный (blocked) / Спящий(sleeping). Эти состояния характеризуют поток как не готовый к работе. Эти состояния объединены, т.к. все они имеют общую черту – поток еще жив (alive), но в настоящее время не может быть выполнен. Другими словами, поток уже не runnable, но он может вернуться в это состояние. Поток может быть заблокирован – это может означать, например, что он ждет освобождения каких-нибудь ресурсов, и разблокируется когда эти ресурсы освободятся. Поток может спать потому, что в его методе run() встретился метод sleep(). Просыпается он тогда, когда время его сна истекло (таймер). Поток может находиться в состоянии ожидания, если в его методе run() встретится метод wait(). Вызов notify() или notifyAll() может перевести поток из состояния ожидания в работоспособное состояние.
    5. Мёртвый (Dead). Поток считается мёртвым, когда его метод run() полностью выполнен. Мёртвый поток не может перейти ни в какое другое состояние, даже если для него вызван метод start().

Исключения и их обработка

Исключениями или исключительными ситуациями (состояниями) называются ошибки, возникшие в программе во время её работы. Откомпилируем и запустим такую программу:

class Main {
public static void main(String[] args) {
int a = 4;
System.out.println(a/0);
}
}

После запуска на консоль будет выведено следующее сообщение:

Exception in thread "main" java.lang.ArithmeticException:
/ by zero at Main.main(Main.java:4)

Из сообщения виден класс случившегося исключения — ArithmeticException.

Ряд исключений можно и нужно обрабатывать в программе. Обработка исключения может быть произведена с помощью операторов try…catch.

class Main {
public static void main(String[] args) {
int a = 4;
try {
System.out.println(a/0);
}
catch (ArithmeticException e) {
System.out.println("Недопустимая операция");
}
}
}

Теперь вместо стандартного сообщения об ошибке будет выполняться блок catch, параметром которого является объект e соответствующего исключению класса. В блок try при этом помещается тот фрагмент программы, где потенциально может возникнуть исключение.

Необязательным добавлением к блокам try…catch может быть блок finally. Помещенные в него команды будут выполняться в любом случае, вне зависимости от того, произошло ли исключение или нет.

Все современные реализации языка Java придерживаются принципа обработки или объявления исключений. Он гласит, что код, который потенциально может сгенерировать контролируемое исключение, должен либо быть заключен в блок try/catch/, либо мы должны объявить, что наш метод может выбросить такое исключение (после ключевого слова throws, после имени метода).

Когда нам следует обрабатывать исключения, а когда объявлять? В основном, метод должен передавать исключение вызвавшей программе только в том случае, если он сам не обладает информацией, достаточной для обработки исключения.

 

Автор: Николай Свирневский

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *