8 Варианты инициализации объектов, примеры. (отложенная инициализация (null), инициализация в месте объявления)

 

Вы можете выбрать подход создания метода, называемого initialize( ) для каждого созданного вами класса. Имя является подсказкой к тому, что он должен быть вызван перед использованием объекта. К сожалению, это означает, что пользователь должен помнить о вызове метода. В Java разработчик классов может гарантировать инициализацию каждого объекта, обеспечив специальный метод, называемый конструктором. Если класс имеет конструктор, Java автоматически вызывает конструктор, когда создается объект, прежде чем пользователь сможет взять его в руки. Поэтому инициализация гарантируется.

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

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

Конструктор является необычным типом метода, поскольку он не имеет возвращаемого значения.

Конструктор по умолчанию является единственным конструктором без аргументов, который используется для создания объекта”. Если вы создаете класс, который не имеет конструкторов, компилятор автоматически создаст конструктор по умолчанию вместо вас. Если вы объявите класс явно в нем не указав ни одного конструктора, компилятор создает новый объект и вызывает конструктор по умолчанию, даже не смотря на то, что он не был явно определен. Без этого мы не имели бы метода построения нашего объекта. Однако если вы определили любой конструктор (с аргументами или без них) компилятор не будет синтезировать конструктор по умолчанию за вас.

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

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

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

 

Примеры:

Инициализация в месте объявления:

class Measurement {

  boolean b = true;

  char c = 'x';

  byte B = 47;

  short s = 0xff;

  int i = 999;

  long l = 1;

  float f = 3.14f;

  double d = 3.14159;

  //. . .

 


 

Отложенная инициализация:

class Measurement {

  Depth o = null;

  boolean b = true;

  // . . .

или

class Measurement {

  Depth o;

  boolean b = true;

  // . . .

Тогда отложенная инициализация произойдет например в конструкторе:

 

Measurement()

{

                  o =  new Depth();

                  // ...

};

Если вы не передадите o начальное значение и, тем не менее, попробуете использовать ее, вы получите ошибку времени выполнения

 

9 Исключения. Обработка исключений. Операторы try, catch и finally

 

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

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

Стоит отметить, что обработка исключения - это не особенность объектно-ориентированного языка, хотя в объектно-ориентированных языках исключение обычно представляется объектом. Обработка исключений появилась раньше объектно-ориентированных языков.

Простым примером выбрасывания исключения является рассмотрение ссылки на объект, называемой t. Возможно, что вы можете передать ссылку, которая не была инициализирована, так что вы можете пожелать проверить ее перед вызовом метода, использующего эту ссылку на объект. Вы можете послать информацию об ошибке в больший контекст с помощью создания объекта, представляющего вашу информацию и “выбросить” его из вашего контекста. Это называется выбрасыванием исключения. Это выглядит так:

if(t == null)

