# ООП / Java for Beginners H.Shildt
## Основы
**Инкапсуляция** - это механизм программирования который связывает вместе код и данные которыми он манипулирует, и это защищает их от внешнего воздействия и злоупотребления. В ООП языке код и данные могут быть связаны таким образом, что будет создан замкнутый черный ящик. Когда код и данные соединяются вместе таким образом, получается **объект**. Другими словами объект это конструкция поддерживающая инкапсуляцию.
Внутри объекта код, данные или и то и другое, могут быть приватными к этому объекту или публичными. Приватный код или данные известны и доступны только внутри своего объекта. Когда код или данные публичны, они доступны другим частям программы за пределами их объекта. Обычно публичные части объекта используются чтобы предоставить контролируемый интерфейс к приватным элементам объекта.
Базовой единицей инкапсуляции в Java является класс. Код и данные, которые составляют класс, называются членами класса. В частности, данные, определенные классом, называются переменными члена или переменными экземпляра. Код, который работает с этими данными, называется методом.
==Метод - это термин Java для подпрограммы. Если вы знакомы с C/C++, это может помочь узнать, что то, что программист на Java называет методом, программист на C/C++ называет функцией.==
**Полиморфизм** - это качество, которое позволяет одному интерфейсу получать доступ к общему классу действий. Конкретное действие определяется точным характером ситуации. Простой пример полиморфизма можно найти в руле автомобиля. Рулевое колесо (то есть интерфейс) является одинаковым независимо от того, какой тип фактического рулевого механизма используется. То есть рулевое колесо работает одинаково, независимо от того, имеет ли ваш автомобиль ручное рулевое управление, гидроусилитель руля или реечное рулевое управление. Поэтому, когда вы знаете, как управлять рулем, вы можете управлять любым типом автомобиля.
Тот же принцип применим и к программированию. Например, рассмотрим стек(который является списком первым вошел, последним вышел). У вас может быть программа, которая требует три различных типа стеков. Один стек используется для целочисленных значений, один для значений с плавающей точкой и один для символов. В этом случае алгоритм, который реализует каждый стек, одинаков, даже если сохраняемые данные отличаются. В не объектно-ориентированном языке вам потребуется создать три различных набора стек подпрограмм, каждый из которых использует разные имена.
В более общем смысле понятие полиморфизма часто выражается фразой «один интерфейс, несколько методов». Это означает, что можно создать общий интерфейс для группы связанных действий. Полиморфизм помогает уменьшить сложность, позволяя использовать тот же интерфейс для указания общего класса действий. Задача компилятора состоит в том, чтобы выбрать конкретное действие (т.е. метод) применительно к каждой ситуации. Вам, программисту, не нужно делать этот выбор вручную. Вам нужно только запомнить и использовать общий интерфейс.
**Наследование** - это процесс, с помощью которого один объект может приобрести свойства другого объекта. Это важно, потому что поддерживает концепцию иерархической классификации.
Если вы подумаете об этом, большая часть знаний является управляемой иерархическими(т.е. сверху вниз) классификациями. Например, яблоко Red Delicious является частью классификации яблоко, которое, в свою очередь, является частью класса фрукт, который относится к более крупному классу продукты питания. То есть, у пищевого класса есть определенные качества(съедобный, питательный и т.д.), которые также, по логике, относятся к его подклассу - фрукт. В дополнение к этим качествам у класса фрукт есть особые характеристики (сочный, сладкий и т.д.), которые отличают его от других продуктов питания. Класс яблоко определяет те качества, которые характерны для яблока (растет на деревьях, не в тропиках и т. д.). Яблоко Red Delicious, в свою очередь, унаследует все качества всех предыдущих классов и определит только те качества, которые делают его уникальным. Без использования иерархий каждый объект должен был бы явно определить все свои характеристики. Используя наследование, объекту нужно определять только те качества, которые делают его уникальным в своем классе. Он может наследовать свои общие атрибуты от своего родителя. Таким образом, именно механизм наследования позволяет одному объекту быть конкретным экземпляром более общего случая.
## Классы, объекты и методы
**Класс** это шаблон определяющий форму объекта. Он определяет как данные, так и код, который будет работать с этими данными. Java использует спецификацию класса для конструирования объектов.
**Объекты** являются экземплярами класса.
Таким образом, класс - это, по сути, набор планов, которые определяют, как построить объект.
> ==Важно прояснить одну проблему: класс - это логическая абстракция. Физическое представление этого класса не существует в памяти пока объект этого класса не будет создан.==
Методы и переменные, составляющие класс, принято называть членами класса.
Данные, которые являются членами класса называюся - переменные экземпляра.
При определении класса, объявляются его точная форма и поведение. Это делается указыванием переменных экземпляра и методов оперирующих переменными.
```java=
class name {
// объявление переменных экземпляра
type var1;
type var2;
...
type varN;
//объявление методов
type method1(parameters) {
// тело метода
}
type method2(parameters) {
// тело метода
}
...
type methodN(parameters) {
//...
}
}
```
Хотя нет синтаксического правила, которое его применяет, хорошо продуманный класс должен определять один и только один логический объект. Например, класс, в котором хранятся имена и телефонные номера, обычно не будет хранить информацию о фондовом рынке или другую несвязанную информацию. Дело в том, что хорошо продуманный класс группирует логически связанную информацию.
```java=
class Vehicle {
int passengers; // число пассажиров
int fuelcap; // объём бензобака
int mpg; // потребление топлива на милю
}
```
```java=
//создать объект типа Vehicle с именем minivan
Vehicle minivan = new Vehicle();
```
При каждом создании экземпляра класса, создается объект который содержит свои собственные копии переменных экземпляра, определенные классом. Т.о. каждый объект типа Vehicle будет содержать свои собственные копии переменных экземпляра : passengers, fuelcap, mpg. Чтобы обратиться к этим переменным используется оператор **точка**(**.**) . Оператор точка связывает имя объекта с его членом.
```java=
object.member
```
==Важно!==
Каждый объект имеет свои собственные копии переменных экземпляра, определенных его классом. Поэтому содержимое переменных в одном объекте может отличаться от содержимого переменных в другом объекте. Между этими объектами нет связи кроме факта того что они одного типа.
### Создание объекта
Чтобы создать какой-нибудь объект, нужно написать имя типа (класс) этого объекта и ключевое слово **new** перед ним.
```Java=
Vehicle minivan = new Vehicle();
```
Это объявление выполняет 2 функции:
1. Объявляет переменную **minivan** типа класса Vehicle(). Эта переменная еще не определяет объект, а просто дает возможность ссылаться на него.
2. Объявление создает экземпляр объекта и присваивает переменной **minivan** ссылку на этот объект при помощи оператора **new**.
Оператор new динамически выделяет память для объекта и возвращает ссылку на него.
Для того, чтобы создать объект класса, в коде надо написать «new имя_класса()»:
```java=
// Объявляем ссылку на объект типа Vehicle, но это еще не объект
Vehicle minivan;
// Создаем объект типа Vehicle, выделяем для него память и присваиваем
// ссылку на объект переменной minivan
minivan = new Vehicle();
```
В операции присвоения, переменные ссылочного типа ведут себя иначе чем переменные примитвных типов, таких как **int**. Когда вы присваиваете одной переменной примитивного типа другую такую же переменную, то переменная слева получает копию значения переменной справа.
А когда вы присваиваете одну переменную ссылочного типа другой, ситуация усложняется потому что вы изменяете сам объект на который ссылается переменная.
Например:
```java=
Vehicle car1 = new Vehicle();
Vehicle car2 = car1; // car2 и car1 ссылаются на один и тот же объкт
car1.mpg = 26;
System.out.println(car1); // 26
System.out.println(car2); // 26
```
### Конструкторы
Конструктор инициализирует объект при его создании. Как правило конструкторы испольуются для задания первоначальных значений переменным экземпляра, определенных в классе. Или для выполнения любых других первоначальных процедур требующихся при создании полностью сформированного класса.
У всех классов есть конструктор, создавали ли вы его или нет. Java автоматически предоставляет конструктор по умолчанию. В этом случае не инициализированные члены экземпляра получают значения по умолчанию.
Для числовых типов **0**, для ссылочных типов **null**, для boolean типов **false**.
Как только мы создадим свой конструктор, конструктор по умолчанию использоваться больше не будет.
```java=
class MyClass {
int x;
// Конструктор для класса MyClass
MyClass() {
x = 10; // при вызове конструктора переменной x присваивается 10
}
}
class constDemo {
public static void main(String[] args) {
MyClass t1 = new MyClass(); // вызывается конструктор MyClass()
MyClass t2 = new MyClass(); // вызывается конструктор MyClass()
System.out.println(t1.x + " " + t2.x); // 10 10
}
}
```
В этом примере мы использовали конструктор без параметров. Хотя это и нормально для некоторых ситуаций, в основном используются конструкторы принимающие один или более параметров.
```java=
class MyClass {
int x;
MyClass(int i) { // Конструктор для класса MyClass
x = i; // при вызове конструктора x присваивается
} //передаваемый параметр i
}
class constDemo {
public static void main(String[] args) {
MyClass t1 = new MyClass(10);//вызывается конструктор MyClass(int i)
MyClass t2 = new MyClass(88);//вызывается конструктор MyClass(int i)
System.out.println(t1.x + " " + t2.x); // 10 88
}
}
```
Общая форма оператора new:
```java=
class-var = new class-name(arg-list);
// class-var - имя создаваемой переменной ссылочного типа.
// class-name - имя класса экземпляр которого создается.
// Оператор new возвращает ссылку на только что созданный объект,
// которая в свою очередь присваивается переменной
// ссылочного типа class-var.
```
**Перегрузка конструкторов**
Конструкторы как и методы можно перегружать, это позволяет конструировать объекты разными способами:
```java=
class ConstOverload {
int x;
ConstOverload() {
System.out.println("Inside ConstOverload().");
x = 0;
}
ConstOverload(int i) {
System.out.println("Inside ConstOverload(int).");
x = i;
}
ConstOverload(double d) {
System.out.println("Inside ConstOverload(double).");
x = (int)d;
}
ConstOverload(int i, int j) {
System.out.println("Inside ConstOverload(int, int).");
x = i*j;
}
}
class MyClass {
public static void main(String[] args) {
ConstOverload t1 = new ConstOverload();
ConstOverload t2 = new ConstOverload(88);
ConstOverload t3 = new ConstOverload(17.23);
ConstOverload t4 = new ConstOverload(2, 4);
System.out.println("t1.x: " + t1.x);
System.out.println("t2.x: " + t2.x);
System.out.println("t3.x: " + t3.x);
System.out.println("t4.x: " + t4.x);
}
}
```

В данном примере конструктор ConstOverload() перегружается четырежды. Во всех перегруженных версиях этого конструктора объект типа ConstOverload создается по-разному. Конкретный вариант конструктора выбирается на основании параметров, которые указываются при выполнении оператора new.
Перегрузка конструкторов чаще всего используется для того, чтобы дать возможность инициализировать один объект на основани и другого объекта.
```java=
class Summation {
int sum;
Summation(int num) {
sum = 0;
for(int i=1; i <= num; i++) {
sum+=i;
}
}
// создаём объект на основе другого
Summation (Summation obj) {
sum = obj.sum;
}
}
class MyClass {
public static void main(String[] args) {
Summation s1 = new Summation(5);
Summation s2 = new Summation(s1); // s2.sum = s1.sum
System.out.println("s1.sum = " + s1.sum);
System.out.println("s2.sum = " + s2.sum);
}
}
//Вывод программы:
//s1.sum = 15
//s2.sum = 15
```
Как следует из приведенного выше примера, использование одного объекта при инициализации другого нередко вполне оправданно. В данном случае при создании объекта s2 нет необходимости вычислять сумму. Даже если подобная инициализация не повышает быстродействие программы, зачастую удобно иметь конструктор, создающий копию объекта.
### Инициализация объекта
==Когда объект создаётся== – его переменным нужно присвоить стартовые данные. Чтобы не было ситуаций, когда ты обращаешься к объекту, а внутри у него нет нужной ему информации для правильной работы.
Рассмотрим для примера объект типа File (файл). Минимальной необходимой информацией для файла является его имя.
```java=
class MyFile
{
private String filename = null;
public void initialize(String name)
{
this.filename = name;
}
…
}
//Пример
MyFile file = new MyFile();
file.initialize("c:\data\a.txt");
```
Представим, что другому программисту, который будет использовать наш класс, удобнее передавать в него не полное имя файла, а директорию и короткое имя файла. Мы бы смогли реализовать эту функциональность с помощью ещё одного метода **initialize**:
```java=
class MyFile
{
private String filename = null;
public void initialize(String name)
{
this.filename = name;
}
public void initialize(String folder, String name)
{
this.filename = folder + name;
}
}
//Пример
MyFile file = new MyFile();
file.initialize("С:\data\", "a.txt");
```
А ещё, часто нужно создать временную копию файла рядом с текущим:
```java=
class MyFile
{
private String filename = null;
public void initialize(String name)
{
this.filename = name;
}
public void initialize(String folder, String name)
{
this.filename = folder + name;
}
// Файл filename будет находиться в той же директории, что и file.
public void initialize(MyFile file, String name) {
this.filename = file.getFolder() + name;
}
}
//Пример
MyFile file = new MyFile();
file.initialize("c:\data\a.txt");
//создаем file2 там же где и file
MyFile file2 = new MyFile();
file2.initialize(file, "a.txt");
```
метод **initialize** надо использовать сразу после создания объекта, чтобы перевести его в валидное состояние.
### Методы
Методы - это подпрограммы, которые манипулируют данными, определенными классом, и во многих случаях обеспечивают доступ к этим данным. В большинстве случаев,
другие части вашей программы будут взаимодействовать с классом через его методы.
Метод содержит одну или более инструкций. В хорошо написанном Java коде, метод выполняет только одну задачу. Вообще методу можно давать любое имя, кроме ключевых слов Java(for, new...). Также имя main() зарезервировано для метода с которого начинается наша программа.
```java=
//Общая форма записи метода
return-type name(parameters) {
// тело метода
}
```
**return-type** указывает на тип данных возвращаемых методом. Если метод ничего не возвращает то будет **void**.
**parameters** - это список пар вида (тип идентификатор) разделенных запятымы. Если в метод ничего не передается при вызове то скобки будут пустыми.
```java=
class Vehicle {
int passengers; // число пассажиров
int fuelcap; // обеъем бензобака
int mpg; // потребление топлива на милю
//Отобразить дистанцию кот. может проехать транспорт на одной заправке
void range() {
System.out.println("Дистанция = " + fuelcap*mpg);
//мы обращаемся к переменным fuelcap,mpg напрямую без точки!
}
}
class AddVehicle {
public static void main(String[] args) {
Vehicle minivan = new Vehicle();
Vehicle sportscar = new Vehicle();
minivan.passenges = 7;
minivan.fuelcap = 16;
minivan.mpg = 21;
sportscar.passenges = 2;
sportscar.fuelcap = 14;
sportscar.mpg = 12;
minivan.range(); // Дистанция = 336
sportscar.range();// Дистанция = 168
}
}
```
метод range() имеет тип void так как ничего не возвращает. Т.к. каждый объект типа Vehicle имеет свой набор переменных, вызванный метод range() использует копии переменных объекта вызвавшего метод.
```java=
// Эта инструкция вызывает метод range() на объекте minivan
minivan.range();
```
**Передача методов параметру**
В общем смысле сущетсвует 2 способа передачи параметров методу:
1. По значению
При таком способе значение аргумента копируется в соответствующий параметр метода, и все изменения внесенные в параметр метода никак не отразятся на аргументе переданном методу.
2. По ссылке
В этом случае параметру метода передаётся ссылка на сам аргумент, а не его значение. Поэтому любые изменения внесённые в параметр метода, изменят сам аргумент.
При вызове метода управление программой передается этому методу. Когда метод завершается, управление передается обратно вызывающей стороне, и выполнение возобновляется со строки кода, следующей за вызовом.
Необходимо отметить следующую особенность метода **range()** : обращение к переменным экземпляра **fuelcap** и **mpg** осуществляется напрямую, без применения точечной нотации. Метод всегда вызывается в отношении какого-либо объекта класса. Как только произошел вызов объект известен. Это означает что переменные **fuelcap** и **mpg** внутри **range()** неявно относятся к копиям переменных объекта который вызвал метод **range()**.
**Возврат из метода**
Есть два условия которые вызывают завершение метода:
1. Когда встречается закрывающая фигурная скобка **}**
2. Когда встречается инструкция **return**
В методе типа **void** можно вызвать немдленное завершение инструкцией - **return;** .
Допустимо иметь в методе несколько инструкций return, особенно если в нем есть несколько ветвей исполнения:
```java=
void method() {
...
if (true) return;
...
if (error) return;
...
}
```
==Применяя инструкции return, следует соблюдать осторожность: слишком большое количество точек возврата из метода нарушает структуру кода. В хорошо-спроектированном методе точки выхода из него находятся в продуманных местах.==
Хотя методы типа void не редки, большинство методов возвращают значения.
```java=
return value;
```
В некоторых случаях возвращаемые значения являются результатом вычислений, в некоторых просто сообщают об успехе или провале действий в методе, а также могут содержать код состояния.
==Не-void метод должен возвращать значение используя инструкцию return value; !==
Можно изменить метод range() из предыдущего примера:
```java=
int range() {
return fuelcap*mpg;
}
```
Тип возвращаемого значения важен потому что тип данных возвращаемых методом должен быть совместим с типом возращаемого значения указанного в определении метода.
**Использование параметров**
При вызове метода, можно передавать ему одно или более значений. Значение передаваемое методу называется **аргументом**. Внутри метода переменная получающая аргумент называется **параметром**. Параметры объявляются внутри круглых скобок идущих после имени метода.
```java=
class chkNum {
// возвращает true если a четное
boolean isEven (int a) { // a - int параметр
if (x%2==0) {
return true;
} else {
return false;
}
}
}
```
Когда используется несколько параметров, каждому параметру указывается свой тип:
```java=
int method(int a, double b, float c) {}
```
**Ключевое слово ++this++**
Когда вызывается метод, ему автоматически передается неявный аргумент являющийся ссылкой на вызывающий объект(то есть объект для которого вызывается метод).
Эта ссылка называется **this**.
```java=
// Программа вычисляющая степень числа
class Pwr {
double b;
int e;
double val;
Pwr(double base, int exp) {
b = base;
e = exp;
val = 1;
if (exp == 0) return;
for (; exp > 0; exp--) val=val*base;
}
double get_pwr() {
return val;
}
}
class demoPwr {
public static void main(String[] args) {
Pwr x = new Pwr(2.0, 5);
System.out.println(x.get_pwr()); // 32.0
}
}
```
Как вы знаете, внутри метода к другим членам класса можно обращаться напрямую, без какого-либо объекта или определения класса.
**return val;** - означает, что будет возвращена копия val ассоциированная с вызывающим объектом.
Это выражение можно переписать так - **return this.val;**
**this** ссылается на объект который вызвал **get_pwr()**
Например если **х** вызвал **get_pwr()**, то **this** будет ссылаться на **х**.
Наш вариант программы без this просто укороченная версия.
```java=
// Версия с this
class Pwr {
double b;
int e;
double val;
Pwr(double base, int exp) {
this.b = base;
this.e = exp;
val = 1;
if (exp == 0) return;
for (; exp > 0; exp--) val=val*base;
}
double get_pwr() {
return this.val;
}
}
```
На самом деле ни один программист не напишет класс Pwr подобным образом, поскольку добавление this не дает никаких преимуществ. В тоже время стандартная форма записи тех же инструкций выглядит значительно проще. Но в ряде случаев this может оказаться очень полезным.
Например в Java разрешается называть параметры и локальные переменные так же как глобальные переменные экземпляра. В этом случае локальные переменные перекрывают переменные экземпляра. Но можно получить доступ к перекрытым переменным при помощи ключевого слова **this**.
Перепишем наш конструктор Pwr(double base, int exp) :
```java=
Pwr(double b, int e) {
this.b = b;
this.e = e;
val = 1;
if (exp == 0) return;
for (; exp > 0; exp--) val=val*base;
}
```
В этом примере параметры b, e перекрывают переменные экземпляра, но **this** их открывает.
**Возврат объектов методами**
Методы могут возвращать данные любых типов, включая объекты классов которые мы создаём.
```java=
class Err {
String msg;//Сообщение об ошибке
int severity;//код серьёзности ошибки
Err(String m, int s) {
msg = m;
severity = s;
}
}
class ErrorInfo {
String[] msgs = {"Output Error","Input Error","Disk Full",
"Index out of bounds"};
int[]howBad = {3, 3, 2, 4};
Err getErrorInfo(int i) {
if (i >= 0 && i < msgs.length)
return new Err(msgs[i], howBad[i]);
else
return new Err("Invalid Error Code", 0);
}
}
class MyMain {
public static void main(String[] args) {
ErrorInfo errorInfo = new ErrorInfo();
Err e;
e = errorInfo.getErrorInfo(2);
System.out.println(e.msg + " Severity: " + e.severity);
e = errorInfo.getErrorInfo(19);
System.out.println(e.msg + " Severity: " + e.severity);
}
}
```
### Модификаторы доступа
1. Модификатор «**public**».
К переменной, методу или классу, помеченному модификатором public, можно обращаться из любого места программы. Это самая высокая степень открытости — никаких ограничений нет.
2. Модификатор «**private**».
К переменной или методу, помеченному модификатором private, можно обращаться только из того же класса, где он объявлен. Для всех остальных классов помеченный метод или переменная — невидимы и «как бы не существуют». Это самая высокая степень закрытости – только свой класс.
3. Без модификатора(**package**).
Если переменная или метод не помечены никаким модификатором, то считается, что они помечены «модификатором по умолчанию». Переменные или методы с таким модификатором(т.е. вообще без какого-нибудь) видны всем классам пакета, в котором они объявлены. И только им. Этот модификатор еще иногда называют «**package**», намекая, что доступ к переменным и методам открыт для всего пакета, в котором находится их класс
4. Модификатор **Protected**
Модификатор доступа стоит в начале объявления члена класса:
```java=
public String string;
private Boolean isError(byte status) {...}
```
### Геттеры и сеттеры
```java=
class Cat {
/*name – это переменная класса. У нее модификатор доступа private,
поэтому она видна только в классе Cat. */
private String name;
//Геттер
public String getName() {
return name;
}
//Сеттер
public void setName(String name) {
this.name = name;
}
}
```
Метод **getName** – это getter, т.е. он возвращает значение переменной-класса name. Имя метода строится по принципу **get** + «имя переменной с большой буквы».
Метод **setName** – это setter, т.е. он используется для присваивания нового значения переменной-класса name. Имя метода строится по принципу **set** + «имя переменной с большой буквы». В этом методе имя параметра совпадает с именем переменной класса, поэтому мы ставим **this** перед именем переменной.
```java=
class MyClass {
private int alpha; // private переменная
public int beta; // public переменная
int gamma; // имеет тип по умолчанию(package)
void setAlpha(int a) {
alpha = a;
}
int getAlpha() {
return alpha;
}
}
class AccessDemo {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.setAlpha(-99); // поменяли значение через сеттер
System.out.println("alpha = " + obj.getAlpha()); // геттер
obj.alpha = 10; // Ошибка!
obj.beta = 20; // Всё в порядке тк beta public
obj.gamma = 30;// // Всё в порядке gamma имеет доступ по умолчанию
}
}
```
### Перегрузка методов
Несколько методов одного класса могут иметь одно и тоже имя, отличаясь лишь набором параметров. Подобные методы называются перегруженными, а сам процесс называют перегрузкой методов. Перегрузка методов является одним из способов реализации принципа полиморфизма в Java.
Чтобы перегузить метод достаточно объявить его другую версию.
Но необходимо соблюдать одно важное ограничение: ==тип и/или число параметров каждого перегруженного метода должно различаться!==
Если 2 метода будут отличать только типом возвращаемых значений этого будет недостаточно!
Когда вызывается перегруженный метод, выполняется та его версия чьи параметры соответстуют передаваемым аргументам.
```java=
class Overload {
void overload() {// первая версия метода
System.out.println("No parameters");
}
void overload(int a) {// вторая версия
System.out.println("One parameter: " + a);
}
int overload(int a, int b) {// третья версия
System.out.println("Two parameters: " + a + " " + b);
return a + b;
}
double overload(double a, double b) {// четвёртая версия
System.out.println("Two double parameters: " + a + " " + b);
return a + b;
}
}
class OverloadDemo {
pubclic static void main(String[] args) {
Overload obj = new Overload();
int resultI;
double resultD;
obj.overload();
System.out.println;
obj.overload(3);
System.out.println();
resultI = obj.overload(1,2);
System.out.println("obj.overload(1, 2) = " + resultI);
resultD = obj.overload(2.0, 5.0);
System.out.println("obj.overload(2.0, 5.0) = " + resultD);
}
}
// Вывод программы:
// No parameters
// One parameter: 3
// Two parameters: 1 2
// obj.overload(1, 2) = 3
// Two double parameters: 2.0 5.0
// obj.overload(2.0, 5.0) = 7.0
```
Пример с ошибкой:
```java=
void overload(int a) {
System.out.println("One parameter: " + a);
}
int overload(int a) {
System.out.println("One parameter: " + a);
}
// Будет ошибка компилятора! Так как методы отличаются только типом
// возвращаемых данных
```
При передаче параметров перегруженным методам также работает автоматическое(расширяющее) преобразование типов.
```java=
class overload2 {
void f(int i) {
System.out.println(i);
}
void f(double d) {
System.out.println(d);
}
}
class Main {
public static void main(String[] args) {
overload2 ovr = new overload2();
int i = 1;
byte b = 100;
long l = 100000L;
double d = 15.0;
float f = 11.5F;
ovr.f(i); // calls ovr.f(int) ---> 1
ovr.f(b); // calls ovr.f(int) Преобразование! --> 100
ovr.f(f); // calls ovr.f(double) Преобразование! --> 11.5
ovr.f(d); // calls ovr.f(double) ---> 15.0
ovr.f(l); // calls ovr.f(double) Преобразование! --> 100000.0
}
}
```
Перегрузка методов поддерживает полиморфизм, потомучто это один из способов которым Java реализует парадигму "один интерфейс разные методы". В языках не поддерживающих полиморфизм все методы должны иметь уникальное имя, даже если они делают похожие действия.
Например нахождения модуля числа в С(abs() - для integer, labs() - для long integer, fabs() - для float). В Java благодаря перегрузке есть только один abs(), но написанный для разных типов данных.
Также при перегрузке методов следует соблюдать правило, что перегруженный метод должен производить связанные операции с оригинальным(Как в примере с нахождением модуля).
### Ключевое слово STATIC
Иногда требуется определить такой член класса, который будет использоваться независимо от каких бы то ни было объектов этого класса. Как правило, доступ к члену класса организуется посредством объекта этого класса, но в то же время можно создать член класса для самостоятельного применения без ссылки на конкретный объект. Для того чтобы создать такой член класса, достаточно указать в самом начале его объявления ключевое слово **static**.
Когда член класса объявляется как **static**, он становится доступным до создания объктов класса и без ссылки на какой-либо объект.
Самый общий пример такого члена это метод **main()**. Он определен как static потомучто JVM вызывает его первым когда запускается программа.
Чтобы обратиться к члену класса типа static, за пределами класса где он был объявлен, нужно указать имя его класса и через точку имя самого члена:
```java=
Timer.count = 10; // Timer - класс статической переменной count
```
Статический метод может быть вызван похожим образом:
```java=
Timer.set();
```
Переменные объявленные как статические, по сути, являются глобальными переменными.
При создании объекта класса, копий статических переменных не создаётся. Все экземпляры класса делят одну и туже статическую переменную.
```java=
class staticDemo {
int x; //Обычная переменная экземпляра
static int y;//Статическая переменная
int sum() {
return x + y;
}
}
class MyClass {
public static void main(...) {
staticDemo ob1 = new staticDemo();
staticDemo ob2 = new staticDemo();
ob1.x = 10;
ob2.x = 20;
System.out.println("ob1.x = " + ob1.x + " ob2.x = " + ob2.x);
System.out.println();
staticDemo.y = 19; // Изменили значение статической переменной
System.out.println("staicDemo.y = " + staticDemo.y);
System.out.println("ob1.sum() = " + ob1.sum);//10 + 19 = 29
System.out.println("ob2.sum() = " + ob2.sum);//20 + 19 = 39
}
}
//Статическая переменная общая для всего класса staticDemo
```
Разница между статическим методом и обычным в том, что статический метод вызывается через имя его класса, а не через объекты класса.
У статических методов есть несколько ограничений:
* В статическом методе допускается непосредственный вызов только других статических методов
* В статическом методе можно напрямую обращаться только к статическим переменным его класса
* У статического метода нет ссылки **this**.
```java=
class StaticError {
int n = 3;
static int val = 124;
static int valDivN() {
return val/n; // Ошибка при компиляции!
}
}
//Возникнет ошибка так как статический метод обратился к
//нестатической переменной!
```
**Статический Блок**
Иногда классу требуется некая инициализация до того как он будет готов создавать объекты. Например необходимо установить соединение с удалённым сайтом.
Или инициализировать статические переменные до того как будет использован любой статический метод.
Для таких ситаций Java позволяет объявить статический блок. Статический блок выполняется при первой загрузке класса, тем самым он выполняется до того как класс будет использован.
```java=
class StaticBlock {
static double rootof2;
static double rootof3;
static {
System.out.println("Inside StaticBlock");
rootof2 = Math.sqrt(2);
rootof3 = Math.sqrt(3);
}
StaticBlock(String msg) {
System.out.println(msg);
}
}
class MyMain {
public static void main(...) {
StaticBlock obj = new StaticBlock("Inside Constructor");
System.out.println("Square root of 2 = " + StaticBlock.rootof2);
System.out.println("Square root of 3 = " + StaticBlock.rootof3);
}
}
// Вывод программы:
//Inside StaticBlock - Статический блок выполняется до создания объекта
//Inside Constructor
//Square root of 2 = 1.4142135623730951
//Square root of 3 = 1.7320508075688772
```
### Вложенные и внутренние классы
Вложенным называется такой класс, который объявляется в другом классе. Вложенный класс не может существовать независимо от класса, в который он вложен. Следовательно, область действия вложенного класса ограничена его внешним классом. Если вложенный класс объявлен в пределах области действия внешнего класса, то он становится членом последнего. Имеется также возможность объявить вложенный класс, который станет локальным в пределах блока.
Существуют два типа вложенных классов. Одни вложенные классы объявляются с помощью модификатора доступа static, а другие - без него.Не статические вложенные классы называются внутренними. Внутренний класс имеет доступ ко всем переменным и методам внешнего класса, в который он вложен, и может обращаться к ним непосредственно, как и все остальные нестатические члены внешнего класса.
Иногда внутренний класс используется для предоставления ряда услуг внешнему классу, в котором он содержится.
```java=
class Outer {
int[] nums;
Outer(int[] n) {
nums = n;
}
void analyze() {
Inner innerobj = new Inner();
System.out.println("Min: " + innerobj.min());
System.out.println("Max: " + innerobj.max());
System.out.println("Average: " + innerobj.avg());
}
class Inner {
int min() {
int m = nums[0];
for(int i = 0; i < nums.length; i++){
if (nums[i] < m) m = nums[i];
}
return m;
}
int max() {
int m = nums[0];
for(int i = 0; i < nums.length; i++){
if (nums[i] > m) m = nums[i];
}
return m;
}
int avg() {
int a = 0;
for(int i = 0; i < nums.length; i++){
a+=nums[i];
}
return a;
}
}
}
class NestedClassDemo {
public static void main(...){
int[] x = {3,2,1,5,6,9,7,8};
Outer obj = new Outer(x);
obj.analyze();
}
}
// Вывод программы:
//Min: 1
//Max: 9
//Average: 5
```
В этом примере внутренний класс **Inner**, обрабатывает массив nums который является членом класса **Outer**. Как уже говорилось внутренний класс имеет прямой доступ к членам класса в котором он находится. А вот обратно не получится.
Например невозможно методу **analyze()** класса **Outer** напрямую вызвать метод **min()**, без создания объекта класса **Inner**.
Как уже упоминалось, класс можно вложить в области действия блока. В итоге получается локальный класс, недоступный за пределами блока.
```java=
class LocalClassDemo {
public static void main(...){
class Example {
Example() {
System.out.println("Block Scope Class");
}
}
Example obj1 = new Example();
}
}
// Вывод программы:
// Block Scope Class
```
В данном примере класс Example недоступен за пределами метода main(), а следовательно, попытка получить доступ к нему из любого метода, кроме main(), приведет к ошибке.
И последнее замечание: внутренний класс может быть безымянным. Экземпляр анонимного внутреннего класса создается при объявлении класса с помощью оператора new.
## Наследование
Одним из трех фундаментальных принципов объектно-ориентированного программирования является наследование, с помощью которого создаются иерархические классификации. На основе наследования можно создать общий класс, определяющий обобщенные характеристики для множества родственных элементов. Затем этот класс может наследоваться другими, более специализированными классами, каждый из которых будет добавлять собственные уникальные характеристики.
В соответствии с терминологией Java наследуемый класс называют суперклассом, а наследующий - подклассом.
Java поддерживает наследование позволяя один класс включать другой класс в своё объявление. Это делается при помощи ключевого слова extends. Тем самым подкласс расширяет суперкласс.
Общая форма объявления подкласса который наследует суперкласс:
```java=
class subclass_name extends superclass_name{
.....
}
```
Пример суперкласса двумерных фигур:
```java=
// Суперкласс для двумерных объектов
class TwoDShape {
double width;
double height;
void showDim(){
System.out.println("Width and Height = " + width + " and "
+ height);
}
}
class Triangle extends TwoDShape {//Triangle наследует TwoDShape
String style;
//Triangle может обращаться к членам TwoDShape как к своим
double area(){
return width * height / 2;
}
void showStyle() {
System.out.println("Triangle is " + style);
}
}
class Shapes{
public static void main(...){
Triangle t1 = new Triangle();
Triangle t2 = new Triangle();
t1.width = 4.0;//Объект класса Triangle может обращаться к членам
t1.height = 4.0;//класса TwoDShape
t1.style = "filled";
t2.width = 8.0;
t2.height = 12.0;
t2.style = "outlined"
System.out.println("Info for t1: ");
t1.showStyle();
t1.showDim();
System.out.println("Area is " + t1.area());
System.out.println();
System.out.println("Info for t2: ");
t2.showStyle();
t2.showDim();
System.out.println("Area is " + t2.area());
}
}
```

