Java对象的创建,持有Java对象,区分对象和类。

首先弄清楚两个关键字:
static静态。可以修饰内部类,成员变量,方法,代码块。static和实例化是互斥的,因为他表示独一份。不管是内部类,变量,方法,还是代码块,都只存在一份于Java虚拟机中,不依附对象的实例化而存在。(代码块的意义是只执行一次,不是说存在于虚拟机中)
static修饰的方法,不能够被重写(final方法也有这个效果)。同时其可以在不创建实例时直接使用。
final不可修改。final在修饰原始数据类型时即表示这个类型是个常量了,不会变。在修饰其它类型的变量时,实际上是表示这个变量的引用是final的。引用不能再指向别的地址,变量本身的内部数据还是可变的。final修饰的变量必须提供初始化,要么在定义时就赋值要么就在构造函数中。
final修饰方法时,该方法不能被重写(static方法也有这个效果)。编译器对待final方法的时候不一样会提供运行效率,这是因为final方法不需要去进行动态绑定,编译器就可以为其生成更高效的代码。
final还可以用在方法头中修饰参数f(final Map m),这时候在方法体内不能出现影响m的final性质的代码。
final还可以修饰类和内部类,如果一个类是final的,那么这个类不能被继承,然后类里的方法自然也默认都是final的。


接下来简单了解Jvm存储模式的基本知识点,引用Thinking in Java中的说法:

  1. 寄存器–Registers。又称程序计数器。CPU的内部的存储,这是最快的存储位置,但是数量十分的有限,在java中,寄存器是编译器根据需要来进行分配的,我们并没有直接的控制权。
  2. 栈–The Stack。堆栈属于常规RAM区域。处理器可以通过“堆栈指针”来直接直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。 在java中,如果需要上移或者下移指针,就需要准确知道所有数据的长度和存在时间。这限制了灵活性,所以java【不会把对象】创建到栈中。而【对象的引用】是要放在栈中的,当然还有其他一些东西(各种基本数据类型)。
  3. 堆(内存堆)–The Heap。一种常规用途的内存池(也在RAM 区域),其中保存了【Java 对象】。和栈不同,Heap最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!
  4. 静态存储(java方法区). 也是在RAM区域,但是是“位于固定位置”。类class的字节码加载的信息,包括类中的静态成员数据放置在该区域。将随时等候调用。这就是文章开头所说的用static修饰的内容,随时等候调用,所以是不依赖实例化对象存在的。Java 对象本身永远都不会置入静态存储空间。
  5. 常数存储–Constant storage.(也是方法区的一部分) 永远不会改变,通常置于程序内部。PS:thinking in java里的描述很费解。 更适当的说法应该是常量池:用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池除了常量池的内容外,还可以动态性的放入常量如String.intern()的使用。
  6. 非RAM存储,即非内存形式,可能会成为流式对象发送给别人,或者存储在磁盘。

再说说class。java.lang.Class是一个特殊的对象。我们编译后的程序都是.class。Class的目的是建立起一个对象的类型,用来决定一个类的外观与行为。任何一个类(包括内部类),都有一个Class对象对应于这个类。在这个Class对象中保存着实例化该类所需要的基本信息。


