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

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

Бесплатный курс по Java: от основ до продвинутого уровня
Здесь вы познакомитесь с объектно-ориентированным программированием в Java: классы, наследование, инкапсуляция и полиморфизм и многое другое.
Рассмотрим передачу ссылочного типа в метод и передачу типа значения в метод.
Для начала передадим в метод тип значения.
Вывод:

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

В Java есть два типа данных – ссылочные типы и типы значений.
int, char ,float и т.д это типы значений. Это простые типы. Это просто число либо символ, либо дробное число и т.д. Это не классы (ссылочные типы), которые могут состоять из многих полей типов значений:
То есть классы могут содержать простые типы типа int,char,float. То есть они сложны так как классы состоят из простых типов значений или также могут содержать в себе другие сложные типы.
Так вот почему же классы ссылочные, а типы значений нет?
Дело в том, что когда мы создаем объект на основе класса с помощью слова new, то в памяти выделяется под него участок и там хранятся значения полей этого объекта.
То есть созданный объект имеет свое состояние в этой области памяти и на эту область памяти можно ссылаться. Можно сказать, что объект это и есть эта область памяти, поэтому можно говорить, что происходит ссылка на объект.
Типы значений же это простые типы они хранят само значение в себе, а не ссылку на участок памяти в котором храниться много разных значений.
Подробнее о ссылках описано в примере далее.
Вывод:

Теперь то же самое только с типами значений.
Вывод:

Статический класс это всегда внутренний класс. Статического не внутреннего класса не бывает.
Обычный внутренний класс и статический внутренний класс часто сравнивают.
Главное отличие статического от обычного внутреннего в том что не нужно создавать экземпляр внешнего класса для обращения к его элементам, но обращаться внутренний статический класс может только к статическим элементам внешнего класса.
То есть отличаеться такой класс от просто внутреннего тем что он не сильно связан с внешним классом, его можно создавать и использовать без объекта внешнего класса, так как, как уже было сказано он не может работать (и ему и не нужно) с нестатическими элементами внешенго класса, а соответственно связь слабая.
Он работает лишь со статическими элементами внешнего класса то есть с элементами, которые общие для всех обьектов внешнего класса, и поэтому связь не сильная, поскольку статические элементы не зависят от обьекта класса, то есть необязательно создавать обьект внешнего класса для использования обьектом внутреннего статического класса статических членов обьекта внешнего класса.
Например есть класс Торт Наолеон.
У него есть внутри какая-то статическая переменная, например колличество масла, и внутренний статический класс Рецепт Торта Наполеон.
В отличие от мотора, который выполняет важные функции в машине и без которого машина не поедет и от которого нет смысла без машины для которой он существует рецепт торта наполеон очевидно может существовать отдельно от самого торта наполеон, но тем не менее он всё равно имеет смысловую связь с тортами наполеон, ведь торты наполеон готовяться по этому рецепту. Поэтому Рецепт торта наполеон есть смысл описать как внутренний статический класс класса Торт Наполеон.
И соответственно обьект внутреннего статического класса Рецепт торта наполеон будет иметь лишь общую связь со всеми обьектами класса торт наполеон, поскольку будет иметь связь только со статическими (общими) элементами класса торт наполеон.
Поясним на примере:
Вывод:

Внутренний класс – класс внутри другого класса.
Некоторая доп. функциональность, которая относиться только к внешнему классу.
Класс внутри другого класса имеет смысл создавать когда внутренний и внешний класс имеют сильную смысловую связь.
Допустим, мы создаём класс Car (машина), и нам нужно описать двигатель этой машины. Логично, что двигатель тесно связан с машиной и не имеет смысла как самостоятельный объект без неё. В таком случае можно определить класс двигателя как внутренний класс внутри класса Car.
Внутренний класс имеет доступ ко всем членам внешнего класса, включая приватные поля. Это ещё больше подчёркивает их взаимосвязь: объект двигателя (внутреннего класса) не существует без объекта машины (внешнего класса).
Соответственно, сначала создаётся объект внешнего класса — машины, а уже на его основе создаётся объект внутреннего класса — двигателя. Это отражает логическую зависимость и облегчает доступ двигателя к данным машины.
Поясним на примере:
Вывод:

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

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

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

