it-swarm-vi.tech

Điều gì là mát mẻ về thuốc generic, tại sao sử dụng chúng?

Tôi nghĩ rằng tôi sẽ cung cấp quả bóng mềm này cho bất cứ ai muốn đánh nó ra khỏi công viên. Thế nào là thuốc generic, ưu điểm của thuốc generic là gì, tại sao, ở đâu, tôi nên sử dụng chúng như thế nào? Xin vui lòng, giữ cho nó khá cơ bản. Cảm ơn.

80
MrBoJangles
  • Cho phép bạn viết mã/sử dụng các phương thức thư viện an toàn kiểu, tức là Danh sách <chuỗi> được đảm bảo là danh sách các chuỗi.
  • Do kết quả của việc sử dụng chung, trình biên dịch có thể thực hiện kiểm tra thời gian biên dịch trên mã để đảm bảo an toàn kiểu, tức là bạn đang cố gắng đưa một int vào danh sách các chuỗi đó? Sử dụng một ArrayList sẽ gây ra lỗi đó trong suốt thời gian chạy.
  • Nhanh hơn so với việc sử dụng các đối tượng vì nó tránh được quyền anh/unboxing (trong đó .net phải chuyển đổi loại giá trị thành loại tham chiếu hoặc ngược lại ) hoặc truyền từ đối tượng sang loại tham chiếu được yêu cầu.
  • Cho phép bạn viết mã áp dụng cho nhiều loại có cùng hành vi cơ bản, ví dụ: Từ điển <chuỗi, int> sử dụng cùng mã bên dưới như Từ điển <DateTime, double>; sử dụng thuốc generic, nhóm khung chỉ phải viết một đoạn mã để đạt được cả hai kết quả với những lợi thế đã nói ở trên.
120
ljs

Tôi thực sự ghét phải lặp lại chính mình. Tôi ghét gõ cùng một điều thường xuyên hơn tôi phải. Tôi không thích nghỉ ngơi nhiều lần với sự khác biệt nhỏ.

Thay vì tạo:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

Tôi có thể xây dựng một lớp có thể sử dụng lại ... (trong trường hợp bạn không muốn sử dụng bộ sưu tập thô vì một số lý do)

class MyList<T> {
   T get(int index) { ... }
}

Bây giờ tôi hiệu quả hơn gấp 3 lần và tôi chỉ phải duy trì một bản sao. Tại sao bạn không muốn duy trì ít mã hơn?

Điều này cũng đúng với các lớp không thu thập, chẳng hạn như Callable<T> hoặc một Reference<T> mà phải tương tác với các lớp khác. Bạn có thực sự muốn gia hạn Callable<T>Future<T> và mọi lớp liên kết khác để tạo các phiên bản loại an toàn?

Tôi không.

46
James Schek

Không cần đánh máy là một trong những lợi thế lớn nhất của Java generic , vì nó sẽ thực hiện kiểm tra kiểu khi biên dịch- thời gian. Điều này sẽ làm giảm khả năng ClassCastExceptions có thể bị ném khi chạy và có thể dẫn đến mã mạnh hơn.

Nhưng tôi nghi ngờ rằng bạn hoàn toàn nhận thức được điều đó.

Mỗi lần tôi nhìn vào Generics nó làm tôi đau đầu. Tôi thấy phần hay nhất của Java là sự đơn giản và cú pháp tối thiểu và tổng quát không đơn giản và thêm một lượng đáng kể cú pháp mới.

Lúc đầu, tôi cũng không thấy lợi ích của thuốc generic. Tôi đã bắt đầu học Java từ cú pháp 1.4 (mặc dù Java 5 đã hết thời gian) và khi tôi gặp phải thuốc generic, tôi cảm thấy rằng đó là mã nhiều hơn để viết, và tôi thực sự không hiểu lợi ích.

Các IDE hiện đại giúp việc viết mã với khái quát dễ dàng hơn.

Hầu hết các IDE hiện đại, phong nha đều đủ thông minh để hỗ trợ viết mã với khái quát, đặc biệt là hoàn thành mã.