了解一个对象的创建过程,是进阶Java技巧的必要知识,这里只是最简单的描述:只有在用到一个对象,一个方法时,才会启动和创建有关的一系列操作。第一步就是去加载。

  1. 加载一个类所做的,是把.class文件以二进制读入内存,在Heap中创建一个java.lang.Class。创建的Class对象是单例的,无论是这个类有多少个对象,Class对象是唯一的。
  2. 第二步连接,执行类中的static部分,或为静态变量分配内存,并为静态变量分配默认值(注意不是代码里的赋值)。连接又可以细分为验证、准备、解析三步,不在此展开。
  3. 第三步初始化,执行静态代码块,并为静态变量初始化成代码里赋的正确值。至此-可以认为是完成了的初始化。
  4. 第四步,在这里开始开始进行对象的初始化。首先初始化成员变量,随后再调用构造函数。

    需要注意的问题:

    1.在继承体系中,首先是执行父类的类初始化,接着是子类的类初始化,接着是父类的对象初始化,再是子类的对象初始化。
    2.如果父类的构造函数,是一个带有参数的构造函数。那么子类必须实现一个构造函数并使用super(参数)作为第一句,这就是为了保证正确的初始化过程。
    3.内部类不会随外部类的加载而加载,而是在要用到内部类的时候才加载。见使用内部类实现延迟加载的单例模式。
    4.有一个费解的问题,如果一个类继承一个静态内部类,那么会发生什么?首先,静态内部类,没有说不能用new来创建自己的对象,也是可以的。只是说不需要。其次,若他被继承了,也就自然的遵循继承体系的初始化过程了。但是,实际中我们很少会写这么“高级”的代码吧?
    5.如果为一个类编写了构造函数,那么就不存在默认构造函数了。构造函数里调用别的构造函数使用this(),而且,这必须是第一句。目的也是保证在正确初始化前你不能做别的事。
    6.如果有人问你为什么可以实现upcasting,上面的流程就保证了子类肯定可以作为一个父类的引用来使用。


接下来再总结一下如何持有对象。
再来看看class,有一句很好的说法“通过class可以找到一个类的祖宗十八代”。通过一个class,能(和反射相关)拿到其实现的所有接口,其父类,其方法,其构造函数,其成员变量,所以我们通过class能够创建并持有对象。产生和new一样的效果。工厂模式中就有很多这样的创建方式。
只要能获得一个Class对象,就可以用newInstance()来创建一个对象。获得class的方法包括Class.forName(),类名.Class,对象.getClass()。

  1. 类名.Class,只做第一步加载。
  2. Class.forName(),会执行类的加载和初始化。
  3. 对象.getClass(),自不必说,连对象都已经存在了。
  4. 类名.this又是啥意思呢?一般看到的ClassName.this是因为在内部类中要使用外部类本身,就使用外部类名.this。显然这个this不会出现在内部类的静态方法中。

instanceof和isInstance(),都是看一个对象是否是一个class的实例,isAssignableFrom是两个class之间的比较,比较一个Class是否是自己或自己的子类,而不是操作实例对象。
isInterface判断一个class是否是接口。

创建对象的另一个方式是clone(),clone的前提是已经存在一个实例对象了。关于clone()方法要理解和注意:一个类要想clone,必须implements Cloneable,否则会抛出CloneNotSupportedException。 而因为有Object.clone()他可以不用重写。但是这样是没什么意义的。因为Object.clone()是protected,只能在继承他的对象内部使用。另外一个原因就是浅层复制。所以在使用clone时:1.实现cloneable接口。2.重写clone()方法包括内部成员的clone方法从而完成深层复制。3.几乎肯定要使用super.clone()作为重写方法的第一句。

一定要注意super.clone()的意思只是调用父类的一个clone方法(总会调用成功,因为最上层的父类Object肯定有这个方法),仅此而已,绝对没有说这个方法就是只clone父类那部分。事实上,object.clone所做的事情是按位复制完整的这个继承对象,也就是在哪个类的clone方法内使用super.clone实际上clone的就是这个类的对象。
ps:序列化也可以实现复制,但是,效率比clone低很多。


关于ClassLoader,JVM加载类到java虚拟机,使用的都是ClassLoader。JVM自己已经实现了三种ClassLoader分别是:Bootstrap(底层native代码实现),Extension,System。依次对应加载路径为:/lib,< Java_Runtime_Home >/lib/ext,CLASSPATH。ClassLoader使用的是双亲委托模式工作:
双亲委派机制描述 (ps:双亲不是指有两个家长…)
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
每一个被加载的类都有一个加载他的ClassLoader的引用通过getClassLoder获取,也可以使用setClassLoader来设置自定义的类加载器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值