Когда НЕ следует вызывать метод super () при переопределении?

113

Когда я создаю свой собственный класс Android, я использую extendего собственный класс. Затем, когда я хочу переопределить базовый метод, я всегда вызываю super()метод, как всегда onCreate, onStopи т. Д.

И я подумал, что это все, так как с самого начала команда Android посоветовала нам всегда вызывать superпереопределение каждого метода.

Но во многих книгах я вижу, что разработчики, более опытные, чем я, часто пропускают звонки, superи я действительно сомневаюсь, что они делают это из-за недостатка знаний. Например, посмотрите на этот базовый класс синтаксического анализатора SAX, в котором superотсутствует startElement, charactersи endElement:

public class SAXParser extends DefaultHandler{
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if(qName.equalsIgnoreCase("XXY")) {
            //do something
        }
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        //do something
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        if(qName.equalsIgnoreCase("XXY")) {
            //do something
        }else () {
            //do something
        }
    }
}

Если вы попытаетесь создать какой-либо метод переопределения через Eclipse или любую другую IDE, superон всегда будет создан как часть автоматизированного процесса.

Это был простой пример. Книги полны подобного кода .

Как они узнают, когда нужно позвонить, superа когда можно не звонить?

PS. Не привязывайтесь к этому конкретному примеру. Это был просто случай, выбранный из множества примеров.

(Это может показаться вопросом новичка, но я действительно сбит с толку.)

sandalone
источник
3
endElementВ документе API говорится: «По умолчанию ничего не делать. Авторы приложений могут переопределить этот метод ...», что означает, что вы можете безопасно вызывать super, потому что он «ничего не делает», но вам это не нужно, и вы действительно можете переопределить его. Часто вы можете определить, нужно ли / можно или не следует этого делать, если вы прочитаете документацию по этому методу.
zapl
1
Небольшой пример: я использую onBackPressed () на экране-заставке, и я НЕ вызываю super, чтобы он не выходил из заставки, он просто отключает кнопку.
Bojan Kogoj
@sandalone Извините за вопрос, но есть ли у вас какой-либо опыт того, как обрабатывать налог на добавленную стоимость с помощью биллинга в приложении, и в каких странах это делается автоматически Google, и в которые я должен вручную сообщать / платить налог на добавленную стоимость? см. stackoverflow.com/questions/36506835/…
Видар Вестнес

Ответы:

144

Вызывая superметод, вы не переопределяете поведение метода, вы его расширяете .

Вызов superбудет выполнять любую логику, которую расширяемый класс определил для этого метода. Учтите, что это может быть важным моментом, когда вы вызываете superреализацию в переопределении вашего метода. Например:

public class A { 
    public void save() { 
         // Perform save logic
    }
}

public class B extends A {
    private Object b;
    @Override
    public void save() { 
        super.save(); // Performs the save logic for A
        save(b); // Perform additional save logic
    }
}

Вызов to B.save()будет выполнять save()логику для обоих Aи Bв этом конкретном порядке. Если бы ты не звонил super.save()внутрь B.save(), тебя A.save()бы не позвонили. И если вы позвоните super.save()позже save(b), A.save()будет эффективно выполнено после B.save().

Если вы хотите переопределить super поведение (то есть полностью игнорировать его реализацию и предоставить все самостоятельно), вам не следует звонить super.

В SAXParserпримере вы предоставите, реализации из DefaultHandlerза эти методы являются просто пустыми, так что подклассы могут переопределить их и обеспечить поведение для этих методов. В javadoc для этого метода это также указано.

public void startElement (String uri, String localName,
    String qName, Attributes attributes) throws SAXException {
    // no op
}

Что super()касается вызова по умолчанию в коде, сгенерированном IDE, как @barsjuуказано в его комментарии, в каждом конструкторе есть неявный вызов super()(даже если вы не пишете его в своем коде), что означает, в этом контексте, вызов super' конструктор по умолчанию. Он IDEпросто записывает его для вас, но он также будет вызван, если вы его удалите. Также обратите внимание, что при реализации конструкторов super()или любых его вариантов с аргументами (т.е. super(x,y,z)) можно вызывать только в самом начале метода.

