Для розв'язання задачі скористаємось методом виключення:
- Код успішно скомпілюється, тому варіант 3 відпадає;
- Runtime виключень також немає, тому варіант 1 - теж не правильний;
- Залишається або
BaseChildBase
, або BaseChildChild
. Оскільки при створенні змінної l3
ми створюємо екземпляр класу ChildLogger
, у якому метод log
перевизначено, у консоль буде виведено BaseChildChild
. Отже, правильна відповідь 4.
Це питання покриває реалізацію двох аспектів ООП у Java - subtype polymorphism та наслідування.
Розглянемо більш цікавий приклад використання наслідування:
package com.learning;
public class Main {
public static void main(String[] args) {
BaseValueCalculator l1 = new BaseValueCalculator();
BaseValueCalculator l2 = new AdvancedValueCalculator();
System.out.println(l1.calculate().value());
System.out.println(l2.calculate().value());
}
}
class BaseValue{
protected final String val;
public BaseValue(String val){
this.val = val;
}
public String value(){
return this.val;
}
}
class CapitalizedValue extends BaseValue{
public CapitalizedValue(String val){
super(val);
}
@Override
public String value(){
return this.val.toUpperCase();
}
}
class BaseValueCalculator {
public BaseValue calculate() {
return new BaseValue("I'm simple calculator!");
}
}
class AdvancedValueCalculator extends BaseValueCalculator {
@Override
final public CapitalizedValue calculate() {
return new CapitalizedValue("I'm advanced calculator!");
}
}
Який результат виконання цього коду?
- RuntimeException
- I'm simple calculator!I'M ADVANCED CALCULATOR!
- Помилка компіляції
- I'm simple calculator!I'm advanced calculator!
Головним питанням у цьому прикладі є те, чи цей код скомпілюється. Return type методів у Java [коваріантний](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science), отже, його можна перевизначити у класах нащадках. Розглянемо щє один приклад, цього разу спробуємо погратись з типами параметрів:
package com.learning;
public class Main {
public static void main(String[] args) {
BaseLogger l1 = new BaseLogger();
ChildLogger l2 = new ChildLogger();
BaseValue message = new BaseValue("lsp is a myth");
l1.log(message);
l2.log(message);
}
}
class BaseLogger {
public void log(BaseValue value) {
System.out.println(value.value());
}
}
class ChildLogger extends BaseLogger {
final public void log(CapitalizedValue value) {
System.out.println(String.format("<<<%s>>>", value.value()));
}
}
class BaseValue {
protected final String val;
public BaseValue(String val) {
this.val = val;
}
public String value() {
return this.val;
}
}
class CapitalizedValue extends BaseValue {
public CapitalizedValue(String val) {
super(val);
}
@Override
public String value() {
return this.val.toUpperCase();
}
}
Який результат виконання даного коду?
- RuntimeException
lsp is a myth <<<lsp is a myth>>>
- Помилка компіляції
lsp is a myth lsp is a myth
Якщо ви відповіли “помилка компіляції”, то вітаю вас: ви помилились. Правильна відповідь - 4, привітаємо всіх, хто так відповів. На відміну від return значень, параметри методу є інваріантними. У прикладі ми спробували зробити параметр нащадка коваріантним, але навіть якщо ми використаємо контраваріантний тип параметру(BaseLogger приймав би CapitalizedValue, а ChildLogger - BaseValue), все одно перевизначення методу log не відбулось би.
Тому у ChildLogger відбувається не overriding, а overloading методу log, і при виклику методу log з параметром типу BaseValue буде викликано реалізацію методу log батьківського класу, а при виклику з параметром типу CapitalizedValue буде викликано перевантажену реалізацію з класу ChildLogger. На практиці, хоч анотація @Override
не є обов'язковою, її слід завжди використовувати для запобігання неочікуваним перевантаженням замість перевизначень.
На цьому на сьогодні все, дякуємо за увагу. Якщо хочеш більше дізнатись про наслідування та варіантність у Java, тицяй на посилання знизу. Побачимось в Академії ;)