Должны ли частные вспомогательные методы быть статическими, если они могут быть статическими

205

Допустим, у меня есть класс, предназначенный для реализации. У меня есть несколько частных «вспомогательных» методов внутри класса, которые не требуют доступа ни к одному из членов класса и работают исключительно с их аргументами, возвращая результат.

public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne(member);
       total += computeMore(member);
       return total;         
   }

   private double computeOne(Something arg) { ... }
   private double computeMore(Something arg) {... } 
} 

Есть ли какая-то конкретная причина для указания computeOneи computeMoreкак статические методы - или какая-то особая причина для этого?

Конечно, проще всего оставить их нестатичными, хотя они могут быть статичными, не вызывая проблем.

avalys
источник
3
Смотрите также, когда не следует использовать ключевое слово static в Java: stackoverflow.com/questions/1766715/…
Марк Батлер

Ответы:

174

Я предпочитаю, чтобы такие вспомогательные методы были private static; что даст понять читателю, что они не будут изменять состояние объекта. Моя IDE также будет показывать вызовы статических методов курсивом, поэтому я буду знать, что метод является статическим, не просматривая сигнатуру.

Эско Луонтола
источник
2
Один из моих любимых потоков. Я использую NetBeans, и он показывает статические вызовы методов с курсивом.
Skiabox
2
Обычно я объявляю эти вспомогательные методы, protected staticчто делает их очень легко тестируемыми в тестовом классе в том же пакете.
Джеймс
2
@ Джеймс или просто static(если мы просто хотим его для тестирования).
mrod
3
«Не будет изменять состояние объекта» - безупречное объяснение того, как отличить его от частного метода.
HopeKing
110

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

Я бы сделал их статичными, так как я обычно делаю это, если это вообще возможно. Но это только я.


РЕДАКТИРОВАТЬ: Этот ответ продолжает понижаться, возможно, из-за необоснованного утверждения о размере байт-кода. Так что я на самом деле проведу тест.

class TestBytecodeSize {
    private void doSomething(int arg) { }
    private static void doSomethingStatic(int arg) { }
    public static void main(String[] args) {
        // do it twice both ways
        doSomethingStatic(0);
        doSomethingStatic(0);
        TestBytecodeSize t = new TestBytecodeSize();
        t.doSomething(0);
        t.doSomething(0);
    }
}

Байт-код (получен с помощью javap -c -private TestBytecodeSize):

Compiled from "TestBytecodeSize.java"
class TestBytecodeSize extends java.lang.Object{
TestBytecodeSize();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

private void doSomething(int);
  Code:
   0:   return

private static void doSomethingStatic(int);
  Code:
   0:   return

public static void main(java.lang.String[]);
  Code:
   0:   iconst_0
   1:   invokestatic    #2; //Method doSomethingStatic:(I)V
   4:   iconst_0
   5:   invokestatic    #2; //Method doSomethingStatic:(I)V
   8:   new     #3; //class TestBytecodeSize
   11:  dup
   12:  invokespecial   #4; //Method "<init>":()V
   15:  astore_1
   16:  aload_1
   17:  iconst_0
   18:  invokespecial   #5; //Method doSomething:(I)V
   21:  aload_1
   22:  iconst_0
   23:  invokespecial   #5; //Method doSomething:(I)V
   26:  return

}

Вызов статического метода занимает два байт-кода (byteops?): iconst_0(Для аргумента) и invokestatic.
Вызов нестатического метода занимает три: aload_1(для TestBytecodeSizeобъекта, я полагаю), iconst_0(для аргумента) и invokespecial. (Обратите внимание, что если бы это не были частные методы, это было бы invokevirtualвместо invokespecial; см. JLS §7.7 Методы вызова .)

Теперь, как я уже сказал, я не ожидаю большой разницы в производительности между этими двумя, за исключением того факта, что invokestaticтребуется на один байт-код меньше. invokestaticи invokespecialоба должны быть немного быстрее, чем invokevirtual, поскольку они оба используют статическое связывание вместо динамического, но я понятия не имею, является ли один из них быстрее, чем другой. Я также не могу найти хорошие ссылки. Самая близкая вещь , которую я могу найти, - это статья JavaWorld 1997 года , которая в основном повторяет то, что я только что сказал:

Самые быстрые инструкции, скорее всего, будут invokespecialи invokestatic, потому что методы, вызываемые этими инструкциями, статически связаны. Когда JVM разрешает символическую ссылку для этих инструкций и заменяет ее прямой ссылкой, эта прямая ссылка, вероятно, будет включать указатель на фактические байт-коды.

Но многое изменилось с 1997 года.

Итак, в заключение ... Я думаю, что я все еще придерживаюсь того, что я сказал раньше. Скорость не должна быть причиной выбора одного над другим, поскольку в лучшем случае это будет микрооптимизация.

Майкл Майерс
источник
все эти отрицательные голоса за то, что кажется очень простым ответом. +1 в интересах правды, справедливости, ...
Стив Б.
Спасибо. (Хотя я мог бы удалить его и получить значок, когда он был в -3.: D)
Майкл Майерс
2
JIT-компилятор в любом случае встроит и оптимизирует их, поэтому количество инструкций байт-кода не имеет никакого отношения к тому, насколько быстро это будет.
Эско Луонтола
3
Вот почему я сказал: «Я не думаю, что это имеет какое-то значение для скорости (и если это так, то, вероятно, оно будет слишком маленьким, чтобы изменить ситуацию в целом)».
Майкл Майерс
7
Прежде чем приступать к каким-либо микрооптимизациям такого рода, вы должны рассмотреть влияние компилятора горячей точки. Итог: напишите свой код для удобства чтения людьми и оставьте оптимизацию для компилятора
Ichthyo
18

Моим личным предпочтением было бы объявить их статическими, так как это четкий признак того, что они не имеют состояния.

Стив Б.
источник
18

Ответ ... это зависит.

Если member - это переменная экземпляра, специфичная для объекта, с которым вы имеете дело, тогда зачем вообще передавать его как параметр?

Например:

public class Example {
   private Something member;

   public double compute() {
       double total = 0;
       total += computeOne();
       total += computeMore();
       return total;         
   }

   private double computeOne() { /* Process member here */ }
   private double computeMore() { /* Process member here */ } 
}
Powerlord
источник
5
+1: хотя пример плохой, слишком много методов, которые МОГУТ быть статичными, являются неприятным запахом кода. Кстати, статические методы на самом деле являются функциональными, они вовсе не ОО. Они могут принадлежать как метод из другого класса, или вы можете пропустить класс, к которому эта функция может принадлежать как метод.
Билл К
11

Одна из причин, по которой вы можете объявить статические вспомогательные методы, заключается в том, что вам нужно вызывать их в конструкторе класса «before» thisили super. Например:

public class MyClass extends SomeOtherClass { 
    public MyClass(String arg) {
       super(recoverInt(arg));
    }

    private static int recoverInt(String arg) {
       return Integer.parseInt(arg.substring(arg.length() - 1));
    }
}

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

oxbow_lakes
источник
Вы можете вызвать метод экземпляра в конструкторе, но если вы делегируете другому конструктору с помощью super (...) или this (...), вы не можете вызвать метод экземпляра до окончания делегирования (но статические хорошо, как вы указываете)
Кип
10

Я не могу придумать явных преимуществ для частного статического метода. Тем не менее, нет никаких особых преимуществ делать их нестатичными. Это в основном вопрос презентации: вы можете сделать их статичными, чтобы четко подчеркнуть тот факт, что они не изменяют объект.

Для метода с разными правами доступа, я думаю, есть два основных аргумента:

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

Кроме того, разница довольно мала, и я сильно сомневаюсь, что дополнительный указатель this, передаваемый методу экземпляра, имеет существенное значение.