Đây là một ví dụ về việc tạo Map<String, Integer> với một HashMap. Mã tôi sẽ phải nhập là:

Map<String, Integer> m = new HashMap<String, Integer>();

Và thực tế, đó là rất nhiều để gõ chỉ để tạo một HashMap mới. Tuy nhiên, trong thực tế, tôi chỉ phải gõ nhiều như vậy trước khi Eclipse biết tôi cần gì:

Map<String, Integer> m = new Ha Ctrl+Space

Đúng, tôi đã cần chọn HashMap từ danh sách các ứng cử viên, nhưng về cơ bản, IDE biết phải thêm gì, bao gồm các loại chung. Với các công cụ phù hợp, sử dụng chung chung là quá tệ.

Ngoài ra, vì các loại đã được biết, khi truy xuất các phần tử từ bộ sưu tập chung, IDE sẽ hoạt động như thể đối tượng đó đã là một đối tượng của loại khai báo của nó - không cần phải truyền cho IDE để biết loại của đối tượng là gì.

Một lợi thế chính của thuốc generic đến từ cách nó chơi tốt với các tính năng mới Java 5. Đây là một ví dụ về việc tung số nguyên vào một Set và tính tổng của nó:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

Trong đoạn mã đó, có ba tính năng mới Java 5:

Đầu tiên, generic và autoboxing của nguyên thủy cho phép các dòng sau:

set.add(10);
set.add(42);

Số nguyên 10 được tự động chuyển thành Integer với giá trị là 10. (Và tương tự cho 42). Sau đó, Integer được ném vào Set được biết là giữ Integers. Cố gắng ném vào String sẽ gây ra lỗi biên dịch.

Tiếp theo, cho mỗi vòng lặp mất cả ba trong số đó:

for (int i : set) {
  total += i;
}

Đầu tiên, Set chứa Integers được sử dụng trong một vòng lặp cho mỗi vòng lặp. Mỗi phần tử được khai báo là int và được phép là Integer được bỏ hộp trở lại nguyên hàm int. Và thực tế là việc bỏ hộp này xảy ra đã được biết vì thuốc generic được sử dụng để chỉ định rằng có Integers được giữ trong Set.

Generics có thể là chất keo kết hợp các tính năng mới được giới thiệu trong Java 5 và nó chỉ giúp mã hóa đơn giản và an toàn hơn. Và hầu hết các IDE đều đủ thông minh để giúp bạn có những gợi ý hay, vì vậy, nói chung, nó sẽ không gõ nhiều hơn.

Và thẳng thắn, như có thể thấy từ ví dụ Set, tôi cảm thấy rằng việc sử dụng Java 5 tính năng có thể làm cho mã ngắn gọn và mạnh mẽ hơn.

Chỉnh sửa - Một ví dụ không có tổng quát

Sau đây là một minh họa về ví dụ Set ở trên mà không sử dụng thuốc generic. Có thể, nhưng không chính xác là dễ chịu:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Lưu ý: Đoạn mã trên sẽ tạo cảnh báo chuyển đổi không được kiểm soát tại thời gian biên dịch.)

Khi sử dụng các bộ sưu tập không chung chung, các loại được nhập vào bộ sưu tập là các đối tượng của loại Object. Do đó, trong ví dụ này, một Object là những gì đang được added vào tập hợp.

set.add(10);
set.add(42);

Trong các dòng trên, autoboxing đang hoạt động - nguyên hàm int value 1042 đang được tự động đóng hộp vào các đối tượng Integer đang được thêm vào Set. Tuy nhiên, hãy nhớ rằng, các đối tượng Integer đang được xử lý là Objects, vì không có thông tin loại nào để giúp trình biên dịch biết loại Set nên mong đợi.

