Объявление

Уважаемые посетители! Если вы обнаружили в каком-нибудь слове ошибку!
Выделите это слово и нажмите Ctrl+Enter одновременно!
Заранее спасибо за сотрудничество!

 Глава №2

Наконец удалось закончить еще одну статью из моего цикла по изучению Java. Это маленький повод для гордости, что я все же не забросил пока это дело. Так, что всех кому это интересно, милости просим под кат =)

 Данная глава дает преставление о таких вещах как:

И так приступим =)

Полностью переписывать главу из книги я не буду, хотя моя супруга иногда говорит, что именно так я и делаю =) Но все же вкратце упомянуть обо всем стоит!

 

Что такое простые типы данных.

Или как их еще называют "примитивные типы данных", это:

  • byte
  • short
  • int
  • long
  • float
  • double
  • char
  • boolean
Список типов переменных приведен в порядке увеличения диапазона возможных хранимых в них данных (от меньшего к большему), за исключением двух последних (о них я упомяну отдельно).

Типы данных от byte до long – это целочисленные типы данных. Учитывая, что язык Java является строго типизированным языком программирования, то это те типы в которых могут содержатся только целые числа (без дробной части). Диапазон допустимых значений у каждой из них свой:

Тип Разрядность в битах Диапазон допустимых значений
byte 8 От -128 до 127
short 16 От -32 768 до 32 767
int 32 От -214 748 3648 до 214 748 3647
long 64 От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807

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

Размер выделенной памяти (разрядность) для того или иного типа данных может быть каким угодно, а вот диапазон хранимых в ней значений всегда будет одним и тем же. Именно благодаря этому в Java и обеспечивается кроссплатформенность!

Типы данных float и double – это в которых можно хранить данные с дробной часть (в школе их называли дробями). Размеры выделяемой для них памяти по умолчанию 32 и 64 бита.

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

Присваивается же значение переменной такого типа в следующем формате:


char ch;
ch = 'a';

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

Поэтому этот тип считают целочисленным. Но это только формально и на самом деле если переменной этого типа присвоить значение какого-либо числа, например:


ch = 90;

То при выводе на экран этой переменной, выведется не число 90, а символ "Z" которому соответствует порядковый номер 90 в таблице Unicode, точнее ASCII которая входит в ее состав!

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

Можно было бы уделить больше вниманию этой теме, но думаю пока это не нужно.

Ссылки по теме:

Следующим типом со своими плюшками является тип boolean, он может содержать только два значения true или false. Но его особенность заключается не в этом, а в том, что он является логическим типом данных!

Что это значит?!

А то, что любая операция сравнения или выполнения логического оператора типа if будет содержать в себе булево значение!

Логический тип данных, или булев тип, или булевый тип (от англ. Boolean или logical data type) — примитивный тип данных в информатике, принимающий два возможных значения, иногда называемых истиной (true) и ложью (false). Присутствует в подавляющем большинстве языков программирования как самостоятельная сущность или реализуется через численный тип данных. В некоторых языках программирования за значение истина полагается 1, за значение ложь — 0.

Примечание: Из-за значений 0 и 1 не следует его приписывать к целочисленным типам данных, это логический тип данных!

 

Литералы

Об этом я не могу ни сказать =)) ни слова без улыбки. Как говорит одна пословица:

Шкатулка просто открывалась.

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

Но (драматическая пауза, барабанная дробь и все такое) литералы, это всего лишь значение переменных. Да именно так! Это то значение, которое мы присваиваем переменным =)

Единственное на, что стоит обратить внимание, так это на то, что строковый литерал – это набор символов, заключенный в двойные кавычки. Например:


System.out.println("Какой-то текст");

Где фраза "Какой-то текст" и будет строковым литералом.

Последнее, что я здесь добавлю, так это шпаргалку по управляющим последовательностям символов для себя умного =)

Управляющая последовательность Описание
\' Вывод одинарной кавычки
\" Вывод двойной кавычки
\\ Вывод обратной косой черты
\r Возврат каретки
\n Перевод на новую строку
\f Перевод на новую страницу
\t Отступ в виде табуляции
\d Возврат на одну позицию
\ddd Восьмеричная константа, где ddd – восьмеричное число
\uxxxx Шестнадцатиричная константа, где xxxx – шестнадцатиричное число

Последовательность этих символов очень широко применяется при выводе информации на экран (как можно понять из самой таблицы) для ее форматирования.

 

Инициализация переменных.

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

Условная форма записи инициализации переменной (по сути она же и операция присваивания значения, о чем немного позже будет упомянуто) выглядит так:

тип переменная = значение;

Здесь "значение" и будет той самой точкой инициализации переменной или переменных.

По мимо такого типа инициализации есть еще "динамическая инициализация", это когда свое значение переменная получает в результате кокой то операции над числами, символами или другими переменными. Например (динамическая инициализация переменных):


Class DynInit {
    public static void main(String[] args) {

        double radius = 4, height = 5;

        // Переменная volume инициализируется динамически во время выполнения программы
        double volume = 3.1416 * radius * radius * height;
        
        System.out.println("Объем: " + volume);
    }
}

 

Область действия переменных

Это то место в коде где данная переменная будет выполнять свои функции и хранить в себе какое-то значение. Когда же эта область закончится, то переменная перестанет существовать и будет стерта из памяти, и как уже можно догадаться, посмотреть или получить ее значение будет невозможно! И при попытке это сделать будет выдана ошибка:


Error:(15, 9) java: cannot find symbol
                symbol:   variable a

location: class com.gmail.vitaliy1984.Test

В котором говорится, что переменной которой присваивается значение не существует!

Так что же это за такое таинственное место?

Все довольно просто – это то место в коде, где происходят какие-либо действия с переменными. Ну, например, так:


class ScopeDemo {
    public static void main(String[] args) {

        int x; // Эта переменная доступная для всего кода в методе main
        x = 10;
        
        if (x == 10) {  // Начало новой области действия
            int y = 20; // Эта переменная доступна только в даном блоке

            //Обе переменные "x" и "y" доступны в данном блоке
            System.out.println("x and y: " + x + " " + y);
            
            x = y * 2;
        }

        //y = 100; // Ошибка! В этом месте переменная "y" недоступна

        // А переменная "x" по-прежнему доступна
        System.out.println("x is " + x);
    }
}

Уж если совсем проще говоря, то переменная будет действовать и жить от первой перед ней фигурной скобки "{" и заканчивая такой же ее закрывающей "}". За их пределами этой переменной просто не существует физически!

Исходя из этого, если переменная была объявлена к примеру, в начале метода (еще одно страшное слово, но оно уже примелькалось и в книге об этом тоже рассказывается), то повторное ее объявление, например, так:


public class Test {
    public static void main(String[] args) {
        
        int i, a;

        for (i = 0; i < 10; i++) {

            int a;
            a = 5 + i;

            System.out.println(a);
        }
    }
}

приведет к ошибке


Error:(8, 17) java: variable a is already defined in method main(java.lang.String[])

Что и не удивительно =)

А вот если в данном примере сначала объявить переменную сначала внутри цикла for, а затем повторно ее объявить уже за пределами цикла ниже:


public class Test {
    public static void main(String[] args) {

        int i;

        for (i = 0; i < 10; i++) {

            int a;
            a = 5 + i;

            System.out.println(a);
        }

        int a;
        a = 100;

        System.out.println(a);
    }
}

То ошибки уже не будет, так как переменная "a" будет иметь разные области действия! То есть первое объявление переменной "a" ограничивает ее область действия циклом, и когда цикл заканчивается, то она перестает существовать и ее повторное объявление уже допустимо. Но с точки зрения логики и стиля кода, такого лучше избегать, так как может привести к путанице при отладке кода.

 

Арифметические операции над переменными.

Самое простое и интуитивно понятно из всего, что есть! Это простые математические операции такие "сложение", "вычитание" и т.д.

Исключение составляют лишь операции деления по модулю, инкремент и декремент

Деление по модулю (остаток от деления) "%"

Деление по модулю - арифметическая операция, результатом которой является остаток от деления целого числа на другое целое число.

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

Дабы не было путаницы, стоит отметить, что это не дробная часть, которая остается после выполнения операции деления. Ну, например:

10 / 3 = 3,333333333333333

То есть это не 0,333333333333333! Это то число, которое остается после проверки того, сколько раз число 3 вмещается в 10-ти. Например:

10 % 3 = 1
3 + 3 + 3 = 9
10 – 9 = 1

Вот такой вот маленький примитивный пример объясняющий работу деления по модулю =)

Операции инкремента "++" и операция декремента "--"

Инкремент, инкрементирование (от англ. increment "увеличение") — операция во многих языках программирования, увеличивающая переменную на 1.
Декремент – обратная операция в отличии от инкремента, которая уменьшает значение на 1.

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


a = a + 1;

или декремента соответственно:


a = a – 1;

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

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

У данных операций есть одна особенность, которая заключается в форме их написания, называются они префиксная и постфиксная формы! В

Префиксная форма (++a или --a) – записав выражение в данной форме операция увеличения или уменьшения на 1, будет выполнена до математического вычисления.

На примере инкремента это выглядит так:


int a = 1;
int b = 2;

int c = ++a + b;

Если обычно в математике 1 + 2 будет равно 3, то в данном случае все немного иначе, сначала переменная "a" которая была равна 1 будет увеличена на 1, тем самым получит значение 2, а уж затем будет выполнено действие сложения, что в результате даст нам уже 4!

Постфиксная форма (a++ или a--) – запись выражения в данной форме говорит о том, что сначала будут выполнятся математические операции над переменными, а только потом будет производится увеличение или уменьшение значения на 1. Данная форма используется чаще нежели предыдущая в циклических операция (примелькавшийся нам цикл for).

На примере инкремента это выглядит так:


int a = 1;
int b = 2;

int c = a++ + b;

System.out.println(c);
System.out.println(a);

В данном случае сначала выполнится операция сложения, что даст переменной c значение 3 и только потом переменная a будет увеличена на 1!

Форма записи этих операций имеет большое значение, так, что путать его нельзя ни в коем случае! Иначе можно получить совсем не тот результат, который ожидаешь.

- Ферштейн?
- Яволь хер майор

 

Операции отношения и логические операции

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

Операции отношения.

Вот честно, не могу понять кто их так назвал, но как по мне, то лучше было бы назвать их в этой книге "Операции сравнения". Ибо именно эту роль по сути они и выполняют.

Они сравнивают значения на равенство, определяют какое значение больше, а какое нет, и исходя из этого дают результат в виде true или false =)

Примечание: операторы сравнения "<", ">", ">=", "<=" применяются к переменным типа от byte до char, а вот к переменным типа boolean их применение будет не логично.

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

Знак операции Значение
== равно
!= не равно
> больше
< меньше
>= больше или равно
<= меньше или равно

 

Чаще всего они используются при работе с операторами ветвления (if).

Логические операции.

Операции, суть которых не в сравнении значений, а больше в их объединении в одно выражение.

Оператор Значение
& и
| или
^ исключающее или
|| укороченная форма или
&& укороченная форма и
! Не

 

Например:


if (a == b & c > a) System.out.println("Какой чудесный день!");

Также, как и операции сравнения, результатом их работы будет тоже значение типа boolean.

Небольшой особенностью данных операций есть то, что некоторые из них имеют "сокращённую форму записи". Наверно я опять-таки придираюсь, но вот как-то сокращенная форма не совсем похожа на сокращенную, почему, можно посмотреть в таблице выше. Как по мне логичный было бы сделать форму записи наоборот =) Но суть не в этом, ведь имелось в виду будет ли выполнятся все условие или нет!

В зависимости от формы написания оператора, будет зависеть выполнение поставленного условия целиком или только его одна часть! Например, если написать условие используя сокращенную форму оператора "и":


if (a != 0 && (b % a) == 0) {
    //здесь какая-то операция
}

В данном случае проверится только первая часть условия, а вторая будет проигнорированная если переменная "a" будет равна нулю, следовательно, операция внутри не будет выполнятся (так предотвращается деление на ноль). А вот если записать данное условие без использования сокращенной формы:


if (a != 0 & (b % a) == 0) {
    //здесь какая-то операция
}

То будут проверятся оба условия, в результате чего будет произведена операция деления на ноль =)

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

Вот, пожалуй, и все, что стоит сказать об этих операция.

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

a b a & b a | b a ^ b !a
false false false false false true
true false false true true false
false true false true true true
true true true true false false

 

Использование круглых скобок

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


a = 10 / b + a * d – 2

И так


a = (10 / b) + (a * d) – 2

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

 

Оператор присваивания и его укороченная форма

 

Операция присваивания

Пожалуй, самое простое, что есть =)

Это операция, когда происходит инициализация переменной или присваивание ей нового значения.


a = 10;

или так


a = b = c = 20;

Это пример данной операции. В последнем случае происходит присваивание сначала переменной "c" затем "b" и только потом "a", использовать его не рекомендуется, как говорят – это плохая практика.

Укороченная форма операции присваивания

Здесь не так как с логическими операторами! Особенность данной операции состоит упрощении определенного действия:


a = a + 10;

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


a += 10;

и это будет тоже самое.

Данная форма может использоваться на всех математических операциях и на части логических операций:

+= -= *= /=
%= &= |= ^=

 

Преобразование и приведение типов данных

 

При присваивании

Не смотря на то, что Java является строго типизированным языком программирование в нем существует так называемые понятия расширение типов и автоматическое преобразование.

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

Примечание: нельзя автоматически преобразовать одну переменную в другу, если диапазон ее значений у конечной переменной другого типа меньше чем у нее самой.

Более полное описание можно было прочитать выше в "Что такое простые типы данных"

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