  throw new NullPointerException();

Здесь выбрасывается исключение, которое позволяет вам — в текущем контексте — отказаться от ответственности, думая о будущем решении.

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

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

Блок try

Если вы находитесь внутри метода, и вы выбросили исключение (или другой метод, вызванный вами внутри этого метода, выбросил исключение), такой метод перейдет в процесс бросания. Если вы не хотите быть выброшенными из метода, вы можете установить специальный блок внутри такого метода для поимки исключения. Он называется блок проверки, потому что вы “проверяете” ваши различные методы, вызываемые здесь. Блок проверки - это обычный блок, которому предшествует ключевое слово try:

try {

  // Код, который может сгенерировать исключение

}

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

Конечно, выбрасывание исключения должно где-то заканчиваться. Это “место” - обработчик исключения, и есть один обработчик для каждого типа исключения, которые вы хотите поймать. Обработчики исключений следуют сразу за блоком проверки и объявляются ключевым словом catch:

try {

  // Код, который может сгенерировать исключение

} catch(Type1 id1) {

  // Обработка исключения Type1

} catch(Type2 id2) {

  // Обработка исключения Type2

} catch(Type3 id3) {

  // Обработка исключения Type3

}

 

// и так далее...

Каждое catch предложение (обработчик исключения) как меленький метод, который принимает один и только один аргумент определенного типа. Идентификаторы (id1, id2 и так далее) могут быть использованы внутри обработчика, как аргумент метода. Иногда вы нигде не используете идентификатор, потому что тип исключения дает вам достаточно информации, чтобы разобраться с исключением, но идентификатор все равно должен быть.

Обработчики должны располагаться прямо после блока проверки. Если выброшено исключение, механизм обработки исключений идет охотится за первым обработчиком с таким аргументом, тип которого совпадает с типом исключения. Затем происходит вход в предложение catch, и рассматривается обработка исключения. Поиск обработчика, после остановки на предложении catch, заканчивается. Выполняется только совпавшее предложение catch; это не как инструкция switch, в которой вам необходим break после каждого case, чтобы предотвратить выполнение оставшейся части.

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

Часто есть такие места кода, которые вы хотите выполнить независимо от того, было ли выброшено исключение в блоке try, или нет. Это обычно относится к некоторым операциям, отличным от утилизации памяти (так как об этом заботится сборщик мусора). Для достижения этого эффекта вы используете предложение finally [53] в конце списка всех обработчиков исключений. Полная картина секции обработки исключений выглядит так:

try {

  // Критическая область: Опасная активность,

  // при которой могут быть выброшены A, B или C

} catch(A a1) {

  // Обработчик ситуации A

} catch(B b1) {

  // Обработчик ситуации B

} catch(C c1) {

  // Обработчик ситуации C

} finally {

  // Действия, совершаемые всякий раз

}

В языках без сборщика мусора и без автоматического вызова деструктора [54], finally очень важно, потому что оно позволяет программисту гарантировать освобождение памяти независимо от того, что случилось в блоке try. Но Java имеет сборщик мусора, так что освобождение памяти, фактически, не является проблемой. Также, язык не имеет деструкторов для вызова. Так что, когда вам нужно использовать finally в Java?

finally необходимо, когда вам нужно что-то установить, отличное от блока памяти, в его оригинальное состояние. Это очистка определенного вида, такое как открытие файла или сетевого соединения, рисование на экране или даже переключение во внешний мир

Используйте исключения для:

Исправления проблем и нового вызова метода, который явился причиной исключения.

Исправления вещей и продолжения без повторной попытки метода.

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

Выполнения того, что вы можете в текущем контексте и повторного выброса того же исключения в более старший контекст.

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

Прекращения программы.

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

Создать более безопасные библиотеки и программы. (Для краткосрочной инвестиции - для отладки - и для долгосрочной инвестиции (Для устойчивости приложения

10 Сетевое взаимодействие в Java. Варианты и примеры использования. Низкоуровневое взаимодействие.

 

Проектировщики сетевой библиотеки Java сделали ее похожей на чтение и запись файлов, за исключением того, что этот “файл” находится на удаленной машине и удаленная машина может в точности определить, что нужно сделать с информацией, которую Вы посылаете либо требуете. Насколько возможно, внутренние детали сетевого общения абстрагированы и обрабатываются внутри JVM и локальной машины, на которой установлена среда Java. Модель программирования такая же, как Вы используете в файле; в действительности, Вы просто окутываете сетевое соединение (“сокет”) потоковым объектом, и Вы действуете используя те же вызовы методов, что и с другими потоковыми объектами. К тому же, встроенная в Java множественность процессов очень удобна при общении в сети: установка нескольких соединений сразу.

 

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

Это достигается с помощью IP (Internet Protocol) адресации, которая может иметь две формы:

Обычная DNS (Domain Name System) форма. Мое доменное имя - bruceeckel.com, и если у меня есть компьютер с именем Opus в моем домене, его доменное имя может быть Opus.bruceeckel.com. Это в точности тип имени, который Вы используете при отсылке почты, и очень часто включается в WWW адрес.

С другой стороны, Вы можете использовать “четырехточечную” форма, в которой четыре номера разделены точками, например 123.255.28.120.

В обоих случаях, IP адрес представляется как 32 битное значение[72] (каждое число из 4-х не может превышать 255), и Вы можете получить специальный объект Java для представления этого номера из формы, представленной выше с помощью метода static InetAddress.getByName( ) в пакете java.net. Результат это объект типа InetAddress, который Вы можете использовать для создания “сокета”.

Простой пример использования InetAddress.getByName( ), показывает что происходит, когда у Вас есть провайдер интернет по коммутируемым соединениям (ISP). Каждый раз, когда Вы дозваниваетесь, Вам присваивается временный IP. Но, пока Вы соединены, Ваш IP адрес ничем не отличается от любого другого IP адреса в интернет. Если кто-то подключится к Вашей машине, используя Ваш IP адрес, он сможет подключиться также к Web или FTP серверу, который запущен на Вашей машине. Конечно, сначала необходимо узнать Ваш IP адрес, а т.к. при каждом соединении присваивается новый адрес, как Вы сможете его узнать?

InetAddress a =

      InetAddress.getByName(args[0]);

    System.out.println(a);

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

Создатели интернет создали специальный адрес, называемый localhost, “локальная петля”, который является IP адресом для тестирования без наличия сети. Обычный способ получения этого адреса в Java это:

InetAddress addr = InetAddress.getByName(null);

Если Вы ставите параметр null в метод getByName( ), то, по умолчанию используется localhost. InetAddress это то, что Вы используете для ссылки на конкретную машину, и Вы должны предоставлять это, перед тем как продолжить дальнейшие действия. Вы не можете манипулировать содержанием InetAddress (но Вы можете распечатать его, как Вы увидите в следующем примере). Единственный способ создать InetAddress - это использовать один из перегруженных статических методов getByName( ) (который Вы обычно используете), getAllByName( ), либо getLocalHost( ).

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

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

Системные службы резервируют номера портов с 1 по 1024, так что Вы не должны использовать ни один из портов, который Вы знаете, что он используется. Первый выбор порта для примеров в этой книге это порт номер 8080 (в память почтенного и древнего 8-битного чипа Intel 8080 на моем первом компьютере, с операционной системой CP/M).

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

В Java, Вы создаете сокет для установления соединения с другой машиной, затем Вы получаете InputStream и OutputStream (либо с помощью соответствующих преобразователей, Reader и Writer) из сокета, который соответствующим образом представляет соединение, как потоковый объект ввода вывода. Есть два класса сокетов, основанных на потоках: ServerSocket - используется сервером, чтобы “слушать” входящие соединения и Socket - используется клиентом для инициирования соединения. Как только клиент создает соединение по сокету, ServerSocket возвращает (с помощью метода accept( ) ) соответствующий объект Socket по которому будет происходить связь на стороне сервера. Начиная с этого момента, у Вас появляется соединение Socket к Socket, и Вы считаете эти соединения одинаковыми, потому что они действительно одинаковые. В результате, Вы используете методы getInputStream( ) и getOutputStream( ) для создания соответствующих объектов InputStream и OutputStream из каждого Socket. Они должны быть обернуты внутри буферов и форматирующих классов, как и любой другой потоковый объект, описанный в Главе 11.

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

Основная схема это создание единичного объекта ServerSocket в серверной части и вызвать метод accept( ) для ожидания нового соединения. Когда accept( ) возвращает управления, Вы берете возвращенный Socket и используете его для создания новой нити(потока), чьей работой является обслуживание этого клиента. Затем Вы вызываете метод accept( ) снова, для ожидания нового клиента.

Примеры, которые Вы увидели используют протоколTransmission Control Protocol (TCP, также известный как сокеты основанные на потоках), который создан для исключительной надежности и гарантирует, что данные будут доставлены туда, куда необходимо. Он позволяет организовать повторную передачу потерянных данных, он предоставляет возможность отсылки отдельных частей через разные маршрутизаторы, в случает если один из них выйдет из строя, и байты будут приняты именно в том порядке, в ктором они были посланы. Весь этот контроль и надежность имеет цену: в TCP высокие накладные расходы.

Существует второй протокол, называемый User Datagram Protocol (UDP), который не гарантирует, что пакеты будут доставлены и не гарантирует доставки в том порядке, в котором они были посланы. Он называется “ненадежным протоколом” (TCP это “надежный протокол”), и это звучит не очень хорошо, однако он намного быстрее и потому может быть полезным. Существуют некоторые приложения, такие как аудио сигналы, в которых потеря нескольких пакетов не очень не имеет большого значения, но скорость очень важна. Либо представьте сервер предоставляющий информацию о времени, где действительно не имеет значени, если одно из сообщений потеряется. Также, некоторые приложения могут посылать UDP сообщения на сервер, а затем, при отсутствии отклика в течение некоторого времени, считать, что сообщение было потеряно.

 

Hosted by uCoz