Jaki jest dobry przypadek użycia statycznego importu metod?


137

Właśnie dostałem komentarz recenzji, że mój statyczny import metody nie był dobrym pomysłem. Import statyczny był metodą z klasy DA, która zawiera głównie metody statyczne. Tak więc w środku logiki biznesowej miałem aktywność da, która najwyraźniej wydawała się należeć do obecnej klasy:

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

Recenzentowi nie zależało na tym, żebym zmienił kod i nie zrobiłem, ale w pewnym sensie się z nim zgadzam. Jednym z powodów braku importu statycznego było mylące miejsce, w którym metoda została zdefiniowana, nie znajdowała się ona w bieżącej klasie ani w żadnej nadklasie, więc zidentyfikowanie jej definicji zajęło trochę czasu (internetowy system recenzji nie ma klikalnego linki takie jak IDE :-) Nie sądzę, żeby to miało znaczenie, statyczne importy są wciąż całkiem nowe i wkrótce wszyscy przyzwyczaimy się do ich lokalizowania.

Ale innym powodem, z którym się zgadzam, jest to, że niekwalifikowane wywołanie metody wydaje się należeć do bieżącego obiektu i nie powinno przeskakiwać kontekstów. Ale gdyby tak naprawdę należało, rozszerzenie tej superklasy miałoby sens.

Tak więc, gdy ma ona sensu statycznych metod importu? Kiedy to zrobiłeś? Czy podobał Ci się wygląd niekwalifikowanych połączeń?

EDYCJA: Popularna opinia wydaje się być taka, że ​​metody statycznego importu, jeśli nikt nie pomyli ich z metodami bieżącej klasy. Na przykład metody z java.lang.Math i java.awt.Color. Ale jeśli abs i getAlpha nie są niejednoznaczne, nie rozumiem, dlaczego readEmployee jest. Podobnie jak w przypadku wielu wyborów programistycznych, myślę, że jest to kwestia osobistych preferencji.

Dziękuję za odpowiedź, zamykam pytanie.


2
Oto bardzo dobre wykorzystanie importu statycznego: ibm.com/developerworks/library/j-ft18
intrepidis

1
@ mr5 składnia jest import statictaka, funkcja tostatic import
Miserable Variable

Odpowiedzi:


150

To pochodzi z przewodnika firmy Sun, kiedy wydali tę funkcję (podkreślenie w oryginale):

Kiedy więc należy używać importu statycznego? Bardzo oszczędnie! Używaj go tylko wtedy, gdy w innym przypadku miałbyś ochotę zadeklarować lokalne kopie stałych lub nadużywać dziedziczenia (Antipattern ze stałym interfejsem). ... Jeśli nadużyjesz funkcji importu statycznego, może to spowodować, że Twój program będzie nieczytelny i niemożliwy do utrzymania, zanieczyszczając jego przestrzeń nazw wszystkimi statycznymi składowymi, które importujesz. Czytelnicy Twojego kodu (w tym Ty, kilka miesięcy po jego napisaniu) nie będą wiedzieć, z której klasy pochodzi statyczny element członkowski. Importowanie wszystkich statycznych elementów członkowskich z klasy może być szczególnie szkodliwe dla czytelności; jeśli potrzebujesz tylko jednego lub dwóch członków, zaimportuj je indywidualnie.

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

Są dwie części, które chcę szczególnie podkreślić:

  • Import statyczny należy używać tylko wtedy, gdy kusiło Cię „nadużywanie dziedziczenia”. Czy w takim przypadku miałbyś pokusę posiadania BusinessObject extend some.package.DA? Jeśli tak, import statyczny może być czystszym sposobem radzenia sobie z tym. Jeśli nigdy nie marzyłbyś o rozszerzeniu some.package.DA, prawdopodobnie jest to słabe wykorzystanie importu statycznego. Nie używaj go tylko do zapisywania kilku znaków podczas pisania.
  • Importuj poszczególnych członków. Powiedz import static some.package.DA.savezamiast DA.*. To znacznie ułatwi ustalenie, skąd pochodzi ta importowana metoda.

