学习了差不多一个多星期了,涉及了相当多的概念,是时候进行一轮复习了。
一、第一章:对象入门
这一章奠定了后面的内容,基本上是概念。主要需要理解:
(1)为什么需要接口?
使用接口,可以带来什么好处?
1.接口是用来实现对对象的控制和访问,是一种规范
2.接口最主要是写给编译器看的
Java是强类型语言,在调用方法时,编译器会检查这个方法是否存在,当定义了接口时,就确保该方法一定会存在。
如果有接口,只要调用接口中的方法,编译器就会直接去实现类去找对应的实现方法了。如果直接实现实现类,那么编译器只能通过反射去实现类里找,这个方法存在吗?使用反射执行效率就会变差了。
3.松耦合。接口负责设计,实现类负责实现。可以随时修改实现类,方便更改。
4.不需要过度设计
(2)对象的创建和存在时间
java和c++的对象创建有什么不同?为什么要这样创建?创建之后如何销毁?
1.c++中对象的创建
静态创建。C++认为程序的执行效率是最重要的一个问题,所以它允许程序员作出选择。
为获得最快的运行速度,存储以及存在时间可在编写程序时决定,只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可。
2.java中对象的创建
动态创建。除非进入运行期,否则根本不知道到底需要多少个对象,也不知道它们的存在时间有多长,以及准确的类型是什么。这些参数都在程序正式运行时才决定的。若需一个新对象,只需在需要它的时候在内存堆里简单地创建它即可。
由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。
由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。
3.关于内存对象的销毁
c++中是手动回收。或者使用其他库提供的gc方案。java中分栈和堆。栈中自动释放,而堆中有gc策略进行回收。
(3)错误机制
java中的违例处理有什么好处?为什么要这样设计?
1.对大多数错误控制方案来说,最主要的一个问题是它们严重依赖程序员的警觉性,而不是依赖语言本身的强制标准。
所以java强制使用语言特性,来减少出错情况。
2.采用的是独立的执行路径,所以不会干扰我们的常规执行代码。这样便使代码的编写变得更加简单,因为不必经常性强制检查代码。
报错的代码部分和业务逻辑部分分离开,能及时发现何处发生错误。
3.违例不能被忽略,所以肯定能在某个地方得到处置。
不然你就休想运行java程序!
4.利用违例能够可靠地从一个糟糕的环境中恢复。此时一般不需要退出,我们可以采取某些处理,恢复程序的正常执行。
显然,这样编制出来的程序显得更加可靠。Java的违例控制机制与大多数程序设计语言都有所不同,违例控制模块是从一开始就封装好的,所以必须使用它!
如果没有自己写一些代码来正确地控制违例,就会得到一条编译期出错提示。这样可保证程序的连贯性,使错误控制变得更加容易。
(4)多线程
为什么要使用多线程?
1.为了最大限度地利用硬件资源,提高程序执行的效率。
2.程序员不必关心到底使用了多少个处理器。
程序在逻辑意义上被分割为数个线程,可以通过多线程的规则简单地给处理器调用,而不是一个处理器一定要完全处理完一个线程。
3.必须注意一个问题:共享资源!
所以java中需要特别注意共享和锁等问题。
4.java中对多线程处理的支持是在对象这一级支持的,所以一个执行线程可表达为一个对象。
如果线程可以以对象形式存在,就有多种使用线程的方法了。
二、第二章:一切都是对象
(1)用句柄操纵对象
创建对象时,jvm中发生了什么?
- 如果是主类型,那么将直接在栈中开辟对象。
- 句柄会创建在栈中,之后指向堆中开辟的对象。
- 我在这里妄言一句,只要是new动态创建的,那就都在堆分配内存去了。如果是基本类型,就在栈里分配内存。(这个待求证)
其实总结到这里,我想到了一个问题…
1 2 3 4 |
Integer integer1 = new Integer(1); Integer integer2 = 1; int num1 = 1; int num2 = new Integer(1); |
这四句代码有什么不同?有什么相同?
我感觉前三行都是可以理解的:
- 第一二行实现的效果是一样的,都在堆中开辟了内存存放1这个对象,然后interger1和interger2这两个句柄在栈中开辟内存,然后各自指向各自的对象。
- 第三行因为int是主类型,所以会直接在栈中开辟空间,然后整个存入。
- 至于第四行,感觉有点懵逼了,要怎么去理解呢?为什么堆中的对象可以把值赋给一个主类型?
后来查了一下,发现了一个新的知识点:java的自动装箱和拆箱机制,可以解释上述代码。
自动拆箱和自动装箱是对于基本数据类型来说的,编译器会帮你自动转换Integer类型与Int类型。
- 比如Integer i = 1; 这样子写是可以编译通过的。
- 但是在JDK1.5之前.必须这么写Integer i = new Integer(1),这个就是自动装箱。
- 同理在JDK1.5之后,int i = new Interger(1) ;,这样子就是自动拆箱。
java的装箱和拆箱机制留在后面提及吧,感觉是java的一个方便的机制,避免出现低级错误。
这里还有一个需要理解的知识:创建过程在jvm中发生了什么?这个原理太重要了(已经深入到jvm了)我在之前的文章《think in java 第二章 一切都是对象 第一部分》中举了一个string的例子,可以回顾。
确实需要另外开篇文章,来记录一下我对这个过程的理解。
(2)对象保存在哪里?
对象保存在哪里?为什么要这样存?
事到如今,我还是觉得这个问题以后单独分析比较好,毕竟和计算机组成等概念紧密结合了。
(3)主要类型
为什么主要类型要放在栈中?为什么句柄要放在栈中?有什么好处?
1.之所以要特别对待,是由于用new创建对象(特别是小的、简单的变量)并不是非常有效,因为new将对象置于“堆”里。
对于这些类型,Java采纳了与C和C++相同的方法。也就是说,不是用new创建变量,而是创建一个并非句柄的“自动”变量。这个变量容纳了具体的值,并置于堆栈中,能够更高效地存取。
2.Java决定了每种主要类型的大小,没有变化,非常稳定,不容易出问题。
就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。这种大小的不可更改正是Java程序具有很强移植能力的原因之一。
3.主类型比较小比较简单,而且需要频繁调用。如果用new去动态生成并且在堆中分配空间,开销比较大划不来,干脆就直接在栈里面进行分配吧,可以高效存储。
int xie = 1,这里的xie就不是句柄了,只是一个指向栈中的1的指针(或者说是变量?)而已。
4.我的理解:句柄也是同理
句柄很问题,比较小,需要经常被调用,所以也可以在栈中进行分配,实现高效存储。
(4)java数组
java数组和c++中有什么不同?数组是如何创建的?为什么要使用数组?
1.不同之处?
在C和C++里使用数组是非常危险的,因为那些数组只是内存块。若程序访问自己内存块以外的数组,或者在初始化之前使用内存(属于常规编程错误),会产生不可预测的后果。所以在C++里,应尽量不要使用数组,换用标准模板库里更安全的容器。
一个Java数组可以保证被初始化,而且不可在它的范围之外访问。由于系统自动进行范围检查,所以必然要付出一些代价:针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。但由此换回的是更高的安全性,以及更高的工作效率。为此付出少许代价是值得的。
总结一下:
c++中只是单纯内存块,无校验,无安全保障,开销小。java中的数组保证被初始化,有校验,有安全保障,但是开销大。
2.数组是如何创建的?
创建对象数组时,实际创建的是一个句柄数组。而且每个句柄都会自动初始化成一个特殊值,并带有自己的关键字:null(空)。一旦Java看到null,就知道该句柄并未指向一个对象。正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为null的一个句柄,就会在运行期报告问题。
总结一下:
对象数组是句柄数组,句柄如果没有初始化成特殊值(没有指向实际对象?那句柄是在什么时候进行的初始化呢?)就会是null。
正式使用前,必须为每个句柄都分配一个对象。若试图使用依然为null的一个句柄,就会在运行期报告问题。(这个部分我其实还是没搞清楚,回头再补充更正吧)
3.这里有个问题还没有搞清楚
初始化数组时jvm中到底发生了什么?如何回收数组资源?jvm如何保证数组的初始化?
(5)作用域
作用域的概念是什么?起到了什么作用?
Java对象不具备与主类型一样的存在时间。用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。所以假若使用下面这段代码:
1 2 3 |
{ String s = new String("a string"); } /* 作用域的终点 */ |
那么句柄s会在作用域的终点处消失。然而,s指向的String对象依然占据着内存空间。在上面这段代码里,我们没有办法访问对象,因为指向它的唯一一个句柄已超出了作用域的边界。
我的理解:
这里的句柄s在超出作用域的时候,就在栈中释放了。而这个String对象,则继续存在堆内存中,等待gc策略的触发,通过gc的回收。
(6)字段和方法
这个其实没什么特别需要注意的。
我的理解:
字段这个说法要注意,如果没赋默认值。局部变量未初始化会在编译期间得到一个警告(虽说不能算严重错误)。
(7)static
static是什么?有什么使用方式?
1.是什么?
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。
只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
我的总结:
要注意static部分的创建时机,在创建对象步骤中的获取class之后,就开始对static部分进行初始化了。多个实例的static变量会共享同一块内存区域。
2.有什么使用方式?
常见三种:
- 字段。
- 方法。
- 代码块。
三、总结
通过这次回顾,感觉我对对象的理解又加强了。前后可以贯通起来,能大致知道是什么,为什么了。