java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以根据字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJB、 RMI等技术都是以此为基础的。
Java的序列化机制只序列化对象的属性值,而不会去序列化什么所谓的方法。其实这个问题简单思考一下就可以搞清楚,方法是不带状态的,就是一些指令,指令是不需要序列化的,只要你的JVM classloader可以load到这个类,那么类方法指令自然就可以获得。
序列化真正需要保存的只是对象属性的值,和对象的类型。
1、Object类的hashCode:返回对象的内存地址经过处理后的结构,由于每个对象的内存地址 (JVM虚拟出来的内存地址) 都不一样,所以哈希码也不一样.
两个对象要完全相同必须哈希值一样。比较用equal()
2、String类的hashCode:根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串内容相同,返回的哈希码也相同。
比较字符串内容相等。用equal()
3、Integer类的hashCode:返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样
比较包装数值类型相等。用equal()
4、简单类型比较相等。用==
为什么存在这两种类型呢?
一:我们都知道在Java语言中,new一个对象存储在堆里,我们通过栈中的引用来使用这些对象;但是对于经常用到的一系列类型如int,如果我们用new将其存储在堆里就不是很有效——特别是简单的小的变量。所以就出现了基本类型,同C++一样,Java采用了相似的做法,对于这些类型不是用new关键字来创建,而是直接将变量的值存储在栈中,因此更加高效。
二:Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到Object[]数组或集合中的操作等等。
注意:比如需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了
二者的区别:
简单一点说:
装箱就是自动将基本数据类型转换为包装器类型;
拆箱就是自动将包装器类型转换为基本数据类型。
public class Main { public static void main(String[] args) { //自动装箱 Integer total = 99; //自定拆箱 int totalprim = total; } }
详解
Integer total = 99; 执行上面那句代码的时候,系统为我们执行了: Integer total = Integer.valueOf(99); int totalprim = total; 执行上面那句代码的时候,系统为我们执行了: int totalprim = total.intValue();
装箱
1.首先来看看Integer.valueOf函数:
public static Integer valueOf(int i) { return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128]; }
它会首先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。
1.1 首先我们来看看Integer的构造函数:
private final int value; public Integer(int value) { this.value = value; } public Integer(String string) throws NumberFormatException { this(parseInt(string)); }
它里面定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。
第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。
1.2 下面看看**SMALL_VALUES[i + 128]**是什么东西
private static final Integer[] SMALL_VALUES = new Integer[256];
它是一个静态的Integer数组对象。
也就是说最终valueOf返回的都是一个Integer对象。
装箱总结:装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能。
拆箱
@Override public int intValue() { return value; }//直接返回value值即可。
1、i >= 128 || i < -128 =====> new Integer(i)
2、i < 128 && i >= -128 =====> SMALL_VALUES[i + 128]
private static final Integer[] SMALL_VALUES = new Integer[256]
;
SMALL_VALUES本来已经被创建好,
也就是说,在i < 128 && i >= -128会根据i的值返回已经创建好的指定的对象
然而在i >= 128 || i < -128是会创建不同的对象,
举例
public class Main { public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1==i2); //true System.out.println(i3==i4); //false } }
1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。
2、i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是它们会分别创建两个不同的对象,所以它们肯定不等。
public class Main { public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1==i2); //false System.out.println(i3==i4); //false } }
因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。
但是对于Double类型来说,我们就不能这样做,因为它在这个范围内个数是无限。
总结:在某个范围内的整型数值的个数是有限的,而浮点数却不是。所以在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。
另外一种情况:
public class Main { public static void main(String[] args) { Boolean i1 = false; Boolean i2 = false; Boolean i3 = true; Boolean i4 = true; System.out.println(i1==i2);//true System.out.println(i3==i4);//true } }
可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
可以看到它并没有创建对象,因为在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。
public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false);
其他情况
1 Integer num1 = 400; 2 int num2 = 400; 3 System.out.println(num1 == num2); //true 说明num1 == num2进行了拆箱操作
1 Integer num1 = 100; 2 int num2 = 100; 3 System.out.println(num1.equals(num2)); //true
equals源码:
1 @Override 2 public boolean equals(Object o) { 3 return (o instanceof Integer) && (((Integer) o).value == value); 4 }
它必须满足两个条件才为true:
1、类型相同
2、内容相同
我们传入的是一个int类型,所以首先会进行装箱,类型相同了,然后比较,返回true。
1 Integer num1 = 100; 2 int num2 = 100; 3 Long num3 = 200; 4 System.out.println(num1 + num2); //200 5 System.out.println(num3 == (num1 + num2)); //true 6 System.out.println(num3.equals(num1 + num2)); //false
1、当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
2、对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:
1 @Override 2 public boolean equals(Object o) { 3 return (o instanceof Long) && (((Long) o).value == value); 4 }
它必须满足两个条件才为true:
1、类型相同
2、内容相同
上面返回false的原因就是类型不同。
1 Integer num1 = 100; 2 Integer num2 = 200; 3 Long num3 = 300; 4 System.out.println(num3 == (num1 + num2)); //true
首先对num3进行拆箱(执行num3的longValue得到基础类型为long的值300)
然后对num1和mum2进行拆箱(分别执行了num1和num2的intValue得到基础类型为int的值100和200)
然后进行相关的基础运算。
int num1 = 100; int num2 = 200; long mum3 = 300; System.out.println(num3 == (num1 + num2)); //true
int num1 = 100; int num2 = 200; long mum3 = 300; System.out.println(num3 == num1); //false
所以,当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)
总结:
1、需要知道什么时候会引发装箱和拆箱
2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
3、equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱
4、当两种不同类型用==
比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱。