Каков хороший вариант использования для статического импорта методов?

137

Только что получил комментарий, что мой статический импорт метода не был хорошей идеей. Статический импорт был методом из класса DA, который имеет в основном статические методы. Итак, в середине бизнес-логики у меня была активность da, которая, казалось, принадлежала текущему классу:

import static some.package.DA.*;
class BusinessObject {
  void someMethod() {
    ....
    save(this);
  }
} 

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

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

Итак, когда имеет смысл использовать статические методы импорта? Когда ты это сделал? Вам понравилось, как выглядят неквалифицированные звонки?

РЕДАКТИРОВАТЬ: популярное мнение, кажется, что методы статического импорта, если никто не собирается путать их как методы текущего класса. Например, методы из java.lang.Math и java.awt.Color. Но если abs и getAlpha не являются неоднозначными, я не понимаю, почему readEmployee. Как и во многих вариантах программирования, я думаю, что это тоже личное предпочтение.

Спасибо за ваш ответ, ребята, я закрываю вопрос.

Жалкая переменная
источник
2
Вот очень хорошее использование статического импорта: ibm.com/developerworks/library/j-ft18
intrepidis
1
@ mr5 синтаксис import static, особенностьstatic import
Miserable Variable

Ответы:

150

Это из руководства Sun, когда они выпустили эту функцию (выделено в оригинале):

Итак, когда вы должны использовать статический импорт? Очень экономно! Используйте его только тогда, когда у вас возникнет искушение объявить локальные копии констант или злоупотребить наследованием (Антипаттерн Constant Interface). ... Если вы чрезмерно используете функцию статического импорта, она может сделать вашу программу нечитаемой и не поддерживаемой, загрязняя ее пространство имен всеми импортируемыми вами статическими элементами. Читатели вашего кода (включая вас через несколько месяцев после того, как вы его написали) не будут знать, из какого класса происходит статический член. Импорт всех статических членов из класса может быть особенно вредным для читабельности; если вам нужен только один или два участника, импортируйте их по отдельности.

( https://docs.oracle.com/javase/8/docs/technotes/guides/language/static-import.html )

Я хочу особо выделить две части:

  • Используйте статический импорт только тогда, когда у вас возникло искушение «злоупотребить наследованием». В этом случае, вы бы испытали желание иметь BusinessObject extend some.package.DA? Если так, статический импорт может быть более чистым способом справиться с этим. Если вы никогда не мечтали бы о расширении some.package.DA, то это, вероятно, плохое использование статического импорта. Не используйте его только для сохранения нескольких символов при наборе.
  • Импорт отдельных участников. Скажи import static some.package.DA.saveвместо DA.*. Это значительно облегчит поиск источника импортированного метода.

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

Росс
источник
9
Согласовано. Я иногда использовал статические операции импорта, где они на самом деле значительно облегчили выполнение кода.
Нил Коффи
65

Другое разумное использование для статического импорта - JUnit 4. В более ранних версиях JUnit методы, подобные assertEqualsи failунаследованные с момента расширения тестового класса junit.framework.TestCase.

// old way
import junit.framework.TestCase;

public class MyTestClass extends TestCase {
    public void myMethodTest() {
        assertEquals("foo", "bar");
    }
}

В JUnit 4 тестовые классы больше не нуждаются в расширении TestCaseи могут вместо этого использовать аннотации. Затем вы можете статически импортировать методы assert из org.junit.Assert:

// new way
import static org.junit.Assert.assertEquals;

public class MyTestClass {
    @Test public void myMethodTest() {
        assertEquals("foo", "bar");
        // instead of
        Assert.assertEquals("foo", "bar");
    }
}

JUnit документы, используя его таким образом.

Роб Хруска
источник
4
Я бы согласился Упрощение тестовых случаев - это то место, где намерение вряд ли будет неправильно понято.
Билл Мичелл
6
У нас это было в нашем проекте и на самом деле были проблемы с людьми, использующими assert () и неправильно думающими, что это происходит из-за их статического импорта пакета Assert. Как только мы обнаружили эту проблему, быстрое сканирование нашей кодовой базы нашло около 30 экземпляров этого в наших тестах, что означает, что 30 утверждений НЕ выполнялись, когда выполнялась тестовая среда, потому что флаг DEBUG не установлен, когда мы запускаем тесты.
Крис Уильямс
27

В конце статьи 19 « Эффективная Java», второе издание , отмечается, что вы можете использовать статический импорт, если вы интенсивно используете константы из служебного класса. Я думаю, что этот принцип применим к статическому импорту как констант, так и методов.

import static com.example.UtilityClassWithFrequentlyUsedMethods.myMethod;

public class MyClass {
    public void doSomething() {
        int foo= UtilityClassWithFrequentlyUsedMethods.myMethod();
        // can be written less verbosely as
        int bar = myMethod();
    }
}

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

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

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

Роб Хруска
источник
1
Мой вопрос о методах статического импорта , а не о полях.
Унылая переменная
7
Возможно, UtilityClassWithFrequentlyUsedMethodsнужно сократить.
Стив Куо
5
@SteveKuo, конечно, меньше, чем InternalFrameTitlePaneMaximizeButtonWindowNotFocusedState : P
Anirban Nag 'tintinmj'
@ Rob-Hruska Не могу ли я просто обернуть статический метод или поле импорта в новый метод или поле, если я планирую часто их использовать? Позволит ли это мне не импортировать статически? такие как: double myPI = Math.PI;и тогда я могу просто продолжать ссылаться myPIвместо Math.PI.
Абдул
@Abdul - Да, ты мог бы сделать это.
Роб Хруска,
14

Я согласен, что они могут быть проблематичными с точки зрения читабельности и должны использоваться экономно. Но при использовании обычного статического метода они действительно могут улучшить читаемость. Например, в тестовом классе JUnit такие методы assertEqualsочевидны, откуда они берутся. Аналогично для методов из java.lang.Math.

Joel
источник
5
И что такого плохого в том, чтобы видеть Math.round (d) по сравнению с round (d)?
Стив Куо
5
@SteveKuo - по той же причине, по которой математики используют однобуквенные имена переменных при работе с формулами: бывают случаи, когда более длинные имена мешают удобочитаемости всего оператора. Рассмотрим формулу, включающую несколько тригонометрических функций. Легко понять математику формула: sin x cos y + cos x sin y. В Java становится: Math.sin(x) * Math.cos(y) + Math.cos(x) * Math.sin(y). Ужасно читать.
ToolmakerSteve
@ToolmakerSteve, поэтому я так упустил usingдирективу в C ++: они могут быть локальными .
Франклин Ю
11

Я думаю, что статический импорт действительно полезен для удаления избыточных имен классов при использовании утилит, таких как Arraysи Assertions.

Не знаю почему, но Росс пропустил последнее предложение, которое упоминает об этом в документации, на которую он ссылается .

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

В основном скопировано с этого блога: https://medium.com/alphadev-blyts/static-imports-are-great-but-underused-e805ba9b279f

Так, например:

Утверждения в тестах

Это наиболее очевидный случай, с которым, я думаю, мы все согласны

Assertions.assertThat(1).isEqualTo(2);

// Use static import instead
assertThat(1).isEqualTo(2);

Утилиты и перечисления

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

List<Integer> numbers = Arrays.asList(1, 2, 3);

// asList method name is enough information
List<Integer> numbers = asList(1, 2, 3);

В пакете java.time есть несколько случаев, когда его следует использовать

// Get next Friday from now, quite annoying to read
LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY));

// More concise and easier to read
LocalDate.now().with(next(FRIDAY));

Пример, когда не использовать

// Ok this is an Optional
Optional.of("hello world");

// I have no idea what this is 
of("hello world");
softarn
источник
10

Я использую его для цвета много.

static import java.awt.Color.*;

Маловероятно, что цвета будут перепутаны с чем-то другим.

jjnguy
источник
1
Это один из лучших вариантов использования, которые я видел, который отличается от старого JUnit / Hamcrest / TestNG.
Кевинарпе
3

Я рекомендую использовать статический импорт при использовании OpenGL с Java, который представляет собой вариант использования, относящийся к «интенсивному использованию констант из служебного класса» категорий

Считают, что

import static android.opengl.GLES20.*;

позволяет переносить оригинальный код на C и писать что-то читаемое, например:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(samplerUniform, 0);
glBindBuffer(GL_ARRAY_BUFFER, vtxBuffer);
glVertexAttribPointer(vtxAttrib, 3, GL_FLOAT, false, 0, 0);

вместо этого распространенного уродства

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
GLES20.glUniform1i(samplerUniform, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vtxBuffer);
GLES20.glVertexAttribPointer(vtxAttrib, 3, GLES20.GL_FLOAT, false, 0, 0);
кремень
источник
2

Статический импорт - единственная «новая» функция Java, которую я никогда не использовал и не собираюсь использовать из-за проблем, которые вы только что упомянули.