Аксель Циглер
источник
4
Преимущество отсутствия статичности в том, что кто-то может создавать подклассы и переопределять поведение метода.
Клинт Миллер
7
Clint это закрытый метод, поэтому вы не можете переопределить метод.
Бхушан Бхангале
Точно, для частного метода я не думаю, что это имеет большое значение. Для публичных методов, однако, я согласен с тем, что вы говорите
Аксель Циглер
8

Правильный ответ:

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

Гьян Сингх
источник
5

или какая-то конкретная причина не [объявлять их как статические]?

Да.

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

Это может звучать глупо (и на самом деле это было бы так, если бы эти методы использовались только вами в 50-строчной программе), но в более крупных приложениях или в библиотеках, используемых кем-то другим, вы можете решить выбрать лучшую реализацию, но не хочу сломать существующий код.

Таким образом, вы создаете подкласс и возвращаете его в новых версиях, а поскольку методы были объявлены как методы экземпляра, вы просто позволяете полиморфизму выполнять свою работу.

Кроме того, по той же причине вы могли бы выиграть, сделав конструктор частным и предоставив статический метод фабрики.

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

Смотрите здесь несколько связанных видео: как разработать хороший API и почему это важно

Хотя он не имеет прямого отношения к обсуждению метода «static против экземпляра», он затрагивает некоторые интересные моменты в дизайне API.

OscarRyz
источник
1
Полностью согласен. Я обычно стараюсь избегать статики и рядовых по этой причине. Я думаю, что большинство других ответов упустили из виду.
Клинт Миллер
22
Мы говорим о частных помощниках, что означает, что они не являются частью публичного API класса. Это означает, что ничто не мешает вам позднее предоставить другую реализацию, сделать ее нестатичной или внести другие изменения.
Jonik
Аааааа ... это хорошая мысль, Джоник. Тем не менее, создание хорошей привычки должно быть достаточной причиной (субъективно говоря, конечно)
OscarRyz
2
Полностью не согласен. Нет веской причины, по которой вы захотите изменить закрытый метод без изменения класса, в котором он определен. Единственный способ сделать то, что вы предлагаете, это манипулирование байтовым кодом.
Питер Лори
2
То, что вы предлагаете, могло бы иметь смысл, если бы метод не был частным, но даже в этом случае я бы предложил вам проектировать на основе текущего тока, а не на основе воображаемой потребности.
Питер Лори
5

Одна из проблем использования статических методов заключается в том, что это может затруднить использование объекта в модульных тестах . Mockito не может создавать насмешки для статических методов, и вы не можете создать реализацию метода подкласса.

Джон Онстотт
источник
2
Вы не должны писать тесты для частных методов. Смотрите здесь . Нет необходимости проверять закрытый метод, статический он или нет.
BW
@BW Это очень субъективная вещь. Есть много веских причин для тестирования частного метода.
Руохола
4

Если метод в основном просто подпрограмма, которая никогда не будет использовать информацию о состоянии, объявите ее статической.

Это позволяет использовать его в других статических методах или при инициализации класса, то есть:

public class Example {
   //...

   //Only possible if computeOne is static
   public final static double COMPUTED_ONE = computeOne(new Something("1"));

   //...
}
койка
источник
3

Статический / нестатический вопрос сводится к тому, «мне действительно нужно будет использовать объект этого класса»?

Итак, вы передаете объект между различными методами? Содержит ли объект информацию, которая полезна вне контекста статических методов? Есть ли причина не определять методы в обоих направлениях, если вы будете использовать их в обоих направлениях?

Если вы находитесь в этой дилемме, мне кажется, что у вас есть все данные, необходимые для метода, плавающие в вашем коде за пределами объекта. Это то, что вы хотите? Будет ли легче просто всегда собирать эти данные в объект каждый раз? Возможно, вы просто амбивалентно относитесь к одной модели. Если вы можете сделать все это одним способом, тогда выберите статический или нестатический и продолжайте.

нет нет
источник
3

Мои предпочтения в таких случаях, чтобы сделать computeOneи computeMoreстатические методы. Причина: инкапсуляция. Чем меньше кода, который имеет доступ к реализации вашего класса, тем лучше.

