一、instanceof的陷阱:
如果前面操作数的编译类型与后面的类型没有任何关系,那么编译将不通过。例如:
String a ="aaa";
System.out.println("a是否属于MATH的类型:"+(a.instanceof Math));
String类型与Math无任何关系,所以编译不通过,那么instanceof编译通过的条件是什么?
通过条件:前面的操作数类型要么与后面类相同,要么是父/子类
二、构造器的陷阱:
当为构造器添加任何修饰符时,编译器不会报错,只是把它当作普通方法来处理。
构造器只是负责初始化,在构造器执行前,Java对象所需要的内存空间,已有new关键字申请创建。
那么有木有不调用构造方法而创建对象的方式?
答案是肯定的。(1)使用反序列化的方式恢复Java对象。(2)使用clone()方法复制Java对象
先介绍第一种方法:
package aaaaa;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.omg.PortableInterceptor.ObjectIdHelper;
class Wolf implements Serializable{
String name;
public Wolf(String name){
System.out.println("调用有参数的构造方法");
this.name =name;
}
public boolean equal(Object obj){
if(this ==obj){
return true;
}
if(obj.getClass() ==Wolf.class){
Wolf target =(Wolf)obj;
return target.name.equals(this.name);
}
return false;
}
public int hashCode(){
return name.hashCode();
}
}
public class Test{
public static void main(String args[]) throws Exception{
Wolf w =new Wolf("灰太狼");
System.out.println("Wolf对象创建完成");
Wolf w1 =null;
try(
//创建对象输出流
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("a.bin"));
//创建对象输入流
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("a.bin"));
)
{//反序列化输出Java对象
oos.writeObject(w);
oos.flush();
//反序列化恢复Java对象
w1 =(Wolf)ois.readObject();
//两个对象的实例方法完全相同
System.out.println(w.equal(w1));
//两个对象不相同
System.out.println(w ==w1);
}
}
}
代码运行情况如下:
调用有参数的构造方法
Wolf对象创建完成
true
false
程序可以通过反序列化机制去破坏单例模式,但大部分时候也不会主动使用反序列化机制去破坏单例模式,但要想保证单例模式不会被破坏,应该为其提供readResolve()方法
如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
class Singleton implements Serializable{
private static Singleton instance;
private String name;
private Singleton(String name) {
System.out.println("调用有参数的构造函数");
this.name=name;
}
public static Singleton getInstance(String name) {
if(instance==null) {
instance =new Singleton(name);
}
return instance;
}
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
public class Test {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Singleton s=Singleton.getInstance("灰");
Singleton s1 =null;
try (
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("b.bin"));
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("b.bin"));
)
{
oos.writeObject(s);
oos.flush();
s1 =(Singleton)ois.readObject();
System.out.println(s==s1);
}
}
}
程序运行返回值是true;
除了使用反序列化机制恢复Java对象无需构造器之外,使用clone()方法复制Java对象也无需调用构造方法。
让Java类实现Cloneable接口,为该Java类提供clone()方法,该方法负责复制。
class Dog implements Cloneable{
private String name;
private double weight;
public Dog(String name, double weight){
System.out.println("调用有参数的构造方法");
this.name =name;
this.weight =weight;
}
public Object clone(){
Dog dog =null;
try{
dog =(Dog)super.clone();
}
catch (CloneNotSuppottedException e){
e.printStackTrace();
}
return dog;
}
无限递归的构造器
因为不管是定义实例变量指定的初始值还是在非静态初始化块中执行初始化的操作,都将提到构造器中执行。
所以要注意以下三个方面:
1、尽量不要在声明变量时指定值为当前对象的实例。
2、尽量不要在初始化块中构造当前类的实例。
3、尽量不要在构造器内调用本类构造器来创建Java对象
当一个类的实例持有当前类的其他实例时需要特别小心,因为程序很容易形成递归调用。
在jvm中调用重载函数匹配参数的时候,具有一定的智能性。
例如:将一个int类型的数据传给一个需要double方法的参数,程序不会报错。
public class Test {
public void info(String name,double age) {
System.out.println("name:"+name+" age:"+age);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info("张三",4);
}
}
程序结果:
name:张三 age:4.0
那么如果我有int类型的方法也有double类型的方法,这样会调用那个方法呢?
public class Test {
public void info(String name,double age) {
System.out.println("name:"+name+" age:"+age);
}
public void info(String name,int age) {
System.out.println("name:"+name+" age:"+age);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info("张三",4);
}
}
程序结果:
name:张三 age:4
jvm会精确匹配到int类型的那个方法。
那如果这样呢?我第一个参数是null,会怎样呢?
public class Test {
public void info(Object obj,double age) {
System.out.println("name:"+obj+" age:"+age);
}
public void info(Object[] obj,int age) {
System.out.println("name:"+obj+" age:"+age);
System.out.println("调用了Object[]方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info(null,4);
}
}
运行结果:
name:null age:4
调用了Object[]方法
根据精确匹配的原则,当实际调用的参数同时满足多个方法时,如果某个方法的形参要求范围更小时,那么这个方法就越精确。
但jvm也有无法断定那个方法更适合的时候,这样就编译不通过,例如:
public class Test {
public void info(Object obj,int age) {
System.out.println("name:"+obj+" age:"+age);
}
public void info(Object[] obj,double age) {
System.out.println("name:"+obj+" age:"+age);
System.out.println("调用了Object[]方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test a =new Test();
a.info(null,4);
}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method info(Object, int) is ambiguous for the type Test
at Test.main(Test.java:14)
非静态内部类的构造器:
public class Test {
public static void main(String[] args) throws Exception {
new Test().test();
}
public void test() throws Exception {
//创建非静态内部类的对象
System.out.println(new Inner());//1
//使用反射来创建Inner对象
System.out.println(Inner.class.newInstance());//2
}
public class Inner{
public String toString() {
return "Inner对象";
}
}
}
一处直接通过new调用Inner无参数的构造方法来创建实例,二处通过反射来调用Inner无参数的构造器创建实例,会发现抛出以下异常
Inner对象
Exception in thread "main" java.lang.InstantiationException: Test$Inner
at java.base/java.lang.Class.newInstance(Class.java:547)
at Test.test(Test.java:11)
at Test.main(Test.java:5)
Caused by: java.lang.NoSuchMethodException: Test$Inner.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3302)
at java.base/java.lang.Class.newInstance(Class.java:532)
... 2 more
这是因为程序表面上调用Inner无参数的构造器创建实例,实际上虚拟机会将底层this(代表当前默认的Test对象)作为实参传入Inner构造器,二处代码通过反射指定调用Inner类无参构造函数,所以引发了运行时异常。