for (Object o : set) {

Đây là một phần rất quan trọng. Lý do vòng lặp for-every hoạt động là vì Set thực hiện giao diện Iterable , trả về một Iterator với thông tin loại, nếu có. (Iterator<T>, đó là.)

Tuy nhiên, vì không có thông tin loại, nên Set sẽ trả về Iterator sẽ trả về các giá trị trong SetObjects, và đó là lý do tại sao phần tử đang được truy xuất trong vòng lặp for-every must thuộc loại Object.

Bây giờ, Object được lấy từ Set, nó cần được truyền thành Integer theo cách thủ công để thực hiện phép cộng:

  total += (Integer)o;

Ở đây, một typecast được thực hiện từ Object đến Integer. Trong trường hợp này, chúng tôi biết điều này sẽ luôn hoạt động, nhưng kiểu chữ thủ công luôn khiến tôi cảm thấy đó là mã dễ vỡ có thể bị hỏng nếu một thay đổi nhỏ được thực hiện ở nơi khác. (Tôi cảm thấy rằng mọi typecast là một ClassCastException đang chờ để xảy ra, nhưng tôi lạc đề ...)

Integer hiện được bỏ hộp thành một int và được phép thực hiện phép cộng vào biến inttotal.

Tôi hy vọng tôi có thể minh họa rằng các tính năng mới của Java 5 có thể sử dụng với mã không chung chung, nhưng nó không rõ ràng và đơn giản như viết mã với khái quát. Và , theo tôi, để tận dụng tối đa các tính năng mới trong Java 5, người ta nên xem xét tổng quát, nếu ít nhất, cho phép kiểm tra thời gian biên dịch để ngăn chặn các lỗi đánh máy không hợp lệ ném ngoại lệ trong thời gian chạy.

20
coobird

Nếu bạn đã tìm kiếm cơ sở dữ liệu lỗi Java trước khi 1.5 được phát hành, bạn sẽ tìm thấy nhiều lỗi hơn bảy lần với NullPointerException so với ClassCastException. Vì vậy, nó không Dường như đó là một tính năng tuyệt vời để tìm lỗi, hoặc ít nhất là các lỗi vẫn tồn tại sau một thử nghiệm khói nhỏ.

Đối với tôi, lợi thế rất lớn của thuốc generic là họ tài liệu bằng mã thông tin loại quan trọng. Nếu tôi không muốn thông tin loại đó được ghi lại bằng mã, thì tôi sẽ sử dụng ngôn ngữ được nhập động hoặc ít nhất là một ngôn ngữ có suy luận kiểu ngầm định hơn.

Giữ cho các bộ sưu tập của một đối tượng không phải là một phong cách xấu (nhưng sau đó, phong cách phổ biến là bỏ qua việc đóng gói một cách hiệu quả). Nó phụ thuộc vào những gì bạn đang làm. Việc chuyển các bộ sưu tập đến "thuật toán" sẽ dễ kiểm tra hơn một chút (tại hoặc trước thời gian biên dịch) với các tổng quát.

15

Generics in Java tạo điều kiện đa hình tham số . Bằng phương tiện tham số loại, bạn có thể truyền đối số cho các loại. Giống như một phương thức như String foo(String s) mô hình một số hành vi, không chỉ đối với một chuỗi cụ thể, mà đối với bất kỳ chuỗi s, vì vậy một loại như List<T> mô hình một số hành vi, không chỉ cho một loại cụ thể, mà cho bất kỳ loại nào . List<T> nói rằng cho bất kỳ loại T, có một loại List có thành phần là Ts . Vì vậy, List thực sự là một hàm tạo kiểu . Nó lấy một kiểu làm đối số và xây dựng một kiểu khác làm kết quả.

Dưới đây là một vài ví dụ về các loại chung tôi sử dụng hàng ngày. Đầu tiên, một giao diện chung rất hữu ích:

public interface F<A, B> {
  public B f(A a);
}

Giao diện này nói rằng đối với một số loại, AB, có một hàm (được gọi là f) có A và trả về một B. Khi bạn triển khai giao diện này, AB có thể là bất kỳ loại nào bạn muốn, miễn là khi bạn cung cấp một hàm f lấy cái trước và trả về cái sau. Dưới đây là một ví dụ triển khai giao diện:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

Trước khi khái quát, tính đa hình đã đạt được bằng cách phân lớp bằng cách sử dụng từ khóa extends. Với thuốc generic, chúng ta thực sự có thể loại bỏ phân lớp và sử dụng đa hình tham số thay thế. Ví dụ, hãy xem xét một lớp tham số (chung) được sử dụng để tính mã băm cho bất kỳ loại nào. Thay vì ghi đè Object.hashCode (), chúng tôi sẽ sử dụng một lớp chung như thế này:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

Điều này linh hoạt hơn nhiều so với sử dụng tính kế thừa, bởi vì chúng ta có thể theo chủ đề sử dụng thành phần và đa hình tham số mà không khóa các cấu trúc phân cấp dễ vỡ.

Mặc dù thế hệ của Java không hoàn hảo. Ví dụ, bạn có thể trừu tượng hóa các loại, nhưng bạn không thể trừu tượng hóa các hàm tạo kiểu. Nghĩa là, bạn có thể nói "cho bất kỳ loại T" nào, nhưng bạn không thể nói "cho bất kỳ loại T nào có tham số loại A".

Tôi đã viết một bài viết về các giới hạn này của Java generic, tại đây.

Một chiến thắng lớn với thuốc generic là chúng cho phép bạn tránh phân lớp. Phân lớp có xu hướng dẫn đến hệ thống phân cấp lớp dễ vỡ, khó mở rộng và các lớp khó hiểu riêng lẻ mà không xem xét toàn bộ hệ thống phân cấp.

Trước khi có tướng, bạn có thể có các lớp như Widget được mở rộng bởi FooWidget, BarWidgetBazWidget, với các tổng quát bạn có thể có một lớp chung duy nhất Widget<A> cần một Foo, Bar hoặc Baz trong hàm tạo của nó để cung cấp cho bạn Widget<Foo>, Widget<Bar>Widget<Baz>.

10
Apocalisp

Generics tránh hiệu suất của đấm bốc và unboxing. Về cơ bản, hãy xem ArrayList vs List <T>. Cả hai đều làm những việc cốt lõi giống nhau, nhưng Danh sách <T> sẽ nhanh hơn rất nhiều vì bạn không phải đóng hộp đến/từ đối tượng.

8
Darren Kopp
  • Bộ sưu tập đã nhập - ngay cả khi bạn không muốn sử dụng chúng, bạn có thể phải xử lý chúng từ các thư viện khác, các nguồn khác.

  • Gõ chung trong tạo lớp:

    lớp công khai Foo <T> {công T nhận () ...

  • Tránh bỏ vai - Tôi luôn không thích những thứ như

    so sánh mới {public int so sánhTo (Object o) {if (o instanceof classIcare Giới thiệu) ...

Về cơ bản, bạn đang kiểm tra một điều kiện chỉ tồn tại vì giao diện được thể hiện dưới dạng các đối tượng.

Phản ứng ban đầu của tôi về thuốc generic cũng tương tự như của bạn - "quá lộn xộn, quá phức tạp". Kinh nghiệm của tôi là sau khi sử dụng chúng một chút, bạn sẽ quen với chúng, và mã mà không có chúng cảm thấy ít được chỉ định rõ ràng hơn, và chỉ kém thoải mái hơn. Bên cạnh đó, phần còn lại của thế giới Java sử dụng chúng để cuối cùng bạn sẽ phải tham gia chương trình, phải không?

5
Steve B.

Để đưa ra một ví dụ tốt. Hãy tưởng tượng bạn có một lớp học tên là Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

Ví dụ 1 Bây giờ bạn muốn có một bộ sưu tập các đối tượng Foo. Bạn có hai tùy chọn, LIst hoặc ArrayList, cả hai đều hoạt động theo cách tương tự.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

Trong đoạn mã trên, nếu tôi cố gắng thêm một lớp FireTruck thay vì Foo, ArrayList sẽ thêm nó, nhưng Danh sách chung của Foo sẽ khiến ngoại lệ bị ném.

Ví dụ hai.

Bây giờ bạn có hai danh sách mảng của mình và bạn muốn gọi hàm Bar () trên mỗi danh sách. Vì hte ArrayList chứa đầy Đối tượng, bạn phải truyền chúng trước khi bạn có thể gọi thanh. Nhưng vì Danh sách chung của Foo chỉ có thể chứa Foos, nên bạn có thể gọi Bar () trực tiếp trên các danh sách đó.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}
5
Peter Lange

Tôi chỉ thích chúng vì chúng cung cấp cho bạn một cách nhanh chóng để xác định loại tùy chỉnh (vì dù sao tôi cũng sử dụng chúng).

Vì vậy, ví dụ thay vì xác định cấu trúc bao gồm một chuỗi và một số nguyên, sau đó phải triển khai toàn bộ một tập hợp các đối tượng và phương thức về cách truy cập vào một mảng của các cấu trúc đó, v.v., bạn chỉ cần tạo một Từ điển

Dictionary<int, string> dictionary = new Dictionary<int, string>();

Và trình biên dịch/IDE thực hiện phần còn lại của việc nâng vật nặng. Từ điển đặc biệt cho phép bạn sử dụng loại đầu tiên làm khóa (không có giá trị lặp lại).

5
Tom Kidd

Lợi ích tốt nhất cho Generics là tái sử dụng mã. Hãy nói rằng bạn có rất nhiều đối tượng kinh doanh và bạn sẽ viết RẤT mã tương tự cho mỗi thực thể để thực hiện các hành động tương tự. (I.E Linq cho các hoạt động SQL).

Với generic, bạn có thể tạo một lớp có thể hoạt động với bất kỳ loại nào được thừa hưởng từ một lớp cơ sở nhất định hoặc thực hiện một giao diện đã cho như sau:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

Lưu ý việc sử dụng lại cùng một dịch vụ được cung cấp các Loại khác nhau trong phương thức DoS Something ở trên. Thật thanh lịch!

Có nhiều lý do tuyệt vời khác để sử dụng thuốc generic cho công việc của bạn, đây là sở thích của tôi.

5
Dean Poulin

Bạn chưa bao giờ viết một phương thức (hoặc một lớp) trong đó khái niệm chính của phương thức/lớp không bị ràng buộc chặt chẽ với một kiểu dữ liệu cụ thể của các biến tham số/thể hiện (danh sách liên kết, hàm max/min, tìm kiếm nhị phân , v.v.).

Bạn chưa bao giờ ước mình có thể sử dụng lại mã/mã mà không cần dùng đến việc sử dụng lại cắt-dán hoặc thỏa hiệp gõ mạnh (ví dụ: tôi muốn một List của Chuỗi, không phải là List of những điều tôi hy vọng là các chuỗi!)?

Đó là lý do tại sao bạn nên muốn để sử dụng thuốc generic (hoặc một cái gì đó tốt hơn).

4
Bert F

Đừng quên rằng generic không chỉ được sử dụng bởi các lớp, chúng cũng có thể được sử dụng bởi các phương thức. Ví dụ: lấy đoạn mã sau:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

Nó đơn giản, nhưng có thể được sử dụng rất thanh lịch. Điều tuyệt vời là phương thức trả về bất cứ thứ gì nó đã được đưa ra. Điều này giúp giải quyết khi bạn đang xử lý các trường hợp ngoại lệ cần được gửi lại cho người gọi:

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

Vấn đề là bạn không bị mất kiểu bằng cách chuyển nó qua một phương thức. Bạn có thể ném đúng loại ngoại lệ thay vì chỉ một Throwable, đó sẽ là tất cả những gì bạn có thể làm mà không cần chung chung.

Đây chỉ là một ví dụ đơn giản về một lần sử dụng cho các phương thức chung. Có khá nhiều điều gọn gàng khác mà bạn có thể làm với các phương pháp chung. Điều tuyệt vời nhất, theo tôi, là kiểu suy luận với thuốc generic. Lấy ví dụ sau (lấy từ Josh Bloch 'hiệu quả Java Phiên bản 2):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

Điều này không làm được gì nhiều, nhưng nó sẽ giảm bớt một số lộn xộn khi các loại chung dài (hoặc lồng nhau; tức là Map<String, List<String>>).

3
jigawot

Từ mặt trời Java, để đáp lại "tại sao tôi nên sử dụng thuốc generic?":

"Generics cung cấp một cách để bạn giao tiếp loại bộ sưu tập với trình biên dịch, để nó có thể được kiểm tra. Một khi trình biên dịch biết loại phần tử của bộ sưu tập, trình biên dịch có thể kiểm tra xem bạn đã sử dụng bộ sưu tập một cách nhất quán chưa và có thể chèn các phôi chính xác cho các giá trị được lấy ra khỏi bộ sưu tập ... Mã sử ​​dụng tổng quát rõ ràng và an toàn hơn .... trình biên dịch có thể xác minh tại thời điểm biên dịch rằng các ràng buộc loại không bị vi phạm trong thời gian chạy [nhấn mạnh của tôi]. Bởi vì chương trình biên dịch mà không có cảnh báo, chúng tôi có thể khẳng định chắc chắn rằng nó sẽ không ném ClassCastException trong thời gian chạy. Hiệu quả ròng của việc sử dụng thuốc generic, đặc biệt là trong các chương trình lớn, là cải thiện khả năng đọc và độ mạnh. [nhấn mạnh của tôi] "

2
Demi

dù sao thì jvm vẫn tạo ra ... nó hoàn toàn tạo ra mã xử lý loại chung là "Đối tượng" và tạo ra các phôi để khởi tạo mong muốn. Java generic chỉ là đường cú pháp.

2
Anonymous Coward

Ưu điểm chính, như Mitchel chỉ ra, là gõ mạnh mà không cần xác định nhiều lớp.

Bằng cách này bạn có thể làm những việc như:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

Nếu không có tướng, bạn sẽ phải chuyển blah [0] sang đúng loại để truy cập các chức năng của nó.

2
Kevin Pang

Tôi biết đây là câu hỏi C #, nhưng generic cũng được sử dụng trong các ngôn ngữ khác và cách sử dụng/mục tiêu của chúng khá giống nhau.

Các bộ sưu tập Java sử dụng generic từ Java 1.5. Vì vậy, một nơi tốt để sử dụng chúng là khi bạn đang tạo đối tượng giống như bộ sưu tập của riêng mình.

Một ví dụ tôi thấy hầu hết mọi nơi là một lớp Pair, chứa hai đối tượng, nhưng cần phải xử lý các đối tượng đó một cách chung chung.

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

Bất cứ khi nào bạn sử dụng lớp Pair này, bạn có thể chỉ định loại đối tượng nào bạn muốn nó xử lý và mọi vấn đề về kiểu cast sẽ xuất hiện vào thời gian biên dịch, thay vì thời gian chạy.

Generics cũng có thể có giới hạn của chúng được xác định bằng các từ khóa 'super' và 'extends'. Ví dụ: nếu bạn muốn xử lý một loại chung nhưng bạn muốn chắc chắn rằng nó mở rộng một lớp có tên là Foo (có phương thức setTitle):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

Mặc dù không thú vị lắm nhưng thật hữu ích khi biết rằng bất cứ khi nào bạn giao dịch với FooManager, bạn sẽ biết rằng nó sẽ xử lý các loại MyClass và MyClass sẽ mở rộng Foo.

2
etchasketch

Tôi sử dụng chúng ví dụ trong GenericDao được triển khai với SpringORM và Hibernate trông giống như thế này

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

Bằng cách sử dụng khái quát, việc triển khai DAO này của tôi buộc nhà phát triển phải vượt qua chúng chỉ là các thực thể mà chúng được thiết kế chỉ bằng cách phân lớp GenericDao

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

Khung nhỏ của tôi mạnh mẽ hơn (có những thứ như lọc, tải chậm, tìm kiếm). Tôi chỉ đơn giản hóa ở đây để cho bạn một ví dụ

Tôi, như Steve và bạn, đã nói lúc đầu "Quá lộn xộn và phức tạp" nhưng bây giờ tôi thấy lợi thế của nó

1
victor hugo

Generics cho phép bạn tạo các đối tượng được gõ mạnh, nhưng bạn không phải xác định loại cụ thể. Tôi nghĩ ví dụ hữu ích tốt nhất là Danh sách và các lớp tương tự.

Sử dụng danh sách chung, bạn có thể có Danh sách Danh sách bất cứ điều gì bạn muốn và bạn luôn có thể tham chiếu kiểu gõ mạnh, bạn không phải chuyển đổi hoặc bất cứ điều gì giống như bạn làm với Mảng hoặc Danh sách tiêu chuẩn.

1
Mitchel Sellers

Generics cho phép bạn sử dụng kiểu gõ mạnh cho các đối tượng và cấu trúc dữ liệu có thể giữ bất kỳ đối tượng nào. Nó cũng loại bỏ các kiểu chữ tẻ nhạt và đắt tiền khi lấy các đối tượng từ các cấu trúc chung (đấm bốc/bỏ hộp).

Một ví dụ sử dụng cả hai là một danh sách liên kết. Một lớp danh sách được liên kết sẽ có ích gì nếu nó chỉ có thể sử dụng đối tượng Foo? Để thực hiện một danh sách được liên kết có thể xử lý bất kỳ loại đối tượng nào, danh sách được liên kết và các nút trong lớp bên trong nút giả định phải chung chung nếu bạn muốn danh sách chỉ chứa một loại đối tượng.

1
Steve Landey

Những lợi ích rõ ràng như "an toàn kiểu" và "không đúc" đã được đề cập để có thể tôi có thể nói về một số "lợi ích" khác mà tôi hy vọng nó có ích.

Trước hết, generic là một khái niệm độc lập với ngôn ngữ và, IMO, nó có thể có ý nghĩa hơn nếu bạn nghĩ về đa hình (thời gian chạy) thông thường cùng một lúc.

Ví dụ, tính đa hình như chúng ta biết từ thiết kế hướng đối tượng có một khái niệm thời gian chạy trong đó đối tượng người gọi được tìm ra trong thời gian chạy khi thực thi chương trình và phương thức liên quan được gọi phù hợp tùy thuộc vào loại thời gian chạy. Trong khái quát, ý tưởng có phần giống nhau nhưng mọi thứ xảy ra vào thời gian biên dịch. Điều đó có nghĩa là gì và làm thế nào bạn sử dụng nó?

(Hãy sử dụng các phương thức chung để giữ cho nó nhỏ gọn) Điều đó có nghĩa là bạn vẫn có thể có cùng một phương thức trên các lớp riêng biệt (giống như bạn đã làm trước đây trong các lớp đa hình) nhưng lần này chúng được trình biên dịch tự động tạo ra phụ thuộc vào các loại được đặt tại thời gian biên dịch. Bạn tham số các phương thức của bạn vào loại bạn đưa ra tại thời điểm biên dịch. Vì vậy, thay vì viết các phương thức từ đầu cho mỗi loại đơn bạn có như bạn làm trong đa hình thời gian chạy (ghi đè phương thức), bạn để trình biên dịch thực hiện công việc trong quá trình biên dịch. Điều này có một lợi thế rõ ràng vì bạn không cần phải suy ra tất cả các loại có thể có thể được sử dụng trong hệ thống của mình, điều này giúp nó có khả năng mở rộng hơn mà không cần thay đổi mã.

Các lớp học hoạt động theo cách khá giống nhau. Bạn tham số loại và mã được tạo bởi trình biên dịch.

Khi bạn có ý tưởng về "thời gian biên dịch", bạn có thể sử dụng các loại "giới hạn" và hạn chế những gì có thể được chuyển qua dưới dạng loại tham số thông qua các lớp/phương thức. Vì vậy, bạn có thể kiểm soát những gì được thông qua, đó là một điều mạnh mẽ, đặc biệt là bạn có một khuôn khổ đang được người khác sử dụng.

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

Không ai có thể thiết lập sth ngoài MyObject bây giờ.

Ngoài ra, bạn có thể "thực thi" các ràng buộc kiểu đối với các đối số phương thức của mình, điều đó có nghĩa là bạn có thể đảm bảo cả hai đối số phương thức của mình sẽ phụ thuộc vào cùng một loại.

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

Hy vọng tất cả điều này có ý nghĩa.

1
stdout

Một ưu điểm khác của việc sử dụng Generics (đặc biệt là với Bộ sưu tập/Danh sách) là bạn có được Kiểm tra loại thời gian biên dịch. Điều này thực sự hữu ích khi sử dụng Danh sách chung thay vì Danh sách đối tượng.

1
Chris Pietschmann

Nếu bộ sưu tập của bạn chứa các loại giá trị, chúng không cần phải đóng hộp/bỏ hộp cho các đối tượng khi chèn vào bộ sưu tập để hiệu suất của bạn tăng đáng kể. Các tiện ích bổ sung thú vị như chia sẻ lại có thể tạo thêm mã cho bạn, như các vòng lặp foreach.

1
gt124

Lý do duy nhất là họ cung cấp Loại an toàn

List<Customer> custCollection = new List<Customer>;

như trái ngược với,

object[] custCollection = new object[] { cust1, cust2 };

như một ví dụ đơn giản.

1
Vin

Tóm lại, generic cho phép bạn chỉ định chính xác hơn những gì bạn định làm (gõ mạnh hơn).

Điều này có một số lợi ích cho bạn:

  • Bởi vì trình biên dịch biết nhiều hơn về những gì bạn muốn làm, nó cho phép bạn bỏ qua rất nhiều kiểu truyền vì nó đã biết rằng kiểu này sẽ tương thích.

  • Điều này cũng giúp bạn có phản hồi sớm hơn về tính chính xác của chương trình. Những điều trước đây đã thất bại trong thời gian chạy (ví dụ: vì một đối tượng không thể được đúc theo loại mong muốn), bây giờ không thành công trong thời gian biên dịch và bạn có thể sửa lỗi trước khi bộ phận kiểm tra của bạn báo cáo lỗi về mật mã.

  • Trình biên dịch có thể làm tối ưu hóa nhiều hơn, như tránh đấm bốc, v.v.

1
Thomas Danecker

Một vài điều cần thêm/mở rộng (nói theo quan điểm .NET):

Các kiểu chung cho phép bạn tạo các lớp và giao diện dựa trên vai trò. Điều này đã được nói ở các thuật ngữ cơ bản hơn, nhưng tôi thấy bạn bắt đầu thiết kế mã của mình với các lớp được triển khai theo cách không biết loại - dẫn đến mã có thể tái sử dụng cao.

Đối số chung về các phương thức có thể làm điều tương tự, nhưng chúng cũng giúp áp dụng nguyên tắc "Nói không hỏi" để truyền, tức là "hãy cho tôi những gì tôi muốn, và nếu bạn không thể, bạn hãy cho tôi biết tại sao".

1
SpongeJim

Sử dụng thuốc generic cho các bộ sưu tập chỉ đơn giản và sạch sẽ. Ngay cả khi bạn chơi khăm nó ở mọi nơi khác, thu được từ các bộ sưu tập là một chiến thắng với tôi.

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

đấu với

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

hoặc là

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

Chỉ riêng điều đó cũng đáng giá "chi phí" của thuốc generic và bạn không cần phải là một Chuyên gia chung để sử dụng cái này và nhận được giá trị.

0
Will Hartung

Generics cũng cung cấp cho bạn khả năng tạo các đối tượng/phương thức có thể tái sử dụng nhiều hơn trong khi vẫn cung cấp hỗ trợ cụ thể về loại. Bạn cũng đạt được rất nhiều hiệu suất trong một số trường hợp. Tôi không biết thông số kỹ thuật đầy đủ về Java Generics, nhưng trong .NET tôi có thể chỉ định các ràng buộc trên tham số Loại, như Triển khai Giao diện, Trình xây dựng và Dẫn xuất.

0
Eric Schneider

Tôi đã từng nói chuyện về chủ đề này. Bạn có thể tìm thấy các slide, mã và ghi âm của tôi tại http://www.adventuresinsoftware.com/generics/ .

0
Michael L Perry