Приведение типов в Java

Часто бывает нужно изменить тип переменной. То есть, например, была создана переменная типа int, и нам в какой-то момент стало необходимо изменить ее тип на byte. Это можно делать с помощью приведения типов.            

Example

Правила такие:

По стрелке – автоматическое преобразование. Против стрелки – используя ().

  • 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 в Java

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

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

Имя, кстати, не обязательно должно быть именно символом T, а может быть вообще любой символ или набор символов, какой мы захотим. T — это не настоящий тип, он универсальный, или можно сказать неопределенный, или просто “какой-то тип”.

Настоящий же тип становится на место ненастоящего при создании объекта данного обобщенного класса. При создании объекта данного класса вместо T будет указываться нужный настоящий тип, который будет подставлен вместо всех T в коде класса. То есть при написании класса мы не знаем заранее, какой тип мы захотим использовать, когда будем создавать объект этого класса.

Example

Гибкость в плане типов внутри класса очевидна.

То есть код класса один (этот код обобщенный, универсальный), а исполняться он может с любым типом, который программист захочет подставить вместо 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

Если при работе 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 с помощью небольшой анналогии.

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

По этой аналогии коробка, — это класс, переменная или метод, аннотация, — это надпись, а тот кто отгружает коробку и читает надпись, — это компилятор 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 и который при этом содержит несколько абстрактных методов, то такой код не скомпилируется.

Search Icon

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

Поясним на примере:                                                

// 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) { //Созданная аннотация чисто для информации //программисту, поэтому в мейн ничего не делаем. } }

Передача в метод ссылочных типов и типов значений

Рассмотрим передачу ссылочного типа в метод и передачу типа значения в метод.

Для начала передадим в метод тип значения.