Osobiście używałem tej funkcji języka bardzo rzadko i prawie zawsze tylko ze stałymi lub wyliczeniami, nigdy z metodami. Dla mnie taki kompromis prawie nigdy nie jest tego wart.


9
Zgoda. Bardzo czasami korzystałem ze statycznego importu, który w rzeczywistości znacznie ułatwił śledzenie kodu.
Neil Coffey

65

Innym sensownym zastosowaniem importu statycznego jest JUnit 4. We wcześniejszych wersjach JUnit metody takie jak assertEqualsi failbyły dziedziczone od czasu rozszerzenia klasy testowej junit.framework.TestCase.

// old way
import junit.framework.TestCase;

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

W JUnit 4 klasy testowe nie muszą się już rozszerzać TestCasei zamiast tego mogą używać adnotacji. Następnie możesz statycznie zaimportować metody assert z 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 dokumenty używając go w ten sposób.


4
Zgadzam się. Upraszczanie przypadków testowych to miejsce, w którym zamiar prawdopodobnie nie zostanie źle zrozumiany.
Bill Michell

6
Mieliśmy to w naszym projekcie i faktycznie mieliśmy problemy z ludźmi używającymi assert () i niepoprawnie myślącymi, że pochodzi to z ich statycznego importu pakietu Assert. Gdy znaleźliśmy ten problem, szybkie skanowanie naszej bazy kodu znalazło około 30 takich przypadków w naszych testach, co oznacza, że ​​30 asercji NIE zostało uruchomionych, gdy środowisko testowe zostało wykonane, ponieważ flaga DEBUG nie jest ustawiona podczas uruchamiania testów.
Chris Williams,

27

Effective Java, Second Edition , na końcu punktu 19 zauważa, że ​​możesz użyć statycznego importu, jeśli intensywnie używasz stałych z klasy narzędziowej. Myślę, że ta zasada miałaby zastosowanie do statycznego importu zarówno stałych, jak i metod.

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();
    }
}

Ma to zalety i wady. Dzięki temu kod jest nieco bardziej czytelny kosztem utraty niektórych natychmiastowych informacji o tym, gdzie zdefiniowano metodę. Jednak dobre IDE pozwoli ci przejść do definicji, więc nie stanowi to większego problemu.

Powinieneś nadal używać tego oszczędnie i tylko wtedy, gdy często używasz rzeczy z importowanego pliku.

Edycja: Zaktualizowano, aby bardziej szczegółowo określać metody, ponieważ właśnie do tego odnosi się to pytanie. Zasada ma zastosowanie niezależnie od tego, co jest importowane (stałe lub metody).


1
Moje pytanie dotyczy metod importowania statycznego , a nie pól.
Nędzna zmienna

7
Być może UtilityClassWithFrequentlyUsedMethodstrzeba je skrócić.
Steve Kuo


@ Rob-Hruska Czy nie mogę po prostu zawinąć statycznej metody importu lub pola w nową metodę lub pole, jeśli planuję ich często używać? Czy to pozwoliłoby mi nie importować statycznie? takie jak: double myPI = Math.PI;a potem mogę po prostu odnosić się do myPIzamiast Math.PI.
Abdul,

@Abdul - Tak, możesz to zrobić.
Rob Hruska

14

Zgadzam się, że mogą być problematyczne z punktu widzenia czytelności i powinny być używane oszczędnie. Ale używając zwykłej metody statycznej, mogą w rzeczywistości zwiększyć czytelność. Na przykład w klasie testowej JUnit metody takie jak assertEqualssą oczywiste, skąd pochodzą. Podobnie dla metod z java.lang.Math.


5
A co jest takiego złego w oglądaniu Math.round (d) versus round (d)?
Steve Kuo

5
@SteveKuo - z tego samego powodu, dla którego matematycy używają jednoliterowych nazw zmiennych podczas manipulowania formułami: zdarzają się sytuacje, gdy dłuższe nazwy kolidują z czytelnością całej instrukcji. Rozważ wzór obejmujący wiele funkcji trygonometrycznych. Łatwo uchwycić wzór matematyczny: sin x cos y + cos x sin y. W Java staje się: Math.sin(x) * Math.cos(y) + Math.cos(x) * Math.sin(y). Straszne do przeczytania.
ToolmakerSteve,

@ToolmakerSteve, dlatego tak bardzo brakowało mi usingdyrektywy w C ++: mogą być lokalne .
Franklin Yu

11

Myślę, że import statyczny jest naprawdę przydatny do usuwania zbędnych nazw klas podczas używania klas narzędzi takich jak Arraysi Assertions.

Nie jestem pewien, dlaczego, ale Ross pominął ostatnie zdanie, które wspomina o tym w dokumentacji, do której się odwołuje .

Użyty właściwie, statyczny import może uczynić program bardziej czytelnym, usuwając standardowy schemat powtarzania nazw klas.

Zasadniczo skopiowane z tego bloga: https://medium.com/alphadev-hardts/static-imports-are-great-but-underused-e805ba9b279f

Na przykład:

Asercje w testach

Jest to najbardziej oczywisty przypadek, z którym wszyscy się zgadzamy

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

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

Wykorzystuje klasy i wyliczenia

Nazwę klasy można usunąć w wielu przypadkach, gdy używa się klas narzędzi ułatwiających odczytanie kodu

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

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

Pakiet java.time ma kilka przypadków, w których powinien być używany

// 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));

Przykład, kiedy nie używać

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

// I have no idea what this is 
of("hello world");

10

Często używam go do koloru.

static import java.awt.Color.*;

Jest bardzo mało prawdopodobne, aby kolory zostały pomylone z czymś innym.


1
To jeden z najlepszych przypadków użycia, jakie widziałem, różniący się od starego JUnit / Hamcrest / TestNG.
kevinarpe

3

Polecam korzystanie z importem statycznym przy użyciu OpenGL z Java, który jest use-case należących do „intensywnego użytkowania stałych z klasy narzędzie” kategorii

Rozważ to

import static android.opengl.GLES20.*;

pozwala na przeniesienie oryginalnego kodu C i napisanie czegoś czytelnego, takiego jak:

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

zamiast tej powszechnej brzydoty:

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

Import statyczny to jedyna „nowa” funkcja Javy, której nigdy nie używałem i nie zamierzam nigdy używać z powodu problemów, o których wspomniałeś.


Dzięki Bombe. Cóż, wydaje mi się, że lepiej rozumieją konieczność rozszerzenia i interfejsu, który zawiera tylko kilka statycznych finałów.
Miserable Variable

2

Używam 'import static java.lang.Math. *' Podczas przenoszenia ciężkiego kodu matematycznego z C / C ++ do Java. Metody matematyczne odwzorowują od 1 do 1 i ułatwiają porównywanie przenoszonego kodu bez kwalifikowania nazwy klasy.


2

Okazało się, że jest to bardzo wygodne podczas korzystania z klas Utility.

Na przykład zamiast używać: if(CollectionUtils.isNotEmpty(col))

Zamiast tego mogę:

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

Która IMO zwiększa czytelność kodu, gdy używam tego narzędzia wiele razy w moim kodzie.


2

Mówiąc o testach jednostkowych: większość ludzi używa statycznych importów dla różnych statycznych metod, które zapewniają symulujące struktury, takie jak when()lub verify().

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

I oczywiście, gdy używasz jedynego potwierdzenia, którego powinieneś używać assertThat(), przydaje się on do statycznego importowania wymaganych dopasowań ścięgien podkolanowych, jak w:

import static org.hamcrest.Matchers.*;

1

Przydają się do ograniczenia słownictwa, szczególnie w przypadkach, gdy wywoływanych jest wiele metod importowanych, a rozróżnienie między metodami lokalnymi i importowanymi jest wyraźne.

Jeden przykład: kod, który zawiera wiele odwołań do java.lang.Math

Inna: klasa konstruktora XML, w której dołączenie nazwy klasy do każdego odwołania ukrywałoby budowaną strukturę


1

Myślę, że import statyczny jest odpowiedni dla NLS w stylu gettext.

import static mypackage.TranslatorUtil._;

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

To zarówno oznacza ciąg jako ciąg, który ma zostać wyodrębniony, jak i zapewnia łatwy i czysty sposób na zastąpienie łańcucha jego tłumaczeniem.


1

Statyczny import IMO to całkiem fajna funkcja. Jest absolutnie prawdą, że duże poleganie na imporcie statycznym sprawia, że ​​kod jest nieczytelny i trudny do zrozumienia, do której klasy należy statyczna metoda lub atrybut. Jednak z mojego doświadczenia wynika, że ​​staje się użyteczną cechą, zwłaszcza przy projektowaniu Utilklas, które zapewniają statyczne metody i atrybuty. Niejednoznaczność pojawiającą się podczas zapewniania importu statycznego można obejść, ustanawiając standardy kodu. Z mojego doświadczenia w firmie takie podejście jest akceptowalne i sprawia, że ​​kod jest czystszy i łatwy do zrozumienia. Najlepiej wstawiam _znak przed metodami statycznymi i atrybutami statycznymi (jakoś przejęte z C). Wygląda na to, że takie podejście narusza standardy nazewnictwa Javy, ale zapewnia przejrzystość kodu. Na przykład, jeśli mamy klasę AngleUtils:

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){

    }
}

W tym przypadku import statyczny zapewnia przejrzystość, a struktura kodu wygląda dla mnie bardziej elegancko:

import static AngleUtils.*;

public class TestClass{

    public void testAngles(){

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

Od razu ktoś może powiedzieć, która metoda lub atrybut pochodzi ze statycznego importu i ukrywa informacje o klasie, do której należy. Nie sugeruję używania importu statycznego dla klas, które są integralną częścią modułu i zapewniają metody statyczne i niestatyczne, ponieważ w tym przypadku ważne jest, aby wiedzieć, która klasa zapewnia określoną funkcjonalność statyczną.


Dzięki za sugestię dotyczącą zmiany nazwy. BTW, podkreślenie z przodu jest tradycyjnie używane w niektórych środowiskach do nazwania prywatnych metod / pól. Rozważam zmodyfikowaną konwencję, taką jak H_import z Helperklasy użytkowej, którą mam, lub C_dla Common, lub U_dla Utility. Alternatywnie, rozważałem użycie jednej lub dwóch nazw klas znaków dla tych szeroko używanych klas, ale obawiałem się, że mogą one czasami kolidować z nazwami lokalnymi - mają jakiś starszy kod z nazwami metod pisanymi wielkimi literami.
ToolmakerSteve

-1

Musisz ich używać, gdy:

  • chcesz użyć switchinstrukcji z wartościami wyliczeniowymi
  • chcesz, aby Twój kod był trudny do zrozumienia

9
To nie jest prawda. (1) Możesz doskonale używać stałych wyliczeniowych bez ich statycznego importowania. (2) Statyczne importowanie, powiedzmy, metod klasy JUnit Assert jest jasne jak dzwonek. „assertTrue (…)” jest tak samo czytelne jak „Assert.assertTrue (…)”, być może bardziej.
Alan Krueger

5
jeśli masz 5 statycznych importów w klasie 500-liniowej, bardzo trudno jest stwierdzić, skąd pochodzą metody.
davetron5000

4
+1, jeśli chcesz, aby Twój kod był trudny do zrozumienia :)
Miserable Variable

-5

Używam ich, kiedy tylko mogę. Mam konfigurację IntelliJ, która przypomina mi, jeśli zapomnę. Wydaje mi się, że wygląda na znacznie czystszy niż w pełni kwalifikowana nazwa pakietu.


13
Myślisz o regularnym imporcie. Import statyczny umożliwia odwoływanie się do członków klasy bez kwalifikowania ich nazwą klasy, np. Import statyczny java.lang.system.out; out.println ("foo"); // zamiast System.out.println ("foo");
sk.

To jest bardzo dobre wyjaśnienie importu statycznego ... szkoda, że ​​nie mogę dać +1 komentarzowi
Eldelshell
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.