1. 基本介绍
1.1 Java是什么
应用领域:
- Java SE: 是本课程的核心,也是java语言的核心,可用来开发桌面软件(现在很少用这个功能)
 - Jave ME: 开发功能机小游戏,已淘汰
 - Jave EE: 用来开发服务端程序,奠定了Java的地位(最广泛的应用领域)
 - Android: 开发智能机程序
 - 大数据: 首选Java开发,升华普通数据
 
运行机制:
前提: 程序员编写的是源代码,计算机最终运行的是机器码。
如何生成机器码:
- 编译型: 程序运行前,由编译器将源代码完全翻译成机器码。
 - 解释型: 程序运行前,由解释器将源代码逐行翻译成机器码,并运行。
 
Java采用的方式: 混合型,先由编译器完全翻译成字节码,再由解释器逐行翻译成机器码,并运行。
为什么: 在90年代,受制于硬件的限制,编译型语言的速度快于解释型语言,但是编译型语言对于跨平台(不同的架构)要花额外的精力去修改,因此Java既有编译型语言运行快的优点,也有解释型语言“一次编写,到处运行”的优势。
从图上可以看出,不同架构的机器有自己的解释器,因此对程序员更友好,不用考虑太多跨平台的问题。
 
1.2 Java Kit开发工具
- JDK(Java Development Kit): 开发工具包,编写Java程序
 - JRE(Java Runtime Environment): Java运行环境,用户基本运行程序的必要
 
1.3 第一个Java程式
- 编写源文件: 后缀.java
 - 编译Java源代码: 
javac First.java得到字节码文件First.class - 运行Java字节码: 
java First程序运行 - Java虚拟机负责解释运行class文件
 
2. 变量
2.1 什么是变量
数据类型用于指导计算机,该为这块内存分配多大的空间。
使用变量时:
- 声明变量, 
int a; - 初始化变量, 
a = 10; - 使用变量, 
a = 200 
需要注意;
- 未声明的变量不能使用
 - 为初始化的变量不能使用
 - 存入的数据必须符合声明的类型
 
2.2 基本数据类型
基本数据类型和它们的范围
| 类型 | bit位 | 范围 | 
|---|---|---|
| byte | 8位 | -2ˆ7~2ˆ7-1 | 
| short | 2字节 | -2ˆ15~2ˆ15-1, 大概3万多 | 
| int | 4字节 | -2ˆ31~2ˆ31-1,大概21亿 | 
| long | 8字节 | -2ˆ63~2ˆ63-1 | 
 整型变量:
1.直接写出的值叫直接量
2.int直接量可直接赋值给long类型,long类型的直接量需要以l/L结尾
// 1.直接写出的值叫直接量 System.out.println(9); System.out.println(3.14); // 2.int // 直接写出的整数是int类型的直接量 int i = 123; // 赋值超出范围,编译报错 // i = 3000000000; // 运算超出范围,结果错误 i = 2147483647; // 0111... System.out.println(i + 1); // 1000... 输出: -2147483648 // 3.byte, short, long // int直接量可直接赋值给long类型 long l = 2; // long类型的直接量需要以l/L结尾 l = 3000000000L; // int直接量可直接赋值给byte,short,但不能超出其范围 byte b = 4; // b = 128; // b = i; short s = 2; // s = 32768; // s = i;
浮点型变量:
先将小数转为科学计数法的形式,再将一串二进制的整数分为三段,分别记录小数的符号,尾数,指数。
float:1位符号,8位指数,23位尾数 -3.4*10ˆ38 ~ 3.4*10ˆ38
double:1位符号,11位指数,52位尾数 -1.8*10ˆ308 ~ 1.8*10ˆ308
浮点数的进度不是无限的,是有误差的,如果业务需求是高精度计算,则需要用java.math.BigDecimal这个包
// 4.float,double // 直接写出的小数是double类型的直接量 double d = 0.3; // float类型的直接量需要以f/F结尾 float f = 0.8F; // 浮点数不精确 System.out.println(300000.02f); System.out.println(300000.03f); System.out.println(3.3f + 0.1f); 输出: 300000.03; 300000.03; 3.3999999
字符型变量:
ASCII 表示256个字符
Unicode 可以表示 65535个字符,含括所有的东亚字符,java采用的是这种字符集
// 5.char char c1 = 'A'; char c2 = 0b01000001; // 65 char c3 = '\u0041'; //unicode(16进制) // 转义字符 char c4 = '\''; char c5 = '\t'; char c6 = '\\'; char c7 = '\n'; // 字符串 String str = "Hello World."; System.out.println(str);
布尔类型:
真假: true/false
2.3 基本类型的关系
数据的相互转换:
前提是: 除了布尔类型外,其余7种均为数字
关键是:与数据类型的范围有关
方式:
- 自动类型 (小变大,能装下)
 - 强制转换类型 (大变小,可能会溢出)
 