class LtoD {
    public static void main(String[] args) {

        long L;
        double D;
        
        L = 100123285L;
        D = L; // Автоматическое преобразование long в double

        //L = D; выдаст ошибку!! так как нельзя преобрзовать double в long
        System.out.println("L и D: " + L + " " + D);
    }
}

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


Error:(11, 13) java: incompatible types: possible lossy conversion from double to long

Но это не повод для расстройства, так как здесь на помощь придет такая вещь как "приведение несовместимых типов".

Приведение несовместимых типов

Это преобразование одного типа данных у которого диапазон значений больше, в тот у которого он меньше! Например, тот же тип double из примера выше в тип long.

Делается это просто, нужно лишь соблюсти формат записи

тип переменная = (нужный_тип) преобразуемая_переменная;

Ну может не совсем просто =) Нужно помнить две вещи:

Тип переменной и тип второй в которую происходит преобразование должны совпадать.

Нельзя переменную одного типа преобразовать в совершенно другой тип! Звучит сумбурно, но на примере станет ясней:


int a;
long b = 20;
a = (double) b;

Это приведет к ошибке:


Error:(8, 13) java: incompatible types: possible lossy conversion from double to int

То есть типы не должны перескакивать один через другой, так сказать перепрыгивать через голову. Они должны бить либо идентичными, либо же меньше. Например, так:


int a;
long b = 20;

a = (short или int) b;

И очень важно помнить, что при таком приведение, часть значения может потеряться или произойдет переполнение типа! И значение будет не таким каким мы его ожидаем увидеть =)

На примере это будет выглядеть так:


int a;
duble b = 20.5;

a = (int) b;

В данном случае переменная преобразуется из типа double в int, но если вывести полученное значение на экран, то выведется только число 20, а оставшиеся 0,5 пропадут =)

Преобразование типов в выражениях

Еще одной занимательно особенностью языка Java является это самое автоматическое приведение типов!

Все дело в том, что по умолчанию используются два типа данных: int и double! Как бы это не звучало парадоксально, но останется фактом =) что все целочисленные переменные при выполнении математических операций будут проходить двойное автоматическое преобразование. Например:


byte a, b, c;
a = 120;
b = 9;

c = (byte) (a + b);

Сначала обе переменные будут преобразованы в тип int, затем будет выполнена математическая операция и полученное значение будет преобразовано опять в тип byte.

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


byte a, b, c;
a = 120;
b = 9;

c = (byte) a + b; //ошибка, пропущены скобки

Error:(10, 22) java: incompatible types: possible lossy conversion from int to byte

То есть формальная запись будет следующей:

переменная = (тип_в_который_преобразуем) (переменная + переменная);

 

Таблица приоритета операций начиная от самого высокого и заканчивая самым низким

( ) [ ] .  
++ -- (префиксная форма) ++ -- (постфиксная форма) ~< !
* / %  
+ -    
>> >>> <<  
> >= < <=
== !=    
&      
^      
|      
&&      
||      
?:      
= op=    

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

Остается только привести список вопросов к самопроверке и домашнее задание.

 

Список вопросов к самопроверке

  1. Почему в Java строго определены диапазоны допустимых значений и области действия простых типов?

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

  1. Что собой представляет символьный тип в Java и чем он отличается от символьного типа в ряде других языков программирования?

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

  1. "Переменная типа boolean может иметь любое значение, поскольку любое ненулевое значение интерпретируется как истинное". Верно или неверно?

Ответ: Нет не верно! Тип boolean может содержать только два значения true или false (истина или ложь). Для каждых видов данных в Java имеется свой тип данных.

  1. Допустим, результат выполнения программы выглядит следующим образом:
  • Один
  • Два
  • Три

Напишите строку кода вызова метода println(), где этот результат выводится в виде одной строки.

Ответ:


System.out.println("Один\nДва\nТри");
  1. Какая ошибка допущены в следующем фрагменте кода?

for (i = 0; i < 10; i++) {
    int sum;
    sum = sum + 1;
}

System.out.println("Сумма: " + sum);

Ответ: В данном примере не соблюдена область видимости (действия) переменной, при попытке откомпилировать будет выдана ошибка:


Test.java:16: error: cannot find symbol
			System.out.println("Сумма: " + sum);
										   ^
symbol:   variable sum
location: class Test
1 error

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


Test.java:11: error: variable sum might not have been initialized
				sum = sum + i;
				^
1 error

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

  1. Поясните различие между префиксной и постфиксной формами записи операции инкремента.

Ответ: Согласно установленного правила префиксная форма инкремента или декремента выполняет действие над переменной до ее использования в выражении, а постфиксная после. Например:


int x, y;
x = 10;
y = ++x;

в результате чего x и y будут иметь значение 11, в случае с постфиксной формой все будет немного иначе:


int x, y;
x = 10;
y = x++;

здесь y будет равна 10, а x будет равна 11.

  1. Покажите, каким образом укороченная логическая операция И может предотвратить деление на нуль.

Ответ:


class SCops {
    public static void main(String[] args) {

        int n, d, q;
        d = 0; // установить для d нулевое значение

        // Второй операнд не вычисляется, поскольку значение переменной d равно  нулю
        if (d != 0 && (n % d) == 0) {

            System.out.println(d + " является делителем " + n);
        }

        /* А теперь те же самые действия выполняются без использования укороченного
         логического  оператора. В результате возникает ошибка деления на нуль:
         Exception in thread "main" java.lang.ArithmeticException: / by zero
         at SCops.main(SCops.java:*)
        */

        if (d != 0 & (n % d) == 0) {
            System.out.println(d + " является делителем " + n);
        }
    }
}

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

  1. До какого типа повышаются типа bute и short при вычислении выражений?

Ответ: До типа int.

  1. Когда возникает потребность в явном приведении типов?

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


double x, y;
int d;

d = (int) (x + y);

в этом случае результатом вычисления будет цело число (его дробная часть потеряется) типа int. Так же и с другими совместимы типами. Такое встречается не часто и поэтому нужно самому внимательно следить за типами данных и их диапазонами, дабы не получить неожиданный результат!

  1. Оказывают ли избыточные скобки влияние на эффективность выполнения программ?

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

  1. Определяет ли блок кода область действия переменных?

Ответ: Да. Переменная объявленная внутри какого-то блока кода, будет недоступно за его пределами, исключение являются только глобальные переменные или переменные класса. То есть если к примеру, переменная была объявлена внутри цикла, то за пределами цикла она уже не существует, а потому будет недоступна! А переменные объявленные до того же цикла, напротив будут доступны внутри цикла.

 

Домашнее задание

Первое

Условие: Изменить программу из примера 2.1 таким образом, чтобы она рассчитала расстояние до крупного объекта по времени за которое вернется эхо. Так если хлопнуть в ладоши, то время за которое вернется эхо, будет равно времени прохождению звука в прямом и обратном направлении.

Листинг программы:


class Echo {
    public static void main(String[] args) {

        double sound;
        double dist;

        sound = 7.2 / 2;     // Рассчитываем время за которое мы услышим эхо, разделив его на 2, узнаем время до объекта без его возврата к нам
        dist = 1100 * sound; // Рассчитываем расстояние до объекта

        System.out.println("Расстояние до скалы равно " + dist + " фута");
    }
}

По принципу решения, программа довольна проста, создаются две переменные типа double "sound" и "dist". Затем исходя из условия, берем время, за которое звук доходит до наблюдателя, он равен 7.2 секунды, и делим его на 2, чтобы узнать время, которое преодолевает звук в одну сторону. Ну, а потом приступаем к расчету расстояния до искомого объекта, для этого мы берем скорость распространения звука в воздухе, она равна 1100 футов в секунду, и умножаем ее на полученный результат от первой операции. В итоге получаем желаемый результат.

Второе

Условие: Напиши программу, которая находила бы простые числа от 2 до 100.

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

Для начала стоит понять, что такое простые числа?!

Простое число (др.-греч. πρώτος ἀριθμός) — натуральное (целое положительное) число, имеющее ровно два различных натуральных делителя — единицу и самого себя. Другими словами, число x является простым, если оно больше 1 и при этом делится без остатка только на 1 и на x. К примеру, 5 — простое число, а 6 является составным числом, так как, помимо 1 и 6, также делится на 2 и на 3.

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

Там же можно немного сжульничать и взять алгоритм решения:

Решетка Эратосфена

  1. Выписать подряд все целые числа от двух до n (2, 3, 4, …, n).
  2. Пусть переменная p изначально равна двум — первому простому числу.
  3. Зачеркнуть в списке числа от 2p до n считая шагами по p (это будут числа кратные p: 2p, 3p, 4p, …).
  4. Найти первое не зачёркнутое число в списке, большее чем p, и присвоить значению переменной p это число.
  5. Повторять шаги 3 и 4, пока возможно.

Теперь этот алгоритм можно перевести в код:


class PrimeNumber {
    public static void main(String args[]) {

        int i, j;
        boolean isprime;

        for(i=2; i < 100; i++) {

            isprime = true;

            // проверить, делится ли число без остатка
            for(j = 2; j <= i / j; j++)

                // если число делится без остатка, значит, оно не простое
                if((i % j) == 0) isprime = false;

            if (isprime) System.out.println(i + " - простое число.");
        }
    }
}

Задаем две переменные "i" и "j" типа int, и одну переменную "isprime" типа boolean.

Далее создаем первый цикл, который будет выполнятся почти 100 раз и будет начинаться с 2, так как это первое простое число (оно больше 1 и делится только на 1, и само на себя). Так же установим значение переменной isprime равным true по умолчанию.

Затем создаем второй цикл, в нем и происходит вся магия! В нем мы устанавливаем значение переменной "j" равное 2, условием выхода из этого цикла будет момент, при котором переменная "j" будет больше или равна математическому выражению, в котором переменная "i" будет делится на переменную "j".

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

Ну, а если вкратце, то выполнятся они будут только при условии, что заданное условие будет иметь значение true.

Теперь можно идти дальше! Теперь мы создадим два логических оператора if. В первом будем проверять делится ли число без остатка, путем деления по модулю, если число делится без остатка, то условие будет истинно и переменной isprime будет присвоено значение false. Этот логический оператор if будет принадлежать вложенному циклу for, учитывая, что они записаны в сокращенной форме (без фигурных скобок, их бы лучше ставить, ибо так проще читать код).

Во втором будет проверятся значение переменной isprime на предмет истинности (забегая немного наперед скажу, что оператор if выполняется только в случае если заданное ему условие как раз таки истинно) и если это так, то на экран будет выводится сообщение, что такое то число является простым!

Не понятно? Вот так и мне было по началу =)

Но как сказал один человек "не можешь понять, распиши на листочке!". Что мы сейчас и сделаем!

Цикл расписанный на листочке

Первое значение переменной "i", которое поступает на вход равно 2, далее устанавливается значение переменной isprime как true, и переходит к вложенному циклу. В нем значением переменной "j" тоже равно 2.

Идет проверка условия выхода с цикла 2 <= 2 / 2? Нет 2 больше 0 значит цикл заканчивается, и первый оператор if так и не выполняется, так как достигнуто условие выхода из цикла, еще до его начала! И дальше управление передается второму оператору if где идет проверка переменной isprime на предмет истинности, а учитывая, что она по умолчанию равна true, то на экран будет выведен надпись:

2 - простое число.

На этом заканчивается первая итерация и начинается все сначала!

Для сокращения количества текста и того, что основной принцип уже описан, я распишу все в цифрах на примере двух итераций первого цикла =)

И так приступим.


//Вторая итерация первого цикла
for (i = 3; 3 < 100; 3++) {

    isprime = true;

    for (j = 2; 2 <= (3 / 2 = 1.5); 2++) //значение условия выхода с цикла будет false
        if((3 % 2) == 0) isprime = false; //эта строка не будет обрабатываться так как цикл закончен и не было ни одной итерации
    
    if (isprime) System.out.println(3 + " - простое число."); //эта операция будет иметь значение true и на экран будет выведен текст "2 - простое число."
}

//Третья итерация первого цикла
for (i = 4; 4 < 100; 3++) {

    isprime = true;

    for (j = 2; 2 <= (4 / 2 = 2); 2++) //значение условия выхода с цикла будет true и цикл запустится на выполнение
        if ( ( (4 % 2) == 0 ) ) isprime = false; //эта строка выполнится так как значение логического условия будет true и переменная isprime получит значение false

    if (isprime) System.out.println(3 + " - простое число."); //эта операция получит значение false и на экран ничего не выведется, так как число 4 не является простым
}

//Четвертая итерация первого цикла
for (i = 5; 5 < 100; 3++) {

    isprime = true;

    //Первая итерация вложенного цикла
    for (j = 2; 2 <= (5 / 2 = 2.5); 2++) //значение условия выхода с цикла будет true и цикл запустится на выполнение

        if ( (((5 % 2) == 0) = 1) ) isprime = false; //эта строка не будет выполнена так как результат логического условия будет false

    //Вторая итерация вложенного цикла
    for (j = 3; 3 <= (5 / 3 = 1.666…); 2++) //значение условия выхода с цикла будет false и цикл будет прерван
        if ( (((5 % 3) == 0) = 2) ) isprime = false; //эта строка не будет обрабатываться так как цикл закончен

    if (isprime) System.out.println(5 + " - простое число."); //эта операция будет иметь значение true и на экран будет выведен текст "5 - простое число."
}

Вот, пожалуй, и все! Такой способ отладки и проверки конечно не используется в виду очевидных факторов, так что тема отладки приложений это уже отдельная глава =). Но для наглядности в данном примере очень даже ничего =)

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

 

Ссылки на примеры и коды програм

Объявление

