1. 自增自减问题
1.1 i++
对于i++
,在程序执行时会先将i
赋值给变量,等语句执行完以后i
的值再加1。如:
1 | public static void main(String[] args) { |
运行结果:
1 | a的值为:1 |
1.2 ++i
对于++i
,程序运行时先将 i
的值加1再赋值给变量,当然语句执行结束后i
的值。如:
1 | public static void main(String[] args) { |
运行结果:
1 | b的值为:2 |
1.3 综合问题
1 | public static void main(String[] args) { |
运行结果:
1 | i的值为:4 |
分析:
通过javap
命令对字节码进行反汇编:javap -c MyCode.class
,得到如下信息:
1 | // Method descriptor #15 ([Ljava/lang/String;)V |
其中,0 iconst_1
和1 istore_1 [i]
表示源码中的int i = 1;
语句:首先静态变量1被初始化,随后赋值给了变量i
。
istore_x
表示将操作树栈的结果赋值给对应的局部变量。
iload_x
表示将局部变量的值压入操作数的栈中。
iinc
表示局部变量自身自增。
imul
表示求乘积。
iadd
表示求和。
1.4 小结
- 表达式中,赋值(=)最后计算
- = 右边的从左到右依次加载值,并压入操作数栈
- 实际算哪个,看运算符的优先级
- 自增、自减操作都是直接修改变量的值,不经过操作数栈
- 最后的赋值操作之前,临时结果也是存储在操作数栈中
建议参考《JVM虚拟机规范》关于指令的部分。
2. 单例模式
Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
单:唯一
例:实例
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。例如:代表JVM运行环境的Runtime
类。
2.1 代码实现要点
实现单例模式的重要点:
某个类只能有一个实例;
- 代码实现:构造器私有化
它必须自行创建这个实例
- 代码实现:含有一个该类的静态变量来保存这个唯一的实例
必须自行向整个系统提供这个实例
代码实现:对外提供获取该实例对象的方式:1)直接暴露。2)用静态变量的get方法获取。
2.2 单例类型
2.2.1 饿汉式
直接创建对象,不存在线程安全问题
- 直接实例化饿汉式(简洁直观)
1 | /* |
- 枚举式(最简洁)
1 | /* |
- 静态代码块饿汉式(适合复杂实例化),适用于初始化一些配置文件。
1 | public class Singleton3 { |
2.2.2 懒汉式
延迟创建对象
- 线程不安全(适用于单线程)
1 | /* |
- 线程安全(适用于多线程)
1 | /* |
- 静态内部类形式(适用于多线程)
1 | /* |
3. 走台阶算法(循环实现)
编程题:
有n步台阶,一次只能上1步或2步,共有多少种走法?
3.1 思路分析
走第 1 个台阶
走第 1 个台阶
只有 1 种走法,即:走1步。
走第 2 个台阶
有 2 种走法,即:可以俩个 1 步,也可以走一个 2 步。
走第 3 个台阶
因为只能走 1 步或者 2 步,所以能走到最后一个台阶,一定是走到了前面某个台阶,再跨一步就到了最后一个台阶。
因此,能走到第 3 个台阶的之前一个台阶,只能以下俩种可能:
- 走到第 1 个台阶, 再走 2 步。
- 走到第 2 个台阶,再走 1 步。
依次类推
走第 n 个台阶(n >= 3)
因为只能走 1 步或者 2 步,所以只能走到最后一个台阶的情况,只能是下面这俩种可能:
走到第 n-2 个台阶了,再走 2 步。
走到第 n-1 个台阶了,再走 1 步。
从上述推理过程可以,推导出数学函数公式:
1 | f(1) = 1 |
这里的f(n)
函数表示走到第 n 个台阶能走到的可能数。
3.2 递归实现
1 | // 递归实现 |
递归实现的最大弊端在于,递归太深可能会导致栈溢出,同时递归调用很耗费CPU且耗时。
3.3 循环实现
在递归中,如:
1 | f(1) = 1 |
从上面的分析可以出:f(5)
中的f(1)+f(2)
已经计算了2 次。
这算是一种不必要的浪费,可以将f(1)+f(2)
的计算结果保存下来,在计算f(4)
的使用这个结果能更快。
所以每次都到的台阶的前 2 种可能记住,那么就省去再次计算。
1 | // 循环实现 |