Java se сервлеты и jsp паттерны Gof junit Log4j ant


Глава 4 НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ



Pdf көрінісі
бет7/22
Дата11.12.2019
өлшемі8,99 Mb.
#53432
1   2   3   4   5   6   7   8   9   10   ...   22
Байланысты:
JAVA Methods Programming v2.march2015 (2)

Глава 4
НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
Если  делегированию  полномочий  уделять  внимание,  
ответственность накопится внизу, подобно осадку.
Закон делегирования Раска
Наследование
Отношение  между  классами,  при  котором  характеристики  одного  класса 
(суперкласса) передаются другому классу (подклассу) без необходимости их 
повторного определения, называется наследованием.
Подкласс наследует поля и методы суперкласса, используя ключевое слово 
extends. Класс может также реализовать любое число интерфейсов, используя 
ключевое слово implements. Подкласс имеет прямой доступ ко всем открытым 
переменным и методам родительского класса, как будто они находятся в под-
классе.  Исключение  составляют  члены  класса,  помеченные  private  (во  всех 
случаях) и «по умолчанию» для подкласса в другом пакете. В любом случае 
(даже если ключевое слово extends отсутствует) класс автоматически наследу-
ет свойства суперкласса всех классов — класса Object.
Множественное наследование классов запрещено, аналог предоставляет ре-
ализация  интерфейсов,  которые  не  являются  классами  и  содержат  описание 
набора методов, задающих поведение объекта класса, реализующего эти ин-
терфейсы. Наличие общих методов, которые должны быть реализованы в раз-
ных классах, обеспечивают им сходную функциональность.
Подкласс дополняет члены суперкласса своими полями и методами. Если 
имена методов совпадают, а параметры различаются, то такое явление называ-
ется перегрузкой методов (статическим полиморфизмом).
Если же совпадают имена и параметры методов, то это называется динами-
ческим  полиморфизмом.  То  есть  в  подклассе  можно  объявить  (переопреде-
лить) метод с тем же именем, списком параметров и возвращаемым значением, 
что и у метода суперкласса.
Способность ссылки динамически определять версию переопределенного 
метода в зависимости от переданного ссылке в сообщении типа объекта назы-
вается полиморфизмом.
Полиморфизм является основой для реализации механизма динамического 
или «позднего связывания».

ОСНОВЫ JAVA
98
В  следующем  примере  переопределяемый  метод  doPayment()  находится 
в двух классах CardAction и CreditCardAction. В соответствии с принципом 
полиморфизма вызывается метод, наиболее близкий к текущему объекту.
/* # 1 # наследование класса и переопределение метода # CardAction.java # 
CreditCardAction.java # CardRunner.java */
package
 by.bsu.inheritance;
public
 class CardAction {
         // поля, конструкторы, методы
         public void doPayment(double amountPayment) {
                  // реализация
                  System.out.println("complete from debt card");
         }
}
package
 by.bsu.inheritance;
public
 class CreditCardAction extends CardAction {
         // поля, конструкторы, методы
         public boolean checkCreditLimit() { // собственный метод
                  return true// stub
         }
         @Override // аннотация указывает на полиморфную природу метода
         public void doPayment(double amountPayment) { // переопределенный метод
                  // реализация
                  System.out.println("complete from credit card");
         }
}
package
 by.bsu.inheritance;
public
 class CardRunner {
    public static void main(String[ ] args) {
         CardAction dc1 = new CardAction();
         CardAction dc2 = new CreditCardAction();
         CreditCardAction cc = new CreditCardAction();
         // CreditCardAction cca = new CardAction()// ошибка компиляции
         dc1.doPayment(15.5); // метод класса CardAction
         dc2.doPayment(21.2); // полиморфный метод класса CreditCardAction
         cc.doPayment(7.0); // полиморфный метод класса CreditCardAction
         cc.checkCreditLimit(); // неполиморфный метод класса CreditCardAction
         // dc2.checkCreditLimit(); // ошибка компиляции – неполиморфный метод
         ((CreditCardAction)dc1).checkCreditLimit(); // ошибка времени выполнения
         ((CreditCardAction)dc2).checkCreditLimit(); // ок
    }
} 
Объект dc1 создается при помощи вызова конструктора класса CardAction, и, 
соответственно, при вызове метода doPayment() вызывается версия метода из клас-
са CardAction. При создании объекта dc2 ссылка типа CardAction инициализирует-
ся объектом типа CreditCardAction. При таком способе инициализации  ссылка 
на суперкласс получает доступ к методам, переопределенным в подклассе.

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
99
При объявлении совпадающих по сигнатуре (имя, тип, область видимости) 
полей в суперклассе и подклассах их значения не переопределяются и никак 
не пересекаются, т. е. существуют в одном объекте независимо друг от друга.  
Такое  решение  является  плохим  примером  кода,  который  не  используется 
в практическом программировании. Не следует использовать вызов полиморф-
ных методов в конструкторе. Эти действия могут привести к использованию 
и  получению  недостоверной  информации  при  работе  метода.  Для  доступа 
к  полям  текущего  объекта  можно  использовать  указатель  this,  для  доступа 
к  полям  суперкласса  —  указатель  super.  Другие  возможности  рассмотрены 
в следующем примере:
/* # 2 # вызов полиморфного метода из конструктора # Dumb.java # Dumber.java */
package 
by.bsu.dumb;
class
 Dumb extends Object {
         this.id = 6; }
         int id;
         Dumb() {
                  System.out.println("конструктор класса Dumb ");
 
        // вызов потенциально полиморфного метода - плохо
                  id = this.getId(); 
                  System.out.println(" id=" + id);
         }
         int getId() { // 1
                  System.out.println("getId() класса Dumb ");
                  return id;
         }
}
class
 Dumber extends Dumb {
         int id = 9; // получится два поля с одинаковыми именами
         Dumber() {
                  System.out.println("конструктор класса Dumber ");
                  id = this.getId();
                  System.out.println(" id=" + id);
         }
         @Override
         int getId() { // 2
                  System.out.println("getId() класса Dumber ");
                  return id;
         }
}
В результате создания экземпляра Dumb objA = new Dumber() последователь-
но будет выведено:
конструктор класса Dumb
getId() класса Dumber
id=0

ОСНОВЫ JAVA
100
конструктор класса Dumber
getId() класса Dumber
id=9
Метод getId
() содержится как в классе Dumb, так и в классе Dumber и яв-
ляется переопределенным. Перед вызовом конструктора Dumber() вызывается 
конструктор класса Dumb. Но так как в обоих случаях создается объект класса 
Dumber, то вызывается метод getId(), объявленный в классе Dumber, который 
в свою очередь оперирует полем id, еще не проинициализированным для клас-
са Dumber. В результате id получит значение по умолчанию, т. е. нуль.
Воспользовавшись преобразованием типов вида ((Dumber) objA).id, легко 
можно получить доступ к полю id из подкласса.
Классы и методы final
Нельзя переопределить метод в порожденном классе, если в суперклассе он 
объявлен со спецификатором final:
class 
ConstMethod {
         // метод method() не может быть полиморфным
         final void method() {}
}
class
 Sub extends ConstMethod {
         // следующий метод невозможен
         @Override
         void method() {} // ошибка компиляции
}
Если разработчик объявляет метод как final, следовательно, он считает, что 
его версия метода окончательна и совершенствованию не подлежит.
Процесс  инициализации  экземпляра  должен  быть  строго  определен 
и не подвергаться изменениям. Исключить подмену реализации метода, вызы-
ваемого в конструкторе, следует объявлением метода как final, т. е. при этом 
метод не может быть переопределен в подклассе. Такое объявление гарантиру-
ет обращение именно к этой реализации. Корректное определение вызова ме-
тода класса из конструктора представлено ниже:
/* # 3 # вызов нестатического final-метода из конструктора # Coin.java */
package
 by.bsu.fund.entity;
