Tại sao sử dụng thẻ hoang dã với câu lệnh nhập Java xấu?


419

Sẽ thuận tiện và sạch sẽ hơn nhiều khi sử dụng một câu lệnh như

import java.awt.*;

hơn là nhập một loạt các lớp riêng lẻ

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

Điều gì là sai với việc sử dụng một ký tự đại diện trong importtuyên bố?

Câu trả lời:


518

Vấn đề duy nhất với nó là nó làm tắc nghẽn không gian tên cục bộ của bạn. Ví dụ: giả sử bạn đang viết một ứng dụng Xoay, và do đó cần java.awt.Event, và cũng đang can thiệp vào hệ thống lịch của công ty com.mycompany.calendar.Event. Nếu bạn nhập cả hai bằng phương thức ký tự đại diện, một trong ba điều sau sẽ xảy ra:

  1. Bạn có một xung đột đặt tên hoàn toàn giữa java.awt.Eventcom.mycompany.calendar.Event, và do đó bạn thậm chí không thể biên dịch.
  2. Bạn thực sự chỉ quản lý để nhập một (chỉ một trong hai lần nhập của bạn .*), nhưng đó là sai và bạn đấu tranh để tìm ra lý do tại sao mã của bạn tuyên bố loại đó là sai.
  3. Khi bạn biên dịch mã của mình thì không có com.mycompany.calendar.Event, nhưng khi chúng thêm mã sau đó, mã hợp lệ trước đó của bạn đột nhiên dừng biên dịch.

Ưu điểm của việc liệt kê rõ ràng tất cả các nhập khẩu là tôi có thể biết trong nháy mắt bạn muốn sử dụng lớp nào, điều này chỉ đơn giản làm cho việc đọc mã dễ dàng hơn nhiều. Nếu bạn chỉ làm một việc nhanh chóng, không có gì sai rõ ràng , nhưng những người bảo trì trong tương lai sẽ cảm ơn bạn vì sự rõ ràng của bạn nếu không.


7
Đó là kịch bản đầu tiên sẽ xảy ra. Trình biên dịch thông báo rằng có hai lớp Sự kiện và báo lỗi.
jan.vdbergh

38
Hãy chắc chắn kiểm tra nhận xét của tôi bên dưới - có một vấn đề lớn hơn với các loại được thêm vào libs của bên thứ ba theo thời gian. Bạn có thể có mã biên dịch dừng biên dịch sau khi ai đó thêm một loại vào tệp mà bạn phụ thuộc.
Scott Stanchfield

6
liên quan đến vấn đề 1: về mặt kỹ thuật, bạn có thể biên dịch, nhưng bạn sẽ phải sử dụng tên lớp đủ điều kiện mỗi lần.
Kip

1
Bạn có thể giải quyết các loại xung đột này mà không liệt kê rõ ràng mọi lớp, điều này gây ra vấn đề của riêng nó.
rpjohnst

196

Đây là một cuộc bỏ phiếu cho nhập khẩu sao. Một câu lệnh nhập được dự định để nhập một gói , không phải là một lớp. Nó là sạch hơn nhiều để nhập khẩu toàn bộ gói; các vấn đề được xác định ở đây (ví dụ java.sql.Datevs java.util.Date) dễ dàng được khắc phục bằng các phương thức khác, không thực sự được giải quyết bằng cách nhập khẩu cụ thể và chắc chắn không biện minh cho việc nhập khẩu khủng khiếp trên tất cả các lớp. Không có gì đáng lo ngại hơn việc mở một tệp nguồn và phải trang qua 100 câu lệnh nhập.

Làm nhập khẩu cụ thể làm cho tái cấu trúc khó khăn hơn; nếu bạn xóa / đổi tên một lớp, bạn cần xóa tất cả các mục nhập cụ thể của nó. Nếu bạn chuyển đổi một triển khai sang một lớp khác trong cùng một gói, bạn phải đi sửa lỗi nhập. Mặc dù các bước bổ sung này có thể được tự động hóa, nhưng chúng thực sự là những cú đánh năng suất mà không có lợi ích thực sự.

Ngay cả khi Eclipse không thực hiện nhập lớp theo mặc định, mọi người vẫn sẽ thực hiện nhập sao. Tôi xin lỗi, nhưng thực sự không có lý do hợp lý nào cho việc nhập khẩu cụ thể.

Đây là cách giải quyết các xung đột trong lớp:

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

28
Tôi đồng ý. Mặc dù tôi sẽ không phản đối việc sử dụng nhập khẩu rõ ràng, tôi vẫn thích sử dụng nhập khẩu sao. Họ nhấn mạnh rằng "đơn vị tái sử dụng" là toàn bộ gói, không phải các loại riêng lẻ. Những lý do khác được liệt kê chống nhập khẩu sao là yếu và theo kinh nghiệm của tôi khi sử dụng nhập khẩu sao chưa bao giờ gây ra bất kỳ khó khăn thực tế nào.
Rogério

32
Xem javadude.com/articles/importondemandisevil.html để biết chi tiết tại sao nó xấu. Ý tưởng cơ bản: nó có thể khiến mã ngừng biên dịch khi các lớp được thêm vào các gói mà bạn nhập (như khi Danh sách được thêm vào java.util ...)
Scott Stanchfield

61
Tất cả các vấn đề bạn đề cập có thể được giải quyết bằng các IDE hiện đại (ẩn nhập, tái cấu trúc tên lớp, v.v ...).
assylias

15
Tôi không nên sử dụng IDE để đọc hoặc viết mã nguồn - mã này có thể tự đọc được mà không cần các công cụ đặc biệt trừ khi ngôn ngữ này rất khó đọc. Trong trường hợp này, Java hoạt động rất tốt - chỉ cần sử dụng nhập sao. Không có lý do để không.
davetron5000

42
@ davetron5000 Nếu mã của bạn chứa hơn 10 lần nhập ký tự đại diện và bạn sử dụng lớp Foovà nếu tôi đọc mã của bạn mà không sử dụng IDE (vì đối số của bạn là tôi không nên sử dụng một), làm sao tôi biết gói nào Foođến từ ? Chắc chắn, bằng cách sử dụng một IDE, IDE sẽ cho tôi biết, nhưng toàn bộ đối số của bạn là tôi sẽ có thể đọc mã mà không cần một mã. Thực hiện nhập khẩu rõ ràng giúp ghi lại mã (lý do tuyệt vời để tránh ký tự đại diện) và nhiều khả năng tôi sẽ đọc mã mà không sử dụng IDE, hơn là tôi sẽ viết mã mà không sử dụng IDE.
Andreas

169

vui lòng xem bài viết của tôi Nhập khẩu theo yêu cầu là xấu xa

Nói tóm lại, vấn đề lớn nhất là mã của bạn có thể bị hỏng khi một lớp được thêm vào gói bạn nhập. Ví dụ:

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

// ...

List list;

Trong Java 1.1, điều này là tốt; Danh sách đã được tìm thấy trong java.awt và không có xung đột.

Bây giờ, giả sử bạn kiểm tra mã làm việc hoàn hảo của mình và một năm sau, một người khác sẽ mang nó ra để chỉnh sửa nó và đang sử dụng Java 1.2.

Java 1.2 đã thêm một giao diện có tên List vào java.util. BÙM! Cuộc xung đột. Mã làm việc hoàn hảo không còn hoạt động.

Đây là một tính năng ngôn ngữ EVIL . Có NO lý do rằng mã nên dừng biên dịch chỉ vì một loại được thêm vào một gói ...

Ngoài ra, nó khiến người đọc khó xác định "Foo" nào bạn đang sử dụng.


35
Đây không phải là một lý do hợp lệ. Nếu bạn đang thay đổi phiên bản java, bằng cách nào đó bạn sẽ gặp một số điều không thành công, điều tương tự nếu bạn thay đổi phiên bản nhị phân mà mã của bạn sử dụng. Trong những trường hợp này, mã sẽ đưa ra một lỗi biên dịch và nó không đáng để sửa (xem câu trả lời trước: stackoverflow.com/a/149282/7595 )
Pablo Fernandez

36
@PabloFernandez - Không - Nếu tôi kiểm tra mã đã có trong kho lưu trữ trong một năm, thì nó vẫn sẽ được biên dịch. Nhập theo yêu cầu có thể dễ dàng thất bại khi các lớp mới được thêm vào các gói hiện có mà tôi đã nhập. Đây không chỉ là vấn đề khi nâng cấp các phiên bản Java. Ngoài ra - nếu một API được thiết kế tốt, nó sẽ không bao giờ phá vỡ mã hiện có khi nâng cấp. Lần duy nhất tôi cần thay đổi mã khi nâng cấp các phiên bản java là do nhập khẩu theo yêu cầu và khi Sun kéo các API XML vào thời gian chạy java.
Scott Stanchfield

3
Thêm một lớp (với một tên duy nhất, đủ điều kiện!) Vào đường dẫn lớp sẽ không ảnh hưởng đến bất cứ điều gì. Vấn đề ở đây là nếu bạn không sử dụng cú pháp nhập theo yêu cầu, thì nó sẽ không. Vì vậy, đừng sử dụng cú pháp xấu mà ngôn ngữ không may cho phép và đây là một vấn đề rất ít thực tế mà bạn có thể gặp phải.
Scott Stanchfield

28
Quan điểm của câu trả lời của tôi là đó là một tính năng ngôn ngữ không cần thiết gây ra vấn đề. Nhiều IDE / trình soạn thảo tự động xử lý mở rộng nhập khẩu. Sử dụng nhập khẩu đủ điều kiện và không có khả năng xảy ra lỗi cụ thể này. Tôi đã bị tấn công bởi điều này khi chịu áp lực phải sửa một lỗi trong mã hiện có và bạn thực sự không cần một thứ như thế này như một sự phân tâm khỏi nhiệm vụ thực tế trong tay. java.util.Listvs java.awt.Listkhông quá tệ để tìm ra, nhưng hãy thử nó khi tên lớp Configurationvà nhiều thư viện phụ thuộc đã thêm nó trong phiên bản maven repo mới nhất của họ.
Scott Stanchfield

5
Nếu tôi cập nhật một jar trong đó các lớp tôi sử dụng tương thích với API, và tôi không sử dụng cú pháp nhập theo yêu cầu, nó sẽ không ảnh hưởng đến tôi. Điều đó có ý nghĩa đối với bạn? Đừng lười biếng trong việc xác định nhập khẩu và đây không phải là vấn đề. Cú pháp nhập theo yêu cầu là một lỗi trong định nghĩa của ngôn ngữ Java; một ngôn ngữ hợp lý không nên cho phép các lỗi như thế này.
Scott Stanchfield

67

không xấu sử dụng một thẻ hoang dã với một tuyên bố nhập khẩu Java.

Trong Clean Code , Robert C. Martin thực sự khuyên bạn nên sử dụng chúng để tránh danh sách nhập khẩu dài.

Đây là khuyến nghị:

J1: Tránh danh sách nhập dài bằng cách sử dụng ký tự đại diện

Nếu bạn sử dụng hai hoặc nhiều lớp từ một gói, sau đó nhập toàn bộ gói với

gói nhập khẩu. *;

Danh sách dài hàng nhập khẩu đang làm nản lòng người đọc. Chúng tôi không muốn làm lộn xộn các mô-đun của chúng tôi với 80 dòng nhập khẩu. Thay vào đó, chúng tôi muốn nhập khẩu là một tuyên bố ngắn gọn về những gói chúng tôi cộng tác.

Nhập khẩu cụ thể là phụ thuộc cứng, trong khi nhập khẩu ký tự đại diện thì không. Nếu bạn đặc biệt nhập một lớp, thì lớp đó phải tồn tại. Nhưng nếu bạn nhập một gói với ký tự đại diện, không có lớp cụ thể nào cần tồn tại. Câu lệnh nhập chỉ đơn giản là thêm gói vào đường dẫn tìm kiếm khi săn tên. Vì vậy, không có sự phụ thuộc thực sự được tạo ra bởi các nhập khẩu như vậy và do đó chúng phục vụ để giữ cho các mô-đun của chúng tôi ít kết nối hơn.

Có những lúc danh sách dài nhập khẩu cụ thể có thể hữu ích. Ví dụ: nếu bạn đang xử lý mã kế thừa và bạn muốn tìm hiểu những lớp nào bạn cần để xây dựng mô hình và sơ khai, bạn có thể xem danh sách các nhập khẩu cụ thể để tìm ra tên đủ điều kiện thực sự của tất cả các lớp đó và sau đó đặt các cuống thích hợp tại chỗ. Tuy nhiên, việc sử dụng này cho nhập khẩu cụ thể là rất hiếm. Hơn nữa, hầu hết các IDE hiện đại sẽ cho phép bạn chuyển đổi các nhập khẩu có ký tự đại diện sang một danh sách các nhập khẩu cụ thể bằng một lệnh duy nhất. Vì vậy, ngay cả trong trường hợp kế thừa, tốt hơn là nhập ký tự đại diện.

Nhập ký tự đại diện đôi khi có thể gây ra xung đột tên và sự mơ hồ. Hai lớp có cùng tên, nhưng trong các gói khác nhau, sẽ cần phải được nhập cụ thể hoặc ít nhất là đủ điều kiện cụ thể khi được sử dụng. Điều này có thể gây phiền toái nhưng hiếm khi sử dụng nhập khẩu ký tự đại diện vẫn tốt hơn so với nhập khẩu cụ thể.


41
Tôi đề nghị với Robert C. Martin sử dụng các mẫu tốt hơn để tạo ra các gói và lớp ngắn gọn hơn mà không yêu cầu 80 dòng nhập khẩu. Rằng nhiều lớp cần thiết để nhập trong một lớp duy nhất chỉ là cầu xin 'Entropy, Entropy, làm ơn phá vỡ tôi ...' và chỉ ra lý do để tránh nhập * được nêu trong anwers Scott Stanchfields
Ray

28
Nhiều như tôi thường yêu những gì chú Bob nói, trong trường hợp này tôi cũng phải không đồng ý với ông.

33
Danh sách dài hàng nhập khẩu đang làm nản lòng người đọc. - Khẳng định này có một giả định không hợp lệ. Các lập trình viên không bắt buộc phải đọc mã nguồn từ trên xuống dưới. Chúng tôi có thể không đọc danh sách nhập khẩu. Khi chúng tôi làm, chúng tôi có thể chỉ đọc một trong những nhập khẩu, để làm rõ. Vào những thời điểm khác, việc nhập khẩu có thể bị thu hẹp hoàn toàn, nếu chúng ta đang làm việc trong một IDE. Bất kể nguồn nào, hôm nay đây là lời khuyên tồi.
Andy Thomas

10
Chỉ để cung cấp một số đối trọng khi đề cập đến các cơ quan có thẩm quyền về vấn đề này: Hướng dẫn về Phong cách Java của Google cũng như Hướng dẫn về Phong cách Java của Twitter (phần lớn dựa trên Google, để công bằng) đặc biệt cấm nhập khẩu ký tự đại diện. Nhưng họ không cung cấp bất kỳ lý do nào cho quyết định này.
Anothernode

1
Có lẽ điểm duy nhất tôi không đồng ý trong Clean Code. Nó phải cuộn qua vài dòng báo cáo nhập khẩu hoặc vật lộn để tìm lớp đến từ đâu. Tôi thích dễ dàng xác định nơi một lớp nhất định đến từ.
Ganesh Satpute

27

Hiệu suất : Không ảnh hưởng đến hiệu suất vì mã byte là như nhau. mặc dù nó sẽ dẫn đến một số chi phí biên dịch.

Biên dịch : trên máy cá nhân của tôi, Biên dịch một lớp trống mà không nhập bất cứ thứ gì mất 100 ms nhưng cùng một lớp khi nhập java. * Mất 170 ms.


3
import java.*Nhập khẩu không có gì. Tại sao nó sẽ làm cho một sự khác biệt?
Hầu tước Lorne

6
Nó làm cho một sự khác biệt bởi vì nó được tìm kiếm trong quá trình biên dịch.
Truyền thuyết Bước sóng

25

Nó làm tắc nghẽn không gian tên của bạn, yêu cầu bạn chỉ định đầy đủ bất kỳ tên lớp nào không rõ ràng. Sự xuất hiện phổ biến nhất của điều này là với:

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

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

Nó cũng giúp làm cho các phụ thuộc của bạn cụ thể, vì tất cả các phụ thuộc của bạn được liệt kê ở đầu tệp.


20
  1. Nó giúp xác định xung đột tên lớp: hai lớp trong các gói khác nhau có cùng tên. Điều này có thể được che dấu với nhập *.
  2. Nó làm cho các phụ thuộc rõ ràng, để bất kỳ ai phải đọc mã của bạn sau đó đều biết bạn muốn nhập gì và bạn không có ý định nhập gì.
  3. Nó có thể làm cho một số trình biên dịch nhanh hơn vì trình biên dịch không phải tìm kiếm toàn bộ gói để xác định các lỗi, mặc dù điều này thường không phải là một vấn đề lớn với các trình biên dịch hiện đại.
  4. Các khía cạnh bất tiện của nhập khẩu rõ ràng được giảm thiểu với các IDE hiện đại. Hầu hết các IDE cho phép bạn thu gọn phần nhập để nó không bị cản trở, tự động điền các mục nhập khi cần và tự động xác định các mục nhập không sử dụng để giúp dọn sạch chúng.

Hầu hết các nơi tôi từng làm việc sử dụng bất kỳ lượng Java đáng kể nào đều biến nhập khẩu rõ ràng thành một phần của tiêu chuẩn mã hóa. Thỉnh thoảng tôi vẫn sử dụng * để tạo mẫu nhanh và sau đó mở rộng danh sách nhập (một số IDE cũng sẽ làm điều này cho bạn) khi sản xuất mã.


Tôi thích hầu hết các điểm của bạn, nhưng cụ thể là số 4 đã giúp tôi nâng cao câu trả lời của bạn. Các IDE hiện đại loại bỏ hầu hết các đối số chống lại việc sử dụng nhập khẩu rõ ràng ...
Sheldon R.

Có lẽ một phần của vấn đề ở đây là cách các thư viện java tiêu chuẩn được trình bày với nhiều lớp trong cùng một gói. Trái ngược với việc áp dụng nhiều hơn một "nguyên tắc có trách nhiệm duy nhất" cho một gói.
Truyền thuyết Bước sóng

11

Tôi thích nhập cụ thể hơn, vì nó cho phép tôi xem tất cả các tham chiếu bên ngoài được sử dụng trong tệp mà không cần xem toàn bộ tệp. (Vâng, tôi biết rằng nó sẽ không nhất thiết phải hiển thị các tài liệu tham khảo đủ điều kiện. Nhưng tôi tránh chúng bất cứ khi nào có thể.)


9

Trong một dự án trước đây, tôi thấy rằng việc thay đổi từ * nhập khẩu sang nhập khẩu cụ thể đã giảm một nửa thời gian biên dịch (từ khoảng 10 phút xuống còn khoảng 5 phút). * -Import làm cho trình biên dịch tìm kiếm từng gói được liệt kê cho một lớp khớp với lớp bạn đã sử dụng. Trong khi thời gian này có thể nhỏ, nó bổ sung cho các dự án lớn.

Một tác động phụ của * -import là các nhà phát triển sẽ sao chép và dán các dòng nhập chung thay vì nghĩ về những gì họ cần.


11
Phải có rất nhiều dòng nhập khẩu hoặc một hệ thống phát triển thực sự thảm hại để điều này trở thành sự thật. Tôi sử dụng nhập- * một tôi có thể biên dịch toàn bộ cơ sở mã của 2107 lớp trong vòng chưa đầy 2 phút.
Lawrence Dol

6

Trong sách DDD

Trong bất kỳ công nghệ phát triển nào, việc triển khai sẽ dựa trên, hãy tìm cách giảm thiểu công việc tái cấu trúc MODULES. Trong Java, không có lối thoát nào khi nhập vào các lớp riêng lẻ, nhưng ít nhất bạn có thể nhập toàn bộ các gói cùng một lúc, phản ánh ý định rằng các gói là các đơn vị gắn kết cao đồng thời giảm nỗ lực thay đổi tên gói.

Và nếu nó làm tắc nghẽn không gian tên cục bộ thì đó không phải là lỗi của bạn - đổ lỗi cho kích thước của gói.


3
  • Không có tác động thời gian chạy, vì trình biên dịch sẽ tự động thay thế * bằng các tên lớp cụ thể. Nếu bạn dịch ngược tệp. Class, bạn sẽ không bao giờ thấy import ...*.

  • C # luôn sử dụng * (ngầm) vì bạn chỉ có thể usinggói tên. Bạn không bao giờ có thể chỉ định tên lớp ở tất cả. Java giới thiệu tính năng này sau c #. (Java rất phức tạp về nhiều mặt nhưng nó nằm ngoài chủ đề này).

  • Trong Intellij Idea khi bạn thực hiện "tổ chức nhập", nó sẽ tự động thay thế nhiều lần nhập của cùng một gói bằng *. Đây là một tính năng bắt buộc vì bạn không thể tắt nó (mặc dù bạn có thể tăng ngưỡng).

  • Các trường hợp được liệt kê bởi trả lời được chấp nhận là không hợp lệ. Không có * bạn vẫn gặp vấn đề tương tự. Bạn cần chỉ định tên pakcage trong mã của mình cho dù bạn có sử dụng * hay không.


1
Trong IntelliJ, nó không phải là một tính năng bắt buộc và nó có thể bị tắt.
Bastien7

3

Đối với bản ghi: Khi bạn thêm một lần nhập, bạn cũng cho biết sự phụ thuộc của mình.

Bạn có thể thấy nhanh sự phụ thuộc của các tệp (không bao gồm các lớp có cùng không gian tên).


Đồng ý. Công cụ tạo động lực không phải là quá nhiều hiệu năng hoặc biên dịch, mà là khả năng đọc mã của con người. Chỉ cần tưởng tượng bạn đang đọc mã mà không có IDE - chẳng hạn trên GitHub. Đột nhiên tìm kiếm mọi tham chiếu không được xác định trong tệp bạn đang đọc trở nên tẻ nhạt.
Leo Orientis

2

Điều quan trọng nhất là việc nhập java.awt.*có thể làm cho chương trình của bạn không tương thích với phiên bản Java trong tương lai:

Giả sử rằng bạn có một lớp có tên là "ABC", bạn đang sử dụng JDK 8 và bạn nhập java.util.*. Bây giờ, giả sử rằng Java 9 xuất hiện và nó có một lớp mới trong gói java.utilmà do sự trùng hợp ngẫu nhiên cũng được gọi là "ABC". Chương trình của bạn bây giờ sẽ không biên dịch trên Java 9, vì trình biên dịch không biết nếu với tên "ABC", bạn có nghĩa là lớp của riêng bạn hoặc lớp mới trongjava.awt .

Bạn sẽ không gặp vấn đề đó khi bạn chỉ nhập các lớp đó một cách rõ ràng từ java.awtđó bạn thực sự sử dụng.

Tài nguyên:

Nhập khẩu Java


3
mẹo: bạn có thể đã sử dụng Streamlàm ví dụ về một lớp mới được thêm vào Java trong java.util trong Java 8 ...
Clint Eastwood

2

Trong số tất cả các điểm hợp lệ được thực hiện ở cả hai phía, tôi không tìm thấy lý do chính của mình để tránh ký tự đại diện: Tôi muốn có thể đọc mã và biết trực tiếp mọi lớp là gì, hoặc nếu định nghĩa đó không phải là ngôn ngữ hoặc các tập tin, nơi để tìm thấy nó. Nếu có nhiều hơn một gói được nhập với * Tôi phải tìm kiếm từng gói để tìm một lớp tôi không nhận ra. Khả năng đọc là tối cao và tôi đồng ý mã không nên yêu cầu IDE để đọc nó.


Nếu bạn đưa ra kết luận hợp lý đầy đủ thì phong cách của bạn sẽ không sử dụng nhập khẩu và thay vào đó là "LinkedList mới" luôn sử dụng "java.util.LinkedList" mới và thực hiện điều này một cách nhất quán ở mọi nơi .
Erwin Smout
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.