В примере , вы даете, вы заявляете , что computeOneи computeMoreне нужно доступ внутренностей класса, так зачем давать возможность для автогрейдеров класса вмешиваться с внутренностями.

maxaposteriori
источник
3

Я хотел бы прояснить несколько вещей, которые другие авторы назвали неправильной информацией.

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

Теперь перейдем к вашему вопросу, должен ли частный вспомогательный метод быть определен как статический или нестатический. Я пойду с ответом Стива, так как пометка статического частного метода показывает, что этот метод не имеет состояния, так как я также следую этому правилу, когда кодирую.

Бхушан Бхангале
источник
но есть места, где можно вызывать только статический метод, как в моем примере и в примере с Крисом Маршаллом
Кип
Да Кип и ваш ответ Криса верны. Я не прокомментировал их, поскольку они являются правильными ответами. Я говорил только о неправильных ответах.
Бхушан Бхангале
3

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

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

Такие частные методы общего пользования являются источником большой части дублирования кода в проекте, потому что каждый разработчик самостоятельно заново изобретает их именно там, где ему нужно это использовать. Так что централизация таких методов - это путь.

Петр Собчик
источник
2

Более конкретно на примере вы дали, кажется , что цель определения этих методов больше для кода ясности , когда вы читаете это , чем для функциональности (они будут определены как частные). В этом случае использование статического кода на самом деле ничего не делает для вас, поскольку назначение статического кода - показать функциональность класса.

нет нет
источник
Этот маленький ответ, похороненный под грузом других ответов, действительно затрагивает суть вопроса: функциональность уровня класса против функциональности уровня объекта.
eljenso
Спасибо, эль, я действительно ценю это. Я тоже не против немного проголосовать :)
не
Я дал ему +1 на момент написания комментария.
eljenso
1
Я не согласен. Понятность кода все еще важна для частных методов. Это может быть не так важно, но к этому нужно стремиться.
LegendLength
1

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

dsimcha
источник
Смотрите мой ответ ниже (я добавил анализ после того, как он был уже на уровне -3, поэтому он не очень заметен).
Майкл Майерс
1

Как говорили многие, сделай это как статичный ! Вот правило большого пальца, которому я следую: если вы думаете, что метод - это просто математическая функция, то есть он не содержит состояний, не включает никаких переменных экземпляра (=> нет синего цвета [в затмении] в методе) и результат метод будет таким же для числа вызовов 'n' (с теми же параметрами, конечно), затем отметьте этот метод как STATIC.

И если вы считаете, что этот метод будет полезен для другого класса, в противном случае переместите его в класс Util, в противном случае поместите метод как private в тот же класс. (минимизация доступности)

джай
источник
1

Не по теме: я бы оставил вспомогательные методы в отдельном классе утилит / помощников, в котором были бы только статические методы.

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

Все
источник
-1: ТАК не форум обмена сообщениями. Вы добьетесь большего успеха, фактически задав правильный вопрос.
Стю Томпсон
1
Я бы предпочел иметь вспомогательные методы вместе с классом, с которым они связаны / связаны как статические. Более того, я могу выставить некоторых на всеобщее обозрение, если они будут использоваться внешне. Таким образом, будет проще поддерживать API для этого класса, если предполагается, что он будет публично представлен и интенсивно использован.
Ивайло Славов
1
class Whatever {

    public static varType myVar = initializeClassVariable();

    private static varType initializeClassVariable() {

        //initialization code goes here
    }
}

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

mug896
источник
1
  1. Без статического модификатора вы не сможете понять, что метод не имеет состояния без дополнительного анализа, который можно легко сделать, когда вы переписываете метод.

  2. Тогда модификатор «static» может дать вам идеи о рефакторинге помимо других вещей, которые другие могут счесть бесполезными. Например, перемещение метода в некоторый класс Utility или преобразование его в метод-член.

Бодрин
источник
0

Я бы объявил их статическими, чтобы пометить их как не имеющих состояния.

В Java нет лучшего механизма для незначительных операций, которые не экспортируются, поэтому я думаю, что приватная статика приемлема.

Uri
источник