Dlaczego używanie karty wieloznacznej z instrukcją importu Java jest złe?


419

Używanie jednego wyrażenia, takiego jak, jest znacznie wygodniejsze i czystsze

import java.awt.*;

niż zaimportować kilka indywidualnych klas

import java.awt.Panel;
import java.awt.Graphics;
import java.awt.Canvas;
...

Co jest złego w używaniu symboli wieloznacznych w importwyciągu?

Odpowiedzi:


518

Jedynym problemem jest to, że zaśmieca on twoją lokalną przestrzeń nazw. Załóżmy na przykład, że piszesz aplikację Swing, a więc potrzebujesz java.awt.Event, a także współpracujesz z systemem kalendarza firmy, który ma com.mycompany.calendar.Event. Jeśli zaimportujesz oba za pomocą metody wieloznacznej, wydarzy się jedna z tych trzech rzeczy:

  1. Masz bezpośredni konflikt nazw między java.awt.Eventi com.mycompany.calendar.Event, więc nie możesz nawet skompilować.
  2. W rzeczywistości udaje Ci się zaimportować tylko jeden (tylko jeden z dwóch importów .*), ale jest to niewłaściwy i próbujesz dowiedzieć się, dlaczego kod twierdzi, że ten typ jest nieprawidłowy.
  3. Kiedy kompilujesz swój kod, nie ma go com.mycompany.calendar.Event, ale kiedy dodają go później, twój poprzednio poprawny kod nagle przestaje się kompilować.

Zaletą jawnego wyszczególnienia wszystkich importów jest to, że mogę na pierwszy rzut oka stwierdzić, której klasy chciałeś użyć, co po prostu znacznie ułatwia czytanie kodu. Jeśli robisz szybką jednorazową rzecz, nie ma nic wyraźnie złego , ale przyszli opiekunowie będą ci wdzięczni za twoją jasność.


7
To pierwszy scenariusz, który się wydarzy. Kompilator zauważa, że ​​istnieją dwie klasy Event i podaje błąd.
jan.vdbergh

38
Sprawdź mój komentarz poniżej - z czasem pojawia się większy problem z dodawaniem typów do bibliotek stron trzecich. Możesz mieć kod kompilacyjny, który przestaje się kompilować po dodaniu typu do jar, od którego zależysz.
Scott Stanchfield

6
w odniesieniu do problemu 1: technicznie możesz skompilować, ale będziesz musiał użyć w pełni kwalifikowanej nazwy klasy za każdym razem.
Kip

1
Możesz rozwiązać tego rodzaju konflikty bez wyraźnego wyszczególnienia każdej klasy, co powoduje własne problemy.
rpjohnst

196

Oto głosowanie na import gwiazd. Instrukcja importu służy do importowania pakietu , a nie klasy. Importowanie całych paczek jest znacznie czystsze; problemy zidentyfikowane tutaj (np. java.sql.Datevs java.util.Date) można łatwo rozwiązać za pomocą innych środków, tak naprawdę nie rozwiązanych konkretnym przywozem i na pewno nie uzasadniają szalenie pedantycznego importu we wszystkich klasach. Nie ma nic bardziej niepokojącego niż otwieranie pliku źródłowego i przeglądanie 100 instrukcji importu.

Dokonanie określonego importu utrudnia refaktoryzację; jeśli usuniesz / zmienisz nazwę klasy, musisz usunąć wszystkie jej określone importy. Jeśli przełączysz implementację na inną klasę w tym samym pakiecie, musisz naprawić importowanie. Te dodatkowe kroki można zautomatyzować, ale w rzeczywistości są one hitami produktywności bez żadnego rzeczywistego zysku.

Nawet gdyby Eclipse nie domyślnie importował klasy, wszyscy nadal importowaliby gwiazdki. Przykro mi, ale tak naprawdę nie ma racjonalnego uzasadnienia dla wykonywania określonych importów.

Oto jak radzić sobie z konfliktami klasowymi:

import java.sql.*;
import java.util.*;
import java.sql.Date;

28
Zgadzam się. Chociaż nie byłbym przeciwny używaniu importu jawnego, nadal wolę używać importu gwiazd. Podkreślają, że „jednostką ponownego użycia” jest całe opakowanie, a nie jego poszczególne typy. Powody, dla których inni wymienieni przeciwko importowi gwiazd są słabe, i z mojego doświadczenia wynika, że ​​używanie importu gwiazd nigdy nie spowodowało żadnych faktycznych trudności.
Rogério,

32
Zobacz javadude.com/articles/importondemandisevil.html, aby dowiedzieć się, dlaczego jest zły. Podstawowy pomysł: może spowodować, że kod przestanie się kompilować, gdy klasy zostaną dodane do importowanych pakietów (na przykład, gdy lista została dodana do java.util ...)
Scott Stanchfield

61
Wszystkie wymienione przez Ciebie problemy można rozwiązać za pomocą nowoczesnych IDE (ukrywanie importu, refaktoryzacja nazwy klasy itp.).
assylias

15
Nie powinienem używać IDE do czytania lub pisania kodu źródłowego - kod powinien być czytelny sam, bez specjalnych narzędzi, chyba że język jest niewiarygodnie mądry. W tym przypadku Java działa świetnie - wystarczy użyć importu gwiazd. Nie ma powodu, aby tego nie robić.
davetron5000

42
@ davetron5000 Jeśli twój kod zawiera ponad 10 importowanych symboli wieloznacznych i używasz klasy Foo, a jeśli czytam twój kod bez użycia IDE (ponieważ twoim argumentem jest to, że nie powinienem go używać), skąd będę wiedział, z którego pakietu Foopochodzi ? Jasne, używając IDE, IDE powie mi, ale cały twój argument jest taki, że powinienem być w stanie odczytać kod bez niego. Wykonywanie jawnych importów pomaga udokumentować kod (świetny powód, aby unikać symboli wieloznacznych) , i jest o wiele bardziej prawdopodobne, że będę czytać kod bez użycia IDE, niż że będę pisać kod bez używania IDE.
Andreas,

169

zobacz mój artykuł Import na żądanie jest zły

Krótko mówiąc, największym problemem jest to, że kod może się zepsuć, gdy klasa zostanie dodana do importowanego pakietu. Na przykład:

import java.awt.*;
import java.util.*;

// ...

List list;

W Javie 1.1 było to w porządku; Lista została znaleziona w java.awt i nie było konfliktu.

Załóżmy teraz, że wpiszesz doskonale działający kod, a rok później ktoś inny wyda go, aby go edytować i używa Java 1.2.

Java 1.2 dodał interfejs o nazwie List do java.util. BUM! Konflikt. Doskonale działający kod już nie działa.

To jest funkcja języka ZŁEGO . Jest NO powodem, że kod powinien zatrzymać kompilacji tylko dlatego, że typ jest dodany do pakietu ...

Ponadto utrudnia czytelnikowi określenie, którego „Foo” używasz.


35
To nie jest poprawna wymówka. Jeśli zmieniasz wersję Java, w jakiś sposób spodziewasz się, że niektóre rzeczy zawiodą, to samo, jeśli zmienisz wersję pliku binarnego używanego przez Twój kod. W takich przypadkach kod zgłasza błąd kompilacji, a jego naprawa jest trywialna (patrz poprzednia odpowiedź: stackoverflow.com/a/149282/7595 )
Pablo Fernandez

36
@PabloFernandez - Nie - Jeśli sprawdzę kod, który był w repozytorium od roku, powinien się kompilować. Importowanie na żądanie może łatwo zakończyć się niepowodzeniem, gdy nowe klasy zostaną dodane do istniejących pakietów, które zaimportowałem. To nie jest tylko problem podczas aktualizacji wersji Java. Ponadto - jeśli interfejs API jest dobrze zaprojektowany, nigdy nie powinien łamać istniejącego kodu podczas aktualizacji. Jedyny raz, kiedy potrzebowałem zmienić kod podczas aktualizacji wersji Java, to z powodu importu na żądanie i kiedy Sun wciągnął API XML do środowiska wykonawczego Java.
Scott Stanchfield

3
DODAWANIE klasy (o unikalnej, w pełni kwalifikowanej nazwie!) Do ścieżki klasy nie powinno mieć wpływu na nic. Chodzi o to, że jeśli nie użyjesz składni importu na żądanie, nie zrobi tego. Więc nie używaj złej składni, na którą niestety pozwala język, i jest to o jeden mniej realny problem, z którym możesz się trafić.
Scott Stanchfield

28
Chodzi mi o to, że jest to niepotrzebna funkcja języka, która powoduje problemy. Wiele IDE / edytorów automatycznie obsługuje rozszerzenie importu. Użyj w pełni kwalifikowanych importów i nie ma szans na wystąpienie tego konkretnego błędu. Uderzyło mnie to pod presją naprawy błędu w istniejącym kodzie i naprawdę nie potrzebujesz czegoś takiego, aby oderwać się od rzeczywistego zadania. java.util.Listvs java.awt.Listnie jest takie złe, żeby to rozgryźć, ale wypróbuj je, gdy nazwa klasy jest, Configurationa wiele bibliotek zależności dodało ją w swojej najnowszej wersji repozytorium maven.
Scott Stanchfield,

5
Jeśli zaktualizuję słoik, w którym klasy, których używam, są zgodne z interfejsem API do przodu, ORAZ nie użyję składni importu na żądanie, nie wpłynie to na mnie wcale. Czy to ma dla ciebie sens? Nie bądź leniwy w definiowaniu importu, a to nie jest problem. Składnia importu na żądanie była błędem w definicji języka Java; rozsądny język nie powinien dopuszczać takich błędów.
Scott Stanchfield

67

To nie złe użycie dziką kartę z oświadczeniem importowej Java.

W Clean Code Robert C. Martin zaleca używanie ich, aby uniknąć długich list importów.

Oto zalecenie:

J1: Unikaj długich list importu, używając symboli wieloznacznych

Jeśli używasz dwóch lub więcej klas z pakietu, zaimportuj cały pakiet za pomocą

pakiet importowy. *;

Długie listy importów zniechęcają czytelnika. Nie chcemy zaśmiecać wierzchołków naszych modułów 80 liniami importu. Chcemy raczej, aby import był zwięzłym stwierdzeniem, z którymi pakietami współpracujemy.

Konkretny import to twarde zależności, podczas gdy import symboli wieloznacznych nie. Jeśli konkretnie zaimportujesz klasę, ta klasa musi istnieć. Ale jeśli zaimportujesz pakiet ze znakiem wieloznacznym, nie będzie potrzeby istnienia żadnych konkretnych klas. Instrukcja importu dodaje pakiet do ścieżki wyszukiwania podczas wyszukiwania nazw. Tak więc nie jest tworzona prawdziwa zależność od takich importów, a zatem służą one do mniejszego powiązania naszych modułów.

Czasami przydatna może być długa lista konkretnych importów. Na przykład, jeśli masz do czynienia ze starszym kodem i chcesz dowiedzieć się, dla jakich klas musisz budować makiety i kody pośredniczące, możesz przejść listę konkretnych importów, aby znaleźć prawdziwe kwalifikowane nazwy wszystkich tych klas, a następnie odpowiednie odcinki na miejscu. Jednak takie wykorzystanie do konkretnego importu jest bardzo rzadkie. Co więcej, większość współczesnych IDE pozwoli ci przekonwertować importowany z symboli wieloznacznych import na listę konkretnych importów za pomocą jednego polecenia. Dlatego nawet w przypadku starszych wersji lepiej importować symbole wieloznaczne.

Importowanie symboli wieloznacznych może czasem powodować konflikty nazw i niejednoznaczności. Dwie klasy o tej samej nazwie, ale w różnych pakietach, będą musiały zostać specjalnie zaimportowane lub przynajmniej określone, jeśli zostaną użyte. Może to być uciążliwe, ale jest na tyle rzadkie, że korzystanie z importu z użyciem symboli wieloznacznych jest ogólnie lepsze niż określone importowanie.


41
Sugeruję Robertowi Martinowi, aby użył lepszych wzorów, aby stworzyć bardziej zwięzłe pakiety i własne klasy, które nie wymagają 80 linii importu. To, że wiele klas potrzebnych do importu w ramach jednej klasy błaga „Entropy, Entropy, złam mnie proszę ...” i wskazuje powód, dla którego należy unikać importu * opisanego w odpowiedziach Scotta Stanchfieldsa
Ray,

28
Chociaż ogólnie podoba mi się to, co ma do powiedzenia wujek Bob, w tym przypadku muszę się z nim nie zgodzić.

33
Długie listy importów zniechęcają czytelnika. - To twierdzenie ma nieważne domniemanie. Programiści nie muszą czytać kodu źródłowego od góry do dołu. W ogóle nie możemy czytać list importu. Kiedy to zrobimy, możemy odczytać tylko jeden import, dla wyjaśnienia. W innych przypadkach import może zostać całkowicie zwinięty, jeśli pracujemy w środowisku IDE. Bez względu na źródło, dziś jest to zła rada.
Andy Thomas

10
Wystarczy podać przeciwwagę, jeśli chodzi o powoływanie się na autorytety w tej kwestii: Przewodnik po stylu Java firmy Google oraz Przewodnik po stylu Java w serwisie Twitter (który w dużej mierze opiera się na Google), w szczególności zabraniają importowania symboli zastępczych. Ale nie podają uzasadnienia tej decyzji.
anothernode

1
Prawdopodobnie jedyny punkt, na który się nie zgodziłem w Clean Code. Musi przewijać kilka wierszy instrukcji importu lub walczyć o to, skąd pochodzi klasa. Wolę łatwo określić, skąd pochodzi dana klasa.
Ganesh Satpute,

27

Wydajność : brak wpływu na wydajność, ponieważ kod bajtu jest taki sam. chociaż doprowadzi to do pewnych kosztów kompilacji.

Kompilacja : na mojej osobistej maszynie kompilowanie pustej klasy bez importowania niczego zajmuje 100 ms, ale ta sama klasa podczas importowania Java. * Zajmuje 170 ms.


3
import java.*nic nie importuje. Dlaczego miałoby to mieć znaczenie?
Markiz Lorne

6
To robi różnicę, ponieważ jest przeszukiwane podczas kompilacji.
LegendLength

25

Zaśmieca twoją przestrzeń nazw, wymagając od ciebie pełnego określenia wszelkich niejednoznacznych nazw klas. Najczęstszym tego zjawiskiem jest:

import java.util.*;
import java.awt.*;

...
List blah; // Ambiguous, needs to be qualified.

Pomaga także uszczegółowić twoje zależności, ponieważ wszystkie twoje zależności są wymienione w górnej części pliku.


20
  1. Pomaga zidentyfikować konflikty nazw klas: dwie klasy w różnych pakietach o tej samej nazwie. Można to maskować za pomocą importu *.
  2. Wyraźnie określa zależności, dzięki czemu każdy, kto będzie musiał później przeczytać kod, wie, co zamierzałeś zaimportować, a co nie.
  3. Może przyspieszyć kompilację, ponieważ kompilator nie musi przeszukiwać całego pakietu w celu zidentyfikowania zależności, choć w przypadku współczesnych kompilatorów nie jest to zwykle wielka sprawa.
  4. Niedogodności związane z jawnym importem są zminimalizowane dzięki nowoczesnym IDE. Większość IDE pozwala zwinąć sekcję importu, więc nie przeszkadza, automatycznie wypełnia import w razie potrzeby i automatycznie identyfikuje nieużywane importy, aby pomóc je wyczyścić.

Większość miejsc, w których pracowałem i które używają znacznej ilości Java, czynią jawne importowanie częścią standardu kodowania. Czasami wciąż używam * do szybkiego prototypowania, a następnie rozwijam listy importu (niektóre IDE też to zrobią dla Ciebie) podczas tworzenia kodu.


Podoba mi się większość twoich punktów, ale to właśnie # 4 sprawiło, że głosowałem za twoją odpowiedzią. Nowoczesne środowiska IDE usuwają większość argumentów przeciwko używaniu jawnego importu ...
Sheldon R.

Być może częścią tego problemu jest sposób, w jaki standardowe biblioteki Java są rozmieszczone z wieloma klasami w tym samym pakiecie. W przeciwieństwie do stosowania w większym stopniu „zasady pojedynczej odpowiedzialności” do pakietu.
LegendLength

11

Preferuję określone importowanie, ponieważ pozwala mi zobaczyć wszystkie odnośniki zewnętrzne użyte w pliku bez patrzenia na cały plik. (Tak, wiem, że niekoniecznie będzie zawierać w pełni kwalifikowane referencje. Ale unikam ich, gdy tylko jest to możliwe).


9

W poprzednim projekcie odkryłem, że zmiana z * -importów na określone importy skróciła czas kompilacji o połowę (z około 10 minut do około 5 minut). * -Import powoduje, że kompilator przeszukuje każdy z wymienionych pakietów w poszukiwaniu klasy pasującej do użytej. Ten czas może być niewielki, ale w przypadku dużych projektów stanowi dużą sumę.

Efektem ubocznym importu * było to, że programiści kopiowali i wklejali wspólne linie importu, zamiast myśleć o tym, czego potrzebowali.


11
Musiało to być wiele linii importu lub naprawdę żałosny system rozwoju, aby było to prawdą. Używam import- * i mogę skompilować całą bazę kodów 2107 klas w mniej niż 2 minuty.
Lawrence Dol

6

W książce DDD

Niezależnie od technologii rozwoju, na której będzie oparta implementacja, poszukaj sposobów zminimalizowania pracy MODUŁÓW refaktoryzacji. W Javie nie ma ucieczki przed importowaniem do poszczególnych klas, ale można przynajmniej zaimportować całe pakiety naraz, odzwierciedlając zamiar, aby pakiety były bardzo spójnymi jednostkami, jednocześnie zmniejszając wysiłek zmiany nazw pakietów.

A jeśli zaśmieca lokalną przestrzeń nazw, to nie twoja wina - obwiniaj rozmiar paczki.


3
  • Nie ma wpływu na środowisko uruchomieniowe, ponieważ kompilator automatycznie zastępuje * konkretnymi nazwami klas. Jeśli zdekompilujesz plik .class, nigdy nie zobaczysz import ...*.

  • C # zawsze używa * (niejawnie), ponieważ można tylko usingspakować nazwę. Nigdy nie można w ogóle podać nazwy klasy. Java wprowadza tę funkcję po c #. (Java jest bardzo trudna pod wieloma względami, ale wykracza poza ten temat).

  • W programie Intellij Idea podczas „organizowania importów” automatycznie zastępuje wiele importów tego samego pakietu znakiem *. Jest to funkcja obowiązkowa, ponieważ nie można jej wyłączyć (chociaż można zwiększyć próg).

  • Przypadek wymieniony w zaakceptowanej odpowiedzi jest nieprawidłowy. Bez * nadal masz ten sam problem. Musisz podać nazwę pakietu w swoim kodzie, bez względu na to, czy używasz * czy nie.


1
W IntelliJ nie jest to funkcja obowiązkowa i można ją wyłączyć.
Bastien7

3

Dla przypomnienia: po dodaniu importu wskazujesz również swoje zależności.

Szybko można zobaczyć, jakie są zależności plików (z wyjątkiem klas o tej samej przestrzeni nazw).


Zgodzić się. Motywator to nie tyle wydajność czy kompilacja, co czytelność kodu. Wyobraź sobie, że czytasz kod bez IDE - na przykład na GitHub. Nagle wyszukiwanie każdego odwołania nie zdefiniowanego w czytanym pliku staje się przytłaczająco nużące.
Leo Orientis

2

Najważniejsze jest to, że importowanie java.awt.*może spowodować, że Twój program będzie niezgodny z przyszłą wersją Java:

Załóżmy, że masz klasę o nazwie „ABC”, używasz JDK 8 i importujesz java.util.*. Teraz załóżmy, że Java 9 wychodzi i ma nową klasę w pakiecie, java.utilktóra przypadkiem jest również nazywana „ABC”. Twój program nie będzie się teraz kompilował na Javie 9, ponieważ kompilator nie wie, czy pod nazwą „ABC” masz na myśli własną klasę lub nową klasę wjava.awt .

Nie będziesz mieć tego problemu, gdy zaimportujesz tylko te klasy, z java.awtktórych faktycznie korzystasz.

Zasoby:

Importowanie Java


3
wskazówka: mogłeś posłużyć Streamjako przykład nowej klasy dodanej w Javie w java.util w Javie 8 ...
Clint Eastwood

2

Spośród wszystkich ważnych argumentów przedstawionych po obu stronach nie znalazłem mojego głównego powodu, aby unikać znaku wieloznacznego: Lubię czytać kod i wiedzieć bezpośrednio, co to jest każda klasa, lub jeśli jej definicja nie jest w języku lub plik, gdzie go znaleźć. W przypadku importowania więcej niż jednego pakietu za pomocą * muszę przeszukać każdy z nich, aby znaleźć klasę, której nie rozpoznaję. Czytelność jest najwyższa i zgadzam się, że kod nie powinien wymagać IDE do odczytu.


Jeśli weźmiesz to do pełnego logicznego wniosku, to w twoim stylu powinno być w ogóle nie używać importowania, a zamiast „nowej Listy Połączonej” zawsze używaj „nowej biblioteki java.util.LinkedList” i rób to konsekwentnie wszędzie .
Erwin Smout
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.