基本数据的类型的大小是固定的,4类八种,各个表示大小是固定的,java为了跨平台。
非基本类型的Java对象,其大小是个问题?
Object ob = new Object();
一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。
4byte+8byte
4byte:Java栈中保存引用的所需要的空间
8byte:Java堆中对象的信息。
因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。
Class NewObject {
int count;
boolean flag;
Object ob;
}
其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,就是内存分为大端和小端,内存对齐的知识,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。
基本类型的包装类型的大小
这种包装类型已经成为对象了,因此需要把他们作为对象来看待.包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化.
值得说明的是,java是可以直接处理基本类型的,但是在有些情况下我们需要将其作为对象来处理,这时就需要将其转化为包装类了.所有的包装类(Wrapper Class)都有共同的方法,他们是:
(1)带有基本值参数并创建包装类对象的构造函数.如可以利用Integer包装类创建对象,Integer obj=new Integer(145);
(2)带有字符串参数并创建包装类对象的构造函数.如new Integer(“-45.36”);
(3)可生成对象基本值的typeValue方法,如obj.intValue();
(4)将字符串转换为基本值的 parseType方法,如Integer.parseInt(args[0]);
(5)生成哈稀表代码的hashCode方法,如obj.hasCode();
(6)对同一个类的两个对象进行比较的equals()方法,如obj1.eauqls(obj2);
(7)生成字符串表示法的toString()方法,如obj.toString().
转换关系:
基本类型——>包装器类
Integer obj=new Integer(145);
包装器类——>基本类型
int num=obj.intValue();
字符串——>包装器类
Integer obj=new Integer(“-45.36”);
包装器类——>字符串包装器类
String str=obj.toString();
字符串——>基本类型
int num=Integer.parseInt(“-45.36”);
基本类型——>字符串包装器类
String str=String.valueOf(5);
自动装包/拆包大大方便了基本类型数据和它们包装类地使用。
自动装包:基本类型自动转为包装类.(int >> Integer)
自动拆包:包装类自动转为基本类型.(Integer >> int)
在JDK1.5之前,我们总是对集合不能存放基本类型而耿耿于怀,现在自动转换机制,解决了我们的问题。
int a = 3;
Collection c = new ArrayList();
c.add(a);//自动转换成Integer.
Integer b = new Integer(2);
c.add(b + 2);
这里Integer先自动转换为int进行加法运算,然后int再次转换为Integer.
一、向上转型与向下转型。
对象类型的转换在Java语言平台中经常遇到,主要包括向上转型与向下转型操作。程序开发人员需要熟练掌握这两个转型的方法以及其中容易出错的地方。如何来了解这两个转型的区别呢?笔者认为,以一个现实的例子作为比喻,可能会更加的容易理解。
如现在有动物、鸟类、燕子三个名词,他们之间有什么关系呢?通常我们都会说,燕子是特殊的鸟类,或者说燕子是鸟类的一种。为此,从对象的定义来看,鸟类就是一个父类,而燕子就是一个子类。或者说,燕子对象就是一个鸟类对象。笔者这里要强调的一点就是,由于燕子是鸟类的一个对象,所以鸟类所具有的特性燕子全部具有。而燕子所具有的特性(如迁徙)则鸟类不一定都具有。在这个例子中,燕子也是一种鸟类。为此可以将燕子的对象堪称是一个鸟类的对象。这种方法在Java语言环境中就叫做“向上转型”。从这个例子中可以看出,向上转型是一个从较抽象类型的类(鸟类)向比较具体的类(燕子)过度。由于具体类(燕子)具有抽象类(鸟类)的全部特性,所以在这个转换过程中是不会有问题的。这就好像一个逻辑判断题说燕子是鸟类的一种,其具有鸟类的全部特性。这个命题至少到现在为止是完全正确的。
但是,在实际工作中,我们还经常会遇到向下转型的情况。也就是说从一个抽象类中(鸟类)引用具体类(燕子)中的对象。也就是说,我们可以说燕子是鸟类的一种。但是现在反过来,如果说鸟类就是燕子,那显然就是以偏概全了,因为燕子并不具有其他鸟类的特性。如鸽子的特性燕子就没有。所以,在应用程序开发中,如果将父类对象赋值给子类的对象,就可能有问题。如果硬要这么做的话,则很有可能发生编译器错误。因为父类对象并不一定是子类的实例。这是什么意思呢?即所说的鸟类(父类对象)并不一定是子类对象(燕子)。因为鸟类对象还有可能是鸽子、白鹭等等。所以,如果将父类对象给子类对象的话,那么就会出现问题。
二、如何实现向下转型?
由于向上转型一般都是安全的,即将一个子类对象直接赋值给父类对象,一般被认为是安全的,如燕子是鸟类在哪里都是成立的。所以在向下转型时不需要采用其他的关键字,我们常常把向下转换叫做隐式转换。但是在这里向上转换是一种不安全的转换方式,如说鸟类就是燕子,这种说法无论在哪里都说不过去。为此默认情况下,进行向下转型时,往往会发生编译器错误。
一般情况下,越是具体的对象所具有的特性越多。如燕子的特性就比鸟类的特性多的多。而越抽象的对象反而具有的特性越少,因为其只具有一些抽象对象的共性特征。在进行向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题。为此在向下转型时,必须确保转换后不会出现问题,即具体对象的特性在抽象对象中也全部具备,只有如此才能够进行转换。而且即使满足这个条件,编译器也不不能够进行隐式转换。而是需要采用关键字进行强制转换。如子类对象名字=(子类名)父类对象名字。如果上面这个语法,就可以实现对象类型的强制转换。
笔者在此强调一遍,在进行向下转型时一定要进行强制转换。即通过子类对象名字=(子类名)父类对象名字进行赋值,而不能够向向上转型那样进行隐式转换。
三、确保向下转型的准确性。
从以上分析中可以看出,向下转型往往被认为是不安全的。当在程序中执行向下转型操作的时候,如果父类对象不是子类对象的实例,就会发生编译器错误。所以在执行向下转型之前要先作一件事情,就是判断父类对象是否为子类对象的实例。也就是说,先要想一想,燕子就是鸟类这个命题是否成立(在某些特定的情况下这个伪命题可能会成立,如燕子的特性与鸟类的特性完全一致)。只有如此,向下转型才不会出现问题。在进行向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题。但是,如果两个转换的对象特性范围一样大的话,可那么就不会有问题了。
在应用程序开发中,往往通过操作符instanceof来完成这个判断。即可以利用这个操作符来判断是否一个雷实现了某个接口,也可以用来判断一个实例对象是否属于一个类。这个操作符的基本格式为:A(某个类的对象引用) instanceo(操作符号) B (某个类的名称)。这个操作符最后返回的是一个布尔值。如果是false的话,则说明A对象不是类B的实例对象。相反,如果返回的值是true的话,则说明对象A是类B的实例对象。
四、向下转型的注意事项。
在进行向下转型时,需要注意以下几方面的内容:
一是要慎用向下转型。由于向下转型容易出问题,为此不到万不得已的时候,最好不用使用向下转型。条条道路通罗马,如果在编程之前,合理规划类,往往可以避免向下转型的发生。只有其他路走不通的情况下,才考虑通过向下转型的技术来解决问题。
二是在进行向下转型的时候,需要做两件事情。一是一定要使用instanceof操作符来判断转型的合法性,即判断父类对象是否为子类对象的实例。这就好像在编写四则运算时,要判断除数不为零一样。这是必须要做的。也是程序员必须要养成的一个习惯。在进行向下转型时,就自然而然会想到需要进行这个判断。只有如此,应用程序的错误才能够降低。而且还能够满足不同的需求。二是需要注意向上转型与向下转型的区别。一般情况下,向上转型往往被认为是安全的,所以在Java语言平台中向上转型采用的是隐式转型。而向下转型由于特性范围大小的不同,为此往往被认为是不安全的。故系统默认情况下进行向下转型时必须采用强制转型的方式。如果不采用强制转型,则即使满足向下转型的条件,其也会发生编译器错误。所以需要切记,向下转型必须要采用强制转型。
三是需要做好备注等注释工作。由于像向下转型等操作是容易出现问题的地方。为此在进行类似的操作时,最好在行注释或者块注释中能够进行说明。这对于后续的维护与代码的升级是很有帮助的。好记性不如烂笔头。如果没有做好相关注释的话,这次可能没有问题,但是下次再代码升级或者其他原因需要调整或者重写原有的代码时,就可能会因为疏忽而导致转型的失败。
最后笔者再次提醒各位程序员,向上转型大家可以放心大胆的用。但是在使用向下转型技术时,大家要慎重,要按部就班(先判断后使用)的进行操作。
对象引用类型分为强引用、软引用、弱引用和虚引用
强引用:
就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收
软引用:
软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。
弱引用:
弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。
强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。
引用类型
StringBuffer str = new StringBuffer("Hello world");
首先,new StringBuffer(”Hello world”)在堆里申请了一坨内存,把创建好的StringBuffer对象放进去。
其次,StringBuffer str声明了一个指针。这个指针本身是存储在栈上的(因为语句写在函数中),可以用来指向某个StringBuffer类型的对象。或者换一种说法,这个指针可以用来保存某个StringBuffer对象的地址。
最后,当中这个等于号(赋值符号)把两者关联起来,也就是把刚申请的那一坨内存的地址保存成str的值。
StringBuffer str2 = str;
实际上就是把str的地址复制给str2,记住,是地址的复制,StringBuffer对象本身并没有复制。所以两个指针指向的是同一个
语句“if(str2 == str)”时,只是判断两个指针的值(也就是对象的地址)是否相等,并不是判断被指向的对象是否内容相同。实际上两个指针的值相同,则肯定是指向同一个对象(所以对象内容必定相同)。但是两个内容相同的对象,它们的地址可能不一样(比如克隆出来的多个对象之间,地址就不同)
final常量的问题
final只是修饰指针的值(也就是限定指针保存的地址不能变)。至于该指针指向的对象,内容是否能变,那就管不着了。
final StringBuffer strConst = new StringBuffer();
可以修改它指向的对象的内容, strConst.append(” “);
但是不能修改它的值strConst = null;
传参的问题
System.out.println(str);
可以认为传进函数的是str这个指针,指针说白了就是一个地址的值,说得再白一点,就是个整数。按照这种理解,就是传值的方式。也就是说,参数传递的是指针本身,所以是传值的。
可以认为传进去的是StringBuffer对象,按照这种理解,就是传引用方式了。因为我们确实是把对象的地址(也就是引用)给传了进去。
不论是传引用还是传值,都可以讲得通,关键取决于你是如何看待参数所传递的东西。