public class LinkValInMethodExample { // Определим метод, в который передается числовой параметр, // к которому будет прибавляться единица. static void doStuff(int y) { y = y + 1; } public static void main(String[] args) { // Сначала передадим в метод тип значения int x = 5; doStuff(x); // Когда мы передаем тип значения в метод doStuff, // мы передаем не саму переменную x, а ее КОПИЮ. System.out.println(x); // Поэтому x все еще равно 5, а не 6, // так как в методе doStuff происходили действия не с самой x, // а с ее копией. } }

Вывод:

Теперь передадим в метод объект ссылочного типа и изменим его в этом методе.

//Создадим простейший класс для демонстрации //передачи в метод ссылки на объект. class Animal { public String eats; public int noOfLegs; } public class LinkValInMethodExample { //Определим метод, в который передаётся объект //и в нем он меняется. (Точнее передаётся ссылка на объект). static void doStuff(Animal Y) { Y.eats = “grass”; Y.noOfLegs = 4; } public static void main(String[] args) { //Теперь же когда мы передаем ссылочный тип //мы передаем в метод ссылку на объект, который //мы создали строчкой кода ниже и через эту ссылку //метод работает с этим созданным объектом Animal x = new Animal(); x.eats = “meat”; x.noOfLegs = 2; doStuff(x); // Передаем копию ссылки (правильно говорить: // мы передаем копию ссылки, то есть мы передаем не саму ссылку x, // а ее копию. То есть когда мы в метод передаем x, то создается // новая ссылка, которая ссылается на тот же объект, что и x, // и через нее уже работает метод с данным объектом. // На созданный объект, который содержит // два поля со значениями “meat” и 2, // метод doStuff использует её и может изменять объект // прямо в методе. System.out.println(x.eats); System.out.println(x.noOfLegs); // Как можно увидеть в консоли: x.eats = “grass”, а x.noOfLegs = 4, // так как в методе происходила // работа с объектом через копию ссылки x. } }

Вывод:

Ссылочные типы и типы значений в Java

В Java есть два типа данныхссылочные типы и типы значений.

int, char ,float и т.д это типы значений. Это простые типы. Это просто число либо символ, либо дробное число и т.д.  Это не классы (ссылочные типы), которые могут состоять из многих полей типов значений:

public class SomeClass { int num; char letter; float floatingNum; }

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

Так вот почему же классы ссылочные, а типы значений нет?

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

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

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

Подробнее о ссылках описано в примере далее.

class SomeClass { int num; char letter; double floatingNum; SomeClass(int num, char letter, double floatingNum) { // Не по теме данного урока! Но это важно знать. this.num = num; // this это ссылка на текущий объект класса. // this на русском это ЭТОТ. То есть ссылка на ЭТОТ // объект класса. То есть если например мы создаем объект // на основе класса SomeClass, то в конструкторе // используется поле num именно ЭТОГО объекта, имеется // ввиду именно того, который мы создаем. // Обычно this используется чтобы не происходило // путаницы с параметрами, так как, как видим, поля класса // и параметры имеют одинаковые имена. // То есть присваиваем полю num объекта класса SomeClass, // значение параметра num переданного в конструктор. this.letter = letter; this.floatingNum = floatingNum; // можно было бы переименовать поля класса или параметры // тогда можно было бы обойтись без this. Но так лучше // так как не нужно придумывать разные имена для полей класса // и параметров. } } public class Types { public static void main(String[] args) { // Продемонстрируем пример, чтобы понять что значит ссылаться. // Создаем два объекта класса SomeClass // Под каждый объект с помощью new // выделяется отдельный участок памяти, // в котором будет храниться его текущее состояние, // то есть текущие значения его полей. // То есть сейчас в одном участке памяти // хранятся значения первого объекта 9, ‘f’, 1.45, // в другом участке второго объекта 2, ‘k’, 6.11. SomeClass someClass1 = new SomeClass(9, ‘f’, 1.45); SomeClass someClass2 = new SomeClass(2, ‘k’, 6.11); // someClass1 это имя, по которому мы можем // обращаться к первому объекту, то есть к первому // участку памяти, то есть someClass1 это ссылка на // первый объект, а someClass2 это имя, по которому // мы можем обращаться ко второму объекту, // то есть ко второму участку памяти, то есть someClass2 // это ссылка на второй объект. // Присваиваем первый объект второму. someClass1 = someClass2; // Что же произойдет? Большинство сразу подумает, что // в участке памяти первого объекта просто поменяются // значения полей, хранящиеся в нем, на те, которые // находятся в участке памяти второго объекта. // НО ЭТО НЕ ТАК. На самом деле имя, по которому // можно было обращаться к первому объекту, // просто перестанет ссылаться на свой участок памяти. // То есть участок памяти первого объекта // начнет ссылаться на участок памяти второго объекта. // Теперь someClass1 и someClass2 — это // один и тот же объект в памяти (второй объект). // Просто манипулировать этим объектом в памяти // теперь можно через разные имена. // И если теперь мы, например, будем менять значение // какого-то поля через имя someClass1: someClass1.num = 10; // К измененному полю мы также сможем // обратиться через второе имя объекта. System.out.println(someClass2.num); // увидим в консоли 10 // Поскольку мы изменили объект, у которого по сути есть // два имени someClass1 и someClass2, // имя не важно, мы можем через любое из них // с ним работать. // Думаю, теперь понятно, почему они ссылочные. } }

Вывод:

Теперь то же самое только с типами значений.

public class Types { public static void main(String[] args) { //Типы значений же это простые типы //они хранят само значение в себе, //а не ссылку на участок памяти. //По аналогии с тем что сверху только с простыми типами. int A=4, B=12; A = B; A = 6; System.out.println(B); //B все еще 12 в отличии от A, которая содержит 6 //так как сначала мы просто изменили значение в A //на значение которое в B, //а потом изменили значение в A на значение 6. //На значение в B действия с A никак не влияют. } }

Вывод:

Статические классы в Java

Статический класс это всегда внутренний класс. Статического не внутреннего класса не бывает.

Обычный внутренний класс и статический внутренний класс часто сравнивают.

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

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

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

Например есть класс Торт Наолеон.

У него есть внутри какая-то статическая переменная, например колличество масла, и внутренний статический класс Рецепт Торта Наполеон.

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

И соответственно обьект внутреннего статического класса Рецепт торта наполеон будет иметь лишь общую связь со всеми обьектами класса торт наполеон, поскольку будет иметь связь только со статическими (общими) элементами класса торт наполеон.

Поясним на примере:

class CakeNapoleon { // класс торт Наполеон private static int butter = 600; public void printRecipe() { // Как видим ниже, внешний класс // не должен создавать объект внутреннего класса. // Можно обращаться к методам внутреннего // напрямую через внутренний класс RecipeOfNapoleon. RecipeOfNapoleon.showRecipe(200); } // Внутренний статический класс рецепт Наполеона static class RecipeOfNapoleon { public static void showRecipe(int Salt) { // Используется статическое поле butter // внешнего класса. // К нестатическим членам внешнего класса доступа нет. System.out.println(“Recipe of Napoleon – ” + Salt + ” of salt ” + “butter” of butter and so on”); } } } class StaticClass { public static void main(String[] args) { // Создаётся экземпляр // внутреннего статического класса RecipeOfNapoleon // без создания объекта внешнего класса CakeNapoleon. CakeNapoleon.RecipeOfNapoleon recipeOfNapol = new CakeNapoleon.RecipeOfNapoleon(); recipeOfNapol.showRecipe(100); } }

Вывод:

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

Внутренний класскласс внутри другого класса.

Некоторая доп. функциональность, которая относиться только к внешнему классу.

Search Icon

Класс внутри другого класса имеет смысл создавать когда внутренний и внешний класс имеют сильную смысловую связь.

Допустим, мы создаём класс Car (машина), и нам нужно описать двигатель этой машины. Логично, что двигатель тесно связан с машиной и не имеет смысла как самостоятельный объект без неё. В таком случае можно определить класс двигателя как внутренний класс внутри класса Car.

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

Соответственно, сначала создаётся объект внешнего класса — машины, а уже на его основе создаётся объект внутреннего класса — двигателя. Это отражает логическую зависимость и облегчает доступ двигателя к данным машины.

Поясним на примере:

class Car { private int carVarible = 100; void print() { //Если во внешнем классе нам нужно //пользоваться внутренним классом //то нужно создать объект внутреннего класса. Motor motor = new Motor(); motor.MotorMethod(); } //Внутренний класс. class Motor { public void MotorMethod() { //Внутренний же класс может обращаться к //элементам внешнего без создания экземпляра System.out.println(carVarible); } } } class InnerClass { public static void main(String[] args) { Car car = new Car(); //Создание внутреннего класса. //Как видим его нельзя создать //без объекта внешнего. Car.Motor motor = car.new Motor(); motor.MotorMethod(); } }

Вывод:

Статические поля в Java

Допустим у нас есть класс и мы хотим чтобы какие-то поля или методы этого класса принадлежали одновременно всем объектам этого класса.

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

Объявляются с помощью ключевого слова static.

Поясним на примере:

import java.io.*; public class StaticVarAndMethod { //статическая переменная static int staticVar; //обычная переменная int NotStatic; public static void main(String args[]) { //создадим пару объектов класса StaticVarAndMethod StaticVarAndMethod staticVAndM = new StaticVarAndMethod(); StaticVarAndMethod staticVAndM1 = new StaticVarAndMethod(); //Переменная staticVar общая //для всех объектов StaticVarAndMethod, //поэтому изменение этой переменной //в одном объекте приведет к её изменению в другом. staticVAndM.staticVar = 100; staticVAndM1.staticVar = 85; //В объекте staticVAndM поле staticVar содержит 85, //поэтому нижняя строчка выведет 85. System.out.println(staticVAndM.staticVar); //В объекте staticVAndM1 поле staticVar тоже содержит 85, //поэтому нижняя строчка выведет 85. System.out.println(staticVAndM1.staticVar); //То есть последнее изменение staticVar повлияло //на staticVar в обоих объектах, так как staticVar //это одно и тоже поле, но оно доступно обоим объектам. //Переменная NotStatic не является общей //для всех объектов StaticVarAndMethod, //поэтому изменение этой переменной //в одном объекте не приведет к её изменению в другом. staticVAndM.NotStatic = 100; staticVAndM1.NotStatic = 87; //В объекте staticVAndM поле NotStatic содержит 100, //поэтому нижняя строчка выведет 100. System.out.println(staticVAndM.NotStatic); //В объекте staticVAndM1 поле NotStatic уже содержит 87, //поэтому нижняя строчка выведет 87. System.out.println(staticVAndM1.NotStatic); //Это не статическое поле, поэтому //у каждого объекта свое личное поле NotStatic. } }

Вывод:


Также бывают статические методы.

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

Search Icon

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

Поясним на примере:

public class StaticVarAndMethod { // Статическая переменная static int staticVar; int NotStatic; // Статический метод public static void deposit(int amount) throws ArrayIndexOutOfBoundsException { // Обращаемся к статическому полю staticVar staticVar += amount; } public static void main(String args[]) { StaticVarAndMethod staticVAndM = new StaticVarAndMethod(); StaticVarAndMethod staticVAndM1 = new StaticVarAndMethod(); // Как видим ниже, вызываем deposit через класс, // а не через какой-либо объект. StaticVarAndMethod.deposit(3); // Добавим 3 к staticVar System.out.println(staticVAndM.staticVar); System.out.println(staticVAndM1.staticVar); // В обоих объектах staticVar теперь 3 } }

Вывод:

Интерфейсы в Java

Интерфейсопределяет поведение классов.

В нем могут быть только поля и абстрактные методы (методы без реализаций, как мы помним).

Интерфейс создается с использованием ключевого слова interface.

Класс может реализовать интерфейс используя ключевое слово implements.

Search Icon

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

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

Поясним на примере:

//интерфейс с именем Flyable interface Flyable { //поля String species = “d”; int age = 10; //и абстрактный метод public void birdVoice(); //abstract уже не пишется так как //это интерфейс, а не абстр. класс } //Пускай теперь Flyable заимплементят //(лучше так говорить реализуют) два класса. //От интерфейса они будут наследовать поля //и должны реализовать все //его абстрактные методы. class Parrot implements Flyable { //реализуем абстрактный метод public void birdVoice(){ System.out.println(“RRAAAR!”); } }; class Sparrow implements Flyable { //реализуем абстрактный метод public void birdVoice(){ System.out.println(“Chick-Chirik”); } }; class InterfaceExample { //Сразу как пример использования //интерфейсов приведем пример полиморфизма //с интерфейсами. Приведем пример //передачи в метод любого объекта //класс которого реализует Flyable. //Создадим такой метод. public static void birdVoiceMethod(Flyable flyable){ flyable.birdVoice(); } //Как видим в метод можно передать Flyable, //а не конкретный Parrot или Sparrow. //Значит что можем туда передать любой объект //класс которого реализует Flyable. //Например birdVoiceMethod(parrot); public static void main(String[] args) { Parrot parrot = new Parrot(); Sparrow sparrow = new Sparrow(); //выводим разные реализации parrot.birdVoice(); sparrow.birdVoice(); //передаем объект попугая(он реализует Flyable) birdVoiceMethod(parrot); //передаем объект воробья(он тоже реализует Flyable) birdVoiceMethod(sparrow); } }

Вывод:


Интерфейсы vs Абстрактные классы

Абстрактные классы и интерфейсы часто сравнивают. Типа зачем интерфейсы если есть абстрактные классы? Или наоборот.

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

Search Icon

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

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

Еще раз. Абстрактный класс это недопиленный класс, объект(экземпляр) которого имеет состояние, а интерфейс просто предоставляет поведение и в этом основная разница.

Как уже было сказано, поля абстрактного класса можно менять.

Продемонстрируем на примере:

abstract class Animal { String name = “Barsik”; } class Cat extends Animal { } class AbstractClass { public static void main(String[] args) { Animal interfeceMutation = new Cat(); interfeceMutation.name = “Kuzya”; System.out.println(interfeceMutation.name); } }

Вывод:

Поля же интерфейса менять нельзя. Их можно только извлекать.

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

interface Animal { String name = “Barsik”; } class Cat implements Animal { } class InterfaceClass { public static void main(String[] args) { Animal interfeceMutation = new Cat(); interfeceMutation.name = “Kuzya”; System.out.println(interfeceMutation.name); } }

Вывод: