Java: Instanceof และ Generics


139

ก่อนที่ฉันจะดูโครงสร้างข้อมูลทั่วไปของฉันเพื่อหาดัชนีของค่าฉันต้องการดูว่ามันเป็นเพียงตัวอย่างของประเภทที่thisได้รับพารามิเตอร์หรือไม่

แต่ Eclipse บ่นเมื่อฉันทำสิ่งนี้:

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

นี่คือข้อความแสดงข้อผิดพลาด:

ไม่สามารถทำการตรวจสอบอินสแตนซ์เทียบกับพารามิเตอร์ type E ใช้แทนลบวัตถุเนื่องจากข้อมูลประเภททั่วไปจะถูกลบเมื่อรันไทม์

จะมีวิธีไหนดีกว่ากัน?

คำตอบ:


80

ข้อความแสดงข้อผิดพลาดระบุทั้งหมด ที่รันไทม์ประเภทหายไปไม่มีวิธีใดที่จะตรวจสอบได้

คุณสามารถจับมันได้โดยการสร้างโรงงานสำหรับวัตถุของคุณดังนี้:

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

จากนั้นในคอนสตรัคเตอร์ของอ็อบเจ็กต์จะจัดเก็บชนิดนั้นดังนั้นตัวแปรเพื่อให้เมธอดของคุณมีลักษณะดังนี้:

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

3
Class.isAssignableFromผมไม่คิดว่าคุณต้องการ
Tom Hawtin - แท

@ ทอมฉันเขียนเมื่อคืนนี้จากความทรงจำและฉันแก้ไขมันเพื่อให้ผ่านชั้นเรียนจริงๆ (duh!) แต่อย่างอื่นฉันไม่เข้าใจว่าทำไมคุณถึงไม่ต้องการมัน (บางทีฉันอาจต้องการกาแฟมากขึ้นเมื่อเช้านี้ฉัน ' m ในถ้วยแรกของฉันเท่านั้น)
Yishai

ฉันอยู่กับทอม คุณช่วยชี้แจงได้ไหม การใช้ isAssignableFrom () จะเป็นสิ่งที่ฉันเลือกสำหรับงานนี้ บางทีฉันอาจจะขาดอะไรไป?
luis.espinal

6
@luis ความคิดเห็นของทอมอาจหมายความว่าฉันควรใช้ isInstance () และส่งผ่านพารามิเตอร์ arg0 จริง มีข้อดีในการหลีกเลี่ยงการตรวจสอบค่าว่าง
Yishai

1
ฉันมีสถานการณ์คล้าย ๆ กัน แต่ฉันไม่สามารถเข้าใจคำตอบได้ทั้งหมด ช่วยชี้แจงให้ดีกว่านี้หน่อยได้ไหมสำหรับหุ่นอย่างฉัน
Rubens Mariuzzo

40

สองตัวเลือกสำหรับการตรวจสอบประเภทรันไทม์ด้วย generics:

ตัวเลือกที่ 1 - ทำให้ตัวสร้างของคุณเสียหาย

สมมติว่าคุณกำลังลบล้าง indexOf (... ) และคุณต้องการตรวจสอบประเภทเพื่อประสิทธิภาพเท่านั้นเพื่อช่วยตัวเองในการทำซ้ำคอลเล็กชันทั้งหมด

สร้างตัวสร้างที่สกปรกเช่นนี้:

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

    this.t = t;
}

จากนั้นคุณสามารถใช้isAssignableFromเพื่อตรวจสอบประเภท

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

ทุกครั้งที่คุณสร้างอินสแตนซ์วัตถุของคุณคุณจะต้องทำซ้ำ:

new MyCollection<Apples>(Apples.class);

คุณอาจตัดสินใจว่ามันไม่คุ้มค่า ในการใช้งานArrayList.indexOf (... )พวกเขาไม่ได้ตรวจสอบว่าประเภทนั้นตรงกัน

ตัวเลือกที่ 2 - ปล่อยให้มันล้มเหลว

หากคุณต้องการใช้วิธีนามธรรมที่ต้องใช้ประเภทที่คุณไม่รู้จักสิ่งที่คุณต้องการจริงๆคือให้คอมไพเลอร์หยุดร้องไห้เกี่ยวกับอินสแตนซ์ของ หากคุณมีวิธีการดังนี้:

protected abstract void abstractMethod(T element);

คุณสามารถใช้มันได้ดังนี้:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

คุณกำลังส่งวัตถุเป็น T (ประเภททั่วไปของคุณ) เพียงเพื่อหลอกคอมไพเลอร์ นักแสดงของคุณไม่ได้ทำอะไรเลยในรันไทม์แต่คุณจะยังคงได้รับ ClassCastException เมื่อคุณพยายามส่งวัตถุผิดประเภทไปยังวิธีนามธรรมของคุณ

หมายเหตุ 1: หากคุณทำการแคสต์ที่ไม่ได้เลือกเพิ่มเติมในวิธีนามธรรมของคุณ ClassCastExceptions ของคุณจะถูกจับที่นี่ นั่นอาจจะดีหรือไม่ดีก็ลองคิดดู

หมายเหตุ 2: คุณจะได้รับการตรวจสอบ null ฟรีเมื่อคุณใช้ instanceof เนื่องจากคุณไม่สามารถใช้งานได้คุณอาจต้องตรวจสอบค่าว่างด้วยมือเปล่า


19

โพสต์เก่า แต่เป็นวิธีง่ายๆในการตรวจสอบอินสแตนซ์ทั่วไป

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

23
ยังไม่ชัดเจนว่าคุณมีส่วนร่วมอะไรที่นี่
Andrew

13

หากคลาสของคุณขยายคลาสด้วยพารามิเตอร์ทั่วไปคุณยังสามารถรับสิ่งนี้ได้ที่รันไทม์ผ่านการสะท้อนจากนั้นใช้เพื่อเปรียบเทียบเช่น

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;
    }
}

ในกรณีข้างต้นในขณะรันไทม์คุณจะได้รับ String.class จาก getParameterizedClass () และแคชดังนั้นคุณจะไม่ได้รับค่าใช้จ่ายในการสะท้อนกลับจากการตรวจสอบหลายครั้ง โปรดทราบว่าคุณสามารถรับชนิดพารามิเตอร์อื่น ๆ ตามดัชนีจากเมธอด ParameterizedType.getActualTypeArguments ()


7

ฉันมีปัญหาเดียวกันและนี่คือวิธีแก้ปัญหาของฉัน (ถ่อมตัวมาก @george: คราวนี้รวบรวมและทำงาน ... )

การทดลองของฉันอยู่ในคลาสนามธรรมที่ใช้ Observer การอัปเดตเมธอด Observable fires (... ) ด้วยคลาส Object ที่สามารถเป็น Object ชนิดใดก็ได้

ฉันต้องการจัดการ Objects ประเภท T เท่านั้น

วิธีแก้ปัญหาคือส่งคลาสไปยังคอนสตรัคเตอร์เพื่อให้สามารถเปรียบเทียบประเภทที่รันไทม์ได้

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);

}

สำหรับการใช้งานเราเพียงแค่ส่งคลาสไปยังผู้สร้าง

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

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

5

หรือคุณสามารถจับความพยายามที่ล้มเหลวในการโยนลงใน E เช่น

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

1
+1 วิธีแก้ปัญหาที่ไม่ผ่านการออกแบบทางวิศวกรรมสำหรับการตรวจสอบง่ายๆ! ฉันหวังว่าฉันจะโหวตได้มากกว่านี้
ฮิกัวโร

24
สิ่งนี้จะไม่ได้ผล E ถูกลบเมื่อรันไทม์ดังนั้นการแคสต์จะไม่ล้มเหลวคุณจะได้รับคำเตือนเกี่ยวกับคอมไพเลอร์
Yishai

1

ในทางเทคนิคคุณไม่ควรทำนั่นคือประเด็นของยาสามัญดังนั้นคุณสามารถทำการตรวจสอบประเภทคอมไพล์

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

แต่ @Override อาจเป็นปัญหาถ้าคุณมีลำดับชั้นของคลาส มิฉะนั้นดูคำตอบของ Yishai


ใช่อินเทอร์เฟซรายการต้องการให้ฟังก์ชันใช้พารามิเตอร์อ็อบเจ็กต์
Nick Heiner

คุณกำลังใช้รายการ? ทำไมคุณไม่ใช้ List <E>
Jason S

(ดูตัวอย่างการประกาศ ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S

@Rosarch: เมธอด indexOf () ของอินเทอร์เฟซรายการไม่ต้องการให้อาร์กิวเมนต์ที่ระบุเป็นประเภทเดียวกับวัตถุที่คุณกำลังมองหา มันจะต้องเป็น .equals () กับมันและอ็อบเจ็กต์ประเภทต่างๆสามารถเป็น .equals () ซึ่งกันและกันได้ นี่เป็นปัญหาเดียวกับวิธีลบ () โปรดดู: stackoverflow.com/questions/104799/…
newacct

1
[[[sheepishly]]] ไม่เป็นไรฉันคิดว่า indexOf () ต้องการ E เป็นพารามิเตอร์มากกว่า Object (ทำไมพวกเขาถึงทำอย่างนั้น! ??!)
Jason S

1

ประเภทรันไทม์ของวัตถุเป็นเงื่อนไขที่ค่อนข้างจะกรอง ฉันขอแนะนำให้เก็บความสกปรกดังกล่าวไว้ห่างจากคอลเลกชันของคุณ ทำได้ง่ายๆโดยให้คอลเลกชันของคุณมอบหมายให้ตัวกรองส่งผ่านในโครงสร้าง

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;
             }
         }}
     );

(แม้ว่าคุณอาจต้องการตรวจสอบประเภทอินสแตนซ์ว่าคุณกำลังใช้Comparatorหรือคล้ายกัน)
Tom Hawtin - แท็กไลน์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.