Phương thức tĩnh trong một lớp chung?


197

Trong Java, tôi muốn có một cái gì đó như:

class Clazz<T> {
  static void doIt(T object) {
    // ...
  }
}

Nhưng tôi hiểu

Không thể tạo tham chiếu tĩnh cho loại T không tĩnh

Tôi không hiểu thuốc generic ngoài những cách sử dụng cơ bản và do đó không thể hiểu được điều đó. Nó không giúp tôi không thể tìm thấy nhiều thông tin trên internet về chủ đề này.

Ai đó có thể làm rõ nếu sử dụng như vậy là có thể, bằng một cách tương tự? Ngoài ra, tại sao nỗ lực ban đầu của tôi không thành công?

Câu trả lời:


270

Bạn không thể sử dụng các tham số loại chung của một lớp trong các phương thức tĩnh hoặc các trường tĩnh. Các tham số loại của lớp chỉ trong phạm vi cho các phương thức cá thể và các trường đối tượng. Đối với các trường tĩnh và các phương thức tĩnh, chúng được chia sẻ giữa tất cả các thể hiện của lớp, thậm chí các thể hiện của các tham số loại khác nhau, vì vậy rõ ràng chúng không thể phụ thuộc vào một tham số loại cụ thể.

Có vẻ như vấn đề của bạn không cần phải sử dụng tham số loại của lớp. Nếu bạn mô tả những gì bạn đang cố gắng làm chi tiết hơn, có lẽ chúng tôi có thể giúp bạn tìm ra cách tốt hơn để làm điều đó.


9
Được nêu lên, câu trả lời này thực sự giải thích vấn đề của người gửi thay vì chỉ cung cấp một cách giải quyết.
Jorn

7
@Andre: Trực giác của bạn không phải là không có cơ sở; C # thực sự đối xử với thuốc generic theo cách này.
jyoungdev

32
"Đối với các trường tĩnh và các phương thức tĩnh, chúng được chia sẻ giữa tất cả các phiên bản của lớp, thậm chí các trường hợp của các tham số loại khác nhau ..." Ouch! Đá vào các loại hạt bằng cách xóa một lần nữa!
BD tại Rive dốc

2
nếu bạn sẽ xem cách lớp / phương thức chung trông như thế nào sau khi biên dịch, bạn sẽ thấy thuộc tính chung đó bị loại bỏ. Và Danh sách <Integer> sau khi biên dịch trông giống như "Danh sách". Vì vậy, không có sự khác biệt giữa Danh sách <Số nguyên> và Danh sách <Dài> sau khi tổng hợp - cả hai đều trở thành Danh sách.
Dainius

3
Tôi nghĩ rằng anh ấy đã giải thích những gì anh ấy đang cố gắng làm khá tốt. Rõ ràng là anh ta đang cố gắng lắc dat chiến lợi phẩm! hah
astryk

140

Java không biết nó Tlà gì cho đến khi bạn khởi tạo một loại.

Có thể bạn có thể thực thi các phương thức tĩnh bằng cách gọi Clazz<T>.doit(something)nhưng có vẻ như bạn không thể.

Cách khác để xử lý mọi thứ là đặt tham số kiểu trong chính phương thức:

static <U> void doIt(U object)

điều này không giúp bạn hạn chế đúng về U, nhưng tốt hơn là không có gì ....


Sau đó, tôi sẽ gọi phương thức mà không chỉ định bất kỳ ràng buộc nào, tức là Clazz.doIt (đối tượng) thay vì Clazz <Object> .doIt (object), phải không? Bạn có nghĩ rằng OK?
André Chalella

Cú pháp thứ hai có một cú pháp chính xác hơn, mà bạn cần nếu trình biên dịch không thể suy ra loại giá trị trả về từ contaxt trong đó phương thức được gọi. Nói cách khác, nếu trình biên dịch cho phép Clazz.doIt (đối tượng), thì hãy làm điều đó.
skaffman

1
Tôi đã thử Clazz <Object> .doIt (object) và gặp lỗi thời gian biên dịch! "Lỗi cú pháp trên token (s), xây dựng không đúng chỗ (s)". Clazz.doIt (đối tượng) hoạt động tốt mặc dù, thậm chí không có cảnh báo.
André Chalella

8
Sử dụng Clazz. <Object> doIt (object). Tôi nghĩ đó là cú pháp kỳ lạ, so sánh với Clazz <int> :: doIt (1) của C ++
Crend King

Tại sao bạn nói nó không giúp bạn hạn chế đúng về U?
Adam Burley

46

Tôi gặp vấn đề tương tự. Tôi tìm thấy câu trả lời của mình bằng cách tải xuống mã nguồn Collections.sorttrong khung java. Câu trả lời tôi đã sử dụng là đặt cái <T>chung trong phương thức, không phải trong định nghĩa lớp.

Vì vậy, điều này đã làm việc:

public class QuickSortArray  {
    public static <T extends Comparable> void quickSort(T[] array, int bottom, int top){
//do it
}

}

Tất nhiên, sau khi đọc các câu trả lời ở trên, tôi nhận ra rằng đây sẽ là một sự thay thế chấp nhận được mà không cần sử dụng một lớp chung:

public static void quickSort(Comparable[] array, int bottom, int top){
//do it
}

8
Trong khi câu trả lời được chấp nhận là đúng về mặt kỹ thuật: đây thực sự là điều tôi đang tìm kiếm khi Google đưa tôi đến câu hỏi này.
Danh sách Jeremy

Đạo cụ để cung cấp một ví dụ bao gồm T extends XXXcú pháp.
Ogre Psalm33

3
@Chris Vì dù sao bạn cũng đang sử dụng thuốc generic, bạn cũng có thể sử dụng chúng theo mọi cách --- cụ thể là không sử dụng các loại thô như Comparable. Hãy thử <T extends Comparable<? super T>>thay thế.
Easoncxz 10/07/2015

15

Có thể thực hiện những gì bạn muốn bằng cách sử dụng cú pháp cho các phương thức chung khi khai doIt()báo phương thức của bạn (chú ý thêm vào <T>giữa staticvoidtrong chữ ký phương thức của doIt()):

class Clazz<T> {
  static <T> void doIt(T object) {
    // shake that booty
  }
}

Tôi đã nhận được trình soạn thảo Eclipse để chấp nhận mã trên mà không Cannot make a static reference to the non-static type Tgặp lỗi và sau đó mở rộng nó sang chương trình làm việc sau (hoàn thành với tham chiếu văn hóa phù hợp với lứa tuổi):

public class Clazz<T> {
  static <T> void doIt(T object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }

  private static class KC {
  }

  private static class SunshineBand {
  }

  public static void main(String args[]) {
    KC kc = new KC();
    SunshineBand sunshineBand = new SunshineBand();
    Clazz.doIt(kc);
    Clazz.doIt(sunshineBand);
  }
}

Cái nào in những dòng này ra bàn điều khiển khi tôi chạy nó:

lắc chiến lợi phẩm đó 'lớp com.eclipseoptions.datamanager.Clazz $ KC' !!!
lắc chiến lợi phẩm đó 'lớp com.eclipseoptions.datamanager.Clazz $ SunshineBand' !!!


Trong trường hợp này, <T> thứ hai có ẩn cái thứ nhất không?
André Chalella

1
@ AndréNeves, Vâng. Cái thứ hai <T>che dấu cái thứ nhất theo cách tương tự như trong class C { int x; C(int x) { ... } }tham số xche dấu trường x.
Mike Samuel

Làm thế nào để giải thích đầu ra của mẫu: có vẻ như thực sự có hai loại liên quan, một loại theo giá trị T tham số? Nếu T thực sự che giấu loại lớp, tôi sẽ mong đợi "lắc lớp chiến lợi phẩm đó com.eclipseoptions.datamanager.Clazz" xuất ra hai lần mà không có tham số.
Pragmateek

1
Nó không có gì để làm với mặt nạ. Một phương thức tĩnh đơn giản là không thể bị ràng buộc bởi kiểu chung của các lớp . Tuy nhiên, nó có thể xác định các loại và ranh giới chung của riêng mình.
mike

Có cách nào để xác định kiểu tĩnh một lần và sử dụng lại cho một số phương thức tĩnh không? Tôi không phải là người thích làm static <E extends SomeClass> E foo(E input){return input;}cho mọi phương pháp khi tôi muốn làm một cái gì đó nhưstatic <E extends SomeClass>; //static generic type defined only once and reused static E foo(E input){return input;} static E bar(E input){return input;} //...etc...
HesNotTheStig

14

Tôi nghĩ rằng cú pháp này chưa được đề cập (trong trường hợp bạn muốn một phương thức không có đối số):

class Clazz {
  static <T> T doIt() {
    // shake that booty
  }
}

Và cuộc gọi:

String str = Clazz.<String>doIt();

Hy vọng điều này sẽ giúp ai đó.


1
Trên thực tế (tôi không biết phiên bản Java), điều này có thể xảy ra ngay cả khi không có <String>vì nó chỉ đơn giản là đối số kiểu vì bạn gán nó cho một biến. Nếu bạn không chỉ định nó, nó chỉ đơn giản là Object.
Adowrath

Đây là một giải pháp hoàn hảo.
Bohhat Ranjan

6

Nó được đề cập chính xác trong lỗi: bạn không thể tạo tham chiếu tĩnh cho loại không tĩnh T. Lý do là tham số loại Tcó thể được thay thế bằng bất kỳ đối số loại nào, ví dụ như Clazz<String>hoặcClazz<integer> vv Nhưng lĩnh vực tĩnh / phương pháp được chia sẻ bởi tất cả các phi đối tượng -static của lớp.

Đoạn trích sau đây được lấy từ tài liệu :

Trường tĩnh của lớp là biến cấp độ lớp được chia sẻ bởi tất cả các đối tượng không tĩnh của lớp. Do đó, các trường tĩnh của tham số loại không được phép. Hãy xem xét các lớp sau:

public class MobileDevice<T> {
    private static T os;

    // ...
}

Nếu các trường tĩnh của tham số loại được cho phép, thì đoạn mã sau sẽ bị nhầm lẫn:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

Bởi vì os trường tĩnh được chia sẻ qua điện thoại, máy nhắn tin và pc, loại os thực tế là gì? Nó không thể là Smartphone, Pager và TabletPC cùng một lúc. Do đó, bạn không thể tạo các trường tĩnh của tham số loại.

Như đã chỉ ra đúng bởi chris trong câu trả lời của anh ta, bạn cần sử dụng tham số kiểu với phương thức chứ không phải với lớp trong trường hợp này. Bạn có thể viết nó như sau:

static <E> void doIt(E object) 

3

Một cái gì đó như sau sẽ giúp bạn gần gũi hơn

class Clazz
{
   public static <U extends Clazz> void doIt(U thing)
   {
   }
}

EDIT: Ví dụ cập nhật với nhiều chi tiết hơn

public abstract class Thingo 
{

    public static <U extends Thingo> void doIt(U p_thingo)
    {
        p_thingo.thing();
    }

    protected abstract void thing();

}

class SubThingoOne extends Thingo
{
    @Override
    protected void thing() 
    {
        System.out.println("SubThingoOne");
    }
}

class SubThingoTwo extends Thingo
{

    @Override
    protected void thing() 
    {
        System.out.println("SuThingoTwo");
    }

}

public class ThingoTest 
{

    @Test
    public void test() 
    {
        Thingo t1 = new SubThingoOne();
        Thingo t2 = new SubThingoTwo();

        Thingo.doIt(t1);
        Thingo.doIt(t2);

        // compile error -->  Thingo.doIt(new Object());
    }
}

Chính xác như Jason S đề nghị.
André Chalella

@Andre đề xuất trước đó không cung cấp hạn chế rằng U phải mở rộng Clazz
ekj

Tôi hiểu, nhưng nó không thêm bất cứ điều gì hữu ích ngoại trừ các ràng buộc. Trong bài viết gốc của tôi, tôi muốn có thể làm điều này mà không cần chuyển U làm tham số.
André Chalella

@Andre Xem cập nhật của tôi ở trên. Kiểu chung chỉ được định nghĩa trong định nghĩa phương thức và không phải thông qua khi gọi phương thức
ekj

@ekj Cảm ơn bạn, tôi hiểu rồi. Xin lỗi, tôi đã đặt ra câu hỏi này ba năm trước, khi tôi đang làm Java hàng ngày. Tôi hiểu ý của bạn, mặc dù tôi nghĩ bạn hiểu rằng đây không phải là chính xác những gì tôi dự định ban đầu. Tuy nhiên, tôi thích câu trả lời của bạn.
André Chalella

2

Khi bạn chỉ định một kiểu chung cho lớp của bạn, JVM sẽ biết về nó chỉ có một thể hiện của lớp của bạn chứ không phải định nghĩa. Mỗi định nghĩa chỉ có loại tham số.

Generics hoạt động giống như các mẫu trong C ++, vì vậy trước tiên bạn nên khởi tạo lớp của mình, sau đó sử dụng hàm với loại được chỉ định.


