JavaRush /Java блог /Random /Кофе-брейк #218. Что такое скрытие переменных, затенение ...

Кофе-брейк #218. Что такое скрытие переменных, затенение и переопределение методов в Java? 13 вопросов и ответов, которые вам могут задать на интервью по Java Collection

Статья из группы Random

Что такое скрытие переменных, затенение и переопределение методов в Java?

Источник: Medium Это руководство поможет вам узнать, как применять в Java скрытие переменных, затенение и переопределение методов. Кофе-брейк #218. Что такое скрытие переменных, затенение и переопределение методов в Java? 13 вопросов и ответов, которые вам могут задать на интервью по Java Collection - 1

Категории переменных

  • Переменные класса (статические) — это переменные, объявленные внутри класса с ключевым словом static, доступные этому классу и его объектам.
  • Переменные экземпляра (нестатические) — это переменные, объявленные внутри класса (без ключевого слова static) и доступные только объектам класса.
  • Локальные переменные — это переменные, объявленные внутри конструктора, методов или блоков.
  • Параметры — это переменные, которые методы или конструкторы принимают в качестве параметра.

Затенение переменной

Затенение переменной (Variable shadowing) происходит, когда мы назначаем переменную внутри блока, и ее имя совпадает с переменной во внешней области этого блока. Перед вами наглядный пример затенения переменных:

class Car {
    String color = "white";
    
    public void printColor(){
        String color = "black";
        System.out.println(color);
    }
}

public class Main {
    public static void main(String[] args){
        Car c = new Car();
        c.printColor(); // печатает black
    }
}
У нас есть класс Car, и его состояние — это белый цвет (white). Существует метод, с помощью которого мы можем наблюдать затенение переменной. В результате выполнения этого кода будет напечатан черный цвет (black), потому что цвет локальной переменной затеняет цвет переменной экземпляра. Это объясняется тем, что в Java, если переменная внутренней области видимости затеняет переменную внешней области видимости, то она считается внутренней. Иными словами, используется значение той переменной, которая находится во внутренней области видимости, потому что она затеняет/скрывает значение той переменной, что находится во внешней области видимости. Если же мы хотим напечатать переменную экземпляра, то мы можем использовать ключевое слово this (означает объект), как показано ниже.

class Car {
    String color = "white";
    
    public void printColor(){
        String color = "black";
        // добавлено ключевое слово this, поэтому будет напечатано white
        System.out.println(this.color);    }
}

Скрытие переменных

Теперь давайте посмотрим на скрытие переменных. Скрытие переменной происходит, когда переменной в дочернем классе (Child) присваивается другое значение, и ее имя совпадает с именем переменной в родительском классе (Parent). Взгляните на блок кода ниже:

class Parent {
    int age  = 38;
}

class Child extends Parent {
    int age = 14;
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        Child c = new Child();

        System.out.println(p.age); // 38
        System.out.println(c.age); // 14
    }
}
У нас есть два класса: Parent (родительский) и Child (дочерний), который его наследует. У них обоих есть переменная age, но с разными значениями. После создания объектов p и c, а затем вызова их возраста (age), возраст в классе Child будет отображаться как 14. Этот процесс интерпретируется как “age в дочернем классе скрывает переменную age в родительском”. Это и есть скрытие переменных. Скрытие переменной не зависит от типа переменной. Если тип age в классе Parent будет изменен на double, это результат не изменит. Давайте посмотрим на блок кода:

class Parent {
    int age  = 38;
}

class Child extends Parent {
    int age = 14;
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        Child c = new Child();

        Parent pAsc = c;

        System.out.println(p.age); // 38
        System.out.println(c.age); // 14
        System.out.println(pAsc.age); // 38
    }
}
Все классы и объекты ту такие же, как в приведенном ранее примере, но в методе main объект c присваивается объекту pAsc с типом Parent. Каждый из объектов “c” и “pAsc” ссылается на один и тот же объект в памяти, потому что мы не создавали новый объект с ключевым словом new. Несмотря на то, что переменная age в дочернем классе скрывает ту же переменную в родительском классе, мы получаем 38 как результат возраста объекта “pAsc”. В этом случае можно сказать, что существует такое правило: если переменная скрыта, Java выбирает переменную по типу объекта. Есть и другой вариант. Когда переменная экземпляра в подклассе имеет то же имя, что и переменная экземпляра в суперклассе, то тогда переменная экземпляра выбирается из ссылочного типа.

Переопределение метода

Переопределение метода — это процесс, при котором реализация метода в родительском классе заменяется дочерним классом:

class Parent {
    int age  = 38;
    void printAge(){
        System.out.println(age);
    }
}

class Child extends Parent {
    int age = 14;
    @Override
    void printAge(){
        System.out.println(age);
    }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        Child c = new Child();

        Parent pAsc = c;
        
        System.out.println(pAsc.age); // 38
        pAsc.printAge(); // 14
    }
}
Здесь у нас есть метод, который печатает возраст (age) в каждом дочернем и родительском классе. Дочерний класс переопределяет метод родительского класса. Для этого используется аннотация @Override. Таким образом, переопределение методов и скрытие переменных в Java отличаются. Хотя Java принимает тип объекта при выборе скрытой переменной, но переопределение метода работает иначе и учитывает сам объект. Если нам нужно, мы можем получить доступ к возрасту класса Parent с помощью ключевого слова super:

class Parent {
    int age  = 38;
    void printAge(){
        System.out.println(age);
    }
}

class Child extends Parent {
    int age = 14;
    @Override
    void printAge(){
        System.out.println(super.age); // напечатает 38
    }
}

Заключение

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

13 вопросов по Java Collection, которые вам могут задать на собеседовании (с ответами)

Источник: Medium Это подборка из нескольких вопросов и ответов по коллекциям в Java, которые могут встретиться вам на собеседовании.

1. Что такое коллекция в Java?

Ответ: Коллекция в Java — это набор классов и интерфейсов, который обеспечивает хранение и управление группой объектов. Коллекция имеет унифицированный интерфейс для обработки различных типов структур данных, таких как списки (List), наборы (Set), карты (Map) и очереди (Queue).

2. В чем разница между ArrayList и LinkedList в Java?

Ответ: ArrayList и LinkedList являются реализациями интерфейса List в Java. Основное различие между ними заключается в том, что ArrayList поддерживается массивом, а LinkedList — связанным списком (linked list). ArrayList обеспечивает быстрый произвольный доступ к элементам, но у него более медленные операции вставки и удаления. LinkedList обеспечивает быструю вставку и удаление, но более медленный произвольный доступ.

3. В чем разница между HashSet и TreeSet в Java?

Ответ: HashSet и TreeSet — это реализации интерфейса Set в Java. Основное различие между ними заключается в том, что HashSet использует хэш-таблицу для хранения элементов, а TreeSet использует отсортированную древовидную структуру. HashSet обеспечивает более быструю вставку и извлечение, но не поддерживает сохранение в отсортированном порядке. TreeSet обеспечивает более медленные операции вставки и извлечения, но сохраняет элементы в отсортированном порядке.

4. В чем разница между HashMap и TreeMap в Java?

Ответ: HashMap и TreeMap — это реализации интерфейса Map в Java. Основное различие между ними заключается в том, что HashMap использует хеш-таблицу для хранения пар ключ-значение (key-value), а TreeMap использует отсортированную древовидную структуру. HashMap обеспечивает более быструю вставку и извлечение, но не поддерживает сохранение в отсортированном порядке. TreeMap обеспечивает более медленные операции вставки и извлечения, но сохраняет элементы в отсортированном порядке.

5. В чем разница между Iterator и ListIterator в Java?

Ответ: Iterator и ListIterator — это интерфейсы в Java, которые используются для обхода коллекции элементов. Основное различие между ними заключается в том, что ListIterator обеспечивает двунаправленный обход списка, а Iterator обеспечивает только однонаправленный обход. То есть, ListIterator можно использовать для обхода списка как в прямом, так и в обратном направлении, а Iterator можно использовать для обхода любой коллекции только в одном направлении.

5. В чем разница между двумя разновидностями отказоустойчивых итераторов в Java — Fail-Fast и Fail-Safe?

Ответ: Отказоустойчивые итераторы Fail-Fast и Fail-Safe — это два типа итераторов в Java, которые используются для обхода коллекций. Основное различие между ними заключается в том, как они обрабатывают одновременные модификации коллекции. Итераторы Fail-Fast вызовут исключение ConcurrentModificationException, если коллекция будет изменена во время итерации, в то время как итераторы Fail-Safe сделают копию коллекции и переберут ее, что позволит изменить исходную коллекцию без появления исключения.

7. В чем разница между ConcurrentHashMap и HashMap в Java?

Ответ: ConcurrentHashMap и HashMap являются реализациями интерфейса Map в Java. Основное различие между ними заключается в том, что ConcurrentHashMap является потокобезопасным, позволяет нескольким потокам одновременно обращаться к Map и изменять ее, в то время как HashMap не является потокобезопасным и может привести к несогласованности данных, если несколько потоков обращаются к нему и изменяют его одновременно.

8. В чем разница между интерфейсами Comparable и Comparator в Java?

Ответ: Оба интерфейса Comparable и Comparator используются для сортировки объектов. Однако они различаются по своему способу работы. Comparable — это интерфейс, который используется для определения естественного порядка объектов класса. Когда класс реализует интерфейс Comparable, его можно отсортировать в естественном порядке с помощью методов Arrays.sort() или Collections.sort(). Интерфейс Comparable содержит единственный вызываемый метод compareTo(), который сравнивает текущий объект с указанным объектом и возвращает отрицательное целое число, ноль или положительное целое число, если текущий объект меньше, равен или больше указанного объекта соответственно. Что касается Comparator, то это интерфейс, который используется для определения внешнего порядка объектов. Он позволяет сортировать объекты на основе различных критериев, отличных от естественного порядка, определенного классом. Класс, реализующий интерфейс Comparator, можно использовать для сортировки объектов любого другого класса, не реализующего интерфейс Comparable. Интерфейс Comparator содержит единственный вызываемый метод compare(), который принимает два объекта и возвращает отрицательное целое число, ноль или положительное целое число, если первый объект меньше, равен или больше второго объекта соответственно. Основное различие между Comparable и Comparator заключается в том, что Comparable используется для определения естественного порядка объектов внутри класса, а Comparator используется для определения порядка объектов, внешних по отношению к классу.

9. В чем разница между синхронизированными и параллельными коллекциями в Java?

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

10. Как используется интерфейс Iterator в коллекциях Java?

Ответ: Интерфейс Iterator используется для обхода коллекции и доступа к ее элементам один за другим. Этот интерфейс обеспечивает единый способ перебора различных типов коллекций, таких как List, Set и Map. Интерфейс Iterator определяет три метода:
  • hasNext(): возвращает true, если в коллекции есть еще элементы для итерации, и false в противном случае.
  • next(): возвращает следующий элемент коллекции.
  • remove(): удаляет из коллекции последний элемент, возвращенный итератором. Этот метод является необязательным и может поддерживаться не всеми реализациями интерфейса Iterator.
Чтобы использовать Iterator, вам нужно сначала получить экземпляр интерфейса из коллекции с помощью метода iterator(). Затем вы можете использовать метод hasNext(), чтобы проверить, есть ли еще элементы для повторения, и метод vnext() для извлечения каждого элемента по очереди. Вот пример использования Iterator для перебора списка строк:

List<String> myList = new ArrayList<>();
myList.add("foo");
myList.add("bar");
myList.add("baz");

Iterator<String> it = myList.iterator();
while (it.hasNext()) {
    String element = it.next();
    System.out.println(element);
}
В этом примере получаем Iterator из List благодаря использованию метода iterator(), а методы hasNext() и next() используются для перебора элементов списка и вывода их на консоль. Интерфейс Iterator — важная часть коллекций Java, поскольку он обеспечивает стандартный способ перебора коллекций независимо от их базовой реализации. Он обеспечивает большую гибкость и совместимость при работе с коллекциями в Java.

11. Как используется интерфейс Map в коллекциях Java?

Ответ: Интерфейс Map используется для представления набора пар ключ-значение. Map — это объект, который сопоставляет ключи со значениями, где каждый ключ уникален и связан с одним значением. Интерфейс Map определяет несколько методов для работы с парами ключ-значение, в том числе:
  • put(key, value): связывает указанное значение с указанным ключом в Map.
  • get(key): возвращает значение, связанное с указанным ключом в Map, или null, если ключ отсутствует.
  • remove(key): удаляет пару ключ-значение, связанную с указанным ключом, из Map.
  • containsKey(key): возвращает true, если Map содержит сопоставление для указанного ключа.
  • keySet(): возвращает набор всех ключей в Map.
  • values(): возвращает коллекцию всех значений в Map.
  • entrySet(): возвращает набор всех пар ключ-значение в Map.
Вот пример создания и использования Map в Java:

Map<String, Integer> wordCounts = new HashMap<>();
wordCounts.put("foo", 2);
wordCounts.put("bar", 5);
wordCounts.put("baz", 1);

int count = wordCounts.get("bar"); // returns 5
wordCounts.remove("baz");
boolean containsFoo = wordCounts.containsKey("foo"); // возвращается true
Set<String> keys = wordCounts.keySet(); // возвращается ["foo", "bar"]
Collection<Integer> values = wordCounts.values(); // возвращается [2, 5]
Set<Map.Entry<String, Integer>> entries = wordCounts.entrySet(); // возвращается [("foo", 2), ("bar", 5)]
В этом примере HashMap используется для хранения количества слов в виде пар ключ-значение со словами в качестве ключей и числом целых чисел в качестве значений. Методы put(), get() и remove() используются для добавления и извлечения пар ключ-значение, а методы containsKey(), keySet(), values() и entrySet() используются для получения информации о Map и ее содержимом. Интерфейс Map является важной частью коллекций Java, поскольку он обеспечивает гибкий и мощный способ представления и управления парами ключ-значение в Java.

12. Как платформа Java Collection обеспечивает безопасность типов?

Ответ: Платформа Java Collection обеспечивает безопасность типов за счет использования дженериков. Они позволяют указать тип элементов в коллекции во время компиляции, что дает компилятору возможность выполнять проверки типов, а также гарантирует, что в коллекцию добавляются или извлекаются только элементы правильного типа. Например, интерфейс List определяется как List<E>, где E — параметр типа, представляющий тип элементов в списке. При создании экземпляра List параметр типа E заменяется конкретным типом, например String или Integer, для создания List, который может содержать только элементы этого типа. Вот пример использования дженериков для создания безопасного типа List:

List<String> myList = new ArrayList<>();
myList.add("foo");
myList.add("bar");
myList.add(123); // ошибка компилятора: несовместимые типы
В данном примере List объявляется с параметром типа String, что означает, что в List можно добавлять только объекты String. При попытке добавить Integer в список компилятор выдает ошибку, поскольку Integer не является подтипом String. Точно так же другие интерфейсы в среде Java Collection, такие как Set, Map и Queue, используют дженерики для обеспечения безопасности типов. Используя универсальные шаблоны, платформа коллекции гарантирует, что только элементы правильного типа могут быть добавлены в коллекцию или извлечены из нее. Это помогает предотвратить ошибки типов во время выполнения и делает программы более надежными и простыми в обслуживании.

13. Как Stream API используется в коллекциях Java?

Ответ: Stream API — это мощная функция коллекций Java, позволяющая эффективно и функционально обрабатывать коллекции. Она предоставляет способ обработки набора элементов декларативным образом, без необходимости использования циклов или изменяемых переменных. Stream API работает с потоками, представляющими собой последовательности элементов, которые могут обрабатываться параллельно или в одном потоке. Поток можно получить из коллекции, массива или другого источника данных и обработать с помощью ряда промежуточных и конечных операций. Промежуточные операции преобразуют поток в другой поток, а конечные операции производят результат или побочный эффект. Некоторые общие промежуточные операции включают filter(), map(), flatMap() и distinct(), а общие терминальные операции включают forEach(), count(), reduce() и collect(). Stream API предоставляет разработчику мощный и выразительный способ обработки коллекций в Java и может быть особенно полезен для работы с большими наборами данных или параллельной обработке. Предоставляя декларативный и функциональный стиль программирования, он упрощает написание лаконичного и удобочитаемого кода, а также повышает производительность и удобство сопровождения.
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