// 1.自动类型转换 char c = 'A'; // 自动转换,因为int的范围比char大 int i = c; System.out.println(i); long l = 100L; double d = l; System.out.println(d); // 2.强制类型转换 int ii = 65; char cc = (char) ii; System.out.println(cc); double dd = 3.14; long ll = (long) dd; System.out.println(ll); // 3.运算时的自动类型转换 // 1)先将byte,short,char转为int // 2)再将int转换为更大的类型 char ccc = 'A'; int iii = 100; double ddd = 3.14; System.out.println(ccc + iii + ddd);
要注意的坑⚠️⚠️⚠️
// 注意如下的坑 byte b = 8; //(b-3)是表达式 // 如果不加(byte) 会报错: error: incompatible types: possible lossy conversion from int to byte b = (byte) (b - 3); // 下列情况是默认规则,不是类型转换 byte k = 5; short m = 6; char n = 7;
3. 运算符
3.1 运算符的分类
运算符的分类
- 算术运算符; 2. 关系运算符; 3. 逻辑运算符;
 - 赋值运算符; 5. 三元运算符; 6. 字符串运算符; 7. 位运算符;
 
1.算术运算符
先看算术运算符
// ++a 先自增,后运算 int a = 100; System.out.println(++a + 100); System.out.println(a); 输出: 201, 101 // b++ 先运算,后自增 int b = 100; System.out.println(b++ + 100); System.out.println(b); 输出: 200, 101
关系运算符: >= == <= 等
3.逻辑运算符 和 短路现象
逻辑运算符: 与&& 或|| 非!, 都作用布尔类型数据上&& 或者 || 存在短路现象⚠️  当某个逻辑值可以直接推算出结果时,后续的表达式不会被执行
int age = 32; double salary = 40000.00; // &&, || 存在短路现象 // salary > 50000.00 为false 因此直接返回false System.out.println(salary > 50000.00 && ++age < 30); System.out.println(age); 输出: false 32 // salary > 30000.00 为true 因此直接返回true System.out.println(salary > 30000.00 || ++age < 30); System.out.println(age); 输出: true 32
4.赋值运算符
赋值运算符: = += -= *= /= %=y=x=1 等价于 x =1; y=x;
5.三元运算符
一定是一个赋值表达式,要有返回值
// 判断是否成年 boolean isAdault = age < 18 ? "未成年" : "成年"; // 判断阿里巴巴工资,谁高于5万,就统一砍为5万,否则维持不变 double salary = salary > 50000.00 ? 50000.00 : salary;
6.字符串运算符
字符串 + 任意类型数据 --> 字符串
// 6.字符串运算符
String s1 = "她的年龄:" + age;
System.out.println(s1);
返回: 她的年龄:32
String s2 = "她的工资:" + salary;
System.out.println(s2);
返回: 她的工资:50000.0
System.out.println("" + 100 + 200);
System.out.println(100 + 200 + "");
返回: 100200 300 7.位运算符和补码的原理
在计算机内部,负数是以补码形式存储的
期望采用加法器电路,来实现减法运算
- 原码
 
最高位存储符号(0 - 正,1 - 负),其他位存数据的绝对值。 如:[-2]原 = 1000 0010
- 反码
 
[正数]反 = [正数]原,[负数]反 = [负数]原 除符号位外按位取反。 如:[-2]反 = 1111 1101
- 补码
 
