天天看点

关于不可变类

参考:http://blog.csdn.net/cjmiou/article/details/40816013

package shishi;

public class shishi1 {
	private int a;
	private String hae = " ?";
	public void set() {
		String a  = "hea";
		hae = a;
	}
	public static void change(shishi1 b,shishi1 c) {
//		int hh = b.a;
//		b.a = c.a;
//		c.a = hh;
		b = c;
		System.out.println("a =" + b.a + " " +"b =" + c.a +" " +
		b + "  ---  " + c );
	}
	
	public static void change(StringBuilder b,int c) {
		b.append(" hello");
		c += 3;
		System.out.println("a = " + b + "  b = " + c );
	}
	
	public void say() {
		System.out.println(this.getClass()+"  say"+"shishi1" + a);
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		StringBuilder a = new StringBuilder("hello");
		int b = 5;
		String ab = "safsfsfsf";
		int con = ab.length();
		while( con--  >0) {
			System.out.println(ab.charAt(con));
		}
		shishi1 a1 = new shishi1();
		a1.a = 123;
		shishi1 b1 = new shishi1();
		b1.a = 132;
		change(a1,b1);
		System.out.println("a =" + a1.a + " " +"b =" + b1.a + " "
			+ a1 +"   "+ b1 );
		change(a,b);
		System.out.println("a = " + a + "  b = " + b );
		a1.set();
		System.out.println(a1.hae);
		
	}

}
           

先说结论:在方法中无法改变原参数的(交换之类的,属于形参的值传递,只会改变形参的引用指向),所以跟该类是否是final class无关,好多人说String类对象无法在方法中进行交换是因为String类是final class,答:并不是,因为Java的传递方式只有值传递,也就是说当方法被调用时,形参中被传递进了原参数的值,对于String对象而言,传进来的是堆中的字符串的地址。而形参进行交换时,只是将形参中存的地址值交换了,与原参数无关,原参数没变化,所以原参数中所存的地址值没变化,依然指向原来的堆中字符串地址。

再提一下关于不可变类:

不可变类:创建该类的实例后,该实例的实例变量无法被改变,即不可变类,其不提供更改属性值的方法,并使用 private final修饰该类的成员变量,private使得子类不可见,final使得不可改,一般提供一个带参数的构造器,以便于初始化

如果需要创建自定义的不可变类,可遵守如下规则:

(1)使用private final 修饰符来修饰该类的属性

(2)提供带参数构造器,用于根据传入参数来初始化类里的属性。

(3)仅为该类的属性提供getter方法,不要为该类提供setter方法,因为普通方法无法修改final修饰的属性

(4)如果有必要,重写Object类中的hashCode和equals方法。在equals方法根据关键属性来作为两个对象相等的标准,除此之外,还应该保证两个用equals方法判断为相等的对象的hashCode也相等。

(5)如果该类中含有引用类型成员变量,如果该成员变量可变,则该类必须要保护该可变变量,比如不利用已有的该可变类对象,而是新建一个可变类对象,比如

public class Person{

    private final Name name;//Name类可变

    public Person(Name name){

        this.name = new Name(name.getFirstName().name.getLastName());//重新创建一个name,保证即使传入的参数被改变,Person依然不变,因为Person中的name变量是新建的,不同于传入的那个,且无法从外界获得,也就无法更改了

    }

}

注意:final class不一定是不可变类!!!!final class仅仅代表不可继承!!!!但是String,Integer等包装类都是不可变类,他们都是final class

关于为什么不可变类标准要声明为 final class,就是为了禁止继承,防止可变的子类对象向上转型为父类对象,然后作为参数传入了形参为父类的方法中,这样,原本为不可变的父类设计的方法就有了漏洞,根据里氏代换原则,子类能够替代父类功能,不可变父类无法保证其子类是不可变类,所以干脆禁止继承,保证了不可变性!另一个原因,父类的方法设置成了final可以保证不会被重写,但是子类仍然可以重载此方法,此时不可变性便被破坏了,所以一定要final class。

关于Integer,由于缓存池的存在,通过new Integer()出来的对象每次都是新的,而通过valueOf则首先检查缓存池,若缓存池中有,则直接返回缓存池中的那个元素。缓存池中只有-128~127

例子如疯狂Java讲义 185页内容,

Integer in1 = new Integer(6);

Integer in2 = new Integer(6);//新的对象,in1!=in2

Integer in3 = Integer.valueOf(6);//将6放入缓存

Integer in4 = Integer.valueOf(6);//直接从缓存中取,in4 == in3

Integer,和String由于是不可变类,所以在方法中改变他们无效,他们只是指向了另一个对象,而不是对原对象进行改变,

等同于直接改变指向例如 b=c,所以无法改变原参数的引用,

StringBuffer和StringBuilder可以改变原参数指向的地址的存储的值,

但是形参更改指向对于原参数没有影响,比如:b = c;这样对于原参数没有任何影响,

对于自定义的类来说,与StringBuffer和StringBuilder类似,在方法中直接改变指向是无效的,比如第12行,对原参数无影响,但是改变指向地址所存的值时,是有效的,

与StringBuffer和StringBuilder一样,比如10~12行

以上都针对形参值传递问题,对于set方法之类的对象调用方法,比如12~15行,48行a1.set();

其中对于对象a1的操作是对于a1本身的操作,(等同于this,只不过省略了),可以对本对象的变量进行引用地址更改和赋值