Java: Instanceof và Generics


135

Trước khi tôi xem qua cấu trúc dữ liệu chung của mình để tìm chỉ mục của một giá trị, tôi muốn xem liệu nó có phải là một thể hiện của loại thisđã được tham số hóa hay không.

Nhưng Eclipse phàn nàn khi tôi làm điều này:

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

Đây là thông báo lỗi:

Không thể thực hiện kiểm tra thể hiện đối với tham số loại E. Thay vào đó, hãy sử dụng Đối tượng xóa của nó vì thông tin loại chung sẽ bị xóa khi chạy

Cách tốt hơn để làm điều đó là gì?

Câu trả lời:


79

Thông báo lỗi nói lên tất cả. Trong thời gian chạy, loại đã biến mất, không có cách nào để kiểm tra nó.

Bạn có thể bắt nó bằng cách tạo một nhà máy cho đối tượng của bạn như thế này:

 public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
 }

Và sau đó trong hàm tạo của đối tượng lưu trữ kiểu đó, biến đến mức phương thức của bạn có thể trông như thế này:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }

3
Tôi không nghĩ bạn muốn Class.isAssignableFrom.
Tom Hawtin - tackline

@Tom, tôi đã viết cái này tối qua từ bộ nhớ, và tôi đã sửa nó để thực sự vượt qua lớp (duh!) Nhưng nếu không, tôi không hiểu tại sao bạn không muốn nó (có lẽ tôi cần thêm cà phê sáng nay, tôi ' m chỉ trên cốc đầu tiên của tôi).
Yishai

Tôi với Tom. Bạn có thể làm rõ điều đó? Sử dụng isAssignableFrom () sẽ là lựa chọn của tôi cho công việc. Có lẽ tôi đang thiếu một cái gì đó?
luis.espinal

6
@luis, nhận xét của Tom có ​​lẽ tôi nên sử dụng isInstance () và truyền tham số arg0 thực tế. Nó có lợi thế là tránh kiểm tra null.
Yishai

1
Tôi có một tình huống tương tự nhưng tôi không thể hiểu đầy đủ câu trả lời. Bạn có thể vui lòng làm rõ nó tốt hơn cho người giả như tôi?
Rubens Mariuzzo

40

Hai tùy chọn để kiểm tra loại thời gian chạy với generic:

Tùy chọn 1 - Tham nhũng hàm tạo của bạn

Giả sử bạn đang ghi đè indexOf (...) và bạn muốn kiểm tra loại chỉ cho hiệu suất, để tự lưu lại toàn bộ bộ sưu tập.

Tạo một nhà xây dựng bẩn thỉu như thế này:

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

Sau đó, bạn có thể sử dụng isAssignableFrom để kiểm tra loại.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Mỗi lần bạn khởi tạo đối tượng của mình, bạn sẽ phải lặp lại chính mình:

new MyCollection<Apples>(Apples.class);

Bạn có thể quyết định nó không xứng đáng. Trong quá trình triển khai ArrayList.indexOf (...) , họ không kiểm tra xem loại đó có khớp không.

Tùy chọn 2 - Hãy để nó thất bại

Nếu bạn cần sử dụng một phương thức trừu tượng yêu cầu loại không xác định của bạn, thì tất cả những gì bạn thực sự muốn là trình biên dịch ngừng khóc về instanceof . Nếu bạn có một phương pháp như thế này:

protected abstract void abstractMethod(T element);

Bạn có thể sử dụng nó như thế này:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Bạn đang truyền đối tượng cho T (loại chung của bạn), chỉ để đánh lừa trình biên dịch. Diễn viên của bạn không làm gì khi chạy , nhưng bạn vẫn sẽ nhận được ClassCastException khi bạn cố chuyển sai loại đối tượng vào phương thức trừu tượng của mình.

LƯU Ý 1: Nếu bạn đang thực hiện các phôi không được kiểm tra bổ sung trong phương thức trừu tượng của mình, ClassCastExceptions của bạn sẽ bị bắt ở đây. Điều đó có thể là tốt hoặc xấu, vì vậy hãy suy nghĩ kỹ.

LƯU Ý 2: Bạn nhận được kiểm tra null miễn phí khi bạn sử dụng instanceof . Vì bạn không thể sử dụng nó, bạn có thể cần kiểm tra null bằng tay không.


18

Bài viết cũ, nhưng một cách đơn giản để thực hiện kiểm tra instanceOf chung.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}

