Методы

Основные проблемные места работы с методами — это перегрузка и переопределение.

Вопрос 1

Что будет, если попытаться откомпилировать и запустить следующий код:

class Test {

public static void applyMethod() {

System.out.println("Using static method");

}

public void applyMethod() {

System.out.println("Using object method");

}

public static void main(String[] args) {

Test t = new Test();

t.applyMethod();

}

}

а. Код откомпилируется, отработает и выведет в консоль строку "Using static method".
б. Код откомпилируется, отработает и выведет в консоль строку "Using object method".
в. Код откомпилируется, но выпадет в ошибку во время выполнения.
г. Код не откомпилируется.

г. Код не откомпилируется.

Во время компиляции возникнет ошибка:

error: method applyMethod() is already defined in class Test

public void applyMethod() {

Несмотря на то, что в первый раз метод applyMethod() объявлен статическим, а во второй — обычным, с точки зрения синтаксиса языка это нарушение и метод считается объявленным дважды, что и приводит к ошибке компиляции.

Вопрос 2

Допустима ли такая перегрузка метода:

class Test {

public void checkOverloading(double d) {

}

public void checkOverloading(Double d) {

}

}

а. да
б. нет

а. да

double и Double — разные типы данных, соответственно checkOverloading(double d) и checkOverloading(Double d)разные сигнатуры метода, а значит такая перегрузка допустима.

Вопрос 3

Что будет, если попытаться откомпилировать и запустить следующий код?

class Test {

public void checkOverloading(double one, Double two) {

System.out.println("fst");

}

public void checkOverloading(Double two, double one) {//1

System.out.println("snd");

}

public static void main(String[] args) {

Test t = new Test();

t.checkOverloading(0d, 0d);//2

}

}

а. Код откомпилируется, выполнится и выведет в консоль "fst".
б. Код откомпилируется, выполнится и выведет в консоль "snd".
в. Во время компиляции в строке //1 возникнет ошибка некорректной перегрузки метода (method checkOverloading is already defined)
г. Во время компиляции в строке //2 возникнет ошибка неоднозначного вызова метода (reference to checkOverloading is ambiguous)

г. Во время компиляции в строке //2 возникнет ошибка неоднозначного вызова метода (reference to checkOverloading is ambiguous)

Списки типов параметров перегруженных методов формально разные, так как double и Double — разные типы и в данном случае помещены в сигнатуры в разном порядке. Но вызывать перегруженные подобным образом методы проблемно. Данный вызов двусмысленный (невозможно определить, какой из нулей упаковывать в объект) и компилятор не позволит по этой причине скомпилировать такой код.

Вопрос 4

Допустима ли такая перегрузка метода:

class Test {

public void checkOverloading(List<String> strList) {

System.out.println("String");

}

public void checkOverloading(List<Integer> intList) {

System.out.println("Integer");

}


}

а. да
б. нет

б. нет

Интерфейс List<E> параметризован, но после компиляции данные о параметрах стираются. Таким образом во время выполнения программы Java не знает, какой из методов выбирать, так они оба для неё выглядят примерно так: checkOverloading(List<Object> par1). Поэтому такая перегрузка считается недопустимой.

Вопрос 5

Что будет, если попытаться откомпилировать следующий код?

interface EntityWithName {

String getName();

}


public class Person implements EntityWithName {

@Override

String getName() {

return "Some name";

}

}

а. Код откомпилируется без проблем.
б. Будет ошибка компиляции из-за того, что интерфейс EntityWithName не является публичным.
в. Будет ошибка компиляции из-за неправильного переопределения метода getName() в классе Person.

в. Будет ошибка компиляции из-за неправильного переопределения метода getName() в классе Person.

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

Метод getName() переопределён неверно по следующей причине. В интерфейсе все методы являются public, поэтому модификаторы доступа обычно опускают, но метод всё равно считается публичным. Всегда.

Person — класс и в нём у методов могут быть разные модификаторы доступа. В данном случае отсутствие модификатора говорит о том, что используется доступ по-умолчанию.

Доступ по-умолчанию считается более узким, чем публичный доступ, а при переопределении метода сужать доступ запрещено. Поэтому данное переопределение некорректно и код не скомпилируется.

Вопрос 6

Что будет, если попытаться откомпилировать следующий код?

abstract class EntityWithName {

public abstract String getName();

}


abstract class Person extends EntityWithName {

public String getName(boolean includeNick) {

return includeNick ? "John Smith aka WildDog" : "John Smith";

}

}


class Employee extends Person {

public static void main(String[] args) {

Person p = new Employee();

System.out.println(p.getName(true));

}

}

а. Код откомпилируется без проблем.
б. Будет ошибка компиляции из-за того, что класс Person не реализует метод getName().
в. Будет ошибка компиляции из-за того, что класс Employee не реализует метод getName().

в. Будет ошибка компиляции из-за того, что класс Employee не реализует метод getName().

Хотя в классе Person реализован метод getName(boolean includeNick), он не реализует абстрактный метод getName() класса EntityWithName, а только перегружает его, так как у методов разный набор параметров. Соответственно метод getName() (версия без параметров) должен быть реализован, чего не происходит, а значит код не скомпилируется.

Класс Person объявлен абстрактным. Это означает, что он не обязан реализовывать никакие абстрактные методы, ни свои, ни своих предков.

Класс Employee — первый не абстрактный класс в цепочке наследования и именно на него ложится задача реализовать все абстрактные методы предков. Но поскольку этого не происходит, то именно из-за этого код не откомпилируется.

Вопрос 7

Что выведет на экран следующий код?

public class Study {


public static void main(String[] args) {

int x = 1;

int y = 2;

summator(x, y);


System.out.println(x);

}


public static void summator(int x, int y) {

x += y;

}

}

а. Код не откомпилируется с ошибкой: error: non-static method summator(int,int) cannot be referenced from a static context.
б. Код откомпилируется и в результате выполнения выведет на консоль 1.
в. Код откомпилируется и в результате выполнения выведет на консоль 3.

б. Код откомпилируется ив результате выполнения выведет на консоль 1.

Вариант "а" заведомо неправильный, так как метод summator() объявлен статическим и может быть вызван из статического метода main.

Поскольку в Java методы принимают параметры по значению, а не по ссылке, в параметр x метода summator() будет передана копия значения переменной x из main(). Что бы далее ни происходило с этой копией в summator()'е, на переменную x из main() это никак не повлияет. Она так и останется равной 1, что мы и увидим на консоли.

Вопрос 8

Что выведет на экран следующий код?

class Animal {

private void favourFood() {

System.out.println("any protein");

}


public static void main(String[] args) {

Animal cat = new Cat();

cat.favourFood();

}

}


class Cat extends Animal {

public void favourFood() {

System.out.println("shrimps");

}

}

а. Код не откомпилируется с ошибкой: error: method favourFood() is already defined in class Animal.
б. Код откомпилируется и в результате выполнения выведет на консоль "any protein".
в. Код откомпилируется и в результате выполнения выведет на консоль "shrimps".

б. Код откомпилируется и в результате выполнения выведет на консоль "any protein".

В классах-наследниках можно определять методы с тем же именем и набором параметров, как в классах-предках, если требуется какой-то метод класса-предка переопределить. Поэтому вариант "а" заведомо неправильный.

В данном примере метод favourFood() определён в предке так:

private void favourFood()

а в наследнике так:

public void favourFood()

поскольку в предке стоит модификатор доступа private, то происходит так называемое сокрытие метода предка, а не его переопределение. При этом, поскольку метод main(), где происходит вызов favourFood() также находится внутри Animal, то этот private метод доступен из для вызова из этого места.

Поэтому при вызове cat.favourFood(); диспетчеризация метода не произойдёт, а будет вызван метод предка (ведь переменная cat объявлена типом Animal), который и выведет на консоль "any protein".

Если бы cat была объявлена так: Cat cat = new Cat();, то сработал бы метод потомка.

Вопрос 9

Что выведет на экран следующий код?

class Animal {

private void favourFood() {

System.out.println("any protein");

}

}


class Cat extends Animal {

public void favourFood() {

System.out.println("shrimps");

}

public static void main(String[] args) {

Animal cat = new Cat();

cat.favourFood();

}

}

а. Код не откомпилируется с ошибкой: error: favourFood() has private access in Animal.
б. Код откомпилируется и в результате выполнения выведет на консоль "any protein".
в. Код откомпилируется и в результате выполнения выведет на консоль "shrimps".

а. Код не откомпилируется с ошибкой: error: favourFood() has private access in Animal.

В данном примере метод main(), в котором происходит вызов favourFood(), находится в классе-потомке Cat. Класс-потомок не имеет доступа к private методам и полям предка. Поэтому в этом месте метод favoutFood() на переменной типа Animal (а переменная cat объявлена типом Animal) вызван быть не может.

Это известно ещё на этапе компиляции, поэтому код просто не откомпилируется.

Кроме того, в данной ситуации не происходит никакой диспетчеризации, поскольку в предке метод favourFood() объявлен private, а в потомке одноимённый метод favourFood() объявлен public. Такой синтаксис приводит к так называемому сокрытию метода, а не переопределению. И во время выполнения программы для таких методов не действует диспетчеризация. Но в данном конкретном случае до выполнения программы дело вообще не дойдёт.