Хави Лопес
источник
14
Также не учтите, что конструкторы for super()(конструктор суперкласса по умолчанию) вызывается всегда, даже если вы его не указываете. Если суперкласс не имеет конструктора по умолчанию, вы получите ошибку компиляции, если вы явно не вызовете один из конструкторов суперкласса в качестве первого оператора в конструкторе подкласса.
barsju
1
Да, в основном это просто лень - для методов, которые, как мы знаем, ничего не делают, просто опускаем это для краткости
Voo
1
Спасибо за ценные комментарии, @barjsu, я включил эти детали в ответ.
Хави Лопес,
16

Как они узнают, когда вы должны позвонить в super, а когда вы можете не звонить?

Обычно, если специальный метод API имеет решающее значение для жизненного цикла базового контекста фреймворка, он всегда будет явно указан и выделен в документации API, например в Activity.onCreate()документации API . Более того, если API соответствует надежной конструкции, он должен выдавать некоторые исключения, чтобы предупредить разработчика-потребителя во время компиляции проекта и убедиться, что он не вызовет ошибку во время выполнения.

Если это явно не указано в документации API, то для разработчика-потребителя вполне безопасно предположить, что метод API не является обязательным для вызова при его переопределении. Разработчик-потребитель должен решить, использовать ли поведение по умолчанию (вызвать superметод) или полностью переопределить его.

Если условие разрешено (я люблю программное обеспечение с открытым исходным кодом), разработчик-потребитель всегда может проверить исходный код API и увидеть, как метод фактически написан под капотом. Проверьте Activity.onCreate()источник и DefaultHandler.startElement()источник, например.

yorkw
источник
Спасибо, что объяснили мне дополнительные вещи. Спасибо, что напомнили мне проверить исходный код.
sandalone
7

Тест, который вы должны провести в своей голове, следующий:

«Я хочу, чтобы все функции этого метода были выполнены за меня, а потом что-то делать?» Если да, то вы хотите позвонить super(), а затем завершить свой метод. Это будет верно для «важных» методов, таких как onDraw(), который обрабатывает множество вещей в фоновом режиме.

Если вам нужна только часть функций (как и в случае с большинством методов, которые вы переопределите), вы, вероятно, не захотите вызывать super().

Майкл
источник
3

Что ж, Хави дал лучший ответ ... но вы, вероятно, знаете, что делает super(), когда вызывается в переопределенном методе ... он показывает, что вы сделали с поведением по умолчанию ...

например:

onDraw() 

метод в классе представления при переопределении ... вы рисуете что-то, прежде чем сказать super.onDraw (), он появляется, когда представление полностью нарисовано ... поэтому здесь вызов superнеобходим, поскольку у Android есть некоторые критически важные вещи (например, onCreate ())

но в то же время

onLongClick()

когда вы переопределяете это, вы не хотите называть super, потому что он вызывает диалоговое окно со списком параметров для EditText или любого другого аналогичного представления .. Это базовое отличие .. у вас есть выбор, чтобы оставить его несколько раз .. но для другие методы, такие как onCreate() , onStop()вы, должны позволить ОС справиться с этим.

ngesh
источник
2

Я не понял ваш вопрос четко, но если вы спрашиваете, почему не вызвать superметод:

Для вызова superметода есть причина : если в родительском классе нет конструктора с нулевым аргументом, то для него невозможно создать дочерний класс, поэтому либо вам нужно сохранить конструктор без аргументов в родительском классе, либо вам нужно для определения super()вызывающего оператора с помощью argument(how much argument constructor you have used in super class)в верхней части конструктора дочернего класса.

Надеюсь, это поможет. Если нет, дайте мне знать.

Викас Гупта
источник
2

Я реализовал список массивов ограничений, например

public class ConstraintArrayList<T> extends ArrayList<T> {
  ConstraintArrayList(Constraint<T> cons) {this.cons = cons;}
  @Override
  public boolean add(T element) {
    if (cons.accept(element))
      return super.add(element);
    return false;
  }
}

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

  1. Расширяемость, где вы хотите расширить возможности суперкласса
  2. Специфика, в которой вы хотите добавить конкретное поведение посредством полиморфизма, например, в распространенном примере семантики перемещения в «Царстве животных», где способ передвижения птиц (полета) и движения лягушек (прыжков) специфичен для каждого подкласса.
хозяйка
источник
2

Для тех, кто также задавался вопросом, какие переопределенные методы из Android Framework должны вызывать, superи нашел этот вопрос - вот текущая подсказка от 2019 года - Android Studio 3+ скажет вам, когда вам это нужно .

введите описание изображения здесь

Dominik
источник