Бомбы
источник
Спасибо Бомбе. Что ж, я верю, что они лучше понимают, что нужно расширять и интерфейс, который просто содержит кучу статических финалов.
Унылая переменная
2

Я использую «import static java.lang.Math. *» При переносе тяжелого математического кода из C / C ++ в java. Математические методы отображают от 1 до 1, что упрощает анализ перенесенного кода без уточнения имени класса.

Fracdroid
источник
2

Я обнаружил, что это очень удобно при использовании классов Utility.

Например, вместо использования: if(CollectionUtils.isNotEmpty(col))

Я могу вместо этого:

import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
if(isNotEmpty(col))

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

Yeikel
источник
2

Говоря о модульных тестах: большинство людей используют статический импорт для различных статических методов, которые предоставляют макетные среды, таких как when()или verify().

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

И, конечно, при использовании одного-единственного утверждения, которое вы должны использовать, assertThat()это удобно для статического импорта требуемых совпадений подколенного сухожилия, как в:

import static org.hamcrest.Matchers.*;
GhostCat
источник
1

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

Один пример: код, который включает в себя несколько ссылок на java.lang.Math

Другой: класс XML-компоновщика, в котором добавление имени класса к каждой ссылке скрывает создаваемую структуру

kdgregory
источник
1

Я думаю, что статический импорт удобен для NLS в стиле gettext.

import static mypackage.TranslatorUtil._;

//...
System.out.println(_("Hello world."));

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

Матиас Вутке
источник
1

IMO статический импорт - довольно приятная особенность. Абсолютно верно, что сильная зависимость от статического импорта делает код нечитаемым и трудно понять, к какому классу относится статический метод или атрибут. Однако, по моему опыту, это становится полезной особенностью, особенно при разработке Utilклассов, которые предоставляют некоторые статические методы и атрибуты. Неоднозначность, возникающую при предоставлении статического импорта, можно обойти, установив стандарты кода. По моему опыту работы в компании, этот подход приемлем и делает код чище и легким для понимания. Предпочтительно я вставляю . Очевидно, этот подход нарушает стандарты именования Java, но обеспечивает ясность кода. Например, если у нас есть класс AngleUtils:_ символ в передние статические методы и статические атрибуты (как-то взятые из C)

public class AngleUtils {

    public static final float _ZERO = 0.0f;
    public static final float _PI   = 3.14f;

    public static float _angleDiff(float angle1, float angle2){

    }

    public static float _addAngle(float target, float dest){

    }
}

В этом случае статический импорт обеспечивает ясность, а структура кода выглядит для меня более элегантно:

import static AngleUtils.*;

public class TestClass{

    public void testAngles(){

        float initialAngle = _ZERO;
        float angle1, angle2;
        _addAngle(angle1, angle2);
    }
}

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

eldjon
источник
Спасибо за предложение переименования. Кстати, подчеркивание перед традиционно используется в некоторых средах для именования частных методов / полей. Я рассматриваю измененное соглашение, например, H_для импорта из Helperимеющегося у меня служебного класса, или C_для Common, или U_для Utility. В качестве альтернативы я рассмотрел возможность использования одного или двух имен символьных классов для этих широко используемых классов, но был обеспокоен тем, что они могут иногда конфликтовать с локальными именами - иметь некоторый устаревший код с именами методов в верхнем регистре.
ToolmakerSteve
-1

Вы должны использовать их, когда:

  • Вы хотите использовать switchоператор со значениями перечисления
  • Вы хотите сделать свой код сложным для понимания
davetron5000
источник
9
Это неправда. (1) Вы можете отлично использовать константы перечисления без их статического импорта. (2) Статический импорт, скажем, методов класса JUnit Assert ясен как звонок. «assertTrue (...)» так же читабельно, как «Assert.assertTrue (...)», возможно, более того.
Алан Крюгер
5
если у вас есть 5 статических импортов в классе из 500 строк, очень трудно сказать, откуда берутся методы.
davetron5000
4
+1 за то, что вы хотите сделать ваш код трудным для понимания :)
Унылая переменная
-5

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

Javamann
источник
13
Вы думаете о регулярном импорте. Статический импорт позволяет ссылаться на члены класса без указания имени класса, например, статический импорт java.lang.system.out; out.println ( "Foo"); // вместо System.out.println ("foo");
ск.
Теперь это очень хорошее объяснение статического импорта ... очень плохо, я не могу +1 комментарий
Eldelshell