[正数]补 = [正数]原,[负数]补 = [负数]反 + 1。 如:[-2]补 = 1111 1110
单例推演:
1 – 1 = 1 + (-1) = 0 原码:0000 0001 + 1000 0001 = 1000 0010 = -2 反码:0000 0001 + 1111 1110 = 1111 1111 = -0 补码:0000 0001 + 1111 1111 = 0000 0000 = 0
推理:
在时钟系统中,从六点到4点可以有两种拨法: 后退两格到4,或者前进10格到4
4 = 6 + (-2) = (6 + -2 + 12) mod 12 =(6 + 10) mod 12 12是这个系统的模,-2和10同余
因此在计算机系统中,比如在计算8位数据时, 0(0000 0000) ~ 255(1111 1111)当从最大值加一后,就变成了0,因此256就是这个8位系统的模。
255 + (-3) = 255 + (256-3) mod 256 = (255 + 253) mod 256 = 508 mod 256 = 252 -3 的同余数就是 256+(-3) 其中-3 := 253(1111 1101)
在实际上,计算机里面都统一是正数,上述的补码系统是人为的一种简化计算体系,为了方便计算同余数,实际还是加法器的计算
 
注意⚠️其中的,有符号右移位操作,是符号位在最高位补齐!
移位运算的最小单位是 int,如果是byte移位也会自动转换成int型
java自带的十进制数转二进制
// 如何将十进制转换为二进制 System.out.println(Integer.toBinaryString(5));
位运算的规则和特殊技巧,参考我的博客
4. 输入和流程控制
4.1 Scanner接收数据
// -->引入 import java.util.Scanner // 创建对象 Scanner scan = new Scanner(System.in); // 接收数据 scan.nextInt(), scan.nextDouble(), scan.nextLine(), ... // 停止输入 scan.close()
举个例子
// 创建Scanner对象
Scanner scan = new Scanner(System.in);
// 开始输入
System.out.println("请输入你的名字!");
String name = scan.nextLine();
System.out.println("请输入你的年龄!");
int age = scan.nextInt();
System.out.println("请输入你的学号!");
double salary = scan.nextDouble();
// 关闭Scanner
scan.close(); 4.2 if语句
分支语句
if (score >= 90) {
    System.out.println("A");
} else if (score >= 80) {
    System.out.println("B");
} else if (score >= 70) {
    System.out.println("C");
} else if (score >= 60) {
    System.out.println("D");
} else {
    System.out.println("E");
} 4.3 switch 语句
switch的语法还是有点生疏
switch (表达式) { 
    case 值1: {
    ...
    break; 
    }
    case 值2: { 
    ...
    break; 
    }
    default: { 
    ...
    } 
} 注意⚠️switch里面的值是: byte, short, int, char, String, Enum. ,但不能是逻辑表达式
若不写break,只要有一个case命中,它就会顺序向下执行
switch (month) {
    case 1:
    case 2:
    case 3:
        System.out.println("第一季度");
        break;
    case 4:
    case 5:
    case 6:
        System.out.println("第二季度");
        break;
    case 7:
    case 8:
    case 9:
        System.out.println("第三季度");
        break;
    case 10:
    case 11:
    case 12:
        System.out.println("第四季度");
        break;
    default:
        System.out.println("错误的月份!");
} 4.4 if与switch对比
三元运算符 比较 if else:
三元运算符简洁但功能单一,if else里面可以放很多行,逻辑更多
if vs switch:
switch只能放表达式,只有数值匹配上才能执行分支,没有 if else 功能强大
5. 循环
- 循环条件
逻辑表达式,决定了是否执行循环体。 - 循环体
循环的主体,如果循环条件允许,该代码块将被重复执行。 - 迭代语句
改变循环条件中的变量,使得循环条件趋近于假,并最终为假,从而导致循环结束。 
5.1 while 循环
求一个十进制数有多少位
int num = 7;
// 记录整数的位数
int len = 0;
// 循环到除尽为止
while (num != 0) {
    len++;
    num /= 10;
    System.out.println("len=" + len + ", num=" + num);
}
// 不可能小于一位,考虑到num=0的情况!
len = len == 0 ? 1 : len; 5.2 do while 循环
先执行,再判断:
do{
    something;
} while(判断逻辑); 和while的区别就是,一定会先执行一次,再判断
// 循环到除尽为止, 不存在num=0的小bug
do{
    len++;
    num /= 10;
    System.out.println("len=" + len + ", num=" + num);
} while (num != 0); 5.3 for循环
for (初始化语句; 循环条件; 迭代语句){
    ...
} 例子一: 求一个数的阶乘
注意⚠️零的阶乘是1,输入的数必须是自然数
if (n < 0) {
    System.out.println("请输入一个自然数!");
} else if (n == 0) {
    System.out.println("0! = 1");
} else {
    // 记录累乘结果
    long s = 1;
    // i ∈ [1, n] 
    for (int i = 1; i <= n; i++) {
        s *= i;
    }
    System.out.println(n + "! = " + s);
} 例子二: 两个变量一起在for里面
for (int m = 0, n = 9; m < 10 && n > -1; m++, n--) {
            System.out.println("m = " + m + ", n = " + n);
    } 5.4 选择哪种循环
根据业务的需求,选择循环的方式,这里给出一个例子对比 do while 和 while循环
// 输入任意多个整数(负数代表输入结束), 求它们的平均数.
Scanner scan = new Scanner(System.in);
int n = 0; // 输入
int sum = 0; // 合计
int amount = 0; // 个数
do {
   n = scan.nextInt();
   if (n >= 0) {
       sum += n;
       amount++;
   }
} while (n >= 0);
// 或者
n = scan.nextInt();
while (n >= 0) {
    sum += n;
    amount++;
    n = scan.nextInt();
}
if (amount > 0) {
    double avg = (double) sum / amount;  
    System.out.println("平均数: " + avg);
}
scan.close(); 5.5 break 和 continue 关键字
break的用法:
结束循环,强制跳出循环体。一旦遇到 break,系统将完全结束该循环,开始执循环之后的代码。
// 判断一个数,是否是质数
// 质数是,只能被自己或者1整除的数
if (n <= 1) {
    System.out.println("请输入一个大于1的正整数!");
} else {
    // 是否为质数
    boolean b = true;
    // i ∈ [2, n-1]
    for (int i = 2; i < n; i++) {
        if (n % i == 0) {
            // 可以整除, 不是质数
            b = false;
            // 得出结果, 跳出循环
            break;
        }
    }
    System.out.println(n + (b ? "是质数" : "不是质数"));
} continue的用法:
忽略本次循环剩下的语句,接着开始下一次循环,并不会终止循环。
// 求0-n的所有的奇数的和
if (n <= 1) {
    System.out.println("请输入一个大于1的正整数!");
} else {
    // 合计值
    int sum = 0;
    // i ∈ [1, n]
    for (int i = 1; i <= n; i++) {
        if (i % 2 == 0) {
            continue;
        }
        sum += i;
    }
    System.out.println("合计值: " + sum);
}
} 5.6 嵌套循环
内层循环作为外层循环的循环体,
每次执行外层循环,内层循环都要完整的执行一遍。假设外层循环执行 m 次,内层循环执行 n 次,
则程序总的循环次数为二者的乘积关系,即 m*n 次。
// 1-100的所有的质数,请打印
for (int i = 2; i <= 100; i++) {
    // 判断i是不是质数
    boolean b = true;
    // j ∈ [2, i-1]
    for (int j = 2; j < i; j++) {
        if (i % j == 0) {
            b = false;
            break;
        }
    }
// 打印判断结果
if (b) {
    System.out.print(i + "\t");
} 5.7 死循环
1. 程序的漏洞
- 忘记写迭代语句;
 - 写了迭代语句,但是迭代语句向着趋近于终止的相反方向发展;
 
double d = 1;
while (d < 10) {
   System.out.println(d);
    // d 会越来越小,永远跳不出循环
   d *= 0.1;
}
// 可以构造的死循环
while (true) {
   System.out.println("Hello.");
}
// 可以构造的死循环
for (; ; ) {
   System.out.println("Hello.");
} 2. 刻意的营造
- 构造死循环,用以处理不确定次数的循环场景;
 - 在循环体内增加判断,当条件达成时利用 break 关键字强制结束循环;
 
// 求平均数,用户输入数字,知道负数结束
Scanner scan = new Scanner(System.in);
int n = 0; // 输入
int sum = 0; // 合计
int amount = 0; // 个数
while (true) {
    // scan.nextInt() 保证每次可以有输入
    n = scan.nextInt();
    if (n < 0) {
        break;
    }
    sum += n;
    amount++;
}
if (amount > 0) {
    double avg = (double) sum / amount;
    System.out.println("平均数: " + avg);
}
scan.close(); 5.8 变量作用域
循环语句中变量的有效范围:
- 在代码块内部声明的变量,只能在代码块内部使用;
 - 在代码块前面声明的变量,可以在代码块内部使用,也可在代码块后面使用;
 - for 循环上声明的变量,只能在循环体内部使用;
 - do while 循环体内部声明的变量,可以在循环体内部使用,不能在循环条件中使用;
 
int m = 0;
while (m < 10) {
    int n = 3;
    System.out.print(m * n + "\t");
    m++;
}
// 试图打印n,但是编译器无法解析,n的生存周期已经结束了
System.out.println(n); 6. 数组
6.1 数组和其遍历
数组的静态初始化,指明其存放的内容
type[] arrayName = {element1, element2, ...}; // 声明时初始化
int[] arr = {1,2,3};
//或者
int[] arr1 = new int[] {1,2,3}; 数组的动态初始化,默认存放的是 整数类型默认值为 0, 浮点类型默认值为 0.0, 字符类型默认值为 ’\u0000’, 布尔类型默认值为 false, 引用类型默认值为 null。
// 默认存放都是0 int[] arr = new int[3];
数组的长度,可以通过它的属性, arraryName.length访问得到
遍历数组的两种方式, for loop 和 foreach loop
// for loop
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]); 
}
// for each loop, 针对数组和集合
for (type variableName : array | collection) {
    System.out.println(variableName); 
} 6.2 数据的工具类
 
