截尾和舍入
float / double 转为 int 总会被截尾
public void findById() {
double above = 0.7, below = 0.4;
float fabove = 0.7f, fbelow = 0.4f;
System.out.println("above"+ (int) above);
System.out.println("below"+ (int) below);
System.out.println("fabove" + (int) fabove);
System.out.println("fbelow" + (int) fbelow);
}
//0
//0
//0
//0
如果想四舍五入,要调用round
long a = Math.round(above);
变量提升
通常在表达式中最大的数据类型决定了表达式最终的数据类型。
这里的大指的是数据类型的存储容量。
比如 long 和 int 运算,结果是 long
char / byte / short 的容量都比 int 小,所以它们和 int 的运算结果是 int。
流程控制
do-while
do-while , do 后面的语句至少会执行一次,即便 while 条件 为 false
do {
System.out.println("1223");
}while (false);
逗号操作符
for(initialization; Boolean-expression; step)
逗号操作符只有在 for() 语句中才用到,可以如下使用, 初始化无数的变量,但是这些变量必须是同一类型:
for(int i=1,j = i+10;i<5;i++,j=i*2){
System.out.println("i " + i + " j " + j);
}
// 1 11
// 2 4
break / continue
break, 退出当前循环,不执行循环中剩余部分
continue, 停止执行当前的迭代,然后退回循环的起始处,开始下一次迭代
标签
Java 的标签只能放在迭代语句之前,中间不能插别的语句。
这个语句我 debug 跟进去,发现和我想的不太一样。
使用场景
标签和 break / continue 配合使用,能够停止嵌套循环。——挺实用的。
int i = 0;
outer:
for(;true;){
inner:
for(;i<10;i++){
System.out.println("i = "+i);
if(i == 2){
System.out.println("continue");
continue;
}
if (i == 3){
System.out.println("break");
i++;
break;
}
if(i == 7){
System.out.println("continue outer");
i++;
continue outer;
}
if(i==8){
System.out.println("break outer");
break outer;
}
for(int k=0;k<5;k++){
if(k == 3){
System.out.println("continue inner");
continue inner;
}
}
}
}
switch
适当的在case 中不加 break,可以起到或的效果。
public void testSwitch(){
Random random = new Random(47);
for(int i = 0;i< 100;i++){
int c = random.nextInt(26) + 'a';
System.out.println((char)c + "," + c + ":");
switch (c){
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
System.out.println("vowe1");
break;
default:
System.out.println("consonant");
}
}
}
构造器
构造器这个概念是 C++ 首先提出来的,类在实例化时编译器会先调用构造器,以确保实例能正确初始化。
构造器必须和类同名,并且构造器没有返回值。
构造器可以重载,也就说是说1个类可以有多个构造器,这些构造器的参数不同。
如果没有构造器,那编译器在编译的时候会自动创建一个内容为空构造器。
this 关键字
this 用来充当返回值,返回实例本身
this / self 在方法内部表示将来实例化时的那个实例。所以需要指明对当前对象的引用时,需要使用 this 关键字。
class Leaf{
private int i = 0;
Leaf increment(){
i++;
return this;
}
public void printResult(){
System.out.println(i);
}
}
@Test
public void testLeaf(){
Leaf leaf = new Leaf();
leaf.increment().printResult();
}
跟 self 一样,返回 this 就可以搞出链式操作来浪了。
leaf.increment().increment().increment().increment().increment().increment().printResult();
this 在构造器中调用构造器
如果一个类有多个构造器,可能有时需要在一个构造器里面调用另外一个构造器,this 能做到。
注意,1个构造器里只能用 1个this。
class Flower{
int petalCount = 0;
String s = "initial value";
Flower(int petals){
petalCount = petals;
System.out.println("Constructor"+ petalCount);
}
Flower(String ss){
s = ss;
System.out.println("Constructor"+ss);
}
Flower(String s, int petals){
this(petals);
// this("sss");
this.s = s;
System.out.println("String & int args");
}
}
当变量名与属性名重复的时候,使用 this.属性名的方式加以区分;如果不重复则按照惯例省略 this
static
static 方法就是没有 this / self 的方法。static 方法最主要的用途就是用类来直接调用静态方法;当然,实例也可以调用静态方法
垃圾回收
1. 引用计数
引用计数是一种简单但速度很慢的垃圾回收技术。
每个对象都有一个引用计数器,当有引用连接至对象时,引用计数加1.
class Foo{
}
Foo f1 = new Foo();
// f1 引用计数器 -> 1
Foo f2 = new Foo();
// f1 引用计数器 -> 1
当一个对象实例的某个引用超过了生命周期或者被设置为1个新值时,对象的引用计数器减1。
class FooSon extends Foo{}
f2 = new FooSon();
// f2 引用计数器 -> 1-1=0;
垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用计数为 0 时,就释放占用的空间。
缺陷:当对象出现循环引用时,就会实现垃圾无法回收
class Dog{
private Tail tail;
}
class Tail{
private Dog dog;
}
public void beatDog(){
Dog newDog = new Dog();
Tail newTail = new Tail();
newDog.tail = newTail;
newTail.dog = newDog;
}
但是引用计数从来没有用于 Java 虚拟机。
在一些更快的模式中,垃圾回收器并非基于引用记数技术。它们依据的思想是:对任何 “活” 的对象,一定能最终追溯到其存活在堆栈或静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。由此,如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有 “活” 的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是此对象包含的所有引用,然后是此对象包含的所有引用,如此反复进行,直到 “根源于堆栈和静态存储区的引用” 所形成的网络全部被访问为止。你所访问过的对象必须都是 “活”的。注意,这就解决了 “交互自引用的对象组” 的问题 —— 这种现象根本不会被发现,因此也就被自动回收了。
在这种方式下, Java 虚拟机将采用一种自适应的垃圾回收技术。至于如何找到存活对象,取决于不同的 Java 虚拟机实现。有一种做法名为 停止—复制(stop-and-copy)。显然这意味着,先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的对象从当前堆复制到另外一个堆,没有被复制的全部是垃圾。当对象被复制到新堆上时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按照前述方法简单、直接地分配新空间了。
前述方法:Java 的堆更像一个传送带,每分配一个新对象,它就往前移动一格。这意味着对象存储空间的分配速度非常快。Java 的 “堆指针”只是简单地移动到尚未分配的区域,其效率比的上 C++ 在堆栈上分配空间的效率。
当把对象从一处搬到另外一处时,所有指向它的那些引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到。
对于这种所谓的 “复制式回收器” 而言,效率会降低,这有两个原因:
首先,得有两个堆,然后得在这两个分离的堆之间来回复制,从而使得维护比实际需要多出一倍的空间。某些 Java 虚拟机对问题的处理是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。
第二个问题在于复制。程序进入稳定状态之后,可能只会产生少量的垃圾。但是复制式回收器依然会将所有内存自一处复制到另一处,非常浪费。为了避免这种情形,一些 Java 虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种工作模式(即 “自适应”)。这种模式称为 标记—清扫,Sun 公司早期版本 Java 虚拟机使用了这种技术。对一般用途而言,“标记=清扫” 方式速度非常慢,但是当你知道只会产生少量垃圾的时候,它的速度就非常快了。
“标记-清扫” 所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找到所有存活的对象。每当它找到一个存活的对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续的空间的话,就得重新整理剩下的对象。
“停止-复制” 的意思是这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时,程序将被暂停。在 Sun 公司的早期文档中会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但是事实上垃圾回收器在 Sun 公司早期版本的 Java 虚拟机中并非以这种方式实现的。当可用内存数较低时,Sun 版本的垃圾回收器会在暂停运行程序,同样,“标记-清楚” 工作也必须在程序暂停的情况下才能进行。
如前文所述,在这里所讨论的 Java 虚拟机中,内存分配以较大的 “块” 为单位。如果对象较大,它会占用单独的块。严格来说,“停止-复制” 要求在释放旧有对象之前,必须先把所哟存活对象从旧堆复制到新堆,这将导致大量内存复制行为。有了块之后,垃圾回收器在回收的时候就可以往废弃的块里拷贝对象了。每个块都用相应的代数(generation count)来记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。来给回收器会定期进行完整的清理动作——大型对象仍然不会被复制(只是其代数会增加),内涵小型对象的那些块则被复制并整理。Java 虚拟机会进行监视,如果所有对象很稳定,来给回收器的效率降低的话,就切换到 “标记——清扫” 方式;同样,Java 虚拟机会跟踪 “标记—清扫”效果,要是堆空间出现很多碎片,就会切换回 “停止-复制” 方式。这就是 “自适应” 技术。
Java 虚拟机中有很多附加技术用以提升速度。尤其是与加载器操作有关的,被称为(Just-In-Time, JIT)编译器技术。这种技术可以把程序全部或部分翻译成本地机器码(这本来是 Java 虚拟机的工作),程序运行速度因此得以提升。当需要装载某个类(通常是在为该类创建的的第一个对象)时,编译器会先找到 .classs 文件,然后将该类的字节码装入内存。此时,有两种方法可供选择。一种就是让即时编译器编译所有的代码。但是这种做法有两个缺陷:这种加载动作散落在整个程序的生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这将导致页面调度,从而降低程序速度。另一种做法称为惰性评估(lazy evaluation),意思是即时编译器只在必要的时候才编译代码。这样,从不被执行的代码也就压根不会被 JIT 所编译。新版 JDK 中的 Java HotSpot 技术就是采用了类似的方法,代码每次执行的时候都会做一些优化,所以执行次数越多,它的速度就越快。