21
Không rõ những gì bạn thực sự đóng góp ở đây.
Andrew

12

Với điều kiện lớp của bạn mở rộng một lớp với một tham số chung, bạn cũng có thể nhận được điều này trong thời gian chạy thông qua sự phản chiếu, và sau đó sử dụng nó để so sánh, tức là

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

Trong trường hợp ở trên, trong thời gian chạy, bạn sẽ nhận được String. Class từ getParameterizedClass () và nó lưu trữ để bạn không nhận được bất kỳ chi phí phản ánh nào khi kiểm tra nhiều lần. Lưu ý rằng bạn có thể nhận các loại tham số hóa khác theo chỉ mục từ phương thức ParameterizedType.getActualTypeArgument ().


7

Tôi đã có cùng một vấn đề và đây là giải pháp của tôi (rất khiêm tốn, @george: lần này biên dịch VÀ làm việc ...).

Con mồi của tôi ở trong một lớp trừu tượng thực hiện Observer. Cập nhật phương thức bắn quan sát (...) với lớp Object có thể là bất kỳ loại Object nào.

Tôi chỉ muốn xử lý các đối tượng loại T

Giải pháp là truyền lớp cho hàm tạo để có thể so sánh các kiểu khi chạy.

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

Để thực hiện, chúng ta chỉ cần chuyển Class cho hàm tạo

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}

5

Hoặc bạn có thể bắt gặp một nỗ lực thất bại để chuyển sang E, vd.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}

1
+1 Một giải pháp thiết kế không quá thực dụng cho một kiểm tra đơn giản! Tôi ước tôi có thể bình chọn nhiều hơn thế này
higuaro

24
Điều này sẽ không làm việc. E bị xóa trong thời gian chạy, vì vậy dàn diễn viên sẽ không thất bại, bạn sẽ nhận được cảnh báo về trình biên dịch về nó.
Yishai

1

Về mặt kỹ thuật, bạn không cần phải, đó là điểm chung, vì vậy bạn có thể thực hiện kiểm tra kiểu biên dịch:

public int indexOf(E arg0) {
   ...
}

nhưng sau đó @Override có thể là một vấn đề nếu bạn có hệ thống phân cấp lớp. Nếu không, hãy xem câu trả lời của Yishai.


yeah, giao diện List yêu cầu hàm lấy tham số đối tượng.
Nick Heiner

Bạn đang thực hiện Danh sách? Tại sao bạn không triển khai Danh sách <E>?
Jason S


@Rosarch: phương thức indexOf () của giao diện List không yêu cầu đối số được cung cấp cùng loại với đối tượng bạn đang tìm kiếm. nó chỉ phải là .equals () với nó và các đối tượng thuộc các loại khác nhau có thể là .equals () với nhau. Đây là vấn đề tương tự như đối với phương thức remove (), xem: stackoverflow.com/questions/104799/iêu
newacct

1
[[[Sheepishly]]] đừng bận tâm, tôi nghĩ indexOf () yêu cầu E là một tham số thay vì Object. (tại sao họ làm vậy ?! ??!)
Jason S

1

Kiểu thời gian chạy của đối tượng là một điều kiện tương đối tùy ý để lọc. Tôi đề nghị giữ cho sự buồn cười như vậy ra khỏi bộ sưu tập của bạn. Điều này chỉ đơn giản đạt được bằng cách ủy quyền bộ sưu tập của bạn cho một bộ lọc được thông qua trong một công trình.

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );

(Mặc dù bạn có thể muốn thực hiện kiểm tra loại cá thể nếu bạn đang sử dụng Comparatorhoặc tương tự.)
Tom Hawtin - tackline
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.