创建对象的方式

有四种显式创建的方式:

  • new创建
  • 运用反射,调用java.lang.Class或java.lang.reflect.Constructor类的newInstance()方法
  • 调用对象的clone()方法
  • 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法
//通过反射创建
Class objClass = Class.forName("Customer");
Customer cl = (Customer)objClass.newInstance();
//通过克隆创建
Customer c3 = (Customer)c2.clone();

通过new或反射的方法创建的时候,都会调用构造方法。但是通过克隆创建不会执行构造方法。在Object类中就定义了clone()方法

protected Object clone()throws CloneNotSupportedException
{
if(!(this instanceof Cloneable))
{
throw new CloneNotSupportException();
...
}
}

这种方式下如果对应类没有实现cloneable接口,那么clone()方法会抛出异常。如果想用clone,那么必须要实现Cloneable接口

clone会创建一个对象,对象的属性值相同,但是内存地址不同。

隐式创建对象

  • 输入main的参数都会转化成String对象
  • +号对String类型创建新对象
  • 加载一个类时,会创建Class实例

创建对象步骤

  1. 给对象分配内存
  2. 将实例变量自动初始化成默认值
  3. 初始化对象。例如private int a = 1;在第二步赋值成0,在第三步中才会变成1.

对于第三步,如果是通过clone的方法创建,那么将原来成员变量的值赋给新的成员变量。如果是用第四种方法,那么是通过输入流读入序列化数据。

构造方法

构造方法时负责初始化的,可以提供参数,也可以不提供,一般设置一个默认构造函数有好处。

  • 方法名要与类名相同
  • 不声明返回类型
  • 不能被static,final,synchronized,abstract,native修饰

重载构造方法

这是为了满足多样化的需求。需要注意一点在一个构造方法中可以用this调用另一个构造方法。但是如果在一个构造方法中使用了this语句,那么它必须在第一行。

默认构造方法

其实和c++一样,在没有构造方法的时候会提供一个默认构造方法,如果写了一个构造方法那么系统就不会提供构造方法。但是最好要有一个不带参数的构造方法。

子类调用父类构造方法

父类构造方法不能被子类继承。在构造子类时,先执行祖先的构造方法,然后一次向下。这样可以确保子类从父类继承的东西可以被使用。可以用super(…)调用父类构造方法。如果没有用super显示构造,那么子类会用父类的默认构造方法。如果子类没有构造方法,将会出现编译错误

构造方法的访问级别

这里主要将private的访问级别的意义。

当构造函数是private时,只有当前类可以访问它。这也代表不能有子类。所以只有一些特殊的场合才会使用它。

  1. 只有静态方法,没有实例方法
  2. 禁止被继承。这个与final的区别就是private不允许其他类创建它的实例,而final可以。
  3. 这个类要把自己封装起来。

静态工厂办法

创建实例最常见的方法是用new调用类的构造方法。在这种情况下,可以创建任意多的实例。如果类想要进一步封装创建实例的细节,并控制实例数目,可以用静态工厂的方法。

静态工厂方法指的是在类中提供一个公有的静态方法,返回类的一个实例。

静态工厂实际上是一种普通的方法,但是它有平常创建实例时所不具有的灵活性。

特点一: 可以有不同于类的名字,这样在重构构造方法时可以更清晰的说明其中的不同,使可读性增大。

例:

public static Person getManInstance(String name, int age){
return new Person(name, age, SEX.man);
}

public static Person getWomanInstance(String name, int age){
return new Person(name, age, SEX.woman);
}

一般都喜欢把静态工厂取名为valueof或getInstance。

特点二: 可以不创建新对象。重复利用原有对象

public class A
{
private static A init = new A();

public static A getinit()
{
return init;
}
}

特点三: 可以创建子类的实例

public class A
{
public static A getson()
{
return new Sub();
}
}

public class Sub extends A
{
...
}

因为静态工厂每次不一定创建新的实例,所以在一些特殊的场合有应用。

单例类

单例类就是只有一个实例的类,这种类只有一个实例。一般是系统中具有唯一性的组件才会使用。这种类一般要消耗很多内存,为了防止随便开然后爆内存,所以设置成单例类。

一种办法是提供public static final的静态常量,然后该常量引用唯一实例。在把构造方法定义成private类型。

public class Globalconfig
{
public static final GlobalConfig INSTANCE = new GlobalConfig();
private GlobalConfig();
...
}

另一种办法就是把构造方法定义成private类型,然后提供构造工厂。
public class Globalconfig
{
public static final GlobalConfig INSTANCE = new GlobalConfig();
private GlobalConfig(){...}
public static GlobalConfig getInstatnces()
{
return INSTANCE;
}
}

枚举类

枚举类是实例数目有限的类。比如,表示性别的Gender类,他只会有两个类。

设计时,同样把构造方法定义成private,然后创建若干个public static final的静态变量。还可以提供静态工厂。另外如果想使用这些实例,直接用类名即可。

不可变类和实例缓存

不可变类指的是创建之后就不可以更改属性值,和final类似。例如Long类,String类。

创建不可变类时,要考虑一下内容:

  • 把属性定义成final类型
  • 不对外公开的(private)set方法
  • public的get方法
  • 在构造方法中初始化所有属性
  • 覆盖Object类的equals()和hashCode()方法。

对于不可变类,因为数据成员不会改变,所以在创建实例时,如果二者数据成员相同,那么可以共有一份空间,这样就减小了内存消耗。这叫做实例缓存。

例如:

Integer a = Integer.valueOf(10);
Integer b = Integer.valueOf(10);
System.out.println(a == b);//会打印true

关于两个对象的等于,在前面一篇博客中已有说明。

这就说明了两者共用了内存空间。那么我们应该如何实现实例缓存呢?

public class Name
{
private static final Set<SoftReference<Name>> names = new HashSet<SoftReference<Name>>();//实例缓存,存放Name的软引用
public sattic Name valueOf(String firstname, Stirng lastname)
{
for(SoftReference<Name> ref:names)//循环查看是否已经存在
{
Name name = ref.get();//获得软引用的Name对象
if(name != null
&& name.firstname.equals(firstname)
&& name.lastname.equals(lastname))
{
return name;
}
//如果缓存中不存在Name对象,就创建新对象,并加入到实例缓存
Name name = new Name(firstname, lastname);
names.add(new SoftReference<Name>(name));
return name;
}
}
}

当然,实例缓存也需要消耗内存空间,如果对所有不可变类都使用,可能有的反而会减少内存。当有如下情况时,才会使用实例缓存。

  • 不可变类实例种类有限
  • 需要频繁访问某些特定的实例。

垃圾回收

在c++中,内存回收是程序员自己负责,因为人难免犯错嘛,可能会导致一系列奇奇怪怪的错误。所以在java中,内存回收是java虚拟机做的。

只有对象不被任何变量引用时,它的内存才会被回收。当垃圾回收器回收内存时,会先调用该对象的finalize()方法,该方法可能使对象复活,导致垃圾回收器取消回收内存。

对垃圾回收器来说,程序由三种状态:

  1. 可触及状态:只要还有引用变量引用对象,那么这个对象就处于可触及状态。
  2. 可复活状态: 当程序中没有引用2变量引用时,就进入了可复活状态,复活的关键是finalize方法,这个方法有可能使他复活
  3. 不可触及状态: 也就是调用了finalize状态并且没有复活之后。这种状态下虚拟机才会真正回收内存。

finalize方法

finalize()方法就是要销毁时执行的方法,如果finalize方法如果出现异常系统也不会报错而是直接清除。下面讲一下如何把它变成可触及状态。

public class Ghost
{
private static final Map<String, Ghost> ghosts = new HashMap<String,Ghost>();
String name ;
public static Ghost getInstance(String name)
{
Ghost ghost = ghost(name);
if(ghost == null)
{
ghost = new Ghost(name);
ghosts.put(name, ghost);
}
return ghost;
}
protected void finalize()
{
ghosts.put(name, this);
System.out.println("execute finalize");
}
}

这个程序在将要被销毁时会调用finalize,之后就把这个对象有放回了Map中,又把这个对象复活了。

但是实际最好不要用finalize,因为这会扰乱正常的回收机制,导致永远无法回收。

清除过期引用

正常情况下,虚拟机都会很好的执行垃圾回收,但是在对象数组等线性结构中,如果只让指针减一,是无法进行垃圾回收的,因为这个数组仍保留对它的引用,但是这个被清除的数据已经无意义了,下面举个例子

private Object[] elements = new Object[capacity];
public Object pop()
{
if(size == 0)
{
throw new EmptyStackException();
}
return elements[--size];
}

这个方法的确没什么问题,也可以得到正确的执行,但是实际上size位置的引用并没有被清除,所以当你不断的增加然后开始不断的删除时,这个栈一直在占用大量的空间。所以要想办法除法java的回收机制

方法:

public Object pop()
{
if(size == 0)
{
throw new EmptyStackException();
}
Object object = elements[--size];
elements[size] = null;
return object;
}

强引用,弱引用,软引用,虚引用

在早期的java中,并没有各种引用。这些引用代表的是清除的级别,如果空间不够了,那么先清除级别低的,这样对程序造成的影响就小。

在java.lang.ref包中,有Reference的抽象父类,下面有SoftReference,WeakReference,PhantomRefence,分别代表软引用,弱引用,虚引用。
ReferenceQueue表示引用队列,它可以和上面三种引用联合使用,以便跟踪虚拟机回收所引起的对象的活动(具体的现在不清楚)。

强引用

强引用就是普通的引用。如果一个类是强引用,那么除非没有变量引用它,虚拟机绝对不会回收它,甚至空间不足的时候虚拟机抛出OutOfMemoryError也不会回收。

软引用

如果一个对象只有软引用,那么空间足够就会留着它,空间不足就会回收它。前面就有一个软引用的例子。

软引用可以和引用队列(ReferenceQueue)配合使用,如果软引用所引用的对象被回收,那么虚拟机就会把这个软引用加入到与之关联的引用队列中

弱引用

弱引用比软引用更没人权。一旦垃圾回收器发现了弱引用,不管有没有内存,都会回收它。只是因为垃圾回收是一个优先级比较低的线程,所以不一定会很快发现弱引用。

虚引用

弱引用都如此了,虚引用肯定更倒霉。同样也是一旦被发现分分钟清除的货。而且优先级比弱引用还要低。虚引用并不会决定对象生命周期。对象持有虚引用和没有引用一样,如果只有虚引用,还是会被清除。

弱引用必须要和引用队列一起使用。当回收一个对象时,如果发现它有虚引用,那么就会在回收对象之前,把这个虚引用对象加入引用队列中。

那么它的作用是什么?因为虚引用会被放到引用队列中,所以可以设一个虚引用,然后通过虚引用是否在引用队列中来判断这个对象时候要被回收。

String str = new String("hello");//创建强引用
ReferenceQueue<String> rq = new ReferenceQueue<String>();//创建引用队列
WeakReference<String> wf = new WeakReference<String>(str,rq);//创建弱引用