Уважаемые посетители! Если вы обнаружили в каком-нибудь слове ошибку!
Выделите это слово и нажмите Ctrl+Enter одновременно!
Заранее спасибо за сотрудничество!

 Глава №2

Наконец удалось закончить еще одну статью из моего цикла по изучению Java. Это маленький повод для гордости, что я все же не забросил пока это дело. Так, что всех кому это интересно, милости просим под кат =)

 Данная глава дает преставление о таких вещах как:

И так приступим =)

Полностью переписывать главу из книги я не буду, хотя моя супруга иногда говорит, что именно так я и делаю =) Но все же вкратце упомянуть обо всем стоит!

 

Что такое простые типы данных.

Или как их еще называют "примитивные типы данных", это:

  • byte
  • short
  • int
  • long
  • float
  • double
  • char
  • boolean
Список типов переменных приведен в порядке увеличения диапазона возможных хранимых в них данных (от меньшего к большему), за исключением двух последних (о них я упомяну отдельно).

Типы данных от byte до long – это целочисленные типы данных. Учитывая, что язык Java является строго типизированным языком программирования, то это те типы в которых могут содержатся только целые числа (без дробной части). Диапазон допустимых значений у каждой из них свой:

Тип Разрядность в битах Диапазон допустимых значений
byte 8 От -128 до 127
short 16 От -32 768 до 32 767
int 32 От -214 748 3648 до 214 748 3647
long 64 От -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807

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

Размер выделенной памяти (разрядность) для того или иного типа данных может быть каким угодно, а вот диапазон хранимых в ней значений всегда будет одним и тем же. Именно благодаря этому в Java и обеспечивается кроссплатформенность!

Типы данных float и double – это в которых можно хранить данные с дробной часть (в школе их называли дробями). Размеры выделяемой для них памяти по умолчанию 32 и 64 бита.

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

Присваивается же значение переменной такого типа в следующем формате:


char ch;
ch = 'a';

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

Поэтому этот тип считают целочисленным. Но это только формально и на самом деле если переменной этого типа присвоить значение какого-либо числа, например:


ch = 90;

То при выводе на экран этой переменной, выведется не число 90, а символ "Z" которому соответствует порядковый номер 90 в таблице Unicode, точнее ASCII которая входит в ее состав!

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

Можно было бы уделить больше вниманию этой теме, но думаю пока это не нужно.

Ссылки по теме:

Следующим типом со своими плюшками является тип boolean, он может содержать только два значения true или false. Но его особенность заключается не в этом, а в том, что он является логическим типом данных!

Что это значит?!

А то, что любая операция сравнения или выполнения логического оператора типа if будет содержать в себе булево значение!

Логический тип данных, или булев тип, или булевый тип (от англ. Boolean или logical data type) — примитивный тип данных в информатике, принимающий два возможных значения, иногда называемых истиной (true) и ложью (false). Присутствует в подавляющем большинстве языков программирования как самостоятельная сущность или реализуется через численный тип данных. В некоторых языках программирования за значение истина полагается 1, за значение ложь — 0.

Примечание: Из-за значений 0 и 1 не следует его приписывать к целочисленным типам данных, это логический тип данных!

 

Литералы

Об этом я не могу ни сказать =)) ни слова без улыбки. Как говорит одна пословица:

Шкатулка просто открывалась.

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

Но (драматическая пауза, барабанная дробь и все такое) литералы, это всего лишь значение переменных. Да именно так! Это то значение, которое мы присваиваем переменным =)

Единственное на, что стоит обратить внимание, так это на то, что строковый литерал – это набор символов, заключенный в двойные кавычки. Например:


System.out.println("Какой-то текст");

Где фраза "Какой-то текст" и будет строковым литералом.

Последнее, что я здесь добавлю, так это шпаргалку по управляющим последовательностям символов для себя умного =)

Управляющая последовательность Описание
\' Вывод одинарной кавычки
\" Вывод двойной кавычки
\\ Вывод обратной косой черты
\r Возврат каретки
\n Перевод на новую строку
\f Перевод на новую страницу
\t Отступ в виде табуляции
\d Возврат на одну позицию
\ddd Восьмеричная константа, где ddd – восьмеричное число
\uxxxx Шестнадцатиричная константа, где xxxx – шестнадцатиричное число

Последовательность этих символов очень широко применяется при выводе информации на экран (как можно понять из самой таблицы) для ее форматирования.

 

Инициализация переменных.

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

Условная форма записи инициализации переменной (по сути она же и операция присваивания значения, о чем немного позже будет упомянуто) выглядит так:

тип переменная = значение;

Здесь "значение" и будет той самой точкой инициализации переменной или переменных.

По мимо такого типа инициализации есть еще "динамическая инициализация", это когда свое значение переменная получает в результате кокой то операции над числами, символами или другими переменными. Например (динамическая инициализация переменных):


Class DynInit {
    public static void main(String[] args) {

        double radius = 4, height = 5;

        // Переменная volume инициализируется динамически во время выполнения программы
        double volume = 3.1416 * radius * radius * height;
        
        System.out.println("Объем: " + volume);
    }
}

 

Область действия переменных

Это то место в коде где данная переменная будет выполнять свои функции и хранить в себе какое-то значение. Когда же эта область закончится, то переменная перестанет существовать и будет стерта из памяти, и как уже можно догадаться, посмотреть или получить ее значение будет невозможно! И при попытке это сделать будет выдана ошибка:


Error:(15, 9) java: cannot find symbol
                symbol:   variable a

location: class com.gmail.vitaliy1984.Test

В котором говорится, что переменной которой присваивается значение не существует!

Так что же это за такое таинственное место?

Все довольно просто – это то место в коде, где происходят какие-либо действия с переменными. Ну, например, так:


class ScopeDemo {
    public static void main(String[] args) {

        int x; // Эта переменная доступная для всего кода в методе main
        x = 10;
        
        if (x == 10) {  // Начало новой области действия
            int y = 20; // Эта переменная доступна только в даном блоке

            //Обе переменные "x" и "y" доступны в данном блоке
            System.out.println("x and y: " + x + " " + y);
            
            x = y * 2;
        }

        //y = 100; // Ошибка! В этом месте переменная "y" недоступна

        // А переменная "x" по-прежнему доступна
        System.out.println("x is " + x);
    }
}

Уж если совсем проще говоря, то переменная будет действовать и жить от первой перед ней фигурной скобки "{" и заканчивая такой же ее закрывающей "}". За их пределами этой переменной просто не существует физически!

Исходя из этого, если переменная была объявлена к примеру, в начале метода (еще одно страшное слово, но оно уже примелькалось и в книге об этом тоже рассказывается), то повторное ее объявление, например, так:


public class Test {
    public static void main(String[] args) {
        
        int i, a;

        for (i = 0; i < 10; i++) {

            int a;
            a = 5 + i;

            System.out.println(a);
        }
    }
}

приведет к ошибке