Arrays.copyOf是属于浅拷贝,若原数组内存放的是基本类型,则是值传递,若内部存放的是对象,则是引用传递,修改一个对象会引起原数组对象的修改。
6.3 内存中的数组
在java中,数组是引用类型,是指向数组连续内存的首地址
int[] arr1 = {10, 20, 30, 40, 50};
// 引用类型赋值
int[] arr2 = arr1;  
6.4 多维数组
初始化:
// 静态初始化
int[][] arr1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// 动态初始化
int[][] arr2 = new int[5][2]; 打印多纬数组:
Arrays.deepToString(arr);
本质是一维数组,可以不是绝对的矩形:
// 本质是一维
int[][] arr3 = new int[3][];
arr3[0] = new int[]{1, 2};
arr3[1] = new int[]{3, 4, 5};
arr3[2] = new int[]{6, 7, 8, 9}; 6.5 数组反转
不调用类方法,去底层实现数组反转
对称的交换可以减少一半的常数时间,相比于反序遍历
// 倒转数组
for (int i = 0; i < arr.length / 2; i++) {
    // arr[0] ~ arr[length-1-0]
    // arr[1] ~ arr[length-1-1]
    // arr[2] ~ arr[length-1-2]
    // arr[i] ~ arr[length-1-i]
    // tips: t = a; a = b; b = t;
    int temp = arr[i];
    arr[i] = arr[arr.length - 1 - i];
    arr[arr.length - 1 - i] = temp;
} 6.6 数组求平均值
需求:
- 输入任意多个整数,直到输入负数为止;
 - 计算这些整数的平均值,并打印出结果;
 
// 初始数组长度设为5
int[] arr = new int[5];
// 当前输入数字下标
int index = 0;
// 循环输入,直到遇到-1停止
int sum = 0;
int len = 0;
double average = 0;
Scanner sc = new Scanner(System.in);
while (true){
    int cur = sc.nextInt();
    if(cur == -1){
        sc.close();
        average = len==0 ? 0 : (double)sum/len;
        break;
    }
    arr[index++] = cur;
    sum += cur;
    len++;
    // 数组扩容二倍
    if(len == 5){
        arr = Arrays.copyOf(arr, arr.length * 2);
    }
} 6.7 数组冒泡排序
最基本的排序思想:
- 每一轮比较出一个最大(小)值,将其移动到数组最右(左)端;
 - 对于长度为 N 的数组,需历经 N-1 轮才能完成所有的比较。
 
