Ключевое слово assert используется для проверки какого-либо условия в ходе разработки.
Программа выдает исключение, то есть происходит ошибкаесли выражение в условии после ключевого слова assert возвращает false.
В итоговом коде не присутствует, так как используется для простого тестирования условий.
Запускается с ключом -ea.
Поясним на примере:
import java.io.*;
public class Assert {
public static void main(String args[]) {
int n=5;
for(int i=0; i < 10; i++) {
n--;
assert n > 0; //произойдет сбой когда n дойдет до нуля
System.out.println(“n equals ” + n);
}
}
}
Вывод:
Как видим, когда выполнение программы доходит до того, что n становиться равным 0 происходит ошибка.
Есть специальные классы обертки – Integer, Char, Float.
Они также как и простые типы значений хранят в себе одно простое значение: Integer – целое число, Char – один символ и т.д.
Зачем же они нужны?
Обычные типы значений не могут иметь значение null, то есть пустота. Обертки же могут.
Также они нужны чтобы указывать тип хранимых значений в коллекциях (коллекции будут рассмотрены позже).
Поясним на примере:
public class Wrappers {
public static void main(String[] args) {
Integer integer = null;
// int num = null; – не скомпилируется
System.out.println(integer);
// запишем просто целое число
integer = 7;
System.out.println(integer);
}
}
Вывод:
Также важно знать что обертки хоть и являются классами и могут иметь объекты, но они не являются ссылочными, то есть ведут они себя также как типы значений.
Для хранения строки мы знаем тип String. Но он не всегда бывает эффективен.
Поэтому есть еще другие классы для хранения строк и взаимодействия с ними, которые нужно знать.
Поясним на примере:
public class StringTypes {
public static void main(String[] args) {
// Создадим String переменную
String output = “Some text”;
// и добавим в конец этой строки единицу и пробел сто раз.
int count = 100;
for(int i = 0; i < count; i++) {
output += i + " "; // При каждой итерации цикла
// создается НОВЫЙ объект String с очередным добавленным i.
// Просто представьте, как это плохо скажется на памяти
}
System.out.println(output);
}
}
Вывод:
Поэтому существует StringBuffer, который решает эту проблему.
public class StringTypes {
public static void main(String[] args) {
int count = 100;
// Поэтому существует StringBuffer, который решает эту проблему
StringBuffer output = new StringBuffer(110);
// append – добавить в объект StringBuffer текст
output.append(“Some text”);
for(int i = 0; i < count; i++) {
output.append(i + " "); // Теперь при каждой итерации
// работаем всего с одним экземпляром StringBuffer
// Профит!
}
System.out.println(output.toString());
}
}
Вывод:
Также есть StringBuilder – то же самое, что и StringBuffer только потокоНЕбезопасен, поэтому работает быстрее
Часто бывает нужно изменить тип переменной. То есть, например, была создана переменная типа int, и нам в какой-то момент стало необходимо изменить ее тип на byte. Это можно делать с помощью приведения типов.
Правила такие:
По стрелке – автоматическое преобразование. Против стрелки – используя ().
byte -> short -> int -> long
int -> double
short -> float -> double
char -> int
Ниже приведен пример с пояснением:
public class Privedeniye {
public static void main(String[] args) {
byte b = 7;
// Пример автоматического преобразования.
// Допустим хотим преобразовать byte в int.
// Смотрим на цепочку выше. int после byte.
// Значит преобразование b из byte в int
// будет автоматическим и можем воспользоваться для
// приведения(преобразования) просто знаком равенства.
// Ниже происходит автоматическое преобразование b
// из byte в int, а потом запись b, которая уже теперь
// типа int в переменную d.
int d = b;
System.out.println(d); // 7
// Важно уточнить, что b все еще типа byte,
// она становилась типа int только в процессе записи
// в d. После записи она все еще byte как и была.
int a = 4;
// Пример преобразования не автоматического.
// Если против стрелки, как уже было сказано,
// то пишем в скобках к какому типу приводим.
// Ниже из типа int к типу byte, то есть это наоборот
// к предыдущему примеру. Значит пишем в скобочках
// нужный тип.
byte c = (byte)a;
System.out.println(c); // 4
// При преобразовании некоторых типов в другие
// может теряться точность. Например
// int -> float
// long -> float
// long -> double
int e = 2147483647;
float f = e; // от типа int к типу float
System.out.println(f);//В консоли будет 2.14748365E9
// то есть вообще другое число.
}
}
Если мы хотим сделать класс более гибким в плане типов, которые в нем используются, то можно воспользоваться обобщениями (generics).
Класс в примере ниже является обобщенным, так как в нем используется универсальный параметр с именем T.
Имя, кстати, не обязательно должно быть именно символом T, а может быть вообще любой символили набор символов, какой мы захотим. T — это не настоящий тип, он универсальный, или можно сказать неопределенный, или просто “какой-то тип”.
Настоящий же тип становится на место ненастоящего при создании объекта данного обобщенного класса. При создании объекта данного класса вместо T будет указываться нужный настоящий тип, который будет подставлен вместо всех T в коде класса. То есть при написании класса мы не знаем заранее, какой тип мы захотим использовать, когда будем создавать объект этого класса.
Гибкость в плане типов внутри класса очевидна.
То есть код класса один (этот код обобщенный, универсальный), а исполняться он может с любым типом, который программист захочет подставить вместо T, когда будет создавать объект.
Универсально? Универсально.
Поясним на примере:
class Account < T > {
private T id;
private int sum;
Account(T id, int sum) {
this.id = id;
this.sum = sum;
}
public T getId() {
return id;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
}
public class Generics {
public static void main(String[] args) {
// Здесь вместо ненастоящего T в <>
// передается настоящий String.
Account < String > acc1 = new Account < String > (“23d4g5”, 5000);
// То есть представьте, что вместо T
// в классе выше везде String,
// и на основе этого класса со String вместо T
// создается объект acc1.
String acc1Id = acc1.getId(); // выводим String id
System.out.println(acc1Id);
// Здесь вместо T передается Integer
Account < Integer > acc2 = new Account < Integer > (234545, 5000);
Integer acc2Id = acc2.getId(); // выводим Integer id
System.out.println(acc2Id);
}
}
Если при работе Java приложения возникает ошибка(исключение) и обработка данной ошибки не предусмотрена в программе этого приложения, то приложение ОСТАНОВИТ свою работу и программисту, который запустил это приложение или пользователю, который использовал это приложение демонстрируется большой и страшный StackTrace, который поясняет что же с приложением случилось и какая ошибка произошла. Такой StackTrace мы все видели когда запускали программу с ошибкой.
С помощью обработки ошибок можно сделать так чтобы приложение не останавливало свою работудаже если произошла ошибка, то есть можно сделать так чтобы пользователю например показалось сообщение “Невозможно сейчас выполнить данную операцию, потому что при выполнении данной операции произошла такая-то ошибка!” уже в виде красивого всплывающего окна вместо страшного StackTrace и главное, что приложение при этом продолжит работать.
Обработка ошибок совершается с помощью try и catch. И еще finally можно иногда.
Обработка исключений с помощью try catch
Если в коде try возникнет исключение указанное в круглых скобочках рядом с catch, то выполняется код в catch.
Поясним на примере:
class Exceptions {
public static void main(String args[]) {
int array[] = new int[2];
try {
// Если в коде try возникнет исключение указанное в круглых скобочках
// рядом с catch, то выполняется код в catch. В круглых скобочках рядом
// с catch ниже можно увидеть ArrayIndexOutOfBoundsException – это
// ошибка выхода за пределы массива. Мы выделили память под два
// элемента массива (new int[2]), а ниже мы обращаемся к третьему.
// Таким образом, если пользователь приведет программу к ошибке,
// то выполнится код в catch и она не завершит работу.
// Здесь происходит ошибка (обратились к индексу за границей массива)
System.out.println(“Access to third:” + array[3]);
} catch (ArrayIndexOutOfBoundsException e) {
// В try произошла ошибка, поэтому выполняется код в catch.
// Здесь мы просто выводим на консоль информацию о произошедшей
// ошибке, которая хранится в e.
// Можно здесь выполнять вполне понятное дело всякие другие операции,
// а не только вывод сообщения на консоль или куда-либо еще.
System.out.println(“Exception:” + e);
} finally {
// finally в коде выполняется всегда независимо
// от того, произошло исключение или нет
array[0] = 6;
System.out.println(“Value of first: ” + array[0]);
System.out.println(“finally completed”);
}
// Нижняя строка в результате сработает независимо
// от того, произошло выше исключение или нет.
// То есть приложение ПРОДОЛЖАЕТ свою работу.
System.out.println(“App is still working!!!!!!!”);
}
}
Вывод:
Throw и Throws
По теме исключений еще нужно знать два ключевых словаthrows и throw.
Поясним на примере:
class Exceptions1 {
public static void deposit(int amount) throws ArrayIndexOutOfBoundsException {
//throws предупреждает программиста, что в методе может возникнуть указанное исключение
//и что обработка этого исключения в текущем коде отсутствует
//обычно по причине что возможности или надобности обработать это исключение сейчас нет.
throw new ArrayIndexOutOfBoundsException();
//throw возбуждает указанное исключение
//То есть ошибка происходит не в результате каких-то операций, например обращение
//к третьему индексу в массиве ограниченному двумя элементами, как было в предыдущем примере кода,
//а мы специально просто берем и с помощью throw new делаем так, чтобы эта ошибка произошла.
}
public static void main(String[] args) {
//вызываем метод с throws и throw
deposit(3);
}
}
Вывод:
Как видим при запуске программы происходит возбужденная нами ошибка.
Поясним что такое аннотации в Java с помощью небольшой анналогии.
Представьте себе склад с коробками. На каждой из коробок есть надпись, которая говорит тому, кто будет отгружать эти коробки из склада, куда эту коробку отгружать.
По этой аналогии коробка, — это класс, переменная или метод, аннотация, — это надпись, а тот кто отгружает коробку и читает надпись, — это компилятор Java. Компилятор смотрит на аннотацию над классом, методом или переменной и выполняет соответствующие действия.
Например с помощью аннотации SafeVarargs над классом можно подавить некоторые предупреждения, которые компилятор бы выдал если бы над этим классом не было этой аннотации. То есть компилятор увидел над классом аннотацию SafeVarargs, которая говорит компилятору “не выдавай предупреждения о классе” и он этого делать не будет.
Или аннотация Documented скажет компилятору задокументировать класс над которым она стоит.
Далее на примерах разберем некоторые важные аннотации.
Аннотация Override
Override — наверное, самая часто встречающаяся аннотация. Этой аннотацией помечается метод чтобы всем программистам смотрящим на него было видно, что метод переопределяется. То есть в родительском классе того класса, в котором находится метод с аннотацией @Override, уже определён метод с таким же именем и сигнатурой.
Поясним на примере:
import java.util.*;
import java.lang.annotation.*;
// Определим родительский класс
class ParentClass {
// Этот метод будет переопределяться наследником.
public String someMethod() { return “parent method”; }
}
public class Annotations extends ParentClass {
// Аннотация Override сообщает программисту,
// который смотрит на метод ниже, что происходит
// переопределение метода родительского класса.
@Override
public String someMethod() {
return “overrided!”;
}
public static void main(String[] args) {
Annotations test = new Annotations();
System.out.println(test.someMethod());
}
}
Вывод:
Также еще служит как страховка.
То есть представим, что мы например случайно удалили или закомментировали метод в родительском классе, который переопределялся наследником. Если этот переопределяемый в наследнике метод был помечен аннотацией Override, то при запуске компилятор выдаст соответствующую ошибку сообщающую о том, что аннотация Override здесь ни к чему, поскольку в родительском методе нет метода с таким названием. В примере ниже в ParentClass мы не определили метод с именем someMethod (точнее метод закомментирован, как видим), поэтому программа выдаст ошибку.
import java.util.*;
import java.lang.annotation.*;
// Определим родительский класс
class ParentClass {
/*
public String someMethod() {return “parent method”;}
*/
}
public class Override1 extends ParentClass {
@Override // аннотация
public String someMethod() {return “overridden”;}
public static void main(String[] args) {
Override1 test = new Override1();
test.someMethod();
}
}
Вывод:
Аннотация @FunctionslInterface
FunctionalInterface – если над интерфейсом написать аннотацию @FunctionalInterface, то в такой интерфейс нельзя будет добавить более одного абстрактного метода (интерфейс с одним единственным абстрактным методом называется функциональным интерфейсом).
То есть если код содержит в себе интерфейс, который помечен аннотацией FunctionalInterface и который при этом содержит несколько абстрактных методов, то такой код не скомпилируется.
Что такое функциональный интерфейс, зачем он нужен и как им пользоваться разберем немного позже.
Поясним на примере:
// Deprecated и FunctionalInterface
import java.util.*;
import java.lang.annotation.*;
@FunctionalInterface
interface Print {
// Здесь в функциональном интерфейсе должен быть
// один абстрактный метод, а их как видим два.
void printString(String testString);
void printAnotherString(String testString);
}
public class AnotherAnnotations {
public static void main(String[] args) {
AnotherAnnotations swDemo = new AnotherAnnotations();
}
}
Вывод:
Как видим код не скомпилировался.
Аннотация @Deprecated
Deprecated – для пометки устаревших методов или классов.
При вызове метода вызовется предупреждение, что метод устарел и что его лучше не использовать.
Поясним на примере:
import java.util.*;
import java.lang.annotation.*;
class ParentClass {
public String getName() { return “mike”; }
// метод ниже устарел
@Deprecated
public void deprecatedMethod() {
System.out.println(“This is a legacy function”);
}
}
public class AnotherAnnotations extends ParentClass {
public static void main(String[] args) {
AnotherAnnotations swDemo = new AnotherAnnotations();
swDemo.deprecatedMethod();
}
}
Вывод:
Как видим, код скомпилировался, но при компиляции было вызвано предупреждение.
Создание собственных (кастомных) аннотаций.
Можно создавать свои аннотации с помощью аннотации@interface.
При создании аннотации используются вспомогательные аннотации. Они пишутся над аннотацией @interface.
@Target – этой аннотацией указываем к чему будет применяться создаваемая аннотация – если укажем TYPE, то только к классу, если METHOD, то только к методу, если FIELD, то только к полю, есть и другие значения, но это основные
@Retention – этой аннотацией указываем где будет жить создаваемая аннотация – если укажем RUNTIME, то создаваемая аннотация должна быть доступна джава машине во время выполнения, если CLASS, то аннотация не будет доступна во время выполнения, но будет в скомпилированном джава class файле, если SOURCE, то создаваемая аннотация будет содержаться только в исходном коде.
@Repeatable(...) – создаваемая аннотация может использоваться несколько раз на методе, классе или поле. В скобочках указывается другая кастомная аннотация. Эта другая кастомная аннотация будет хранить в виде массива ВСЕ повторения применения над конкретным методом или классом, или полем той кастомной аннотации над которой мы писали Repeatable.
@Inherited – создаваемая аннотация наследуется классами, которые наследуют класс к которому применена аннотация.
@Documented – информация о создаваемой аннотации появиться в JavaDoc-документации
Поясним на примере:
import java.util.*;
import java.lang.annotation.*;
//Создадим аннотацию с именем RepeatableCompany
//Для начала к создаваемой аннотации RepeatableCompany
//применим необходимые вспомогательные аннотации.
//Применим @Inherited. Значит, что к наследникам
//классов к которым будет применена создаваемая
//аннотация тоже будет применена эта аннотация.
//То есть можно увидеть, что аннотация
//RepeatableCompany применена к классу CompanyClass,
//а значит и к классу CreateAnnotation она тоже будет
//применена так как CreateAnnotation это наследник
//класса CompanyClass.
@Inherited
//Применим @Documented.
//Значит создаваемая аннотация будет задокументирована
@Documented
//Применим @Target(ElementType.TYPE).
//Значит создаваемая аннотация будет использоваться
//над классами
@Target(ElementType.TYPE)
//Применим @Repeatable(RepeatableCompanies.class).
//Значит создаваемую аннотацию можно будет применять
//несколько раз на один и тот же класс.
//RepeatableCompanies – это аннотация, которая
//содержит массив из всех аннотаций RepeatableCompany,
//примененных к классу и к какому нибудь
//классу храниться в RepeatableCompanies.
@Repeatable(RepeatableCompanies.class)
//Применим @Retention(RetentionPolicy.RUNTIME). Значит
//создаваемая аннотация доступна при работе программы.
@Retention(RetentionPolicy.RUNTIME)
//Теперь приступим непосредственно
//к созданию @RepeatableCompany.
@interface RepeatableCompany {
//При создании аннотации в ней можно определять
//поля аннотации. Ниже можно увидеть пример
//пары полей. В аннотации они определяются чтобы
//при применении аннотации над каким-то классом,
//можно было записываться в такие
//поля какие-то данные которые будут
//доступны при выполнении над объектом класса
//к которому была применена аннотация.
//Ниже можно увидеть пример аннотации которая
//будет создана просто и не будет выполнять
//никаких действий над классом к которому она
//будет применяться, поля аннотации будут
//содержать какие-то данные которые можно будет
//использовать при выполнении программы.
//Пример ниже показывает как аннотация смотрит
//внутри и как она будет применяться.
//Например:
@RepeatableCompany(name=”Toyota Motor”,city=”Tokyo”)
//аннотация будет видна в коде класса
//CompanyClass в котором она указана.
String name() default “CompanyName”;
String city() default “CompanyCity”;
//default это дефолтное значение поля.
//То есть программист может не указывать
//name = “Toyota Motor”, city = “Tokyo” в скобочках
//у аннотации, а просто применить над классом
//аннотацию без ничего вот так: @RepeatableCompany.
//И так как в скобочках ничего не было указано
//то name и city будут содержать дефолтные
//значения – “CompanyName” и “CompanyCity”.
}
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface RepeatableCompanies {
//массив аннотаций RepeatableCompany
RepeatableCompany[] value() default{};
}
@RepeatableCompany
@RepeatableCompany(name = “Toyota Motor”,city = “Tokyo”)
@RepeatableCompany(name = “Woltzvagen”,city = “Germany”)
class CompanyClass {
public String getName() {return “mike”;}
}
class CreateAnnotation extends CompanyClass {
public static void main(String[] args) {
//Созданная аннотация чисто для информации
//программисту, поэтому в мейн ничего не делаем.
}
}