Error:(8, 17) java: variable a is already defined in method main(java.lang.String[])

Что и не удивительно =)

А вот если в данном примере сначала объявить переменную сначала внутри цикла for, а затем повторно ее объявить уже за пределами цикла ниже:


public class Test {
    public static void main(String[] args) {

        int i;

        for (i = 0; i < 10; i++) {

            int a;
            a = 5 + i;

            System.out.println(a);
        }

        int a;
        a = 100;

        System.out.println(a);
    }
}

То ошибки уже не будет, так как переменная "a" будет иметь разные области действия! То есть первое объявление переменной "a" ограничивает ее область действия циклом, и когда цикл заканчивается, то она перестает существовать и ее повторное объявление уже допустимо. Но с точки зрения логики и стиля кода, такого лучше избегать, так как может привести к путанице при отладке кода.

 

Арифметические операции над переменными.

Самое простое и интуитивно понятно из всего, что есть! Это простые математические операции такие "сложение", "вычитание" и т.д.

Исключение составляют лишь операции деления по модулю, инкремент и декремент

Деление по модулю (остаток от деления) "%"

Деление по модулю - арифметическая операция, результатом которой является остаток от деления целого числа на другое целое число.

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

Дабы не было путаницы, стоит отметить, что это не дробная часть, которая остается после выполнения операции деления. Ну, например:

10 / 3 = 3,333333333333333

То есть это не 0,333333333333333! Это то число, которое остается после проверки того, сколько раз число 3 вмещается в 10-ти. Например:

10 % 3 = 1
3 + 3 + 3 = 9
10 – 9 = 1

Вот такой вот маленький примитивный пример объясняющий работу деления по модулю =)

Операции инкремента "++" и операция декремента "--"

Инкремент, инкрементирование (от англ. increment "увеличение") — операция во многих языках программирования, увеличивающая переменную на 1.
Декремент – обратная операция в отличии от инкремента, которая уменьшает значение на 1.

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


a = a + 1;

или декремента соответственно:


a = a – 1;

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

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

У данных операций есть одна особенность, которая заключается в форме их написания, называются они префиксная и постфиксная формы! В

Префиксная форма (++a или --a) – записав выражение в данной форме операция увеличения или уменьшения на 1, будет выполнена до математического вычисления.

На примере инкремента это выглядит так:


int a = 1;
int b = 2;

int c = ++a + b;

Если обычно в математике 1 + 2 будет равно 3, то в данном случае все немного иначе, сначала переменная "a" которая была равна 1 будет увеличена на 1, тем самым получит значение 2, а уж затем будет выполнено действие сложения, что в результате даст нам уже 4!

Постфиксная форма (a++ или a--) – запись выражения в данной форме говорит о том, что сначала будут выполнятся математические операции над переменными, а только потом будет производится увеличение или уменьшение значения на 1. Данная форма используется чаще нежели предыдущая в циклических операция (примелькавшийся нам цикл for).

На примере инкремента это выглядит так:


int a = 1;
int b = 2;

int c = a++ + b;

System.out.println(c);
System.out.println(a);

В данном случае сначала выполнится операция сложения, что даст переменной c значение 3 и только потом переменная a будет увеличена на 1!

Форма записи этих операций имеет большое значение, так, что путать его нельзя ни в коем случае! Иначе можно получить совсем не тот результат, который ожидаешь.

- Ферштейн?
- Яволь хер майор

 

Операции отношения и логические операции

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

Операции отношения.

Вот честно, не могу понять кто их так назвал, но как по мне, то лучше было бы назвать их в этой книге "Операции сравнения". Ибо именно эту роль по сути они и выполняют.

Они сравнивают значения на равенство, определяют какое значение больше, а какое нет, и исходя из этого дают результат в виде true или false =)

Примечание: операторы сравнения "<", ">", ">=", "<=" применяются к переменным типа от byte до char, а вот к переменным типа boolean их применение будет не логично.

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

Знак операции Значение
== равно
!= не равно
> больше
< меньше
>= больше или равно
<= меньше или равно

 

Чаще всего они используются при работе с операторами ветвления (if).

Логические операции.

Операции, суть которых не в сравнении значений, а больше в их объединении в одно выражение.

Оператор Значение
& и
| или
^ исключающее или
|| укороченная форма или
&& укороченная форма и
! Не

 

Например:


if (a == b & c > a) System.out.println("Какой чудесный день!");

Также, как и операции сравнения, результатом их работы будет тоже значение типа boolean.

Небольшой особенностью данных операций есть то, что некоторые из них имеют "сокращённую форму записи". Наверно я опять-таки придираюсь, но вот как-то сокращенная форма не совсем похожа на сокращенную, почему, можно посмотреть в таблице выше. Как по мне логичный было бы сделать форму записи наоборот =) Но суть не в этом, ведь имелось в виду будет ли выполнятся все условие или нет!

В зависимости от формы написания оператора, будет зависеть выполнение поставленного условия целиком или только его одна часть! Например, если написать условие используя сокращенную форму оператора "и":


if (a != 0 && (b % a) == 0) {
    //здесь какая-то операция
}

В данном случае проверится только первая часть условия, а вторая будет проигнорированная если переменная "a" будет равна нулю, следовательно, операция внутри не будет выполнятся (так предотвращается деление на ноль). А вот если записать данное условие без использования сокращенной формы:


if (a != 0 & (b % a) == 0) {
    //здесь какая-то операция
}

То будут проверятся оба условия, в результате чего будет произведена операция деления на ноль =)

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

Вот, пожалуй, и все, что стоит сказать об этих операция.

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

a b a & b a | b a ^ b !a
false false false false false true
true false false true true false
false true false true true true
true true true true false false

 

Использование круглых скобок

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


a = 10 / b + a * d – 2

И так


a = (10 / b) + (a * d) – 2

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

 

Оператор присваивания и его укороченная форма

 

Операция присваивания

Пожалуй, самое простое, что есть =)

Это операция, когда происходит инициализация переменной или присваивание ей нового значения.


a = 10;

или так


a = b = c = 20;

Это пример данной операции. В последнем случае происходит присваивание сначала переменной "c" затем "b" и только потом "a", использовать его не рекомендуется, как говорят – это плохая практика.

Укороченная форма операции присваивания

Здесь не так как с логическими операторами! Особенность данной операции состоит упрощении определенного действия:


a = a + 10;

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


a += 10;

и это будет тоже самое.

Данная форма может использоваться на всех математических операциях и на части логических операций:

+= -= *= /=
%= &= |= ^=

 

Преобразование и приведение типов данных

 

При присваивании

Не смотря на то, что Java является строго типизированным языком программирование в нем существует так называемые понятия расширение типов и автоматическое преобразование.

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

Примечание: нельзя автоматически преобразовать одну переменную в другу, если диапазон ее значений у конечной переменной другого типа меньше чем у нее самой.

Более полное описание можно было прочитать выше в "Что такое простые типы данных"

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


