契子:明年就要离开学校找工作了,时间过的真快,想一想这几年,做了一些事,也有一些事并没有做好,有很多收获,也有不少遗憾。感性的话在此不宜多说,既然选择了程序员这条道路,也要有把它到做事业的态度。在正式找工作前还有几个月的时间,做东西,尝试新的技术固然很爽,但是基础也很重要,在这短短的几个月的时间里,我将把以前学过的一些知识,Java,数据结构,算法,网络,OS&Linux,J2EE等等知识查缺补漏,好好梳理一遍,不光是为了找工作,也是一种必须要坚持的态度。
对于Java知识的整理,基于《Effetive Java》2nd和《Java编程思想》4th辅以JVM和设计模式的相关知识,结合书本上的知识和我的理解进行整理。好了,开始我的一篇——创建和销毁对象。
1. Java中的构造器:
构造器是一种特殊类型的方法,它和类同名,没有返回类型,和new关键字结合可以返回对象实例的引用。TIJ中说它是一种静态方法,但是通过字节码我们可以看到其实并没有static关键字,它的行为也和其他静态方法有异(可以访问非静态成员变量),因此这种说法并不完全准确,这里不再深究。
1.1 定义构造器:
一个类可以有多个构造器,如果你没有定义构造器,Java编译器会在语义分析的阶段,首先添加一个默认构造器。
多个构造器可以通过方法重载(overload)实现,注意只有同方法名和不同参数列表可以区别不同的重载版本,返回类型并不能区分。
尤其是使用基本类型参数重载时,要注意类型的自动转换如(char—>int,小转大)和窄化转换(强制类型转换,大转小),当然会使用最匹配的类型。
1.2 this关键字:
通过this指针我们可以访问类的实例变量和方法,但最好是在必要的时候(需要返回或使用该实例,内部类访问外部类同名实例变量方法,构造器设置属性等)使用它,否则你不必添加它,编译同样会帮你添加。
在存在多个重载版本的构造器时我们可以在构造器内使用this调用其他构造器,可以避免一些重复的代码:
public ConstructorTest(int a) {
this.a = a;
}
public ConstructorTest(int a, String s) {
this(a);
this.s = s;
}
PS:在构造器存在很多参数情况下,重叠构造器是一种选择,但是更好的做法是使用Builder模式,后面会讲到。
1.3 static关键字:
static(静态),static方法和static变量是类方法和类变量,它们不能使用this引用,都放在方法区中,供各个线程共享。static变量初始化和static初始化其,会在类加载(隐式加载或显示加载)后执行一次。
2. 清理,终结对象(finalize)/垃圾回收(CG):
释放资源,终结和垃圾回收有什么关系:
Finalize方法到底有什么用:
总的来说除此以外尽量不要使用finalize方法。
3. 初始化:
如果想真正弄清楚对象初始化,而不是仅仅记住一些像成员变量的初始值这样的规则,我觉得应该了解一个类在第一个创建对象时是如何从字节码编程的可用的对象的。
在第一次使用一个类的时候,无论是显示加载一个类(Class.forName等)还是隐式加载一个类(A.staticVariable,new A())时,首先要有ClassLoader进行加载:
(1)ClassLoader首先通过类名定位到类文件的位置(通过classpath等),将字节码加载到内存,通过准备、字节码验证和resolve等环节将等到一个个Class对象,放到方法区中;
(2)在此之后就是类初始化,这是类中的静态变量和静态初始化器将按照位置顺序进行初始化工作,静态变量同样放在方法区中;
(3)如果你进行是实例创建的化,接下来的工作首先是在堆上分配内存了,具体的方法可能有指针碰撞和空闲列表;
(4)获得了内存空间后,首先全部置零,这也就是为什么类的成员变量会还有初始值的原因,之后如果指定了初始化值,同样这里也是按顺序进行的;
(5)最后将执行<init>也就是我们定义使用的构造器来进行我们自定义的初始化过程了,这里就可以获得我们想要的对象实例的引用了。
所以在类中,各个部分的初始化顺序是:静态变量,静态初始化器(按位置顺序)——>非静态成员变量(按位置顺序)——>构造器;
说完了基本过程,我们来看看在Java中一些具体的类型是怎样进行初始化的。
3.1 数组初始化:
public class MStack {
private static final int DEFAULT_SIZE = 20;
private Object[] elements = new Object[DEFAULT_SIZE];
private int size = 0;
public MStack() {
elements = new Object[DEFAULT_SIZE];
}
public void push(Object element) {
ensureCapacity();
elements[size++] = element;
}
public Object pop() {
if(size == 0) {
throw new RuntimeException("empty stack cannot pop");
}
return elements[--size];
}
public void ensureCapacity() {
if(size == elements.length) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
这是《Effective Java》的一个例子,该例中的Stack在pop是并没有将已经出栈的引用置为null;这些引用是“过期引用”,这些引用虽然没有被使用,但是它将随着Arrays.copy一起被复制到更大的数组中,对于JVM来说它们同样是存活的对象,但是对我们的应用程序来说这些是无用的。在一个需要长期运行的服务中如果出现这样的问题很容易导致OOM。
3.2 可变参数列表:
public class VarArgsInit {
//overload with var argument
public static void f(Long...longs) {
System.out.println("f_long_varArgs");
}
public static void f(Character...characters) {
System.out.println("f_character_varArgs");
}
public static void f(float f, Character...characters) {
System.out.println("f_float_character_varArgs");
}
public static void g(float f, Character...characters) {
System.out.println("g_float_character_varArgs");
}
public static void g(char c, Character...characters) {
System.out.println("g_char_character_varArgs");
}
public static void main(String[] args) {
// f(); //Error:(19, 9) java: reference to f is ambiguous
// f();
f(1, 'a'); //OK
// f('a', 'b'); //Error:(19, 9) java: reference to f is ambiguous
g('a', 'b'); //OK
}
这个例子中,f('a','b')会引起编译错误,因为它会同时匹配第3个和第2个f()版本(因为'a'可以转换成float),解决方法,很简单g方法的两个版本就不会有这种冲突。
3.3 枚举:
final enum hr.test.Color {
// 所有的枚举值都是类静态常量
public static final enum hr.test.Color RED;
public static final enum hr.test.Color BLUE;
public static final enum hr.test.Color BLACK;
public static final enum hr.test.Color YELLOW;
public static final enum hr.test.Color GREEN;
private static final synthetic hr.test.Color[] ENUM$VALUES;
// 初始化过程,对枚举类的所有枚举值对象进行第一次初始化
static {
0 new hr.test.Color [1]
3 dup
4 ldc <String "RED"> [16] //把枚举值字符串"RED"压入操作数栈
6 iconst_0 // 把整型值0压入操作数栈
7 invokespecial hr.test.Color(java.lang.String, int) [17] //调用Color类的私有构造器创建Color对象RED
10 putstatic hr.test.Color.RED : hr.test.Color [21] //将枚举对象赋给Color的静态常量RED。
......... 枚举对象BLUE等与上同
102 return
};
// 私有构造器,外部不可能动态创建一个枚举类对象(也就是不可能动态创建一个枚举值)。
private Color(java.lang.String arg0, int arg1){
// 调用父类Enum的受保护构造器创建一个枚举对象
3 invokespecial java.lang.Enum(java.lang.String, int) [38]
};
public static hr.test.Color[] values();
public static hr.test.Color valueOf(java.lang.String arg0);
}
从字节码解析中,首先可以看到:public enum MEnum {
E1;
static class A {
private A(){
}
}
public static void main(String[] args) throws Exception {
Class<A> a = A.class;
Constructor constructor = a.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
Class<?> ec = MEnum.class;
Constructor constructor1 = ec.getDeclaredConstructor(String.class, int.class);
constructor1.setAccessible(true);
constructor1.newInstance("YJH", 2);
}
}
结果:A类可以正常创建,而enum类型,java.lang.IllegalArgumentException: Cannot reflectively create enum objects,因为在class.newInstance中有这样的检查: if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
(2)values静态方法;4. 创建和销毁对象的实践:
4.1 使用静态工厂方法代替构造器:
优点:4.2 多个构造器参数时使用Builder模式:
public final class HasBuilder {
private final int i1;
private final int i2;
private final String s1;
public HasBuilder(Builder builder) {
this.i1 = builder.i1;
this.i2 = builder.i2;
this.s1 = builder.s1;
}
public static class Builder {
private int i1;
private int i2;
private String s1;
public Builder i1(int i1) {
this.i1 = i1;
return this;
}
public Builder i2(int i2) {
this.i2 = i2;
return this;
}
public Builder s1(String s1) {
this.s1 = s1;
return this;
}
public HasBuilder build() {
return new HasBuilder(this);
}
}
}
你可以通过在build构建在具体的设值方法里进行约束检查。4.3 建立合适的单例:
public class SingletonWithInnerClass {
private SingletonWithInnerClass() {
System.out.println("initialized");
}
private static class SingletonHolder {
private static final SingletonWithInnerClass s = new SingletonWithInnerClass();
}
public static SingletonWithInnerClass getInstance() {
return SingletonHolder.s;
}
public static void main(String[] args) {
Class c = SingletonWithInnerClass.class; //这里并没有进行初始化
System.out.println("start initialization:");
SingletonWithInnerClass singletonWithInnerClass = SingletonWithInnerClass.getInstance();
}
}
这段代码的输出结果:initialized