public
 class Coin {
        private double diameter;
        // поля
        public Coin(double diameter) {
                  super();
                  initDiameter(diameter); // обращение к final-методу
        }

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
101
        public final void initDiameter(double value) { // можно public final заменить на private
                  if (value > 0) {
                              diameter = value;
                  } else {
                              System.out.println("Oтрицательный диаметр!");
                  }
        }
        // методы
}
Рекомендуется при разработке классов из конструкторов вызывать методы, 
на которые не распространяются принципы полиморфизма. Метод может быть 
еще объявлен как private с таким же результатом.
Нельзя создать порожденный класс для класса, объявленного со специфика-
тором final:
// класс String не может быть суперклассом
public final class 
String { /* код */ }
// следующий класс невозможен
class
 MegaString extends String { /* код */ // ошибка компиляции
Если необходимо создать собственную реализацию final-класса, то создает-
ся класс-оболочка, где в качестве поля представлен final-класс. В свою очередь 
необходимые  имена  методов  делегируются  из  final-класса,  но  им  придается 
необходимая разработчику функциональность.
Такой подход гарантирует невозможность прямого использования класса-
оболочки вместо оборачиваемого класса и наоборот.
// # 4 # класс-оболочка для класса String # WrapperString.java 
package
 by.bsu.wrapper;
public
 class WrapperString {
          private String str;
          public  WrapperString() {
                    str = new String();
          }
          public  WrapperString(String s) {
                    str = new String(s);
          }
          public int indexOf(int arg) { // делегированный метод
                    // новая функциональность
                    return arg;
          }
          // другие делегированные методы
}
Класс WrapperString не является наследником класса String, и его объект не мо-
жет быть использован для передачи по ссылке на класс String. Класс WrapperString 
может быть суперклассом, поэтому его поведение можно изменять.

ОСНОВЫ JAVA
102
Использование super и this
Ключевое слово super применяется для обращения к конструктору супер-
класса и для доступа к полю или методу суперкласса. Например:
super
(список_параметров); /* обращение к конструктору суперкласса 
                                   с передачей параметров или без нее*/
super
.id = 35; /* обращение к атрибуту суперкласса */
super
.getId(); // вызов метода суперкласса
Первая форма super применяется только в конструкторах для обращения 
к  конструктору  суперкласса  только  в  качестве  первой  строки  кода  и  только 
один раз.
Вторая форма super используется для доступа из подкласса к переменной id 
суперкласса. Третья форма специфична для Java и обеспечивает вызов из под-
класса переопределенного метода суперкласса, причем если в суперклассе этот 
метод не определен, то будет осуществляться поиск по цепочке наследования 
до тех пор, пока он не будет найден.
Во всех случаях с использованием super можно обратиться только к бли-
жайшему суперклассу, т. е. «перескочить» через суперкласс, чтобы обратиться 
к его суперклассу, невозможно.
Следующий код показывает, как, используя this, можно строить одни кон-
структоры на основе других.
// # 5 # super и this в конструкторе # Point1D.java # Point2D.java # Point3D.java 
package
 by.bsu.point;
public
 class Point1D {
          private int x;
          public Point1D(int x) {
                    this.x = x;
          }
}
package
 by.bsu.point;
public
 class Point2D extends Point1D {
          private int y;
          public Point2D(int x, int y) {
                    super(x);
                    this.y = y;
          }
} 
package
 by.bsu.point;
public
 class Point3D extends Point2D {
          private int z; 
          public Point3D(int x, int y, int z) {
                    super(x, y);
                    this.z = z;

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
103
          }
          public Point3D() {
                    this(-1, -1, -1); // вызов конструктора Point3D с параметрами
          }
}
В классе Point3D второй конструктор для завершения инициализации объ-
екта  обращается  к  первому  конструктору.  Такая  конструкция  применяется 
в случае, когда в класс требуется добавить конструктор по умолчанию с обяза-
тельным использованием уже существующего конструктора.
Ссылка this используется, если в методе объявлены локальные переменные 
с тем же именем, что и переменные экземпляра класса. Локальная переменная 
имеет преимущество перед полем класса и закрывает к нему доступ. Чтобы 
получить доступ к полю класса, требуется воспользоваться явной ссылкой this 
перед именем поля, так как поле класса является частью объекта, а локальная 
переменная нет.
Инструкция this() должна быть единственной в вызывающем конструкторе 
и быть первой по счету выполняемой операцией, т. е. обращение к конструкто-
ру суперкласса становится невозможным.
Переопределение методов и полиморфизм
Способность Java делать выбор метода, исходя из типа объекта во время 
выполнения, называется «поздним связыванием». При вызове метода его по-
иск  происходит  сначала  в  данном  классе,  затем  в  суперклассе,  пока  метод 
не будет найден или не достигнут Object — суперкласс для всех классов.
Если два метода с одинаковыми именами и возвращаемыми значениями на-
ходятся в одном классе, то списки их параметров должны отличаться. То же 
относится  к  методам,  наследуемым  из  суперкласса.  Такие  методы  являются 
перегружаемыми (overloading). При обращении вызывается доступный метод, 
список параметров которого совпадает со списком параметров вызова.
Если объявление метода подкласса полностью, включая параметры, совпада-
ет с объявлением метода суперкласса (порождающего класса), то метод подклас-
са переопределяет (overriding) метод суперкласса. Переопределение методов яв-
ляется основой концепции динамического связывания, реализующей полимор-
физм.  Когда  переопределенный  метод  вызывается  через  ссылку  суперкласса, 
Java определяет, какую версию метода вызвать, основываясь на типе объекта, 
на который имеется ссылка. Таким образом, тип объекта определяет версию ме-
тода на этапе выполнения. В следующем примере рассматривается реализация 
полиморфизма на основе динамического связывания. Так как суперкласс содер-
жит методы, переопределенные подклассами, то объект суперкласса будет вызы-
вать методы различных подклассов в зависимости от того, на объект какого под-
класса у него имеется ссылка.

ОСНОВЫ JAVA
104
/* # 6 # динамическое связывание методов # Point1D.java # Point2D.java # Point3D.java 
# PointReport.java # Runner.java */
package
 by.bsu.point;
public
 class Point1D {
          private int x;
          public Point1D(int x) {
                    this.x = x;
          }
          public double length() {
                    return Math.abs(x);
          }
          @Override
          public String toString() {
                    return " x=" + x;
          }
}
package
 by.bsu.point;
public
 class Point2D extends Point1D {
          private int y; 
          public Point2D(int x, int y) {
                    super(x);
                    this.y = y;
          }
          @Override
          public double length() {
                    return Math.hypot(super.length(), y);
                    /* просто length() нельзя, т.к. метод будет вызывать сам себя, что 
                       приведет к бесконечной рекурсии и ошибке во время выполнения  */
          }
          @Override
          public String toString() {
                    return super.toString() + " y=" + y;
          }
} 
package
 by.bsu.point;
public
 class Point3D extends Point2D {
          private int z; 
          public Point3D(int x, int y, int z) {
            super(x, y);
            this.z = z;
          }
          public Point3D() { 
            this(-1, -1, -1); // вызов конструктора Point3D с параметрами
          }
          @Override
          public double length() {
                    return Math.hypot(super.length(), z);
          } 

НАСЛЕДОВАНИЕ И ПОЛИМОРФИЗМ
105
          @Override
          public String toString() {
                    return super.toString() + " z=" + z;
          }
}
package
 by.bsu.point;
public
 class PointReport {
          public void printReport(Point1D p) {
                    System.out.printf("length=%.2f %s%n", p.length(), p);
                    // вызовы out.print(p.toString()) и out.print(p) для объекта идентичны !
          }
}
package
 by.bsu.point;
public
 class Runner {
          public static void main(String[] args) {
                    PointReport d = new PointReport();
                    Point1D p1 = new Point1D(-7);
                    d.printReport(p1);
                    Point2D p2 = new Point2D(3, 4);
                    d.printReport(p2);
                    Point3D p3 = new Point3D(3, 4, 5);
                    d.printReport(p3);
          }
}
Результат:
length=7.00  x=-7
length=5.00  x=3 y=4
length=7.07  x=3 y=4 z=5
Использование аннотации @Override позволяет выделить в коде переопре-
деленный метод и сгенерирует ошибку компиляции в случае, если програм-
мист допустит грамматическую ошибку (опечатку) в описании сигнатуры по-
лиморфного метода.
Следует помнить, что при вызове toString() обращение super всегда проис-
ходит к ближайшему суперклассу. Переадресовать вызов, минуя суперкласс, 
невозможно! Аналогично при вызове super() в конструкторе обращение про-
исходит к соответствующему конструктору непосредственного суперкласса.
Основной вывод: выбор версии переопределенного метода производит-
ся на этапе выполнения кода.
Все методы Java являются виртуальными (ключевое слово virtual, как в C++, 
не используется).
Статические методы можно перегружать и «переопределять» в подклассах, 
но их доступность всегда зависит от типа ссылки и атрибута доступа, и никог-
да — от типа самого объекта.

ОСНОВЫ JAVA
106
Методы подставки
После выхода пятой версии языка появилась возможность при переопреде-
лении методов указывать другой тип возвращаемого значения, в качестве кото-
рого можно использовать только типы, находящиеся ниже в иерархии наследо-
вания, чем исходный тип.
/* # 7 # методы-подставки # Point1DCreator.java # Point2DCreator.java #  
Point3DCreator.java # BuildRunner.java*/
package
 by.bsu.creator;
public
 class Point1DCreator {
          public Point1D createPoint() {
                    System.out.println("Point1D");
                    return new Point1D(1);
          }
}
package
 by.bsu.creator;
public
 class Point2DCreator extends Point1DCreator {
          @Override
          public Point2D createPoint() { // метод - подставка
                    System.out.println("Point2D");
                    return new Point2D(2, 5);
          }
}

Достарыңызбен бөлісу:
1   2   3   4   5   6   7   8   9   10   ...   22




©engime.org 2024
әкімшілігінің қараңыз

    Басты бет