class LtoD {
    public static void main(String[] args) {

        long L;
        double D;
        
        L = 100123285L;
        D = L; // Автоматическое преобразование long в double

        //L = D; выдаст ошибку!! так как нельзя преобрзовать double в long
        System.out.println("L и D: " + L + " " + D);
    }
}

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


Error:(11, 13) java: incompatible types: possible lossy conversion from double to long

Но это не повод для расстройства, так как здесь на помощь придет такая вещь как "приведение несовместимых типов".

Приведение несовместимых типов

Это преобразование одного типа данных у которого диапазон значений больше, в тот у которого он меньше! Например, тот же тип double из примера выше в тип long.

Делается это просто, нужно лишь соблюсти формат записи

тип переменная = (нужный_тип) преобразуемая_переменная;

Ну может не совсем просто =) Нужно помнить две вещи:

Тип переменной и тип второй в которую происходит преобразование должны совпадать.

Нельзя переменную одного типа преобразовать в совершенно другой тип! Звучит сумбурно, но на примере станет ясней:


int a;
long b = 20;
a = (double) b;

Это приведет к ошибке:


Error:(8, 13) java: incompatible types: possible lossy conversion from double to int

То есть типы не должны перескакивать один через другой, так сказать перепрыгивать через голову. Они должны бить либо идентичными, либо же меньше. Например, так:


int a;
long b = 20;

a = (short или int) b;

И очень важно помнить, что при таком приведение, часть значения может потеряться или произойдет переполнение типа! И значение будет не таким каким мы его ожидаем увидеть =)

На примере это будет выглядеть так:


int a;
duble b = 20.5;

a = (int) b;

В данном случае переменная преобразуется из типа double в int, но если вывести полученное значение на экран, то выведется только число 20, а оставшиеся 0,5 пропадут =)

Преобразование типов в выражениях

Еще одной занимательно особенностью языка Java является это самое автоматическое приведение типов!

Все дело в том, что по умолчанию используются два типа данных: int и double! Как бы это не звучало парадоксально, но останется фактом =) что все целочисленные переменные при выполнении математических операций будут проходить двойное автоматическое преобразование. Например:


byte a, b, c;
a = 120;
b = 9;

c = (byte) (a + b);

Сначала обе переменные будут преобразованы в тип int, затем будет выполнена математическая операция и полученное значение будет преобразовано опять в тип byte.

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


byte a, b, c;
a = 120;
b = 9;

c = (byte) a + b; //ошибка, пропущены скобки

Error:(10, 22) java: incompatible types: possible lossy conversion from int to byte

То есть формальная запись будет следующей:

переменная = (тип_в_который_преобразуем) (переменная + переменная);

 

Таблица приоритета операций начиная от самого высокого и заканчивая самым низким

( ) [ ] .  
++ -- (префиксная форма) ++ -- (постфиксная форма) ~< !
* / %  
+ -    
>> >>> <<  
> >= < <=
== !=    
&      
^      
|      
&&      
||      
?:      
= op=    

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

Остается только привести список вопросов к самопроверке и домашнее задание.

 

Список вопросов к самопроверке

  1. Почему в Java строго определены диапазоны допустимых значений и области действия простых типов?

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

  1. Что собой представляет символьный тип в Java и чем он отличается от символьного типа в ряде других языков программирования?

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

  1. "Переменная типа boolean может иметь любое значение, поскольку любое ненулевое значение интерпретируется как истинное". Верно или неверно?

Ответ: Нет не верно! Тип boolean может содержать только два значения true или false (истина или ложь). Для каждых видов данных в Java имеется свой тип данных.

  1. Допустим, результат выполнения программы выглядит следующим образом:
  • Один
  • Два
  • Три

Напишите строку кода вызова метода println(), где этот результат выводится в виде одной строки.

Ответ:


System.out.println("Один\nДва\nТри");
  1. Какая ошибка допущены в следующем фрагменте кода?

for (i = 0; i < 10; i++) {
    int sum;
    sum = sum + 1;
}

System.out.println("Сумма: " + sum);

Ответ: В данном примере не соблюдена область видимости (действия) переменной, при попытке откомпилировать будет выдана ошибка:


Test.java:16: error: cannot find symbol
			System.out.println("Сумма: " + sum);
										   ^
symbol:   variable sum
location: class Test
1 error

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


Test.java:11: error: variable sum might not have been initialized
				sum = sum + i;
				^
1 error

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

  1. Поясните различие между префиксной и постфиксной формами записи операции инкремента.

Ответ: Согласно установленного правила префиксная форма инкремента или декремента выполняет действие над переменной до ее использования в выражении, а постфиксная после. Например:


int x, y;
x = 10;
y = ++x;

в результате чего x и y будут иметь значение 11, в случае с постфиксной формой все будет немного иначе:


int x, y;
x = 10;
y = x++;

здесь y будет равна 10, а x будет равна 11.

  1. Покажите, каким образом укороченная логическая операция И может предотвратить деление на нуль.

Ответ:


class SCops {
    public static void main(String[] args) {

        int n, d, q;
        d = 0; // установить для d нулевое значение

        // Второй операнд не вычисляется, поскольку значение переменной d равно  нулю
        if (d != 0 && (n % d) == 0) {

            System.out.println(d + " является делителем " + n);
        }

        /* А теперь те же самые действия выполняются без использования укороченного
         логического  оператора. В результате возникает ошибка деления на нуль:
         Exception in thread "main" java.lang.ArithmeticException: / by zero
         at SCops.main(SCops.java:*)
        */

        if (d != 0 & (n % d) == 0) {
            System.out.println(d + " является делителем " + n);
        }
    }
}

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

  1. До какого типа повышаются типа bute и short при вычислении выражений?

Ответ: До типа int.

  1. Когда возникает потребность в явном приведении типов?

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


double x, y;
int d;

d = (int) (x + y);

в этом случае результатом вычисления будет цело число (его дробная часть потеряется) типа int. Так же и с другими совместимы типами. Такое встречается не часто и поэтому нужно самому внимательно следить за типами данных и их диапазонами, дабы не получить неожиданный результат!

  1. Оказывают ли избыточные скобки влияние на эффективность выполнения программ?

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

  1. Определяет ли блок кода область действия переменных?

Ответ: Да. Переменная объявленная внутри какого-то блока кода, будет недоступно за его пределами, исключение являются только глобальные переменные или переменные класса. То есть если к примеру, переменная была объявлена внутри цикла, то за пределами цикла она уже не существует, а потому будет недоступна! А переменные объявленные до того же цикла, напротив будут доступны внутри цикла.

 

Домашнее задание

Первое

Условие: Изменить программу из примера 2.1 таким образом, чтобы она рассчитала расстояние до крупного объекта по времени за которое вернется эхо. Так если хлопнуть в ладоши, то время за которое вернется эхо, будет равно времени прохождению звука в прямом и обратном направлении.

Листинг программы:


class Echo {
    public static void main(String[] args) {

        double sound;
        double dist;

        sound = 7.2 / 2;     // Рассчитываем время за которое мы услышим эхо, разделив его на 2, узнаем время до объекта без его возврата к нам
        dist = 1100 * sound; // Рассчитываем расстояние до объекта

        System.out.println("Расстояние до скалы равно " + dist + " фута");
    }
}

По принципу решения, программа довольна проста, создаются две переменные типа double "sound" и "dist". Затем исходя из условия, берем время, за которое звук доходит до наблюдателя, он равен 7.2 секунды, и делим его на 2, чтобы узнать время, которое преодолевает звук в одну сторону. Ну, а потом приступаем к расчету расстояния до искомого объекта, для этого мы берем скорость распространения звука в воздухе, она равна 1100 футов в секунду, и умножаем ее на полученный результат от первой операции. В итоге получаем желаемый результат.

Второе

Условие: Напиши программу, которая находила бы простые числа от 2 до 100.

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

Для начала стоит понять, что такое простые числа?!

Простое число (др.-греч. πρώτος ἀριθμός) — натуральное (целое положительное) число, имеющее ровно два различных натуральных делителя — единицу и самого себя. Другими словами, число x является простым, если оно больше 1 и при этом делится без остатка только на 1 и на x. К примеру, 5 — простое число, а 6 является составным числом, так как, помимо 1 и 6, также делится на 2 и на 3.

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

Там же можно немного сжульничать и взять алгоритм решения:

Решетка Эратосфена

  1. Выписать подряд все целые числа от двух до n (2, 3, 4, …, n).
  2. Пусть переменная p изначально равна двум — первому простому числу.
  3. Зачеркнуть в списке числа от 2p до n считая шагами по p (это будут числа кратные p: 2p, 3p, 4p, …).
  4. Найти первое не зачёркнутое число в списке, большее чем p, и присвоить значению переменной p это число.
  5. Повторять шаги 3 и 4, пока возможно.

Теперь этот алгоритм можно перевести в код:


class PrimeNumber {
    public static void main(String args[]) {

        int i, j;
        boolean isprime;

        for(i=2; i < 100; i++) {

            isprime = true;

            // проверить, делится ли число без остатка
            for(j = 2; j <= i / j; j++)

                // если число делится без остатка, значит, оно не простое
                if((i % j) == 0) isprime = false;

            if (isprime) System.out.println(i + " - простое число.");
        }
    }
}

Задаем две переменные "i" и "j" типа int, и одну переменную "isprime" типа boolean.

Далее создаем первый цикл, который будет выполнятся почти 100 раз и будет начинаться с 2, так как это первое простое число (оно больше 1 и делится только на 1, и само на себя). Так же установим значение переменной isprime равным true по умолчанию.

Затем создаем второй цикл, в нем и происходит вся магия! В нем мы устанавливаем значение переменной "j" равное 2, условием выхода из этого цикла будет момент, при котором переменная "j" будет больше или равна математическому выражению, в котором переменная "i" будет делится на переменную "j".

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

Ну, а если вкратце, то выполнятся они будут только при условии, что заданное условие будет иметь значение true.

Теперь можно идти дальше! Теперь мы создадим два логических оператора if. В первом будем проверять делится ли число без остатка, путем деления по модулю, если число делится без остатка, то условие будет истинно и переменной isprime будет присвоено значение false. Этот логический оператор if будет принадлежать вложенному циклу for, учитывая, что они записаны в сокращенной форме (без фигурных скобок, их бы лучше ставить, ибо так проще читать код).

Во втором будет проверятся значение переменной isprime на предмет истинности (забегая немного наперед скажу, что оператор if выполняется только в случае если заданное ему условие как раз таки истинно) и если это так, то на экран будет выводится сообщение, что такое то число является простым!

Не понятно? Вот так и мне было по началу =)

Но как сказал один человек "не можешь понять, распиши на листочке!". Что мы сейчас и сделаем!

Цикл расписанный на листочке

Первое значение переменной "i", которое поступает на вход равно 2, далее устанавливается значение переменной isprime как true, и переходит к вложенному циклу. В нем значением переменной "j" тоже равно 2.

Идет проверка условия выхода с цикла 2 <= 2 / 2? Нет 2 больше 0 значит цикл заканчивается, и первый оператор if так и не выполняется, так как достигнуто условие выхода из цикла, еще до его начала! И дальше управление передается второму оператору if где идет проверка переменной isprime на предмет истинности, а учитывая, что она по умолчанию равна true, то на экран будет выведен надпись:

2 - простое число.

На этом заканчивается первая итерация и начинается все сначала!

Для сокращения количества текста и того, что основной принцип уже описан, я распишу все в цифрах на примере двух итераций первого цикла =)

И так приступим.


//Вторая итерация первого цикла
for (i = 3; 3 < 100; 3++) {

    isprime = true;

    for (j = 2; 2 <= (3 / 2 = 1.5); 2++) //значение условия выхода с цикла будет false
        if((3 % 2) == 0) isprime = false; //эта строка не будет обрабатываться так как цикл закончен и не было ни одной итерации
    
    if (isprime) System.out.println(3 + " - простое число."); //эта операция будет иметь значение true и на экран будет выведен текст "2 - простое число."
}

//Третья итерация первого цикла
for (i = 4; 4 < 100; 3++) {

    isprime = true;

    for (j = 2; 2 <= (4 / 2 = 2); 2++) //значение условия выхода с цикла будет true и цикл запустится на выполнение
        if ( ( (4 % 2) == 0 ) ) isprime = false; //эта строка выполнится так как значение логического условия будет true и переменная isprime получит значение false

    if (isprime) System.out.println(3 + " - простое число."); //эта операция получит значение false и на экран ничего не выведется, так как число 4 не является простым
}

//Четвертая итерация первого цикла
for (i = 5; 5 < 100; 3++) {

    isprime = true;

    //Первая итерация вложенного цикла
    for (j = 2; 2 <= (5 / 2 = 2.5); 2++) //значение условия выхода с цикла будет true и цикл запустится на выполнение

        if ( (((5 % 2) == 0) = 1) ) isprime = false; //эта строка не будет выполнена так как результат логического условия будет false

    //Вторая итерация вложенного цикла
    for (j = 3; 3 <= (5 / 3 = 1.666…); 2++) //значение условия выхода с цикла будет false и цикл будет прерван
        if ( (((5 % 3) == 0) = 2) ) isprime = false; //эта строка не будет обрабатываться так как цикл закончен

    if (isprime) System.out.println(5 + " - простое число."); //эта операция будет иметь значение true и на экран будет выведен текст "5 - простое число."
}

Вот, пожалуй, и все! Такой способ отладки и проверки конечно не используется в виду очевидных факторов, так что тема отладки приложений это уже отдельная глава =). Но для наглядности в данном примере очень даже ничего =)

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

 

Ссылки на примеры и коды програм

Developed by: Magnum © 2005 - 2018