Абстрактные классы и интерфейсы часто сравнивают. Типа зачем интерфейсы если есть абстрактные классы? Или наоборот.
Ключевое отличие абстрактного класса от интерфейса в том, что у абстрактного класса есть состояние. То есть у него есть поля у которых есть состояние, то есть значения этих полей можно изменять, то есть у абстрактного класса по сути есть экземпляр(объект), а экземпляры наследников абстрактного класса имеют доступ к полями экземпляра абстрактного класса, который они наследуют.
Хотя важно заметить, что сам объект абстрактного класса напрямую создать нельзя, он как бы создается когда мы создаем объект его наследника.
А у интерфейса нет экземпляра и не может быть, так как интерфейс это просто поведение или каркас, которого должен придерживаться реализующий его класс. Экземплярами могут быть только реализующие этот интерфейс классы.
Еще раз. Абстрактный класс это недопиленный класс, объект(экземпляр) которого имеет состояние, а интерфейс просто предоставляет поведение и в этом основная разница.
Как уже было сказано, поля абстрактного класса можно менять.
Продемонстрируем на примере:
Вывод:

Поля же интерфейса менять нельзя. Их можно только извлекать.
Как можно увидеть ниже скомпилировать код в котором меняеться поле интерфейса не получиться.
Вывод:

Абстрактный класс – общий концепт чего-либо.
Это класс, который содержит в себе нереализованные методы (называются абстрактными методами), которые наследники должны реализовать.
Под ‘нереализованные’ имеется ввиду, что такие методы только объявляются в абстрактном классе, то есть у них нет тела, то есть кода внутри {}.
Абстрактные методы помечаются ключевым словом abstract.
Приведем пример абстрактного класса. Например птица. Птица это общий концепт для любой птицы, будь-то воробей или попугай.
Абстрактный класс птицы может иметь не только абстрактные методы, а и методы с реализацией.
Например.
Любая птица имеет крылья. Махание крыльями примерно ОДИНАКОВОЕ у всех птиц (ну по крайней мере представим так). Соответственно можно создать в абстрактном классе не абстрактный, а обычный метод с реализацией для махания крыльями и все классы птиц, которые наследуют от абстрактного класса Птица будут по умолчанию пользоваться методом для махания крыльями из абстрактного класса.
Абстрактный же метод в абстрактном классе Птица будет иметь смысл определить в таком случае:
Любая птица издает какие-то звуки. Но в этом уже случае почти все птицы издают РАЗНЫЕ звуки. Значит в абстрактном классе Птица должен быть абстрактный метод для издавания звуков, который наследующие от этого абстрактного класса птицы обязаны переопределить.
То есть суть в том, что если мы знаем, что у класса, который мы создаем будут наследники и у всех них будет метод, который будет выполнять одно и то же действие (например, издавание звуков у птиц), но результат этого метода у всех наследников будет разным (разные звуки), то есть смысл объявить создаваемый нами класс абстрактным и объявить этот метод абстрактным.
Из примеров абстрактных классов можно еще привести, например, Форма (форму реализует например куб и треугольник), средство передвижения (реализует машина или велосипед)
Воспринимайте абстрактный класс как недопиленный класс, который наследники должны допилить по своему посредством реализации абстрактных методов этого абстрактного класса. Недопиленный он потому что в нем всё же присутствуют реализации (пример метода махания крыльями), но в нем и хватает абстрактных методов, которые должны допилить наследники.
Поясним на примере:
Вывод:

Полиморфизм — возможность использовать одно и то же имя для разных реализаций методов или конструкторов.
В Java полиморфизм реализуется с помощью перегрузки (overloading) и переопределения (overriding).
Перегрузка позволяет в пределах одного класса создавать несколько методов (или конструкторов) с одинаковым именем, а отличаться они будут колличеством или типом параметров.
Возникает закономерный вопрос: когда мы воспользуемся одним из таких методов с одинаковым именем, как компилятор определяет, какую реализацию выбрать? Ответ прост — он делает это на основании числа и типов переданных аргументов.
Рассмотрим пример перегрузки конструкторов:
Вывод:

Даже несмотря на то, что у всех конструкторов одинаковое имя Animal, компилятор различает их по параметрам. Это и есть пример перегрузки конструктора, которая является одной из форм полиморфизма.
Однако помним, что полиморфизм может применяться не только к конструкторам, но и к методам.
Под полиморфизмом также понимается переопределение метода родительского класса в классе-наследнике.
Например, ниже в классе Animal (Животное) определён метод move() (Движение), который просто сообщает, что животное может двигаться. Его реализация — вывод строки "The animal moves." — является универсальной и подходит для любого животного. Такой метод вполне может использоваться без изменений в классах-наследниках.
Однако в некоторых случаях желательно уточнить поведение метода. Например, если мы создадим класс Cat (Кот), который наследуется от Animal, то он унаследует и метод move(). Но в контексте кошки может быть полезно уточнить поведение этого метода, поскольку, разумеется, куда лучше, если move() будет описывать именно движения, характерные для кошки — например, "The cat gracefully walks and jumps." — а не просто общее "The animal moves."
В таких случаях метод move() можно переопределить в классе-наследнике, чтобы сделать поведение более конкретным и реалистичным.
Это и есть проявление полиморфизма: один и тот же метод, но разная реализация в разных классах.
Рассмотрим пример:
Вывод:

Каждое животное, наследующее класс Animal, может по-своему переопределить метод move(). Так, у птицы это может быть “летает”, у собаки — “бежит”, и так далее.
Переопределение методов — мощный механизм, позволяющий делать поведение объектов более точным и соответствующим их конкретному типу, оставаясь при этом в рамках единой абстракции.
Наследование дает возможность одному классу получить доступ к полям и методам другого класса. Однако между этими классами должна быть логическая связь: от общего к частному.
Например, в программе ниже создаётся класс Animal (Животное).
Затем создаётся класс Cat (Кот), который с помощью ключевого слова extends наследует Animal, потому что кот — это частный случай животного, то есть кот является животным.
Животными, разумеется, являются и собака, и птица, и кенгуру и т.д., поэтому они тоже могут наследовать от Animal. То есть класс Animal общий в том плане, что от него могут наследовать классы разных конкретных животных.
При наследовании класса Animal, класс Cat получает доступ напрямую ко всем полям и методам класса Animal.
Например, если у класса Animal есть поля: количество ног, тип питания и т.д., то класс Cat, наследующий Animal, также будет обладать этими полями. И если мы создадим объект кота в методе main, то сможем обращаться к этим полям через объект кота — как будто они определены непосредственно в классе Cat. Таким образом, можно сказать, что создаётся единый объект, состоящий из данных класса Animal и класса Cat.
Рассмотрим пример:
Вывод:

Основное преимущество наследования очевидно — уменьшение количества кода за счет того, что довольно большая часть кода может быть вынесена в класс родитель, а наследники этот код могут просто использовать, так сказать, “подключаясь” с помощью extends к классу родителю.
Кроме того, наследование помогает логично организовать структуру программы — от общего к частному — и упрощает сопровождение: изменения в коде родителя автоматически отражаются во всех наследниках.
В Java поля класса никогда не следует определять как public, то есть они не должны быть доступны напрямую. Вместо этого поля должны быть private, а доступ к ним должен обеспечиваться с помощью специальных методов – геттеров и сеттеров.
геттер – это public метод через который мы получаем значение private поля.
сеттер – это public метод через который мы задаем значение private полю.
То есть мы работаем с private полем не напрямую (напрямую это так – somecat.name = “Barsik”;), а через public методы класса, то есть методы, которые доступны не только в пределах класса.
Определяется один геттер и один сеттер на каждое private поле класса.
Поясним на примере:
Вывод:

На первый взгляд может показаться, что проще объявить поле public и обращаться к нему напрямую через объект. Зачем усложнять?
Однако на деле, чтобы была возможность при необходимости накладывать ограничения на допустимые значения поля класса, нужно использовать сеттер. Сеттер — это обычный метод, а значит, мы можем добавить в него любую дополнительную логику, которая будет отфильтровывать недопустимые значения и не позволять им попасть в поле. Такой подход помогает контролировать данные, которые записываются в поля объекта, и предотвращает появление нежелательных значений.
Допустим, если в классе Cat есть поле name, которое public, то напрямую через объект этого класса можно задать значение этого поля. Например “З” (вот так – somecat.name = “З”;).
Ну кто называет кошку одним символом? – Никто. Поэтому в сеттере можно задать ограничение, чтобы имя кошки содержало минимум два символа.
Поясним на примере:
Вывод:
