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

Зачем нужны абстрактные классы

Идея абстрактного класса заключается в следующем предположении — для работы иногда вам требуются не полностью готовые классы, а «заготовки» (полуфабрикаты, если хотите). Они уже кое-что умеют, но в «сыром виде» их использовать нельзя. Причем здесь стоит выделить два момента:

  1. Создать экземпляр такого класса нельзя
  2. Такой класс требует доработки под какие-либо конкретные условия.

Например в Java есть весьма наглядный класс java.util.Calendar. В его арсенале достаточно много полезных и нужных функций, но есть одна особенность — он не реализует какой-то конкретный календарь. Думаю, вы знакомы с тем фактом, что на Земле люди живут по разным календарям. Светские власти и католики живут по Григорианскому календарю. Русская православная церковь живет по Юлианскому календарю. А если ваша программа будет работает на марсоходе (что на самом деле так и есть — Java работает на марсоходе Spirit), то ей придется учитывать марсианский календарь. Как вы понимаете календари имеют различия, но вряд ли вы удивитесь, если вам скажут, что возможность прибавить 5 дней к какой-либо дате должна присутствовать во всех вариантах. Т.е. во всех этих календарях есть общий набор функций, который может иметь одинаковую реализацию. Отсюда рождается идея абстрактного класса, который с одной стороны не может создавать объекты, а с другой стороны, может иметь уже готовые функции.
Еще одним примером абстрактного класса может служить уже знакомый нам класс JComponent. Этот класс умеет многое, он только не умеет рисовать что-либо. И если его этому научить — создать класс на его основе и переопределить метод paintComponent, то мы получим то, что нам надо.
Создание абстрактного класса на самом деле достаточно сложная архитектурная задача. Необходимость использовать именно абстрактный класс проявляется не сразу. Требуется провести анализ задачи и набора классов, который позволит принять решение.
А вот техническая сторона достаточно простая — для объявления абстрактного класса достаточно добавить ключевое слово abstract в описании класса.

Если вы попробуете создать объект этого класса, то компилятор выдаст сообщение об ошибке.
Кроме того, что мы можем заставить разработчика НЕ пользоваться нашим классом впрямую, мы можем еще более жестко подойти к наследованию — установить правила, которые заставят класс-наследник реализовать определенные методы.
Для этого необходимо не только класс описать как абстрактный, но и метод, который должен обязательно реализовать наследник. Форма записи достаточно несложная. Здесь только надо отметить, что тело метода отсутствует совсем — сразу за описанием метода ставится точка с запятой. Например:

В нашем примере не только класс объявлен как абстрактный, что запрещает создание экземпляра такого класса, но и требуется переопределить метод processModel.
Внимательный читатель возможно отметил факт, что я в одном случае слово abstract поставил в самом начале, а в втором примере после слова public. Это сделано специально — я хотел продемонстрировать, что можно делать объявления и так и так.

И наконец мы сделаем более сложный пример, который продемонстрирует использование абстрактного класса. В части Полиморфизм мы создали приложение, которое рисовало на форме три вида фигур: треугольник, прямоугольник, овал. В этом приложении мы использовали абстрактный класс JComponent, который не имеет обязательности для переопределения метода для рисования paintComponent. Создадим абстрактный класс, который наследуется от JComponent и имеет абстрактный метод, который надо переопределить в классах-наследниках. Вот такой класс:

Как видим теперь наш класс не просто абстрактный и значит не может быть создан объект такого класса, но также необходимо переопределить метод paintShape. Напишем реализацию наших классов для треугольника, овала и прямоугольника:

Треугольник

Прямоугольник

Овал

Класс для формы

В конце класс для запуска нашего приложения

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

33 comments to Абстрактные классы

  • javaNoob  says:

    Лично я, пока узрел в описанном примере, что класс АбстрактнойФигуры стал выполнять роль прослойки-посредника между JComponent и нашими фигурами(навязывая им(фигурам) свою схему прорисовки). Т.е. теперь у фигур появилась некая общая но абстрактная фигура. Архитектура выходит более логичная.
    ЗЫ. Пока я это писал, меня озарило — определяя в абстрактном классе какой то функционал мы даём себе гарантию, что любой наследник нашего абстрактного класса будет вынужден следовать его правилам, чего не получилось бы если мы захотели впендюрить вместо абстракта обычный класс. Или не так ? Вобщем я уже сам не понимаю что написал :).
    Ещё вопрос — сильно ли усложняется обектно-ориентированное проектирование в Java, если нет чёткого понимания, когда применяются абстрактные классы?

    • admin  says:

      По поводу асбтрактного класса с обязательной функциональностью — это совершенно правильная мысль. Если совместить это с объявлением final, то тогда очень красиво получается.
      Что касается вопроса — я исхожу из требований задачи. Специально создавать абстрактный класс наверно не есть хорошо. Но если он ложится в логическое описание задачи — почему бы не использовать.

  • Serg  says:

    А скажите, для чего тут нужен protected void paintComponent(Graphics g) и почему без него фигуры не рисуются?

    • admin  says:

      Этот метод есть у класса JComponent и он вызывается из paint. Разработчики Swing решили, что надо сделать именно так — переопределять paintComponent. Вот мы так и сделали.

  • beginer  says:

    и все таки я не понял зачем нужны абстрактные классы
    и зачем их использовать если есть интерфейсы работающие по той же схеме

    • admin  says:

      Интерфейсы — это просто объявление, что класс должен реализовать. Но класс в этом случае должен реализовать ВСЕ методы интерфейса. Абстрактный класс уже предоставляет некоторую функциональность. Например — для табличной модели существует интерфейс TableModel — http://docs.oracle.com/javase/7/docs/api/javax/swing/table/TableModel.html
      Реализовать полностью всю функциональность — это большая задача. Для упрощения заранее сделан класс AbstractTableModel — http://docs.oracle.com/javase/7/docs/api/javax/swing/table/TableModel.html который позволяет гораздо проще реализовать функциональность — там уже что-то работает.

  • beginer  says:

    кстати
    под созданием экземпляров я понимаю что-то вроде
    someClass object = new someClass();
    вначале сказано что экземпляров создавать запрещено
    но у меня получилось их создать вышеуказанным способом
    какого такого ?!
    разве не должны появиться исключения и т.д.?
    кстати
    недавно начал изучать джаву так что простите за тупые вопросы

    • admin  says:

      Надо код смотреть — не может компилятор для абстрактного класса TestClass разрешить такой оператор

      TestClass tc = new TestClass();

      • beginer  says:

        public static void main(String[] args) throws IOException {
        AbCL object1 = new AbCL() {

        };
        object1.a=1;
        object1.b=1;
        object1.c=1;
        object1.s=»a»;
        object1.d=»b»;
        object1.f=»c»;
        }
        }

        abstract class AbCL{
        int a,b,c;
        String s,d,f;
        }

        является ли это созданием экземпляра ??

        • admin  says:

          Вы здесь схитрили — Вы же создали новый анонимный класс, унаследованный от абстрактного. Так что это уже не абстрактный класс, а его потомок. И он не абстрактный. Так что здесь все честно.
          Попробуйте сделать в классе AbCL абстрактный метод и тогда в анонимном классе Вам придется его переопределить.

  • beginer  says:

    пришлось реализовать абстрактный метод
    а в остальном все так же
    это считается за экземпляр или нет ?)
    я уже капец как запутался

    • admin  says:

      Вы создали экземпляр класса. Но Вы использовали для создания объекта НЕ класс AbCL — Вы использовали его НАСЛЕДНИКА. То, что класс анонимный, ничего не меняет в плане наследования. Все равно это полноценный НАСЛЕДНИК абстрактного класса AbCL. И этот наследник уже НЕ абстрактный класс. Значит можно создать объект.

      • beginer  says:

        что за анонимный класс ?
        откуда ?
        а что если :

        package nextstep_01;

        abstract public class NewClass {
        int a,b,c;
        void cout(){
        System.out.println(«l9l9l9»);
        }
        }

        package nextstep_01;

        public class NextStep_01 {

        public static void main(String[] args){
        NewClass ob = new NewClass() {};
        ob.a=1;
        ob.b=2;
        ob.c=3;
        ob.cout();
        }
        }

        • beginer  says:

          кстати
          та же самая чертовщина с интерфейсом
          что значит анонимные классы ???

        • admin  says:

          Почитайте тут про анонимные классы — http://java-course.ru/begin/class_description_ext/ У вас такой и получился.
          И в этом примере тоже самое — вот строка
          NewClass ob = new NewClass() {};
          Вы же не только вызвали конструкторв — Вы же еще зачем-то использовали ФИГУРНЫЕ СКОБКИ сразу после конструктора.
          Вы создали анонимный класс — посмотрите в директорию, где у вас скомпилированные классы лежат — там их бужет больше, чем файлов .java

          Было настолько курьезно, что я внес дополнения в статью именно по этому случаю.

  • beginer  says:

    хехехе
    без обид )
    большое спасибо за терпение !

  • Nibbler  says:

    Правильно ли я понимаю, что в абстрактном классе JComponent нет нереализованных методов, которые в обязательном порядке должны быть переопределены в классах-наследниках? Насколько я смог просмотреть исходники, даже метод paintComponent(), который мы переопределяем в нашем случае, в родительском классе определен:

    Т.е., в противном случае нам нужно было бы реализовать все-все заявленные но не реализованные методы класса JComponent в нашем классе-наследнике?

    • admin  says:

      Именно так — если есть абстрактные методы, то они должны либо быть реализованы в потомке, либо потомок сам должен быть абстрактным. JComponent несколько запутывает — в нем действительно нет абстрактных методов, но сам он абстрактный. Почему так решили сделать — могу только догадываться. Например, чтобы быть уверенным, что работаем именно с его наследником. Или просто, чтобы думали.

  • Сергей  says:

    Как быть с такой ситуацией, если нельзя создать объект абстрактного класса, то почему здесь в примере все прекрасно создается?

    Socket socket = new Socket(ipAddressServer, Client.PORT);//создает сокет использую IP-адрес сервера и порт
    //берем входной и выходной потоки сокета, теперь можем получать и отсылать данные клиентом
    InputStream sin = socket.getInputStream();
    OutputStream sout = socket.getOutputStream();

    • admin  says:

      Где именно создается объект абстрактного класса ?

  • Сергей  says:

    Конструкция InputStream sin = socket.getInputStream();
    InputStream абстрактный класс. Метод getInputStream() класса Socket по документации имеет сигнатуру
    public InputStream getInputStream() throws IOException, то есть, при вызове метод возвращает ссылку на объект типа InputStream. И если InputStream абстрактный класс, то как может существовать ссылка на его объект, я чего-то не догоняю.

    • admin  says:

      То, что метод заявлен, как возвращающий InputStream совсем не значит, что он действительно возвращает именно этот класс. Он возвращает НАСЛЕДНИКА. Еще раз почитайте о полиморфизме.

  • Сергей  says:

    Метод getInputStream класса Socket возвращает ссылку на объект абстрактного класса InputStream:
    InputStream sin = socket.getInputStream();

  • v  says:

    как я понял классы JComponent и наш AbstractShape абстрактного типа, но в нашем классе остались ТОЛЬКО 2 метода, которые предлагает IDE: paintComponent (который был) и paintShape (который мы создали/переопределили), т.е. мы выкинули ненужные нам методы, но функционал оставшихся методов не изменялся, правильно я понимаю?

    • admin  says:

      В нашем классе остались ВСЕ методы, которые есть у JComponent. Мы ПЕРЕОПРЕДЕЛИЛИ некоторые из них. Но все остальные не исчезли. И мы можем их использовать в нашем классе в том виде, в котором они есть у предка — JComponent.

  • v  says:

    глупый наверно вопрос: т.е. мы, получается, можем использовать другие методы JComponent в нашем AbstractShape (на текущий момент), которые IDE не предлагает и это будет работать? мое понимание такое в данном примере: мы даем установку (переопределение или разрешение) абстрактному классу AbstractShape использовать метод paintComponent (как есть в полном объеме), который называем paintShape, соответственно, другие методы, если они не переопределены, работать не будут. или IDE обманывает?

    • admin  says:

      Можем. Только либо protected (если мы наследники) либо public. По идее там такие есть и IDE должна их показывать.

  • Sid  says:

    А зачем paintComponent вызывает метод paintShape и почему без него не идет прорисовка объясните пожалуйста

    • Sid  says:

      Я ведь так понял что paintShape создан именно для того, что бы пошел вызвов AbstractShape?

      • admin  says:

        Вызов paintShape сделан в AbstractSahpe для того, чтобы все наследники могли только переопределить paintShape и больше ничего не делать.

  • Роман  says:

    Добрый день!
    Уважаемый администратор,
    не могу въехать, каким образом вызывается метод paintShape, если мы только создаем объект класса с этим методом(new Component()). То же самое дальше, где происходит вызов метода paintComponent, если мы только наследуем класс который содержит этот метод?

    Спасибо!

    • admin  says:

      По порядку — JFrame в момент рисования вызывает у каждого компонента метод paint. Он в свою очередь вызывает paintComponent.
      Метод paintShape мы определили в классе-предке как абстрактный и (ЧТО ВАЖНО) мы его вызываем в paintComponent. Теперь для каждого класса осталось только определить paintShape. Поулчаем цепочку:
      JFrame вызывает paint — paint вызывает paintComponent — paintComponent вызывает paintShape. У каждого компонента он свой.

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.