java 类的实例化(instantiation)具有显性的和隐性的区别。
写 Java 代码时,我们所使用 new 的方法实例化最简单直接的显性实例化。而隐性的实例化则出现在 java 程序的整个生命周期中,包括 String、Class,StringBuffer 或者 StringBuilder 的实例化等等。
显性的实例化
new 关键字实例化对象
调用相应的构造函数完成实例化。(类中的非静态成员变量如果有初始化语句,都会被隐式的加入到构造函数中)代码如下:
public class Test {
String strA = "xyz";
String strB ;
public Test(String str){
strB = str ;
}
public static void main(String[] args){
Test t = new Test("abc");
}
}
在 eclipse 中装了 ASM bytecode 插件后,观察.class 文件中的构造函数对应的字节码如下:
INVOKESPECIAL Object.<init>() : void
ALOAD 0: this
LDC "xyz"
PUTFIELD Test.strA : String
ALOAD 0: this
ALOAD 1: str
PUTFIELD Test.strB : String
RETURN
关键在于LDC"xyz"
这条指令,明显可以看出,这是用于 strA 初始化的字符串。
由此我们可以归纳出,在没有调用本类中其他的构造函数的情况下,每次类的构造函数中都会按如下顺序进行:
- a)隐式(或显性)的调用父类的构造函数
- b)然后执行写在构造函数外的成员变量的初始化赋值
- c)最后再执行构造函数中的命令。
如果是有显性的调用本类其他构造函数(必须是放在构造函数第一步执行),那么对于这个构造函数,处理过程就简单些了:
- a)调用那个构造函数。
- b)执行之后的代码。
利用 java 反射机制
反射机制是是 java 动态性中的关键之一,调用 java.lang.reflect.Constructor 的 newInstance()方法能创建对象。
public class Test {
public Test(){
System.out.println("Created by invoking newInstance()");
}
public Test(String str){
System.out.println(str);
}
public static void main(String[] args)
throws ClassNotFoundException , InstantiationException ,
IllegalAccessException {
Test t1 = new Test("Created with new"); //常规的方法
Class myClass = Class.forName("Test"); //获得了对应于 Test 类的 Class 对象,如果没有加载,会先加载这个类,再返回。
Test t2 = (Test)myClass.newInstance(); //调用 newInstance()创建对象。
}
}
其他
其他还有对象的 clone()方法,以及串行化后的解串行化过程。
隐性的实例化
隐性的实例化主要有如下几类:
- 1.String 和 String 数组。main(String[] args)中拥有的 args 参数为 String 数组类型,这些 command line 参数将会首先被实例化。
- 2.Class 的实例化。由于类的加载过程中,会生成相应类的 Class 对象,这些也会被隐性的实例化。
- 3.JVM 在执行类加载的过程中,对常量池中的 CONSTANT_String_info 项会实例化出对应的 String 对象。这里涉及到常量池解析的知识。
- 4.在 String 的操作中,可能存在隐性的 StringBuffer 或者 StringBuilder 的实例化。
- 5.int 和 Integer 这些类型转化过程中的装箱、拆箱。
比如如下代码:
public class Test {
public static void main(String[] args){
String str1 = "abc";
String str2 = "def";
String str = str1 + str2 ;
}
}
在 eclipse 中装了 ASM bytecode 插件后,直接观察.class 文件对应的字节码:
NEW StringBuilder
DUP
ALOAD 1: str1
INVOKESTATIC String.valueOf(Object) : String
INVOKESPECIAL StringBuilder.<init>(String) : void
ALOAD 2: str2
INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder
INVOKEVIRTUAL StringBuilder.toString() : String
ASTORE 3
实际上,这里 str1 和 str2 合并的过程,是使用了 StringBuilder 来间接完成的,首先以 str1 的值构造一个 StringBuilder,然后调用其中的 append()方法,将 str2 串联上来。
值得注意的是:老版本的 java 使用 StringBuffer 完成这一步,但 StringBuffer 是线程安全的,效率略低,于是在新版本 java 中出现了非线程安全的 StringBuilder,这类似于 Hashtable 和 hashset 的关系。
总结:
Java中,类的实例化方法有四种途径:
1)使用new操作符
2)调用Class对象的newInstance()方法
3)调用clone()方法,对现有实例的拷贝
4)通过ObjectInputStream的readObject()方法反序列化类
1.ClassInstance.java
import java.io.*;
class ClassInstance implements Cloneable, Serializable {
private String str = "测试...";
public void fun(){
System.out.println(str);
}
public ClassInstance(String str){
System.out.println("有参类的实例化");
this.str += str;
}
public ClassInstance(){
System.out.println("无参类的实例化");
}
public Object clone(){
return this;
}
}
2.ClassInstanceTest.java
import java.io.*;
import java.lang.reflect.*;
public class ClassInstanceTest{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException, IOException,InvocationTargetException, NoSuchMethodException{
//第一种类的实例化方式
ClassInstance ci01 = new ClassInstance("01");
ci01.fun();
//第二种类的实例化方式
ClassInstance ci02 = (ClassInstance) Class.forName("ClassInstance").newInstance();
ci02.fun();
//第三种类的实例化方式
ClassInstance ci03 = (ClassInstance) ci01.clone();
ci03.fun();
//第四种类的实例化方式
FileOutputStream fos = new FileOutputStream("ci.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(ci01);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("ci.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);
ClassInstance ci04 = (ClassInstance) ois.readObject();
ois.close();
fis.close();
ci04.fun();
System.out.println("--------------------额外测试--------------------");
ClassInstance ci05 = null;
//额外的思考 在第二种类实例化的方式中有没有一种方法实现有参数的构造方式
//获得类的构造信息
Constructor[] ctor = Class.forName("ClassInstance").getDeclaredConstructors();
//找到我们需要的构造方法
for(int i=0;i<ctor.length;i++ ){
Class[] cl = ctor[i].getParameterTypes();
if(cl.length == 1){
//实例化对象
ci05 = (ClassInstance) Class.forName("ClassInstance").getConstructor(cl).newInstance(new Object[]{"05"});
}
}
ci05.fun();
}
}
3.输出结果
有参类的实例化
测试...01
无参类的实例化
测试...
测试...01
测试...01
-------------------额外测试--------------------
有参类的实例化
测试...05
除了这几种情况可以实例化一个Java类对象外,隐式调用实例化也是利用了已上集中情况。例如常见的方法:
public class ClassInstance{
public ClassInstance(){
}
public ClassInstance getInstance(){
return new ClassInstance();
}
}
通过观察结果,我们发现无论哪一种方式必须经过的一步---调用构造方法。无论怎样构造函数作为初始化类的意义怎样都不会改变。