Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1

https://t.me/data_analysis_ml

Всем привет, дамы и господа Software Engineers! Давайте поговорим о вопросах на собеседовании. О том, к чему нужно готовиться и что нужно знать. Это отличный повод для того, чтобы повторить или же изучить с нуля эти моменты.

У меня получилась довольно объемная подборка часто задаваемых вопросов об ООП, Java Syntax, исключениях в Java, коллекциях и многопоточности, которую для удобства я разобью на несколько частей. Важно: мы будем говорить только о версии Java до 8. Все нововведения с 9, 10, 11, 12, 13 не будут учитываться здесь. Любые идеи/замечания, как улучшить ответы, приветствуются. Приятного прочтения, поехали!

Java собеседование: вопросы по ООП

1. Какие особенности есть у Java?

Ответ:

  1. ООП концепты:
    1. объектная ориентированность;
    2. наследование;
    3. инкапсуляция;
    4. полиморфизм;
    5. абстракция.
  2. Кроссплатформенность: программа на Java может быть запущена на любой платформе без каких-либо изменений. Единственное, что нужно — установленная JVM (java virtual machine).
  3. Высокая производительность: JIT(Just In Time compiler) позволяет высокую производительность. JIT конвертирует байт-код в машинный код и потом JVM стартует выполнение.
  4. Мультипоточность: поток выполнения, известный как Thread. JVM создает thread, который называется main thread. Программист может создать несколько потоков наследованием от класса Thread или реализуя интерфейс Runnable.

2. Что такое наследование?

Под наследованием подразумевается, что один класс может наследовать(“extends”) другой класс. Таким образом можно переиспользовать код с класса, от которого наследуются. Существующий класс известен как superclass, а создаваемый — subclass. Также еще говорят parent и child.

public class Animal {
   private int age;
}

public class Dog extends Animal {

}

где Animal — это parent, а Dogchild.

3. Что такое инкапсуляция?

Такой вопрос часто встречается на собеседовании Java-разработчика. Инкапсуляция — это сокрытие реализации при помощи модификаторов доступа, при помощи геттеров и сеттеров. Это делается для того, чтобы закрыть доступ для внешнего использования в тех местах, где разработчики считают нужным. Доступный пример из жизни — это автомобиль. У нас нет прямого доступа к работе двигателя. Для нас работа заключается в том, чтобы вставить ключ в зажигание и включить двигатель. А какие уже процессы будут происходить под капотом — не наше дело. Даже более того, наше вмешательство в эту деятельность может привести к непредсказуемой ситуации, из-за которой можно и машину сломать, и себе навредить. Ровно то же самое происходит и в программировании. Хорошо описано в википедии. Статья об инкапсуляции есть и на JavaRush.

4. Что такое полиморфизм?

Полиморфизм — это способность программы идентично использовать объекты с одинаковым интерфейсом без информации о конкретном типе этого объекта. Как говорится, один интерфейс — множество реализаций. При помощи полиморфизма можно объединять и использовать разные типы объектов по их общему поведению. Например, есть у нас класс Animal, у которого есть два наследника — Dog и Cat. У общего класса Animal есть общее поведение для всех — издавать звук. В случае, когда нужно собрать воедино всех наследников класса Animal и выполнить метод “издавать звук”, используем возможности полиморфизма. Вот как будет это выглядеть:

List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());

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

Вопросы на собеседовании — Java Syntax

5. Что такое конструктор в Java?

Следующие характеристики являются валидными:

  1. Когда новый объект создается, программа использует для этого соответствующий конструктор.
  2. Конструктор похож на метод. Его особенность заключается в том, что нет возвращающего элемента (в том числе и void), а его имя совпадает с именем класса.
  3. Если не пишется никакого конструктора явно, пустой конструктор будет создан автоматически.
  4. Конструктор может быть переопределен.
  5. Если был создан конструктор с параметрами, а нужен еще и без параметров, его нужно писать отдельно, так как он не создается автоматически.

6. Какие два класса не наследуются от Object?

Не ведитесь на провокации, нет таких классов: все классы прямо или через предков наследуются от класса Object!

7. Что такое Local Variable?

Еще один из популярных вопросов на собеседовании Java-разработчика. Local variable — это переменная, которая определена внутри метода и существует вплоть до того момента, пока выполняется этот метод. Как только выполнение закончится, локальная переменная перестанет существовать. Вот программа, которая использует локальную переменную helloMessage в методе main():

public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. Что такое Instance Variable?

Instance Variable — переменная, которая определена внутри класса, и она существует вплоть до того момента, пока существует объект. Пример — класс Bee, в котором есть две переменные nectarCapacity и maxNectarCapacity:

public class Bee {

   /**
    * Current nectar capacity
    */
   private double nectarCapacity;

   /**
    * Maximal nectar that can take bee.
    */
   private double maxNectarCapacity = 20.0;

  ...
}

9. Что такое модификаторы доступа?

Модификаторы доступа — это инструмент, при помощи которого можно настроить доступ к классам, методам и переменным. Бывают следующие модификаторы, упорядоченные в порядке повышения доступа:

  1. private — используется для методов, полей и конструкторов. Уровень доступа — только класс, внутри которого он объявлен.
  2. package-private(default) — может использоваться для классов. Доступ только в конкретном пакете (package), в котором объявлен класс, метод, переменная, конструктор.
  3. protected — такой же доступ, как и package-private + для тех классов, которые наследуются от класса с модификатором protected.
  4. public — используется и для классов. Полноценный доступ во всем приложении.

10. Что такое переопределение (overriding) методов?

Переопределение методов происходит, когда child хочет изменить поведение parent класса. Если нужно, чтоб выполнилось-таки то, что есть в методе parent, можно использовать в child конструкцию вида super.methodName(), что выполнит работу parent метода, а уже потом добавить логику. Требования, которые нужно соблюдать:

  • сигнатура метода должна быть такая же;
  • возвращаемое значение должно быть таким же.

11. Что такое сигнатура метода?

Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 3

Сигнатура метода — это набор из названия метода и аргументов, какие принимает метод. Сигнатура метода является уникальным идентификатором для метода при перегрузке методов.

12. Что такое перегрузка методов?

Перегрузка методов — это свойство полиморфизма, в котором при помощи изменения сигнатуры метода можно создать разные методы для одних действий:

  • одно и то же имя метода;
  • разные аргументы;
  • может быть разный возвращаемый тип.

Например, один и тот же add() из ArrayList может быть перегружен следующим образом и будет выполнять добавление разным способом, в зависимости от входящих аргументов:

  • add(Object o) — просто добавляет объект;
  • add(int index, Object o) — добавляет объект в определенный индекс;
  • add(Collection<Object> c) — добавляет список объектов;
  • add(int index, Collection<Object> c) — добавляет список объектов, начиная с определенного индекса.

13. Что такое Interface?

Множественное наследование не реализовано в джаве, поэтому чтобы преодолеть эту проблему, были добавлены интерфейсы в том виде, в котором мы их знаем 😉 Долгое время у интерфейсов были только методы без их реализации. В рамках этого ответа поговорим именно о них. Например:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}

Из этого вытекают некоторые нюансы:

  • все методы в интерфейсе — публичные и абстрактные;
  • все переменные — public static final;
  • классы не наследуют их (extends), реализовывают (implements). Причем реализовывать можно сколь угодно много интерфейсов.
  • классы, которые реализуют интерфейс, должны предоставить реализацию всех методов, которые есть в интерфейсе.

Вот так:

public class Cat implements Animal {
   public void makeSound() {
       // реализация метода
   }

   public void eat() {
       // реализация
   }

   public void sleep() {
       // реализация
   }
}

14. Что такое default method в Interface?

Теперь поговорим о дефолтных методах. Для чего, для кого? Эти методы добавили, чтобы все сделать “и вашим, и нашим”. О чем это я? Да о том, что с одной стороны нужно было добавить новую функциональность: лямбды, Stream API, с другой стороны, нужно было оставить то, чем славится джава — обратную совместимость. Для этого нужно было ввести уже готовые решения в интерфейсы. Так к нам и пришли дефолтные методы. То есть, дефолтный метод — это реализованный метод в интерфейсе, у которого есть ключевое слово default. Например, всем известный метод stream() в интерфейсе Collection. Проверьте, этот интерфейс вовсе не так прост как кажется ;). Или также не менее известный метод forEach() из интерфейса Iterable. Его также не был до тех пор, пока не добавили дефолтные методы. Кстати, еще можно почитать на JavaRush об этом.

15. А как тогда наследовать два одинаковых дефолтных метода?

Исходя из предыдущего ответа на то, что такое дефолтный метод, можно задать другой вопрос. Если можно реализовать методы в интерфейсах, то теоретически можно реализовать два интерфейса с одинаковым методом, и как такое делать? Есть два разных интерфейса с одинаковым методом:

interface A {
   default void foo() {
       System.out.println(“Foo A”);
   }
}

interface B {
   default void foo() {
       System.out.println(“Foo B”);
   }
}

И есть класс, который реализует эти два интерфейса. Но только как выбрать специфический метод интерфейса А или В? Для этого есть конструкция такого вида: A.super.foo():

public class C implements A, B {
   public void fooA() {
       A.super.foo();
   }

   public void fooB() {
       B.super.foo();
   }
}

Таким образом, метод fooA() будет использовать дефолтный метод foo() из интерфейса A, а метод fooB(), соответственно, метод foo() из интерфейса B.

16. Что такое абстрактные методы и классы?

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

public abstract void foo();

Абстрактным классом называется класс, который имеет также abstract слово:

public abstract class A {

}

У абстрактного класса есть несколько особенностей:

  • на его основе нельзя создать объект;
  • он может иметь абстрактные методы;
  • он может и не иметь абстрактные методы.

Абстрактные классы нужны для обобщения какой-то абстракции (сорян за тавтологию), которой в реальной жизни нет, но она содержит множество общих поведений и состояний (то есть, методов и переменных). Примеров из жизни — хоть отбавляй. Всё вокруг нас. Это может быть “животное”, “машина”, “геометрическая фигура” и так далее.

17. Какая разница между String, String Builder и String Buffer?

Значения String хранятся в пуле стрингов (constant string pool). Как только будет создана строка, она появится в этом пуле. И удалить ее будет нельзя. Например:

String name = "book";

…переменная будет ссылаться на стринг пул Constant string pool

Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 4

Если задать переменной name другое значение, получится следующее:

name = "pen";

Constant string pool

Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 5

Таким образом, эти два значения так и останутся там. String Buffer:

  • значения String хранятся в стеке(Stack). Если значение изменено, значит новое значение будет заменено на старое;
  • String Buffer синхронизирован, и поэтому он потокобезопасный;
  • из-за потокобезопасности скорость работы оставляет желать лучшего.

Пример:

StringBuffer name = “book”;
Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 6

Как только значение name сменится, в стеке измениться значение:

Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 7

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

18. Какая разница между абстрактным классом и интерфейсом?

Абстрактный класс:

  • абстрактные классы имеют дефолтный конструктор; он вызывается каждый раз, когда создается потомок этого абстрактного класса;
  • содержит как абстрактные методы, так и не абстрактные. По большому счету может и не содержать абстрактных методов, но все равно быть абстрактным классом;
  • класс, который наследуется от абстрактного, должен реализовать только абстрактные методы;
  • абстрактный класс может содержать Instance Variable(смотри вопрос №5).

Интерфейс:

  • не имеет никакого конструктора и не может быть инициализирован;
  • только абстрактные методы должны быть добавлены (не считая default methods);
  • классы, реализующие интерфейс, должны реализовать все методы (не считая default methods);
  • интерфейсы могут содержать только константы.

19. Почему доступ по элементу в массиве происходит за O(1)?

Это вопрос буквально с последнего собеседования. Как я узнал позже, это вопрос задается для того, чтобы увидеть, как человек мыслит. Ясно, что практического смысла в этих знаниях немного: хватает только лишь знания этого факта. Для начала нужно уточнить, что O(1) — это обозначение временной сложности алгоритма, когда операция проходит за константное время. То есть это обозначение самого быстрого выполнения. Чтобы ответить на этот вопрос, нужно понять, что мы знаем о массивах? Чтоб создать массив int, мы должны написать следующее:

int[] intArray = new int[100];

Из этой записи можно сделать несколько выводов:

  1. При создании массива известен его тип.Если известен тип, то понятно, какого размера будет каждая ячейка массива.
  2. Известно, какого размера будет массив.

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

А как получается О(1) в доступе к объектам в ArrayList?

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

Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1 - 8

и мы хотим создать коллекцию с элементами, у которых тип A, и добавить разные реализации — B, C, D:

List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());

Как в этой ситуации понять, какой будет размер у каждой ячейки, ведь каждый объект будет разным и может иметь разные дополнительные поля (или быть полностью различными). Что делать? Здесь вопрос ставится так, чтобы запутать и сбить с толку. Мы же знаем, что на самом деле в коллекции хранятся не объекты, а лишь ссылки на эти объекты. А у всех ссылок размер один и тот же, и он известен. Поэтому здесь работает подсчет места так же, как и в предыдущем вопросе.

21. Автоупаковка (autoboxing) и Автораспаковка (unboxing)

Историческая справка: автоупаковка и автораспаковка – одно из главных нововведений JDK 5. Автоупаковка (autoboxing) – процесс автоматического преобразования из примитивного типа в соответствующий класс обертку. Автораспаковка (unboxing) – делает ровно обратное к автоупаковке – преобразует класс обертку в примитив. А вот если окажется значение обертки null, то при распаковке будет выброшено исключение NullPointerException.

Соответствие примитив – обертка

ПримитивКласс обертка
booleanBoolean
intInteger
byteByte
charCharacter
floatFloat
longLong
shortShort
doubleDouble

Автоупаковка происходит:

  • когда присваивают примитиву ссылку на класс обертку:ДО Java 5://ручная упаковка или как это было ДО Java 5. public void boxingBeforeJava5() { Boolean booleanBox = new Boolean(true); Integer intBox = new Integer(3); // и так далее к другим типам } после Java 5: //автоматическая упаковка или как это стало в Java 5. public void boxingJava5() { Boolean booleanBox = true; Integer intBox = 3; // и так далее к другим типам }
  • когда передают примитив в аргумент метода, который ожидает обертку:public void exampleOfAutoboxing() { long age = 3; setAge(age); } public void setAge(Long age) { this.age = age; }

Автораспаковка происходит:

  • когда присваиваем классу обертке примитивную переменную://до Java 5: int intValue = new Integer(4).intValue(); double doubleValue = new Double(2.3).doubleValue(); char c = new Character((char) 3).charValue(); boolean b = Boolean.TRUE.booleanValue(); //и после JDK 5: int intValue = new Integer(4); double doubleValue = new Double(2.3); char c = new Character((char) 3); boolean b = Boolean.TRUE;
  • В случаях с арифметическими операциями. Они применяются только к примитивным типам, для этого нужно делать распаковку к примитиву.// До Java 5 Integer integerBox1 = new Integer(1); Integer integerBox2 = new Integer(2); // для сравнения нужно было делать так: integerBox1.intValue() > integerBox2.intValue() //в Java 5 integerBox1 > integerBox2
  • когда передают в обертку в метод, который принимает соответствующий примитив:public void exampleOfAutoboxing() { Long age = new Long(3); setAge(age); } public void setAge(long age) { this.age = age; }

22. Что такое ключевое слово final и где его использовать?

Ключевое слово final можно использовать для переменных, методов и классов.

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

Пробежали по верхам, теперь обсудим более подробно.

final переменные

;Java дает нам два способа создать переменную и присвоить ей некоторое значение:

  1. Можно объявить переменную и инициализировать ее позже.
  2. Можно объявить переменную и сразу же назначить ее.

Пример с использованием final переменной для этих случаев:

public class FinalExample {

   //статическая переменная final, которая сразу же инициализируется:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";

   //final переменная, которая не инициализирована, но работать будет только если
   //инициализировать это в конструкторе:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // final поле FinalExample.FINAL_EXAMPLE_NAME не может быть заасайнено
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // final поле Config.creationTime не может быть заасайнено
//    finalExample.creationTime = 1L;
   }
}

Можно ли считать Final переменную константой?

Поскольку у нас не получится присвоить новое значение для final переменной, кажется, что это переменные константы. Но это только на первый взгляд. Если тип данных, на который ссылается переменная — immutable, то да, это константа. А если тип данных mutable, то есть изменяемый, при помощи методов и переменных можно будет изменить значение объекта, на который ссылается final переменная, и в таком случае назвать ее константой нельзя. Так вот, на примере видно, что часть финальных переменных действительно константы, а часть — нет, и их можно изменить.

public class FinalExample {

   //неизменяемые финальные переменные:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   //изменяемые фильнаные переменные
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}

Local final переменные

Когда final переменная создается внутри метода, ее называют local final переменная:

public class FinalExample {

   public static void main(String[] args) {
       // Вот так можно
       final int minAgeForDriveCar = 18;

       // а можно и так, в цикле foreach:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}

Мы можем использовать ключевое слово final в расширенном цикле for, потому что после завершения итерации цикла for каждый раз создается новая переменная. Только это все не относится к нормальному циклу for, поэтому приведенный ниже код выдаст ошибку времени компиляции.

// final local переиенная j не может быть назначена
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

Final класс

Нельзя расширять класс, объявленный как final. Проще говоря, никакой класс не может наследоваться от данного. Прекрасным примером final класса в JDK является String. Первый шаг к созданию неизменяемого класса — пометить его как final, и тогда нельзя будет его расширить:

public final class FinalExample {
}

// Здесь будет ошибка компиляции
class WantsToInheritFinalClass extends FinalExample {
}

Final методы

Когда метод маркирован как final, его называют final метод (логично, правда?). Final метод нельзя переопределять у класса наследника. К слову, методы в классе Object — wait() и notify() — это final, поэтому у нас нет возможность их переопределять.

public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // здесь будет ошибка компиляции
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

Как и где использовать final в Java

  • использовать ключевое слово final, чтобы определить некоторые константы уровня класса;
  • создавать final переменные для объектов, когда вы не хотите, чтобы они были изменены. Например, специфичные для объекта свойства, которые мы можем использовать для целей логирования;
  • если не нужно, чтобы класс был расширен, отметить его как окончательный;
  • если нужно создать immutable< класс, нужно сделать его финальным;
  • если нужно, чтоб реализация метода не менялась в наследниках, обозначить метод как final. Это очень важно, чтобы быть уверенным, что реализация не изменится.
https://secure.esputnik.com.ua/4Q0Ef6d2SOs

23. Что такое mutable immutable?

Mutable

Mutable называются объекты, чьи состояния и переменные можно изменить после создания. Например такие классы, как StringBuilder, StringBuffer. Пример:

public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // этот сеттер может изменить поле name
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("first address");
       System.out.println(obj.getAddress());

       // обновляем поле name, значит это mutable объект
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

Immutable

Immutable называются объекты, состояния и переменные которых нельзя изменить после создания объекта. Чем не отличный ключ для HashMap, да?) Например, String, Integer, Double и так далее. Пример:

// сделаем этот класс финальным, чтобы никто не мог его изменить
public final class ImmutableExample {

   private String address;

   ImmutableExample (String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   //удаляем сеттер

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("old address");
       System.out.println(obj.getAddress());

       // Поэтому никак не изменить это поле, значит это immutable объект
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}

24. Как написать immutable класс?

После того, как выясните, что такое mutable и immutable объекты, следующий вопрос будет закономерный — как написать его? Чтоб написать immutable неизменяемый класс, нужно следовать простым пунктам:

  • сделать класс финальным.
  • сделать все поля приватными и создать только геттеры к ним. Сеттеры, разумеется, не нужно.
  • Сделать все mutable поля final, чтобы установить значение можно было только один раз.
  • инициализировать все поля через конструктор, выполняя глубокое копирование (то есть, копируя и сам объект, и его переменные, и переменные переменных, и так далее)
  • клонировать объекты mutable переменных в геттерах, чтобы возвращать только копии значений, а не ссылки на актуальные объекты.

Пример:

/**
* Пример по созданию immutable объекта.
*/
public final class FinalClassExample {

   private final int age;

   private final String name;

   private final HashMap<String, String> addresses;

   public int getAge() {
       return age;
   }


   public String getName() {
       return name;
   }

   /**
    * Клонируем объект перед тем, как вернуть его.
    */
   public HashMap<String, String> getAddresses() {
       return (HashMap<String, String>) addresses.clone();
   }

   /**
    * В конструкторе выполняем глубокое копирование для mutable объектов.
    */
   public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
       System.out.println("Выполняем глубокое копирование в конструкторе");
       this.age = age;
       this.name = name;
       HashMap<String, String> temporaryMap = new HashMap<>();
       String key;
       Iterator<String> iterator = addresses.keySet().iterator();
       while (iterator.hasNext()) {
           key = iterator.next();
           temporaryMap.put(key, addresses.get(key));
       }
       this.addresses = temporaryMap;
   }
}

https://t.me/javatg

Источник

+1
0
+1
0
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *