☰
java 6 多态
2026/10/15 7:39:45 网站建设 项目流程

instanceof是 Java 里的类型判断运算符,用来在运行时检查一个对象是不是某个类(或接口)的实例。它返回true或false,最经典的用途就是:先判断类型,再安全地强转。

一、基本语法

对象引用instanceof类型
Objectobj="hello";if(objinstanceofString){Strings=(String)obj;// 安全强转,不会抛 ClassCastExceptionSystem.out.println(s.length());}

二、三个核心规则(面试常考)

1. 判断的是“实际运行时类型”,不是声明类型

Animala=newDog();// 声明是 Animal,实际是 DogSystem.out.println(ainstanceofAnimal);// trueSystem.out.println(ainstanceofDog);// true ← 看的是实际对象System.out.println(ainstanceofCat);// false

2. 对接口也有效

Listlist=newArrayList<>();System.out.println(listinstanceofList);// trueSystem.out.println(listinstanceofSerializable);// true(ArrayList 实现了它)

3.null永远返回false,不会空指针

Strings=null;System.out.println(sinstanceofString);// false ← 很安全

这也是为什么instanceof常和判空合并:不用先写if (s != null && ...)。

三、最经典用法:先判断再强转

voidhandle(Objectobj){if(objinstanceofString){Strings=(String)obj;System.out.println("字符串长度:"+s.length());}elseif(objinstanceofInteger){Integern=(Integer)obj;System.out.println("数字:"+n);}else{System.out.println("未知类型");}}

四、Java 16+ 的模式匹配(强烈推荐用这个)

从 Java 16 开始(预览于 14/15),instanceof可以直接绑定变量,省掉手动强转:

voidhandle(Objectobj){if(objinstanceofStrings){// s 自动就是 String 类型System.out.println(s.length());}elseif(objinstanceofIntegern){System.out.println(n);}}

注意作用域:变量只在判断为true的分支里可用。

if(objinstanceofStrings&&s.length()>5){// ✅ 短路求值,合法System.out.println(s);}// System.out.println(s); ← 编译错误,这里 s 不可用

五、常见坑

坑 1:父子关系搞反 / 无继承关系的两个类直接编译报错

Strings="hi";// if (s instanceof Integer) {} ← 编译错误!String 和 Integer 互不继承,编译器知道不可能

但如果是父类引用指向子类对象,就可以判断:

Objecto="hi";if(oinstanceofInteger){}// ✅ 编译通过(只是运行结果为 false)

坑 2:instanceof对子类也返回 true,如果你要“精确匹配”得用getClass()

Animala=newDog();System.out.println(ainstanceofAnimal);// trueSystem.out.println(a.getClass()==Animal.class);// false ← 精确匹配

重写equals时经常需要这个区别。

坑 3:别用它代替多态

// ❌ 坏味道:一堆 instanceof + 强转if(animalinstanceofDog)((Dog)animal).bark();elseif(animalinstanceofCat)((Cat)animal).meow();// ✅ 好写法:让子类自己实现animal.makeSound();

如果instanceof链条越来越长,通常说明该用多态、策略模式或访问者模式了。

六、相关 API 对比

方式形式区别
instanceofobj instanceof Dog看继承链,包含子类;null返回 false
Class.isInstance()Dog.class.isInstance(obj)等价于 instanceof,但类型可在运行时动态决定
getClass() == X.classobj.getClass() == Dog.class精确匹配,不含子类
Class.isAssignableFrom()Dog.class.isAssignableFrom(obj.getClass())等价于 instanceof 的类版本,常用于框架/反射

反射场景示例:

Class<?>type=Class.forName(className);if(type.isInstance(obj)){...}

七、一个完整小例子

classAnimal{}classDogextendsAnimal{voidbark(){System.out.println("汪");}}classCatextendsAnimal{voidmeow(){System.out.println("喵");}}publicclassMain{publicstaticvoidmain(String[]args){Animal[]animals={newDog(),newCat(),newDog()};for(Animala:animals){if(ainstanceofDogd){// Java 16+ 模式匹配d.bark();}elseif(ainstanceofCatc){c.meow();}}}}

输出:

汪 喵 汪

如果你用的是 Java 8~15,把instanceof Dog d换成instanceof Dog再加(Dog) a强转就行,逻辑完全一样。


第二节 Object 类常用方法
Object类中定义的方法大多数都是属于native方法,native表示的是本地方法,实现方式是在C++中。

  1. getClass()
packagecom.cyx.polymorphism.object;importcom.cyx.polymorphism.device.TV;publicclassObjectTest{publicstaticvoidmain(String[]args){TVtv=newTV();Classclazz=tv.getClass();Stringname=clazz.getSimpleName();//获取类名System.out.println(name);StringclassName=clazz.getName();//获取类的全限定名System.out.println(className);ClasssuperClass=clazz.getSuperclass();//获取父类的定义信息StringsuperName=superClass.getSimpleName();//获取父类的名称System.out.println(superName);StringsuperClassName=superClass.getName();//获取父类的全限定名System.out.println(superClassName);Strings="admin";ClassstringClass=s.getClass();Class[]interfaceClasses=stringClass.getInterfaces();for(inti=0;i<interfaceClasses.length;i++){ClassinterfaceClass=interfaceClasses[i];StringinterfaceName=interfaceClass.getSimpleName();//获取接口的名称System.out.println(interfaceName);StringinterfaceClassName=interfaceClass.getName();//获取接口的全限定名System.out.println(interfaceClassName);}}}

//getClass() 方法返回一个Class对象,该对象具有可用于获取有关该类的信息的方法,例如其名称
(getSimpleName()),其超类//(getSuperclass())及其实现的接口(getInterfaces())。

  1. hashCode()

Object类中的hashCode()方法返回的就是对象的内存地址。一旦重写hashCode()方法,那么Object
类中的hashCode()方法就是失效,此时的hashCode()方法返回的值不再是内存地址。

根据定义,如果两个对象相等,则它们的哈希码也必须相等,反之则不然。
重写了equals方法,就需要重写hashCode方法,才能满足上面的结论
面试题:请描述 == 和 equals 方法的区别
基本数据类型使用 == 比较的就是两个数据的字面量是否相等。引用数据类型使用 == 比较的是内存地
址。equals方法来自Object类,本身实现使用的就是 ==,此时它们之间没有区别。但是Object类中的
equals方法可能被重写,此时比较就需要看重写逻辑来进行。

  1. finalize()
    finalize()是 Java 里最著名、也最该被遗忘的方法。它定义在Object中,签名是:
protectedvoidfinalize()throwsThrowable{}

设计初衷是:对象被 GC 回收之前,JVM 会调用它一次,给你一个“临终遗言”的机会去释放资源(比如关文件、关连接)。

但现实是:它几乎从不该被使用,并且在 Java 9 已被标记为@Deprecated。下面说清它是什么、为什么被废、以及该用什么替代。


一、它原本想干什么

classMyResource{privateFileInputStreamfis;@Overrideprotectedvoidfinalize()throwsThrowable{if(fis!=null)fis.close();// 想兜底关闭资源super.finalize();}}

想法很美好:万一用户忘了 close,GC 时我帮他擦屁股。很多老版本 JDK 类(如FileInputStream、Socket)过去就是这么干的。


二、为什么它被废了(四个致命问题)

1.调用时机完全不可预测,甚至可能永远不调用
MyResourcer=newMyResource();r=null;System.gc();// 只是"建议",JVM 完全可以不理// finalize() 不一定执行

更糟的是:程序正常退出时,JVM 不会为了执行 finalize 而等待。所以依赖它做“保存数据”“释放关键资源”是 100% 不可靠的。

2.严重拖慢 GC,甚至导致 OOM

带finalize()的对象不会被一次回收,而是:

  • 第一次 GC:发现它有 finalize → 把它塞进Finalizer 队列→ 对象复活到下一轮
  • FinalizerThread(单线程)从队列取出并执行 finalize
  • 第二次 GC:才能真正回收

后果:

  • 回收成本翻倍,堆里堆积大量“等死”对象
  • FinalizerThread 是单线程,一个对象的 finalize 卡住(比如里面有个慢网络请求),全堆的待回收对象全部堵死→ 内存泄漏型 OOM,而且堆栈里还看不出你的业务代码
3.异常会被静默吞掉

finalize 里抛出的未捕获异常不会打印堆栈,只会终止这个对象的 finalize 流程,对象就此变成“无法回收的僵尸”。排查起来极其痛苦。

4.安全漏洞:Finalizer Attack

构造器抛出异常时,对象的 finalize仍然可能被调用,恶意子类可以在 finalize 里拿到这个“半初始化对象”的引用并泄露出去:

classSecure{Secure(){if(!hasPermission())thrownewSecurityException();}}classEvilextendsSecure{staticvolatileEvilleaked;@Overrideprotectedvoidfinalize(){leaked=this;}// 偷到引用!}

这也是为什么安全敏感的类要声明成final,或者显式提供一个空的finalize()来阻止子类继承。


三、还有一个坑:对象“复活”

classZombie{staticZombieholder;@Overrideprotectedvoidfinalize()throwsThrowable{holder=this;// 把自己救活了}}

更诡异的是:finalize 保证最多执行一次。复活后的对象再次死亡时,不会再走 finalize —— 兜底清理逻辑彻底失效。这种代码在生产环境属于灾难级。


四、正确的替代方案

✅ 方案 1:AutoCloseable+ try-with-resources(99% 的场景用这个)
classMyResourceimplementsAutoCloseable{privateFileInputStreamfis;publicMyResource(Stringpath)throwsFileNotFoundException{this.fis=newFileInputStream(path);}@Overridepublicvoidclose(){// 确定性释放,你说了算try{fis.close();}catch(IOExceptionignore){}}}// 使用try(MyResourcer=newMyResource("a.txt")){// ...}// 自动 close,即使中间抛异常

这是唯一推荐的主流做法。资源的生命周期应该由程序员显式控制,而不是赌 GC 什么时候来。

✅ 方案 2:java.lang.ref.Cleaner(Java 9+,真的要做“兜底”时用)

如果你确实需要一个“用户忘了 close 时的保险丝”,用 Cleaner:

importjava.lang.ref.Cleaner;classMyResourceimplementsAutoCloseable{privatestaticfinalCleanerCLEANER=Cleaner.create();privatefinalCleaner.Cleanablecleanable;privateFileInputStreamfis;publicMyResource(Stringpath)throwsFileNotFoundException{this.fis=newFileInputStream(path);// 注意:清理动作必须放在静态内部类/lambda 里,不能捕获 this!this.cleanable=CLEANER.register(this,()->{try{fis.close();}catch(IOExceptionignore){}});}@Overridepublicvoidclose(){cleanable.clean();// 用户正常 close,立即清理}}

关键点:Cleaner 基于 PhantomReference(虚引用),不会阻止对象被回收,也没有 finalize 那种“复活一轮”的开销。JDK 自己的FileInputStream等在较新版本里已经改用这种方式。

✅ 方案 3:虚引用PhantomReference+ 引用队列(框架级、极底层场景)

一般业务代码用不到,Netty 的直接内存回收、一些 NIO 缓冲池会用到。


五、几个高频面试题

问题答案
finalize 一定会执行吗?不会。GC 时机不确定,程序退出时可能不执行
finalize 会执行几次?最多一次(即使对象复活后再次死亡也不再执行)
能手动调用 finalize 吗?语法上可以(它是普通方法),但绝不要这么做,语义完全错误
System.gc()能保证 finalize 执行吗?不能,它只是建议 GC,且不等待 finalizer 完成。System.runFinalization()也只是建议
为什么不直接删除 finalize?兼容性。大量老代码和老库覆盖了它,直接移除会破坏行为(forRemoval=false)
覆盖 finalize 时要注意什么?必须在finally里调用super.finalize(),否则父链的清理断掉

六、一句话总结

finalize()是一个不可靠、有性能代价、有安全风险的“垃圾回收前回调”,Java 9 已废弃。资源释放请用try-with-resources/close();实在需要兜底请用Cleaner。看到项目里有@Override protected void finalize(),基本可以判定这是一段需要改造的老代码。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询