Sự khác biệt giữa instanceof và Class.isAssignableFrom (Cách) là gì?


458

Điều nào sau đây là tốt hơn?

a instanceof B

hoặc là

B.class.isAssignableFrom(a.getClass())

Sự khác biệt duy nhất mà tôi biết là, khi 'a' là null, lần đầu tiên trả về false, trong khi lần thứ hai ném ngoại lệ. Ngoài ra, họ luôn cho kết quả tương tự?


17
Đối với các bản ghi, isInstance () là phương thức thuận tiện nhất để kiểm tra xem một đối tượng có thể được đúc thành một loại lớp hay không (để biết thêm chi tiết, xem: tshikatshikaaa.blogspot.nl/2012/07/ .)
Jérôme Verstrynge

Câu trả lời:


498

Khi sử dụng instanceof, bạn cần biết lớp trong Bthời gian biên dịch. Khi sử dụng isAssignableFrom()nó có thể là động và thay đổi trong thời gian chạy.


12
tôi không hiểu - vui lòng giải thích lý do tại sao chúng tôi không thể viết a instanceof Bref.getClass(). Làm thế nào đây có thể là câu trả lời được chấp nhận với rất ít lời giải thích (hoặc thiếu nó)?
Eliran Malka

65
Cú pháp là a instanceof Brefkhông a instanceof Bref.class. Đối số thứ hai cho toán tử instanceof là một tên lớp, không phải là một biểu thức phân giải thành một thể hiện đối tượng lớp.
Brandon Bloom

2
có "năng động" không cần phải nói :) Khác với hiệu suất, đây là một sự khác biệt thực sự.
peterk

2
@EliranMalka có lẽ bạn có thể có một lớp được tạo trong thời gian chạy. Giống như các đối tượng proxy.
Wagner Tsuchiya

Vì vậy, trong B.class.isAssignableFrom(a.getClass()), B được biết đến, và a instanceof Blà tốt hơn. Đúng?
Florian F

208

instanceofchỉ có thể được sử dụng với các loại tham chiếu, không phải các loại nguyên thủy. isAssignableFrom()có thể được sử dụng với bất kỳ đối tượng lớp nào:

a instanceof int  // syntax error
3 instanceof Foo  // syntax error
int.class.isAssignableFrom(int.class)  // true

Xem http://java.sun.com/javase/6/docs/api/java/lang/Class.html#isAssignableFrom(java.lang.Class) .


10
Tôi không thấy quan điểm của việc sử dụng instanceof / isAssignableFrom với các kiểu nguyên thủy.
Jimmy T.

116

Nói về hiệu suất:

TL; DR

Sử dụng isInstance hoặc instanceof có hiệu suất tương tự. isAssignableFrom chậm hơn một chút.

Sắp xếp theo hiệu suất:

  1. isstance
  2. ví dụ (+ 0,5%)
  3. isAssignableFrom (+ 2,7%)

Dựa trên điểm chuẩn 2000 lần lặp trên JAVA 8 Windows x64, với 20 lần lặp khởi động.

Về lý thuyết

Sử dụng trình xem mềm như mã byte, chúng ta có thể dịch từng toán tử thành mã byte.

Trong ngữ cảnh của:

package foo;

public class Benchmark
{
  public static final Object a = new A();
  public static final Object b = new B();

  ...

}

JAVA:

b instanceof A;

Mã byte:

getstatic foo/Benchmark.b:java.lang.Object
instanceof foo/A

JAVA:

A.class.isInstance(b);

Mã byte:

ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Class isInstance((Ljava/lang/Object;)Z);

JAVA:

A.class.isAssignableFrom(b.getClass());

Mã byte:

ldc Lfoo/A; (org.objectweb.asm.Type)
getstatic foo/Benchmark.b:java.lang.Object
invokevirtual java/lang/Object getClass(()Ljava/lang/Class;);
invokevirtual java/lang/Class isAssignableFrom((Ljava/lang/Class;)Z);

Đo lường có bao nhiêu hướng dẫn mã byte được sử dụng bởi mỗi toán tử, chúng ta có thể mong đợi instanceofisInstance nhanh hơn isAssignableFrom . Tuy nhiên, hiệu suất thực tế KHÔNG được xác định bởi mã byte mà bởi mã máy (phụ thuộc vào nền tảng). Chúng ta hãy làm một điểm chuẩn vi mô cho mỗi toán tử.

Điểm chính xác

Tín dụng: Theo lời khuyên của @ aleksandr-dubinsky và cảm ơn @yura đã cung cấp mã cơ sở, đây là điểm chuẩn JMH (xem hướng dẫn điều chỉnh này ):

class A {}
class B extends A {}

public class Benchmark {

    public static final Object a = new A();
    public static final Object b = new B();

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testInstanceOf()
    {
        return b instanceof A;
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testIsInstance()
    {
        return A.class.isInstance(b);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public boolean testIsAssignableFrom()
    {
        return A.class.isAssignableFrom(b.getClass());
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(TestPerf2.class.getSimpleName())
                .warmupIterations(20)
                .measurementIterations(2000)
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

Đã cho các kết quả sau (điểm là một số thao tác trong một đơn vị thời gian , do đó điểm càng cao càng tốt):

Benchmark                       Mode   Cnt    Score   Error   Units
Benchmark.testIsInstance        thrpt  2000  373,061 ± 0,115  ops/us
Benchmark.testInstanceOf        thrpt  2000  371,047 ± 0,131  ops/us
Benchmark.testIsAssignableFrom  thrpt  2000  363,648 ± 0,289  ops/us

Cảnh báo

  • điểm chuẩn là JVM và nền tảng phụ thuộc. Do không có sự khác biệt đáng kể giữa mỗi hoạt động, nên có thể nhận được một kết quả khác nhau (và có thể là thứ tự khác nhau!) Trên một phiên bản JAVA và / hoặc các nền tảng khác nhau như Solaris, Mac hoặc Linux.
  • điểm chuẩn so sánh hiệu suất của "là B là một thể hiện của A" khi "B mở rộng A" trực tiếp. Nếu hệ thống phân cấp lớp sâu hơn và phức tạp hơn (như B mở rộng X kéo dài Y kéo dài Z kéo dài A), kết quả có thể khác.
  • Thông thường nên viết mã trước tiên chọn một trong các toán tử (thuận tiện nhất) và sau đó cấu hình mã của bạn để kiểm tra xem có tắc nghẽn hiệu suất không. Có thể toán tử này không đáng kể trong ngữ cảnh mã của bạn hoặc có thể ...
  • liên quan đến điểm trước đó, instanceoftrong bối cảnh mã của bạn có thể được tối ưu hóa dễ dàng hơn so isInstancevới ví dụ ...

Để cho bạn một ví dụ, lấy vòng lặp sau:

class A{}
class B extends A{}

A b = new B();

boolean execute(){
  return A.class.isAssignableFrom(b.getClass());
  // return A.class.isInstance(b);
  // return b instanceof A;
}

// Warmup the code
for (int i = 0; i < 100; ++i)
  execute();

// Time it
int count = 100000;
final long start = System.nanoTime();
for(int i=0; i<count; i++){
   execute();
}
final long elapsed = System.nanoTime() - start;

Nhờ có JIT, mã được tối ưu hóa tại một số điểm và chúng tôi nhận được:

  • instanceof: 6ms
  • isstance: 12ms
  • isAssignableFrom: 15ms

Ghi chú

Ban đầu bài đăng này đang thực hiện điểm chuẩn của riêng mình bằng cách sử dụng vòng lặp for trong JAVA thô, điều này cho kết quả không đáng tin cậy vì một số tối ưu hóa như Just In Time có thể loại bỏ vòng lặp. Vì vậy, chủ yếu là đo trình biên dịch JIT mất bao lâu để tối ưu hóa vòng lặp: xem Kiểm tra hiệu suất độc lập với số lần lặp để biết thêm chi tiết

Câu hỏi liên quan


6
Yep, instanceoflà một mã byte, sử dụng logic cơ bản giống như checkcast(mã byte sau khi truyền). Nó vốn dĩ sẽ nhanh hơn các tùy chọn khác, bất kể mức độ tối ưu hóa JITC.
Hot Licks

1
Mà có ý nghĩa, như isAssignableFrom()là năng động.
Matthieu

vâng, với kết quả JMH là hoàn toàn khác nhau (cùng tốc độ cho tất cả).
Yura

Xin chào, điểm chuẩn tuyệt vời, vừa gặp phải tình huống isAssignableFrom được gọi hàng ngàn lần, thay đổi thành thể hiện thực sự tạo ra sự khác biệt. Câu trả lời này sẽ có giá trị một bài đăng blog ở đâu đó ...;)
Martin

33

Tương đương trực tiếp hơn a instanceof B

B.class.isInstance(a)

Công trình này (lợi nhuận false) khi anullquá.


Thật tuyệt, nhưng điều này không trả lời câu hỏi và đáng lẽ phải là một bình luận.
Madbreaks

23

Ngoài sự khác biệt cơ bản nêu trên, có một sự khác biệt tinh tế cốt lõi giữa toán tử instanceof và phương pháp isAssignableFrom trong Class.

Đọc instanceofnhư là Đây là (phần bên trái) là ví dụ của phần này hoặc bất kỳ lớp con nào của phần này (phần bên phải) và đọc x.getClass().isAssignableFrom(Y.class)như là Tôi có thể viết ra X x = new Y(). Nói cách khác, toán tử instanceof kiểm tra xem đối tượng bên trái là cùng hoặc lớp con của lớp bên phải, trong khi isAssignableFromkiểm tra xem chúng ta có thể gán đối tượng của lớp tham số (từ) cho tham chiếu của lớp mà phương thức được gọi không.
Lưu ý rằng cả hai đều xem xét trường hợp thực tế không phải là kiểu tham chiếu.

Xét một ví dụ về 3 lớp A, B và C trong đó C kéo dài B và B kéo dài A.

B b = new C();

System.out.println(b instanceof A); //is b (which is actually class C object) instance of A, yes. This will return true.  
System.out.println(b instanceof B); // is b (which is actually class C object) instance of B, yes. This will return true.  
System.out.println(b instanceof C); // is b (which is actually class C object) instance of C, yes. This will return true. If the first statement would be B b = new B(), this would have been false.
System.out.println(b.getClass().isAssignableFrom(A.class));//Can I write C c = new A(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(B.class)); //Can I write C c = new B(), no. So this is false.
System.out.println(b.getClass().isAssignableFrom(C.class)); //Can I write C c = new C(), Yes. So this is true.

3
b instanceof Atương đương với A.class.isAssignableFrom(b.getClass())(như OP nhận thấy). Ví dụ của bạn là đúng nhưng không liên quan.
Karu

new Y()có thể không hợp pháp nếu Ylà trừu tượng hoặc không có hàm tạo mặc định công khai, bạn có thể nói X x = (Y)nulllà hợp pháp nếu và chỉ khi đó x.getClass().isAssignableFrom(Y.class)là đúng.
Động cơ Trái đất

2
Tại sao 'b.getClass (). IsAssignableFrom (A. class)' trong ví dụ này? Tôi đoán ví dụ nên đảo ngược A. class.isAssignableFrom (b.getClass ()).
loshad vtapkah

14

Ngoài ra còn có một sự khác biệt khác:

null dụof X là false không có vấn đề gì với X

null.getClass (). isAssignableFrom (X) sẽ ném NullPulumException


4
-1, không chính xác: null instanceof X(trong đó X là một số lớp được biết tại thời gian biên dịch) sẽ luôn trả về false.
Caspar

4
@Caspar trong khi bạn đúng, ý tưởng cơ bản là một điểm tốt. Tôi chỉnh sửa bài viết để nó là chính xác.
erickson

1
Điều này rất hữu ích, trường hợp cạnh luôn luôn quan trọng :).
nghìn tỷ đồng

Để tương đương với dòng đầu tiên, dòng thứ hai có X.class.isAssignableFrom(null.getClass())nên không? Nhưng có, việc gọi getClass()tham chiếu null sẽ dẫn đến NPE.
William Giá

Câu trả lời này bỏ lỡ vấn đề - một sự vô hiệu hóa null không liên quan vì sự thất bại xảy ra bên ngoài hoạt động (bạn luôn cần kiểm tra null trước khi bạn sử dụng một tham chiếu như thế). Nói chung getClass()không nên được sử dụng isAssignableFromở nơi đầu tiên - hoạt động có nghĩa là trong trường hợp không có đối tượng. Nếu bạn có tài liệu tham khảo đối tượng a, sử dụng a instanceof SomeClass(nếu bạn làm biết loại SomeClass) hoặc someObject.getClass().isInstance(a)(nếu bạn không biết loại someObject).
AndrewF

12

Có một sự khác biệt khác. Nếu loại (Lớp) để kiểm tra là động, ví dụ được truyền dưới dạng tham số phương thức, thì thể hiện sẽ không cắt nó cho bạn.

boolean test(Class clazz) {
   return (this instanceof clazz); // clazz cannot be resolved to a type.
}

nhưng bạn có thể làm:

boolean test(Class clazz) {
   return (clazz.isAssignableFrom(this.getClass())); // okidoki
}

Rất tiếc, tôi thấy câu trả lời này đã được bảo hiểm. Có lẽ ví dụ này hữu ích cho ai đó.


3
thực sự không có câu trả lời nào thực sự đúng làAssignableFrom work w / class, Class.isInstance là tương tự của 'instanceof'
bestsss

Để đưa nhận xét đúng của @ bestsss vào mã cụ thể: Bởi vì bạn có một đối tượng ( this), clazz.isInstance(this)sẽ tốt hơn trong ví dụ của bạn.
AndrewF

7

Chủ đề này cung cấp cho tôi một số cái nhìn sâu sắc về sự instanceofkhác biệt từisAssignableFrom , vì vậy tôi nghĩ rằng tôi muốn chia sẻ một cái gì đó của riêng tôi.

Tôi đã thấy rằng sử dụng isAssignableFrom là cách duy nhất (có thể không phải là duy nhất, nhưng có thể là dễ nhất) để tự hỏi liệu một tham chiếu của một lớp có thể lấy các thể hiện của một lớp khác, khi một trường hợp không có lớp nào để so sánh.

Do đó, tôi không thấy việc sử dụng instanceoftoán tử để so sánh khả năng chuyển nhượng là một ý tưởng hay khi tất cả những gì tôi có là các lớp, trừ khi tôi dự tính tạo một thể hiện từ một trong các lớp; Tôi nghĩ rằng điều này sẽ cẩu thả.


5

thể hiện không thể được sử dụng với các kiểu nguyên thủy hoặc các kiểu chung. Như trong đoạn mã sau:

//Define Class< T > type ... 

Object e = new Object();

if(e instanceof T) {
  // Do something.
}

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

Không biên dịch do xóa kiểu xóa tham chiếu thời gian chạy. Tuy nhiên, mã dưới đây sẽ biên dịch:

if( type.isAssignableFrom(e.getClass())){
  // Do something.
}

4

Xem xét tình huống sau đây. Giả sử bạn muốn kiểm tra xem loại A có phải là siêu hạng của loại obj hay không, bạn có thể đi

... A. class.isAssignableFrom (obj.getClass ()) ...

HOẶC LÀ

... obj dụ của A ...

Nhưng giải pháp isAssignableFrom yêu cầu loại obj có thể nhìn thấy ở đây. Nếu đây không phải là trường hợp (ví dụ, loại obj có thể thuộc lớp bên trong riêng), tùy chọn này sẽ bị loại. Tuy nhiên, giải pháp instanceof sẽ luôn hoạt động.


2
Điều đó không đúng. Vui lòng xem stackoverflow
Kẻ

1
Bạn có thể giải thích "Điều đó không đúng"? Nhận xét mà bạn đề cập không liên quan gì đến kịch bản trong bài viết của tôi. Tôi có một số mã kiểm tra sao lưu giải thích của tôi.
đại số

Nếu bạn có một tham chiếu không null đến một thể hiện đối tượng ( objtrong ví dụ này) thuộc bất kỳ loại nào thì bạn có thể gọi getClass()phương thức công khai trên nó để lấy siêu dữ liệu phản chiếu cho lớp triển khai. Điều này đúng ngay cả khi loại lớp triển khai đó sẽ không hiển thị hợp pháp tại vị trí đó tại thời điểm biên dịch. Thời gian chạy vẫn ổn vì, để bạn giữ objtham chiếu, một số đường dẫn mã cuối cùng đã có quyền truy cập hợp pháp vào lớp đã tạo và đưa nó (bị rò rỉ?) Cho bạn.
William Giá

0
isAssignableFrom(A, B) =

if (A == B) return true
else if (B == java.lang.Object) return false
else return isAssignableFrom(A, getSuperClass(B))

Mã giả ở trên là một định nghĩa về, nếu các tham chiếu của loại / lớp A được gán từ các tham chiếu của loại / lớp B. Đây là một định nghĩa đệ quy. Đối với một số nó có thể hữu ích, đối với những người khác nó có thể gây nhầm lẫn. Tôi thêm nó trong trường hợp ai đó sẽ thấy nó hữu ích. Đây chỉ là một nỗ lực để nắm bắt sự hiểu biết của tôi, nó không phải là định nghĩa chính thức. Nó được sử dụng trong một triển khai Java VM nhất định và hoạt động cho nhiều chương trình ví dụ, vì vậy trong khi tôi không thể đảm bảo rằng nó nắm bắt tất cả các khía cạnh của isAssignableFrom, thì nó không hoàn toàn tắt.


2
Vui lòng giải thích những gì mã này làm và cách nó trả lời câu hỏi.
Vụ kiện của Quỹ Monica

0

Nói về hiệu suất "2" (với JMH):

class A{}
class B extends A{}

public class InstanceOfTest {

public static final Object a = new A();
public static final Object b = new B();

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testInstanceOf()
{
    return b instanceof A;
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsInstance()
{
    return A.class.isInstance(b);
}

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public boolean testIsAssignableFrom()
{
    return A.class.isAssignableFrom(b.getClass());
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(InstanceOfTest.class.getSimpleName())
            .warmupIterations(5)
            .measurementIterations(5)
            .forks(1)
            .build();

    new Runner(opt).run();
}
}

Nó cho:

Benchmark                            Mode  Cnt  Score   Error  Units
InstanceOfTest.testInstanceOf        avgt    5  1,972 ? 0,002  ns/op
InstanceOfTest.testIsAssignableFrom  avgt    5  1,991 ? 0,004  ns/op
InstanceOfTest.testIsInstance        avgt    5  1,972 ? 0,003  ns/op

Để chúng ta có thể kết luận: instanceof nhanh như isInstance ()isAssignableFrom () không xa (+ 0,9% thời gian thực thi). Vì vậy, không có sự khác biệt thực sự bất cứ điều gì bạn chọn


0

Làm thế nào về một số ví dụ để hiển thị nó trong hành động ...

@Test
public void isInstanceOf() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Base case, handles inheritance
    Assert.assertTrue(anEx1 instanceof Exception);
    Assert.assertTrue(anEx2 instanceof Exception);
    Assert.assertTrue(anEx3 instanceof Exception);

    //Other cases
    Assert.assertFalse(anEx1 instanceof RuntimeException);
    Assert.assertTrue(anEx2 instanceof RuntimeException);
    Assert.assertTrue(anEx3 instanceof RuntimeException);
}

@Test
public void isAssignableFrom() {
    Exception anEx1 = new Exception("ex");
    Exception anEx2 = new RuntimeException("ex");
    RuntimeException anEx3 = new RuntimeException("ex");

    //Correct usage = The base class goes first
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx1.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx2.getClass()));
    Assert.assertTrue(Exception.class.isAssignableFrom(anEx3.getClass()));

    //Incorrect usage = Method parameter is used in the wrong order
    Assert.assertTrue(anEx1.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx2.getClass().isAssignableFrom(Exception.class));
    Assert.assertFalse(anEx3.getClass().isAssignableFrom(Exception.class));
}

-2

một số thử nghiệm chúng tôi đã làm trong nhóm của chúng tôi cho thấy rằng A.class.isAssignableFrom(B.getClass())hoạt động nhanh hơn B instanceof A. điều này có thể rất hữu ích nếu bạn cần kiểm tra điều này trên số lượng lớn các yếu tố.


13
Hừm, nếu bạn có một nút cổ chai instanceof, tôi tin rằng bạn có vấn đề nghiêm trọng về thiết kế ...
sleske

1
Câu trả lời của JBE đưa ra một giả thuyết khác với giả thuyết của bạn.
Alastor Tâm trạng
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.