// 轮次: length -1
for (int i = 0; i < arr.length - 1; i++) {
    // 比较的次数
    // 0: 0 ~ length - 1
    // 1: 0 ~ length - 1 - 1
    // 2: 0 ~ length - 1 - 2
    // i: 0 ~ length - 1 - i
    // 循环的边界
    // j = arr.length - 1 - i - 1
    // j + 1 = arr.length - 1 - i
    for (int j = 0; j < arr.length - 1 - i; j++) {
        // 从大到小: 将小的数从左向右转移.
        if (arr[j] < arr[j + 1]) {
            int temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
        }
} 7. 方法
7.1 参数
函数的参数分为,基本类型参数 和 引用类型参数.
在main函数中,程序执行是由栈帧控制的,swapNum传入的是基本类型,在各自的栈帧内会为a和b分配内存空间,一个函数的操作不会影响另一个。
在函数swapArray中传入的是引用类型的参数,实际传入的是引用,指向相同的内存空间,因此会相互影响修改。
public static void main(String[] args) {
    int a = 1;
    int b = 2;
    swapNum(a, b);
    System.out.println("a = " + a + ", b = " + b);
    int[] arr = {1, 2, 3, 4, 5};
    swapArray(arr);
    System.out.println("arr = " + Arrays.toString(arr));
}
// 交换两个数
public static void swapNum(int m, int n) {
    int t = m;
    m = n;
    n = t;
    System.out.println("m = " + m + ", n = " + n);
}
// 交换数组首尾的两个数
public static void swapArray(int[] array) {
    if (array == null || array.length < 2) {
        return;
    }
    int t = array[0];
    array[0] = array[array.length - 1];
    array[array.length - 1] = t;
} 7.2 可变参数
什么是可变参数
- 在定义方法时,可以声明数量不确定的参数,这样的参数叫可变的参数;
 - 一个方法最多声明一个可变参数,并且该参数必须位于参数列表的末尾;
 - 可变参数的本质是一个数组,调用时可以分开传入多个值,也可以直接传入一个数组。
 
◼ 如何声明可变参数
修饰符 返回值类型 方法名(类型 参数1, 类型 参数2, ..., 类型... 参数N) { }
public static void main(String[] args) {
    System.out.println(sum());
    System.out.println(sum(1));
    System.out.println(sum(1, 2));
    System.out.println(sum(1, 2, 3));
    System.out.println(sum(new int[]{1, 2, 3, 4, 5}));
}
public static int sum(int... nums) {
    int s = 0;
    for (int num : nums) {
        s += num;
    }
    return s;
} 7.3 方法重载
◼ 什么是方法重载
在同一个类里,定义多个名称相同、参数列表不同的方法,叫做方法重载。
若只是参数名字不一样不是方法重载,参数名字和类型一样但返回值类型不一样不是方法重载
◼ 方法重载的作用
对于调用者而言,多个重载的方法就像是一个方法,便于记忆、便于调用。
总结:
重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
参考资料
如: System.out.println(), Arrays.toString(), Math.abs() 都是方法重载
// java中Arrays.toString()的源码,针对不同类型的参数,重载了toString这个方法
public static String toString(int[] a) {
    if (a == null)
        return "null";
    int iMax = a.length - 1;
    if (iMax == -1)
        return "[]";
    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; ; i++) {
        b.append(a[i]);
        if (i == iMax)
            return b.append(']').toString();
        b.append(", ");
    }
}
public static String toString(float[] a) {
    if (a == null)
        return "null";
    int iMax = a.length - 1;
    if (iMax == -1)
        return "[]";
    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; ; i++) {
        b.append(a[i]);
        if (i == iMax)
            return b.append(']').toString();
        b.append(", ");
    }
} 7.4 调试程序
idea里面设置断点,调试程序,常用的快捷方式
执行下一步,调入方法内部,执行到下个断点

京公网安备 11010502036488号