Рубрики
Без рубрики

Вложенные классы Java и лямбда-выражения

[[Java WIL 🤔 Сообщение #2] Java обладает очень богатым набором функций, которые предоставляют разработчикам множество возможностей… С пометкой java, программирование, учебное пособие, новички.

[ Java WIL 🤔 Сообщение #2]

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

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

содержание

  • Вложенные классы
  • Зачем Использовать Вложенные Классы
  • Внутренние классы
  • Статические Вложенные Классы
  • Затенение во вложенных классах
  • Сериализация вложенных классов
  • Локальные и анонимные классы
  • Лямбда-выражения
  • Функциональный Интерфейс
  • Целевой Ввод В Лямбдах
  • Ссылки на методы
  • Когда использовать Вложенные классы, Локальные классы, Анонимные классы и Лямбда-выражения

📌 Вложенные классы

Вложенный класс – это класс внутри другого класса, то есть член его заключающего класса. Он делится на две категории: нестатический и статический .

  1. Нестатические вложенные классы называются внутренними классами . У них есть доступ к другим членам заключающего класса, даже если они объявлены закрытыми.
  2. Вложенные классы, объявленные статическими, называются статическими вложенными классами . У них нет доступа к другим членам заключающего класса.
  3. В качестве члена внешнего класса вложенный класс может быть объявлен private , public , protected или package-private .
  4. Обратите внимание, что внешние классы могут быть объявлены только public или пакет-частный .

📌 Зачем Использовать Вложенные Классы

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

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

  1. Внутренний класс связан с экземпляром своего окружающего класса и имеет прямой доступ к методам и полям этого объекта.
  2. Поскольку внутренний класс связан с экземпляром, ОН НЕ МОЖЕТ САМ ОПРЕДЕЛЯТЬ КАКИЕ-ЛИБО СТАТИЧЕСКИЕ ЧЛЕНЫ. .
  3. Объекты, являющиеся экземплярами внутреннего класса, существуют внутри экземпляра внешнего класса. Таким образом, экземпляр внутреннего класса может существовать только внутри экземпляра внешнего класса и имеет прямой доступ к методам и полям включающего экземпляра.
  4. Чтобы создать экземпляр внутреннего класса, сначала необходимо создать экземпляр внешнего класса.
  5. Существует два особых вида внутренних классов: локальные классы и анонимные классы .
public class OuterClass {
    private class InnerClass {
        // class content here
    }
}

📌 Статические Вложенные Классы

  1. Как и в случае с методами класса, статический вложенный класс связан со своим внешним классом.
  2. Статический вложенный класс не может напрямую ссылаться на переменные экземпляра или методы, определенные в его заключающем классе. Он может использовать их только через ссылку на объект.
  3. Статический вложенный класс взаимодействует с членами экземпляра своего внешнего класса и другими классами точно так же, как и любой другой класс верхнего уровня. Таким образом, статический вложенный класс – это поведенческий класс верхнего уровня, который был вложен в другой класс верхнего уровня для удобства упаковки.
public class OuterClass {
    static class StaticNestedClass {
       // class contents here
    }
}

📌 Затенение во вложенных классах

  1. Если объявление типа в определенной области имеет то же имя, что и другое объявление во включающей области, то это объявление затеняет объявление включающей области.
  2. На затененное объявление нельзя ссылаться только по его имени. Обратите внимание, что вы действительно можете сделать это: Теневой тест.this.x . Перейдите в раздел “затенение” по этой ссылке .
  3. Ссылайтесь на переменные-члены, которые охватывают большие области, по имени класса, к которому они принадлежат. Например, оператор ff. обращается к переменной-члену класса Shadow Test из метода.
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

📌 Сериализация

  1. Сериализация внутренних классов, включая локальные и анонимные классы, настоятельно не рекомендуется . Когда компилятор Java компилирует определенные конструкции, такие как внутренние классы, он создает синтетические конструкции ; это классы, методы, поля и другие конструкции, которые не имеют соответствующей конструкции в исходном коде. Синтетические конструкции позволяют компиляторам Java реализовывать новые функции языка Java без изменений в JVM. Однако они могут различаться в разных реализациях.
  2. Могут возникнуть проблемы с совместимостью, если внутренний класс сериализуется, а затем десериализуется с помощью другой реализации JRE.
  3. Подробнее читайте в разделе Сериализации по этой ссылке

📌 Локальные и анонимные классы

  1. Существует два типа внутренних классов: локальные и анонимные. Внутренний класс внутри тела метода называется local class . Внутренний класс также может быть объявлен внутри тела метода без их именования, т.е. анонимный класс .
  2. Те же модификаторы, которые используются для других членов внешнего класса, могут быть использованы для внутреннего класса. Например, модификаторы доступа private , общедоступный , пакет-частный protected может использоваться во внутреннем классе точно так же, как они используются для полей экземпляра внешнего класса.

📌 Подробнее о местных занятиях

public class SomeClass {
    public void someMethod() {
        class SomeLocalClass {
            // class contents here
        }
    }
}
  1. Локальные классы – это классы, определенные в блоке, который представляет собой группу из нуля или более операторов.
  2. Локальные классы могут быть определены внутри любого блока, т.е. в теле метода, цикле for или предложении if.
  3. Локальный класс имеет доступ к членам своего заключающего класса. Он также имеет доступ к локальным переменным.
  4. ВАЖНО: Локальный класс может получить доступ только к локальным переменным, которые объявлены final. Когда локальный класс обращается к локальной переменной или параметру окружающего блока, он захватывает переменную или параметр.
  5. Начиная с Java 8, локальный класс может получать доступ к локальным переменным и параметрам заключительного блока, которые являются окончательными или фактически окончательными. Переменная или параметр , значение которого никогда не изменяется после его инициализации , является фактически окончательной .
  6. Начиная также с Java SE 8, если вы объявляете локальный класс в методе, он может получить доступ к параметрам метода.
  7. Локальные классы похожи на внутренние классы, поскольку они не могут определять или объявлять какие-либо статические члены.
  8. Локальные классы в статических методах могут ссылаться только на статические члены окружающих классов.
  9. Локальные классы нестатичны, поскольку они имеют доступ к элементам экземпляра заключающего блока. Следовательно, они не могут содержать большинство видов статических объявлений.
  10. Интерфейс не может быть объявлен внутри блока. Интерфейсы по своей сути статичны.
  11. Статические инициализаторы или интерфейсы-члены не могут быть объявлены внутри локального класса.
  12. Локальный класс может иметь статические члены при условии, что они являются постоянными переменными. Постоянная переменная – это переменная примитивного типа или типа String, которая объявляется окончательной и инициализируется постоянным выражением во время компиляции. Постоянное выражение времени компиляции обычно представляет собой строку или арифметическое выражение, которое может быть вычислено во время компиляции.

📌 Анонимные классы

    SomeAnonyMousClass anonClass = new SomeAnonyMousClass(){
        // instance field declarations
        // methods 
        // should contain no constructor
    };
  1. Анонимные классы делают код более лаконичным. Это позволяет одновременно объявлять и создавать экземпляры класса. Они похожи на локальные классы, за исключением того, что у них нет имени.
  2. В то время как локальные классы являются объявлением класса, анонимные классы являются выражениями , что означает, что класс определен в другом выражении.
  3. Синтаксис выражения анонимного класса аналогичен вызову конструктора, за исключением того, что в блоке кода содержится определение класса.
HelloWorld helloWorld = new HelloWorld() {
  // code here
};
  1. Выражение анонимного класса состоит из следующего: new operator, имени интерфейса для реализации или класса для расширения, круглых скобок, содержащих аргументы конструктора, точно так же, как обычное выражение для создания экземпляра класса, и тела, которое является телом объявления класса.
  2. Поскольку определение анонимного класса является выражением, оно должно быть частью инструкции. Это объясняет, почему после закрывающей фигурной скобки стоит точка с запятой.

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

  1. Как и локальные классы, анонимные классы могут захватывать переменные; они имеют одинаковый доступ к локальным переменным охватывающей области.
  2. Анонимный класс имеет доступ к членам своего заключающего класса.
  3. Анонимный класс не может получить доступ к локальным переменным в своей охватывающей области, которые не объявлены как final или фактически final.
  4. Подобно вложенному классу, объявление типа в анонимном классе затеняет любые другие объявления во включающей области, которые имеют то же имя.
  5. Анонимные классы также имеют те же ограничения, что и локальные классы, в отношении своих членов: статические инициализаторы или интерфейсы-члены не могут быть объявлены; анонимный класс может иметь статические члены при условии, что они являются постоянными переменными.
  6. Обратите внимание, что ff может быть объявлен в анонимном классе a. Поля b. дополнительные методы c. инициализаторы экземпляра d. местные классы
  7. ВАЖНО: Вы не можете объявлять конструкторы в анонимном классе.
  8. Анонимные классы идеально подходят для реализации интерфейса, содержащего два или более методов.

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

📌 Лямбда-выражения

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

📌 Синтаксис лямбда-выражений

Лямбда-выражение состоит из ff.

  • Разделенный запятыми список формальных параметров, заключенных в круглые скобки. Тип данных параметров в лямбда-выражении может быть опущен. Более того, круглые скобки можно опустить, если имеется только один параметр.
  • Маркер стрелки ->
  • Тело, состоящее из одного выражения или блока инструкций. Оператор return также может быть использован, однако имейте в виду, что оператор return не является выражением в лямбдах, поэтому они должны быть заключены в фигурные скобки. Лямбды можно рассматривать как анонимные методы – методы без имен. Например,
p -> p.getAge() >= 18
    && p.getAge() <= 25

📌 Функциональный интерфейс

Функциональный интерфейс – это любой интерфейс, содержащий только один абстрактный метод . Он может содержать один или несколько методов по умолчанию или статических методов. Поскольку он содержит только один абстрактный метод, имя может быть опущено при его реализации. При этом вместо использования выражения анонимного класса используется лямбда-выражение. JDK определяет несколько стандартных функциональных интерфейсов, которые можно найти в пакете java.util.function .

📌 Доступ к локальным переменным Охватывающей области в лямбда-выражениях

  1. Подобно локальным и анонимным классам, лямбды могут захватывать переменные; они имеют одинаковый доступ к локальным переменным охватывающей области. Однако, в отличие от локальных и анонимных классов, лямбды не имеют проблем с затенением .
  2. Лямбды лексически ограничены . Это означает, что они не наследуют никаких имен от супертипа и не вводят новый уровень области видимости. Объявления в лямбдах интерпретируются так же, как и во внешней среде.
  3. Если параметр, переданный лямбде, объявлен во включающей области, то компилятор выдает ошибку, Параметр лямбда-выражения {} не может повторно объявить другую локальную переменную, определенную во включающей области . Это происходит потому, что лямбда-выражения не вводят новый уровень определения области видимости. Следовательно, лямбды могут напрямую обращаться к полям, методам и локальным переменным охватывающей области.
  4. Подобно локальным и анонимным классам, лямбда-выражение может обращаться только к локальным переменным и параметрам заключающего блока, которые являются final или фактически окончательный (значение не должно изменяться после инициализации).

📌 Целевой ввод в лямбдах

Итак, как можно определить тип лямбда-выражения, например, тип p в приведенном ниже примере?

p -> p.getAge() < 18

Когда среда выполнения Java вызывает метод, в котором передается лямбда-выражение, она ожидает определенный тип данных, поэтому лямбда-выражение имеет этот тип. Тип данных, который ожидают эти методы, называется target type . Чтобы определить тип лямбда-выражения, компилятор Java использует целевой тип контекста или ситуации, в которой было найдено лямбда-выражение. Таким образом, лямбда-выражения могут использоваться только в ситуации, когда компилятор Java может определить целевой тип , т.е. в:

  • объявления переменных
  • назначения
  • заявления о возврате
  • инициализаторы массива
  • аргументы метода или конструктора
  • тела лямбда-выражений
  • условные выражения
  • приведение выражений
📌 Целевые типы и аргументы метода

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

Например, если функциональные интерфейсы java.lang. Запускаемые и java.util. Вызываемые реализованы и перегружены определенным классом, подобным этому,

void invoke(Runnable r) {
    r.run();
}

 T invoke(Callable c) {
    return c.call();
}

Какой метод будет вызван приведенным ниже утверждением?

String s = invoke(() -> "done");

Метод с аргументом Callable будет вызван, потому что лямбда-выражение возвращает значение, в данном случае строку done . Обратите внимание, что метод invoke(Runnable) не возвращает значения.

📌 Сериализация лямбд

Лямбда-выражение может быть сериализовано, если его целевой тип и его захваченные аргументы являются сериализуемыми. Однако, как и внутренние классы, 🛑 сериализация лямбд категорически не рекомендуется .

📌 Ссылки на методы

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

Arrays.sort(personListAsArray, Person::compareByAge);

Ссылка на метод Person::compareByAge семантически совпадает с лямбда-выражением где compareByAge – это статический метод класса Person.

(person1, person2) -> Person.compareByAge(person1, person1)
Виды ссылок на методы

Существует четыре типа ссылок на методы

Ссылка на статический метод Содержащий класс::имя статического метода Человек::Сравнительный возраст
Ссылка на метод экземпляра определенного объекта containingObject::имя метода экземпляра персона 1::Сопоставляемое имя
Ссылка на метод экземпляра произвольного объекта определенного типа ContainingType::имя метода Строка::конкатенация
Ссылка на конструктор Имя класса::новый Хэш-набор::новый

📌 Когда использовать Вложенные классы, Локальные классы, Анонимные классы и Лямбда-выражения

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

  • Локальный класс . Используется, если необходимо создать более одного экземпляра класса, получить доступ к его конструктору и/или ввести новый именованный тип.
  • Анонимный класс . Используется, если объявлены поля или дополнительные методы необходимы
  • Лямбда-выражения .

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

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

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

Как всегда, поздравляем с дальнейшим ростом и обучением 🍷 !

Рекомендации

[1] Вложенные классы Java [2] Вложенные классы в Java

Оригинал: “https://dev.to/pat_the99/java-nested-classes-and-lambda-expressions-18o6”