2022年9月28日 10:51 周三转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!
基础概念及常识
#
Java语言特点
- 面向对象(封装、继承、多态)
- 平台无关性(Java虚拟机)
- 等等
JVM并非只有一种,只要满足JVM规范,可以开发自己专属JVM
JDK与JRE
- JDK,JavaDevelopmentKit,包含JRE,还有编译器(javac)和工具(如javadoc、jdb)。能够创建和编译程序
- JRE,Java运行时环境,包括Java虚拟机、Java类库,及Java命令等。但是不能创建新程序
字节码,采用字节码的好处
- Java中,JVM可以理解的代码称为字节码(.class文件),不面向任何处理器,只面向虚拟机
- Java程序从源代码到运行的过程

- java代码必须先编译为字节码,之后呢,.class–>机器码,这里JVM类加载器先加载字节码文件,然后通过解释器进行解释执行(也就是字节码需要由Java解释器来解释执行)
- Java解释器是JVM的一部分
编译与解释并存
- 编译型:通过编译器将源代码一次性翻译成可被该平台执行的机器码,执行快、开发效率低
- 解释型:通过解释器一句一句的将代码解释成机器代码后执行,执行慢,开发效率高
- 如图

为什么说 Java 语言“编译与解释并存”?
这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class
文件),这种字节码必须由 Java 解释器来解释执行。
Java与C++区别
- 没学过C++,Java不提供指针直接访问内存
- Java为单继承;但是Java支持继承多接口
- Java有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存
注释分为 单行注释、多行注释、文档注释

标识符与关键字
标识符即名字,关键字则是被赋予特殊含义的标识符
自增自减运算符
当 b = ++a
时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++
时,先赋值(赋值给 b),再自增(自己增加 1)
continue/break/return
continue
:指跳出当前的这一次循环,继续下一次循环。break
:指跳出整个循环体,继续执行循环下面的语句。return
用于跳出所在方法,结束该方法的运行。
变量
- 成员变量和局部变量
- 成员变量可以被
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及 static
所修饰;但是,成员变量和局部变量都能被 final
所修饰 - 从变量在内存中的存储方式来看,如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用 static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡(即方法栈弹出后消亡)。
- final必须显示赋初始值,其他都自动以类型默认值赋值
- 静态变量:被类所有实例共享
字符型常量与字符串常量区别
...
2022年9月23日 10:31 周五代码
#
static int s;
int i;
int j;
{
int i = 1;
i++;
j++;
s++;
}
public void test(int j) {
j++;
i++;
s++;
}
public static void main(String[] args) {
Exam5 obj1 = new Exam5();
Exam5 obj2 = new Exam5();
obj1.test(10);
obj1.test(20);
obj2.test(30);
System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);
System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);
}
运行结果
#
分析
#
就近原则
#
代码中有很多修改变量的语句,下面是用就近原则+作用域分析的图

...
2022年9月22日 21:20 周四编程题
#
有n步台阶,一次只能上1步或2步,共有多少种走法
分析
#
分析
n = 1,1步 f(1) = 1
n = 2, 两个1步,2步 f(2) = 2
n = 3, 分两种情况: 最后1步是2级台阶/最后1步是1级台阶,
即 f(3) = f(1)+f(2)
n = 4, 分两种情况: 最后1步是2级台阶/最后1步是1级台阶,
即f(4) = f(2)+f(3)
也就是说,不管有几(n)个台阶,总要分成两种情况:最后1步是2级台阶/最后1步是1级台阶,即 f(n)= f(n-2) + f(n-1)
递归
#
public static int f(int n){
if(n==1 || n==2){
return n;
}
return f(n-2)+f(n-1);
}
public static void main(String[] args) {
System.out.println(f(1)); //1
System.out.println(f(2)); //2
System.out.println(f(3)); //3
System.out.println(f(4)); //5
System.out.println(f(5)); //8
}
- debug调试
方法栈
f(4)—->分解成f(2)+f(3)
f(2)—返回-
f(3)—f(2)返回—f(1)返回 【f(3)分解成f(2)和f(1)】
方法栈的个数:

使用循环
#
public static int loop(int n){
if (n < 1) {
throw new IllegalArgumentException(n + "不能小于1");
}
if (n == 1 || n == 2) {
return n;
}
int one=2;//最后只走1步,会有2种走法
int two=1;//最后走2步,会有1种走法
int sum=0;
for(int i=3;i<=n;i++){
//最后跨两级台阶+最后跨一级台阶的走法
sum=two+one;
two=one;
one=sum;
}
return sum;
}

...
2022年9月22日 10:24 周四代码
#
public class Exam4 {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 2;
int[] arr = {1, 2, 3, 4, 5};
MyData my = new MyData();
change(i, str, num, arr, my);
System.out.println("i = " + i);
System.out.println("str = " + str);
System.out.println("num = " + num);
System.out.println("arr = " + Arrays.toString(arr));
System.out.println("my.a = " + my.a);
}
public static void change(int j, String s, Integer n, int[] a,
MyData m) {
j+=1;
s+="world";
n+=1;
a[0]+=1;
m.a+=1;
}
}
结果
...
2022年9月22日 08:50 周四代码
#
public class Son extends Father{
private int i=test();
private static int j=method();
static {
System.out.print("(6)");
}
Son(){
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test(){
System.out.print("(9)");
return 1;
}
public static int method(){
System.out.print("(10)");
return 1;
}
public static void main(String[] args) {
Son s1=new Son();
System.out.println();
Son s2=new Son();
}
}
public class Father {
private int i=test();
private static int j=method();
static {
System.out.print("(1)");
}
Father(){
System.out.print("(2)");
}
{
System.out.print("(3)");
}
public int test() {
System.out.print("(4)");
return 1;
}
public static int method() {
System.out.print("(5)");
return 1;
}
}
输出:
...
2022年9月21日 14:22 周三特点
#
- 该类只有一个实例
- 该类内部自行创建该实例
- 能向外部提供这个实例
几大方法
#
饿汉式
#
随着类的加载进行初始化,不管是否需要都会直接创建实例对象
public class Singleton1 {
public static final Singleton1 INSTANCE=new Singleton1();
private Singleton1() {
}
}
枚举
#
枚举类表示该类型的对象是有限的几个
public enum Singleton2 {
INSTANCE
}
使用静态代码块
#
随着类的加载进行初始化
public class Singleton2 {
public static final Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
}
如图,当初始化实例时需要进行复杂取值操作时,可以取代第一种方法

懒汉式
#
延迟创建对象
public class Singleton4 {
//为了防止重排序,需要添加volatile关键字
private static volatile Singleton4 INSTANCE;
private Singleton4() {
}
/**
* double check
* @return
*/
public static Singleton4 getInstance() {
//2 先判断一次,对于后面的操作(此时已经创建了对象)能减少加锁次数
if (INSTANCE == null) {
//如果这里不加锁会导致线程安全问题,可能刚进了判断语句之后,执行权被剥夺了又创建好了对象,
//所以判断及创建对象必须是原子操作
synchronized (Singleton4.class) {
if (INSTANCE == null) {
//用来模拟多线程被剥夺执行权
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果这个地方不加volatile,会出现的问题是,指令重排 1,2,3是正常的,
//会重排成1,3,2 然后别的线程去拿的时候,判断为非空,但是实际上运行的时候,发现里面的数据是空的
//1 memory = allocate();//分配对象空间
//2 instance(memory); //初始化对象
//3 instance = memory; //设置instance指向刚刚分配的位置
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
}
使用静态内部类
#
public class Singleton6 {
private Singleton6(){
}
private static class Inner{
private static final Singleton6 INSTANCE=new Singleton6();
}
public static Singleton6 getInstance(){
return Inner.INSTANCE;
}
}
- 只有当内部类被加载和初始化的时候,才会创建INSTANCE实例对象
- 静态内部类不会自动随着外部类的加载和初始化而初始化,他需要单独去加载和初始化
- 又由于他是在内部类加载和初始化时,创建的,属于类加载器处理的,所以是线程安全的
2022年9月21日 10:04 周三题目
#
int i=1;
i=i++;
int j=i++;
int k = i+ ++i * i++;
System.out.println("i="+i);
System.out.println("j="+j);
System.out.println("k="+k);
讲解
#
对于操作数栈和局部变量表的理解
#
对于下面的代码
反编译之后,查看字节码
0 bipush 10
2 istore_1
3 bipush 9
5 istore_2
6 iload_1
7 istore_2
8 return
如下图,这三行代码,是依次把10,9先放到局部变量表的1,2位置。
之后呢,再把局部变量表中1位置的值,放入操作数栈中
最后,将操作数栈弹出一个数(10),将数值赋给局部变量表中的位置2
如上图,当方法为静态方法时,局部变量表0位置存储的是实参第1个数
(当方法为非静态方法时,局部变量表0位置存储的是this引用)
对于下面这段代码
int i=10;
int j=20;
i=i++;
j=++j;
System.out.println(i);
System.out.println(j);
编译后的字节码
...