2
Java generic khá khác với các mẫu C ++. Các lớp chung được biên soạn bởi chính nó. Trên thực tế, mã tương đương trong C ++ sẽ hoạt động (mã gọi sẽ như thế nào Clazz<int>::doIt( 5 ))
David Rodríguez - dribeas

Tôi thích câu trả lời này tốt hơn câu trả lời được chấp nhận ... ngoài tham chiếu mẫu C ++, nhận xét về loại chung chỉ dành cho một phiên bản của lớp thay vì toàn bộ lớp được đặt tại chỗ. Câu trả lời được chấp nhận không giải thích điều này và chỉ cung cấp một cách giải quyết không liên quan gì đến lý do tại sao bạn không thể sử dụng loại chung của lớp trong một phương thức tĩnh.
Jorn

1

Ngoài ra, để nói một cách đơn giản, nó xảy ra do thuộc tính "Xóa" của các tổng quát. Điều đó có nghĩa là mặc dù chúng ta định nghĩa ArrayList<Integer>ArrayList<String>, tại thời điểm biên dịch, nó vẫn là hai loại cụ thể khác nhau nhưng trong thời gian chạy, JVM sẽ xóa các loại chung và chỉ tạo một lớp ArrayList thay vì hai lớp. Vì vậy, khi chúng tôi định nghĩa một phương thức kiểu tĩnh hoặc bất cứ điều gì cho một cái chung, nó được chia sẻ bởi tất cả các thể hiện của cái chung đó, trong ví dụ của tôi, nó được chia sẻ bởi cả hai ArrayList<Integer>ArrayList<String>. Đó là lý do tại sao bạn gặp lỗi. Tham số loại chung của một lớp không phải là Được phép trong một bối cảnh tĩnh!


1

@BD tại Rivenhill: Vì câu hỏi cũ này đã nhận được sự chú ý vào năm ngoái, chúng ta hãy tiếp tục một chút, chỉ để thảo luận. Cơ thể của doItphương pháp của bạn không làm gì cả - Tcụ thể. Đây là:

public class Clazz<T> {
  static <T> void doIt(T object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }
// ...
}

Vì vậy, bạn hoàn toàn có thể loại bỏ tất cả các biến loại và chỉ cần mã

public class Clazz {
  static void doIt(Object object) {
    System.out.println("shake that booty '" + object.getClass().toString()
                       + "' !!!");
  }
// ...
}

Đồng ý. Nhưng hãy trở lại gần hơn với vấn đề ban đầu. Biến loại đầu tiên trên khai báo lớp là dự phòng. Chỉ có cái thứ hai về phương pháp là cần thiết. Ở đây chúng tôi đi một lần nữa, nhưng nó chưa phải là câu trả lời cuối cùng:

public class Clazz  {
  static <T extends Saying> void doIt(T object) {
    System.out.println("shake that booty "+ object.say());
  }

  public static void main(String args[]) {
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  }
}
// Output:
// KC
// Sunshine

interface Saying {
      public String say();
}

class KC implements Saying {
      public String say() {
          return "KC";
      }
}

class SunshineBand implements Saying {
      public String say() {
          return "Sunshine";
      }
}

Tuy nhiên, tất cả đều quá ồn ào về việc không có gì, vì phiên bản sau hoạt động theo cùng một cách. Tất cả những gì nó cần là loại giao diện trên tham số phương thức. Không có biến loại trong tầm nhìn ở bất cứ đâu. Đó thực sự là vấn đề ban đầu?

public class Clazz  {
  static void doIt(Saying object) {
    System.out.println("shake that booty "+ object.say());
  }

  public static void main(String args[]) {
    Clazz.doIt(new KC());
    Clazz.doIt(new SunshineBand());
  }
}

interface Saying {
      public String say();
}

class KC implements Saying {
      public String say() {
          return "KC";
      }
}

class SunshineBand implements Saying {
      public String say() {
          return "Sunshine";
      }
}

0

Vì các biến tĩnh được chia sẻ bởi tất cả các thể hiện của lớp. Ví dụ: nếu bạn đang có mã sau

class Class<T> {
  static void doIt(T object) {
    // using T here 
  }
}

T chỉ có sẵn sau khi một phiên bản được tạo. Nhưng các phương thức tĩnh có thể được sử dụng ngay cả trước khi các thể hiện có sẵn. Vì vậy, các tham số loại chung không thể được tham chiếu bên trong các phương thức và biến tĩnh

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.