В классе **TwoDShape** определены свойства обобщенной двумерной фигуры, частными случаями которой могут быть квадрат, треугольник, прямоугольник и т.п. Класс **Triangle** является конкретной разновидностью класса **TwoDShape**, в данном случае это треугольник. Класс **Triangle** включает в себя все элементы класса **TwoDShape**, а также поле **style** и методы **area()** и **showStyle()**.
Поскольку класс **Triangle** включает все члены суперкласса **TwoDShape**, в теле метода **area()** доступны переменные экземпляра **width** и **height**. Кроме того, объекты **t1** и **t2** в методе **main()** могут непосредственно обращаться к переменным **width** и **height**, как если бы они принадлежали классу **Triangle**.
И хотя TwoDShape является суперклассом для Triangle, это отдельный независимый класс который может быть использован сам по себе.
```java=
//Этот код абсолютно валиден.
TwoDShape shape = new TwoDShape();
shape.width = 10;
shape.height = 20;
shape.showDim();
```
Но при этом объект(**shape**) класса **TwoDShape** не имеет никакой информации или доступа к подклассам **TwoDShape**.

Для любого создаваемого подкласса можно определить только один суперкласс. Java не поддерживает наследование нескольких суперклассов одним подклассом. Однако можно создать иерархию наследования в которой подкласс будет суперклассом для другого подкласса.
Главное преимущество наследования в том, что раз создав суперкласс который определяет атрибуты общие для набора объектов, его можно использовать для создания любого числа подклассов.
### Доступ к членам класса и наследование
Для того чтобы предотвратить неправомерное использование и изменение, переменные класса объявляют как private. Наследование класса не отменяет это ограничение доступа.
```java=
class TwoDShape {
double width;
double height;
}
class Triangle extends TwoDShape {//Triangle наследует TwoDShape
String style;
double area(){
return width * height / 2;//Ошибка нельзя получить доступ к приват-
//ным полям суперкласса!
}
}
```
Для обхода подобного ограничения можно использовать специальные методы доступа:
Геттеры и Сеттеры.
```java=
class TwoDShape {
double width;
double height;
// Геттеры для height и width
double getHeight() {
return height;
}
double getWidth() {
return width;
}
// Сеттеры для height и width
void setHeight(double h) {
height = h;
}
void setWidth(double w) {
width = w;
}
}
class Triangle extends TwoDShape {//Triangle наследует TwoDShape
String style;
double area(){
return getWidth() * getHeight() / 2;//Теперь ошибки доступа не будет
}
}
```
Eсли переменная экземпляра используется только методами, определенными в классе, то она должна быть закрытой. Если значение переменной экземпляра не должно выходить за определенные границы, ее следует объявить как закрытую, а обращение
к ней выполнять с помощью специальных методов доступа.
### Конструкторы и наследование
В иерархии классов допускается, чтобы суперклассы и подклассы имели собственные конструкторы. В связи с этим возникает вопрос, какой именно конструктор отвечает за создание объекта подкласса: конструктор суперкласса, конструктор подкласса или же оба одновременно? На этот вопрос можно ответить так: конструктор суперкласса используется для построения родительской части объекта, а конструктор подкласса - для остальной его части. И в этом есть своя логика, поскольку суперклассу не известны и недоступны любые собственные члены подкласса, а значит, каждая из указанных частей объекта должна конструироваться по отдельности.
Если конструктор определен только в подклассе, то все происходит очень просто: конструируется объект подкласса, а родительская часть объекта автоматически создается конструктором суперкласса, используемым по умолчанию.
Если конструкторы объявлены как в подклассе, так и в суперклассе, то все немного усложняется, поскольку в этом случае будут выполняться оба конструктора. При этом на помощь приходит ключевое слово super, которое может применяться в двух общих формах. Первая форма используется для вызова конструктора суперкласса, а вторая - для доступа к членам суперкласса, скрытых членами подкласса.
**Использование ключевого слова super для вызова конструкторов суперкласса**
Подкласс может вызвать конструктор суперкласса использовав следующую форму **super**:
```java=
super(parameter-list);
```
## Пакеты
Файлы в компьютере группируются по папкам. Классы в Java (а каждый класс лежит в отдельном файле) группируются по пакетам, которые являются папками на диске.
Полным уникальным именем класса» является «имя пакета» + «имя класса:

**Полное имя класса всегда уникально!**
Каждый раз писать длинное имя, например java.util.ArrayList, очень неудобно. Поэтому в Java добавили возможность «импортировать классы».
Делается это конструкцией вида **import java.util.ArrayList;**
Да, в разных пакетах могут лежать классы с одинаковыми именами. Но мы не можем импортировать в наш класс два класса с одинаковыми именами, поэтому к одному из них придётся обращаться по полному имени.
Лучше всегда класть классы в пакеты, а не в корень папки src. Когда классов мало, это ещё не представляет проблему, но когда классов много – очень легко запутаться. Поэтому всегда создавай классы только в пакетах.
В Java принято давать классам и пакетам осмысленные имена. Многие компании выпускают свои библиотеки (набор классов) и, чтобы не было путаницы, называют пакеты этих классов по имени компании/сайта.
{"metaMigratedAt":"2023-06-15T04:08:39.714Z","metaMigratedFrom":"Content","title":"ООП / Java for Beginners H.Shildt","breaks":true,"contributors":"[{\"id\":\"d85d1b13-52ca-4eb4-895f-efb21b7b2ad0\",\"add\":48140,\"del\":3233}]"}