Java 语言基础
任务一:初识计算机和Java语言
Jdk的目录结构
- bin目录 - 该目录下主要存放JDK的各种工具命令。
- conf目录 - 该启录下主要存放jdk的相关配置文件。
- include目录 - 该目录下主要存放了一些平台的头文件。
- jmods目录 - 该目录下主要存放了JDK的各种模块。
- legal目录 - 该目录下主要存放了JDK各模块的授权文档。
- lib目录 - 该目录下主要存放了JDK工具的一些补充jar包和源代码。
Java开发的常用工具
-
文本编辑器(TE,Text Editor)
记事本、Notepad++、Edit Plus、UltraEdit、…
-
集成开发环境(IDE,Integrated Development Environment )
Jbuilder、NetBeans、Eclipse、MyEclipse、IDEA、…
Java11新特性之简化的编译运行
使用java xxx.java 进行编译运行,打印最终结果(慎用):
如果当前目录已存在要执行的java文件的class文件,会报错,因为已经有人抢干了这种状态下java命令要干的的工作了。
常用的快捷键
- windows+d 回到桌面
- windows+e 打开计算机
- windows+tab 切换任务
- alt+tab 切换任务
环境变量的配置
基本概念:通常情况下可执行文件只能在该文件所在的路径中使用,为了使得该可 执行文件可以在任意路径中使用,则需要将该文件所在的路径信息配置到 环境变量Path中。
注意:低版本的jdk环境变量还需要配置CLASSPATH,新版本已经不需要配置CLASSPATH,但是为保证向下兼用问题最好还是要保留设置。
跨平台原理
个人总结认为主要还是因为Java字节码可以通过JVM翻译为具体平台能够执行的机器指令。(由于Sun定义了JVM规范)依赖于不同的操作系统大多都提供了JVM实现(即具体的JVM虚拟机),才使得相同的一个字节码文件可以在不同的系统上运行,从而使Java有了“一次编译,到处使用”的美名。
任务二:变量和数据类型
变量
声明变量的本质就是在内存中申请一个存储单元,由于该存储单元中的数据内容可以发生改变,因此得名为"变量" 。
由于存放的数据内容大小不一样,导致所需存储单元的大小不一样,在 Java语言中使用数据类型加以描述,为了便于下次访问还需要给该变量指定一个名字,用于记录该变量对应的存储单元。
标识符的命名法则(笔试)
- 由数字、字母、下划线以及$等组成,其中数字不能开头(举例:因为如果一个变量名为:125L,Java编译器就分不清它是数值还是变量名了)。
- 不能使用Java语言的关键字,所谓关键字就是Java语言用于表示特殊含义 的单词。
- 区分大小写,长度没有限制但不宜过长。
- 尽量做到见名知意,支持中文但不推荐使用。
- 标识符可以给类/变量/属性/方法/包 起名字
单个字节表示的整数范围(重中之重)
在计算机中单个字节表示八位二进制位,其中最高位(最左边)代表符号位, 使用0代表非负数,使用1代表负数,具体表示的整数范围如下:
- 非负数表示范围:0000 0000 ~ 0111 1111 => 0 ~ 127 => 0 ~ 2^7-1
- 负数表示范围:1000 0000 ~ 1111 1111 => -128 ~ -1 => -2^7 ~ -2^0
- 单个字节表示的整数范围是:-2^7 ~ 2^7-1,也就是-128 ~ 127.
注意:之所以正数范围只到127,可以理解为0占了一位
浮点类型
Java语言中用于描述小数数据的类型:float 和 double,推荐double类型
其中float类型在内存空间占4个字节,叫做单精度浮点数,可以表示7位 有效数字,范围:-3.403E38~3.403E38。
其中double类型在内存空间占8个字节,叫做双精度浮点数,可以表示15 位有效数字,范围:-1.798E308~1.798E308。
注意:之所以float和double与int和long的位数相等确表示的范围更大,是因为在底层二进制结构中,它们是以符号位,指数/阶码(整数10的次方)和小数/尾数组成的
Java程序中直接写出的小数数据叫做直接量,默认为double类型,若希望 表达float类型的直接量,则需要在直接量的后面加上f或者F.
笔试考点:
// 4.笔试考点
System.out.println(0.1 + 0.2);
// 0.30000000000000004 运算时可能会有误差,若希望实现精确运算则借助java.math.BigDecimal类型
字符类型
Java语言中用于描述单个字符的数据类型:char类型。如:‘a’、 '中’等。
其中char类型在内存空间中占2个字节并且没有符号位,表示的范围是: 0 ~ 65535,由于现实生活中很少有数据能够被单个字符描述,因此以后 的开发中更多的使用由多个字符串起来组成的字符串,使用String类型加 以描述,如:“hello”、 “奇点”等。
ASCII码表:
要求掌握的ASCII有:‘0’ - 48 ‘A’ - 65 ‘a’ - 97 空格 - 32 换行符 - 10
重点
在Java中,基本数据类型和String类型的变量作为参数向方法中传递时,传递的只是它们的值的拷贝,而不是引用地址,所以在方法中改变这些类型的参数,原变量是不会发生改变的,只有对象*(包括数组,数组也是对象哦!不然为什么要new呢?)*作为参数时向方法中传递的才是引用地址。
**但是注意:**Java中没有真正的引用传递 只有值传递!传引用参数指的还是原来的那个引用,但是Java里面参数类型是对象时是复制了原来的引用到一块新的内存,两者之间没有关系。
任务三:运算符
注意:/ 运算符会将结果的小数部分丢弃,若想保留:
// 让其中一个操作数乘以1.0即可(推荐)
System.out.println(5*1.0 / 2); // 2.5
System.out.println(5.0 / 2); // 2.5 当操作数为变量 a 时,直接 a.0 是错误的表示
赋值运算符的考点
// 5.笔试考点1
byte b1 = 10;
System.out.println("b1 = " + b1); // b1 = 10
//b1 = b1 + 2; // 错误: 不兼容的类型: 从int转换到byte可能会有损失 byte + int 相加结果还是int类型
//b1 = b1 + (byte)2; // 错误: 不兼容的类型: 从int转换到byte可能会有损失 byte + byte 相加结果还是int类型 编译器优化
//b1 = (byte)(b1 + 2); // 强制类型转换,将int类型转换为byte
b1 += 2; // 真正等价于b1 = (byte)(b1 + 2);
System.out.println("b1 = " + b1); // b1 = 12
System.out.println("-----------------------------------");
// 6.笔试考点2
//ia == 2; // - 表示判断变量ia的数值是否等于2
//2 == ia; // - 表示判断2是否等于变量ia的数值,从结果上来说等价,推荐该方式
//ia = 2; // - 表示将2赋值给变量ia,覆盖变量ia原来的数值
//2 = ia; // - 编译报错 错误: 意外的类型
移位运算符(了解)
- ‘<<’ 左移运算符,用于将数据的二进制位向左移动,右边使用0补充
- ‘>>’ 右移运算符,用于将数据的二进制位向右移动,左边使用符号位补充
- ‘>>>’ 表示逻辑右移运算符,用于将数据的二进制位向右移动,左边使用0 补充。
// 1.声明一个byte类型的变量并初始化
byte b1 = -13;
// 逻辑右移 对于非负数来说,逻辑右移和右移的效果一致
System.out.println(b1 >>> 2); // 1073741820
// 注意因为位运算结果也是默认使用int接收,所以右移时是在int大小即32位的二进制的最左边填0,如下:
// 先求 -13 在二进制中的表示byte类型:(13)0000 1101 => (取反)1111 0010 => (+1)1111 0011
// 当进行位运算时,用int接收:
// 1111 11...... 1111 0011 => (无符号右移)0011 11...... 1111 1100 => 2^31 - 1 - 2^30 - 3 = 1073741820
位运算符(了解)
- & 表示按位与运算符,按照二进制位进行与运算,同1为1,一0为0.
- | 表示按位或运算符,按照二进制位进行或运算,一1为1,同0为0.
- ~ 表示按位取反运算符,按照二进制位进行取反,1为0,0为1.
- ^ 表示按位异或运算符,按照二进制位进行异或运算,同为0,不同为1.(像是国内领结婚证,同性为假,异性为真,不管他国哦)
任务四:流程控制语句
switch case分支结构
- 计算变量/表达式的数值 => 判断是否匹配字面值1
- => 若匹配,则执行语句块1 => 执行break跳出当前结构
- => 若不匹配,则判断是否匹配字面值2
- => 若匹配,则执行语句块2 => 执行break跳出当前结构
- => 若不匹配,则执行语句块n
注意:在switch case中由于没有break,然后使得代码不能跳出来,虽然跟后面的case不匹配,但依然会执行case后面的代码直到遇到break跳出来,这种现象叫做case穿透。
for循环
案例题目:
使用for循环打印1-100的所有奇数,使用三种方式:
/*
* 编程使用for循环实现1 ~ 100之间所有奇数的打印
*/
public class ForNumTest {
public static void main(String[] args) {
// 1.使用for循环打印1 ~ 100之间的所有奇数
// 方式一:根据奇数的概念进行打印
for(int i = 1; i <= 100; i++) {
// 若当前i的数值是奇数时则打印,否则不打印 奇数就是不能被2整除的数,也就是对2取余的结果不为0
if(i % 2 != 0) {
System.out.println("i = " + i);
}
}
System.out.println("---------------------------------------------------");
// 方式二:根据等差数列的概念来打印 每两个数据之间相差2
for(int i = 1; i <= 100; i += 2) {
System.out.println("i = " + i);
}
System.out.println("---------------------------------------------------");
// 方式三:根据通项公式的规则来打印 2*i-1
for(int i = 1; i <= 50; i++) {
System.out.println("i = " + (2 * i - 1));
}
}
}
案例题目:
使用for循环打印三位数中所有水仙花数。所谓“水仙花数”即一个整数满足其值等于各个数位的立方和。如:153是一个水仙花数,因为153=13+53+3^3:
/*
* 编程使用for循环打印三位数中的所有水仙花数
*/
public class ForWaterTest {
public static void main(String[] args) {
// 1.使用for循环打印所有的三位数
for(int i = 100; i <= 999; i++) {
// 3.拆分三位数中的各个数位上的数字
// 123 / 100 = 1; 123 % 100 => 23 / 10 = 2; 123 % 10 = 3;
int ia = i / 100; // 拆分百位数
int ib = i % 100 / 10; // 拆分十位数
int ic = i % 10; // 拆分个位数
// 2.针对每个三位数都要判断该数是否为水仙花数,若是则打印,否则不打印
// 判断该数是否等于各个数位的立方和
if((ia*ia*ia + ib*ib*ib + ic*ic*ic) == i) {
System.out.println("i = " + i);
}
}
}
}
双重for循环的特点
- 外层循环用于控制打印的行数,内层循环用于控制打印的列数,外层循环改一下,内层循环从头到尾跑一圈。
- 在以后的开发中若需要打印多行多列时,需要使用双重循环。
- 多重循环不宜嵌套太多层,否则效率很低。一般到三重循环即可,最常 见的就是双重循环。
break关键字跳出多层循环
在嵌套的循环结构中,break用于退出所在循环体。
如果要退出外层循环体,需要使用标号的方式。
for (...) { outer: for (...) {
for(...) { for(...) {
break; break outer;
} }
} }
案例题目:
- 使用双重for循环打印2~100之间的所有素数。
- 当一个数只能被1和它本身整除时,这个数就叫做素数或质数。
注意:只需要判断2到该数的平方根即可,因为随着除数的增大商必然减小,会造成重复的判断
while循环和for循环比较
- while循环和for循环完全可以互换,当然推荐使用for循环。
- while循环更适合于明确循环条件但不明确循环次数的场合中。
- for循环更适合于明确循环次数或范围的场合中。
- while(true) 等价于 for( ; ; ) 都表示无限循环。
案例题目:
提示用户输入一个任意位数的正整数然后反向输出。
思路:
只需要采用从后往前取当前位的数值的方式,即先对该数进行对10取余得到个位,再对该数除以10丢弃掉个位,这时之前的十位就变成了个位,重复之前的过程,直到该数变为0即可停止运算,图解:
代码:
/*
* 编程使用while循环实现任意正整数的反向输出
*/
import java.util.Scanner;
public class WhileReverseTest {
public static void main(String[] args) {
// 1.提示用户输入一个正整数并使用变量记录 123
System.out.println("请输入一个正整数:");
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
// 2.使用while循环进行拆分并打印
//while(num > 0) {
//System.out.print(num % 10); // 拆分个位数
//num /= 10; // 丢弃个位数
//}
// 2.使用while循环拆分整数中的每个数字并记录到变量中
int res = 0;
int temp = num; // 指定变量作为num的替身
while(temp > 0) {
res = res*10 + temp % 10; // 3 32 321
temp /= 10; // 12 1 0
}
// 3.打印逆序后的结果
System.out.println(num + "逆序后的结果是:" + res);
}
}
do while循环
do-while循环主要用于至少执行一次循环体的场合中。
笔试考点:有没有分号
// 典故: 十动然拒
int i = 1;
while(i <= 10000); {
System.out.println("I Love You !");
i++;
}
任务五:数组以及应用
一维数组的初始化方式
- 动态方式:基本类型的数组(数据元素为基本类型)创建后,其元素的初始值:byte、 short、char、int、long为0;float和double为0.0;boolean为false。
- 静态方式:可以在数组声明的同时进行初始化,具体如下: 数据类型[] 数组名称 = {初始值1, 初始值2, …};
特殊初始化方式:
// 6.特殊的写法 静态方式
// boolean[] arr4 = {true, true, false, false}; 这种方式是静态方式的简化版
boolean[] arr4 = new boolean[]{true, true, false, false};
// 打印数组中的每个元素值
for(int i = 0; i < arr4.length; i++) {
System.out.println("下标为" + i + "的元素是:" + arr4[i]); // true true false false
}
内存结构之栈区
- 栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开始到结束会有多次变量的声明。
内存结构之堆区
- JVM会在其内存空间中开辟一个称为“堆”的存储空间,这部分空间用于存储使用new关键字创建的数组和对象。
一维数组的内存结构:
一维数组增删改查之插入操作:
一维数组增删改查之删除操作
将数据55从数组中删除,删除方式为后续元素向前移动,最后一个位置置为0,操作方式与插入一样,只是反方向copy数据。
一维数组增删改查之查找操作
查找数组中是否有元素22,若有则修改为220,只需要使用for循环遍历查找即可,找到之后修改数组中当前元素的值为22,建议如果之后没有操作就跳出循环。
也可使用二分法查找,但是必须是有序数组才能使用:
// 6.二分法查找数组中是否有元素220,若有则修改为22
Arrays.sort(arr); // 二分法查找之前,一定要对数组元素排序
int low = 0; // 开始位置
int high = arr.length - 1; // 结束位置
int middle = 0; //索引位置
while(low <= high) {
middle = (low + high) / 2; // 中间的值
// 如果要查找的值等于中值,直接修改并跳出循环
if (220 == arr[middle]) {
arr[middle] = 22;
break;
}
// 如果要查找的值大于中值,证明在右边
if (220 > arr[middle]) {
// 重新指定起点,保证下次查找右边
low = middle + 1;
}
// 如果要查找的值小于中值,证明在左边
if (220 < arr[middle]) {
// 重新指定起点,保证下次查找左边
high = middle - 1;
}
}
// 打印数组中所有元素的数值
System.out.print("数组中的元素有:");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " "); // 0 11 33 44 22
}
System.out.println();
数组的优缺点
- 可以直接通过下标(或索引)的方式访问指定位置的元素,速度很快。
- 数组要求所有元素的类型相同。
- 数组要求内存空间连续,并且长度一旦确定就不能修改。
- 增加(准确的说是插入)和删除元素时可能移动大量元素,效率低。
案例题目:
-
声明一个初始值为11 22 33 44 55的一维数组并打印所有元素
-
声明一个长度为3元素类型为int类型的一维数组并打印所有元素
-
实现将第一个数组中间3个元素赋值到第二个数组中
可以直接使用Java官方提供的拷贝功能:
// 表示将数组arr中下标从1开始的3个元素拷贝到数组brr中下标从0开始的位置 // 数组拷贝方法: // System.arraycopy(源数组, 源数组起始位置, 目标数组, 目标数组起始位置, 个数); System.arraycopy(arr, 1, brr, 0, 3);
-
再次打印第二个数组中的所有元素
笔试考点:
// 表示将变量arr的数值赋值给变量brr,覆盖变量brr中原来的数值
// 数组名arr的内存空间中存放的是数据在堆区中的内存地址信息,赋值后让brr变量中存放了arr所指向堆区的内存地址
// 也就是让brr和arr指向了同一块堆区空间,其本质上就是改变指向而已
brr = arr;
// 打印第二个数组中的所有元素
System.out.print("第二个数组中的元素有:");
for(int i = 0; i < brr.length; i++) {
System.out.print(brr[i] + " "); // 11 22 33 44 55
}
System.out.println();
案例题目:
- 编程统计用户输入任意一个正整数中每个数字出现次数的统计并打印。
- 如:123123 => 1出现2次,2出现2次,3出现2次
思路:
将数字当中的每一位拆分出来肯定是0-9的数字,那么就使用一个长度为10的数组,让每位的数值对应数组的角标,0-9这10个数出现的次数只需要存储到它们对应角标位置的元素中即可,图解:
代码实现:
import java.util.Scanner;
public class ArrayCountTest {
public static void main(String[] args) {
// 1.提示用户输入一个正整数并使用变量记录
System.out.println("请输入一个正整数:");
Scanner sc = new Scanner(System.in);
long num = sc.nextLong();
// 2.准备一个长度为10元素类型int类型的一维数组,默认值为0
int[] arr = new int[10];
// 3.拆分正整数中的每个数字并统计到一维数组中
long temp = num;
while(temp > 0) {
arr[(int)(temp%10)]++;
temp /= 10;
}
// 4.打印最终的统计结果
for(int i = 0; i < arr.length; i++) {
if(arr[i] > 0) {
System.out.println("数字" + i + "出现了" + arr[i] + "次!");
}
}
}
}
数组工具类
概念
java.util.Arrays类可以实现对数组中元素的遍历、查找、排序等操作。
数组工具类的常用方法
常用方法如下:
方法签名(默认都是静态方法) | 描述 |
---|---|
String toString(int[] a) | 输出数组中的内容 |
void fill(int[] a, int val) | 将参数指定元素赋值给数组中所有元素 |
boolean equals(int[] a, int[] a2) | 判断两个数组元素内容和次序是否相同 |
static void sort(int[] a) | 对数组中的元素进行从小到大排序 |
int binarySearch(int[] a, int key) | 从数组中查找参数指定元素所在的位置 |
二维数组
概念
二维数组本质上就是由多个一维数组摞在一起组成的数组,二维数组中的每个元素都是一维数组,而一维数组中的每个元素才是数据内容。
声明和初始化方式
- 数据类型 数组名称 = new 数据类型[行数] [列数];
- 数据类型[][] 数组名称 = {{元素1, 元素2,…}, …};
笔试考点:
// Java中支持声明一个梯形的二维数组:
int[][] arr3 = new int[3][];
arr3[0] = new int[3];
arr3[1] = new int[4];
arr3[2] = new int[5];
总结
任务一:
jdk目录结构:
- bin目录,存放一些二进制可执行文件;
- conf目录,存放配置文件;
- include目录,存放头文件;
- jmods目录,存放模块相关文件;
- legal目录,存放jdk的一些授权文档;
- lib目录,存放jdk的补充jar包和源码;
java11支持使用直接使用java命令直接编译运行,但是不推荐使用。
java的常用命名必须配置环境变量才能在任意目录下访问。
java的跨平台原理是通过提供的不同平台的jvm具体实现将源代码编译成支持各平台的字节码去执行。
任务二:
变量是在内存中申请一个存储单元存储数据,这个数据是可变的。
基本数据类型:byte(1),short(2),int(4),long(8),float(4),double(8),boolean(认为是1个字节),char(2)。
float和double底层二进制结构是符号位+指数位+小数位。
常用ASCII码:0-48,A-65,a-97,空格-32,换行-10
任务三:
算术运算符:+,-,*,/,%
赋值运算符:=,+=,/=,*=…
关系运算符:==,!=,>=,<=…
自增减运算符:++(前后),–(前后)
逻辑运算符:&&,||,!..
三目运算符:?:
移位运算符:<<,>>,>>>(无符号右移,左边补0)
位运算符:&,|,~(取反),^
任务四:
if语句:if,if else,if else if(三次以上)
switch case:支持byte,short,int,char,jdk1.5支持枚举,jdk1.7支持字符串String
for循环:适用于确定循环次数的场景,双重for适用于打印多行多列,一般三层,不宜多
break跳出当前循环,continue本次循环继续执行下次循环,使用标识符+break跳出多层循环,如:break outer;
while循环:适用于不确定循环次数的场景,while(true)可代替for( ; ; )
do while循环:不管满不满足条件至少执行一次
任务五:
一维数组:在内存中是一段连续的存储空间,实际数据在堆中,栈中的变量存放的是数组的引用地址,初始化方式有动态和静态方式,了解特殊静态方式:new int[]{1,2,3,4,5}
数组优缺点:
- 可直接通过下标访问指定位置元素
- 存放数据类型要一致
- 长度一旦声明不可变
- 增删不可避免的要移动大量元素::“增删慢,查询快”就是这么来的
数组工具类:toString打印,sort升序排序,equals比较两个数组中的内容和顺序是否一致,fill指定数值初始化数组,binarySearch二分法查找指定元素下标
二维数组:本质是多个一维数组摞在一起,每个元素都是一个数组,可使用双重for循环遍历,外层控制行,内层控制列,二维数组支持梯形结构,即行固定,列不固定
笔试题:
1.设计一套砝码要求能称量出1 ~ 100g之问的任意重量,请问至少需要多少个砝码?以及每个砝码各自的重量是多少(砝码只能放在一侧)?
1g - 1 8g - 1 2 4 8 1~15
2g - 1 1 1~2 16g - 1 2 4 16 1~31
1 2 1~3 32g - 1 2 4 16 32 1~63
4g - 1 2 1 1~4 64g - 1 2 4 16 32 64 1~127
1 2 2 1~5
1 2 3 1~6
1 2 4 1~7
2.简述&和&&之间的主要区别:
- 概念:按位与和逻辑与。
- 相同点:都可以实现逻辑与的功能,即两边条件都为真则结果为真,否则为假。
- 不同点:&&有短路特性,&&只能表示逻辑与功能,&还可以表示按位与功能。
3.简述i++与++i的异同之处:
相同点:
都表示i自身加一的效果
不同点:
运算顺序区别
- i++是先使用i的值表示表达式的结果,然后自身加一(先用后加);
- ++i是先自身加一,然后使用i的值表示表达式的结果(先加后用);
性能区别
i++底层是:
int a = i;
i = i + 1;
return a;
++i底层是:
i = i + 1;
return i;
所以++i性能更高。
注意:之所以平时i++用的多,是为了方便程序开发人员阅读理解,可读性更好,更能体现代码执行流程,而且虽然两个性能有差别,但是随着计算机硬件水平的提高,可以忽略不计。
4.如何使用最有效率的方法计算2乘以8?
答:使用位运算,在计算机底层位运算比算术运算效率要快(因为数据在计算机底层是以二进制形式存储的,而移位运算符就是拿着二进制数据去移动,所以不需要进制转换等其他操作,因此算术运算要快),左移n位即乘以2n,右移n位即除以2n。
Java写法:
2 <<< 3;
5.在不借助第三个变量的情况下,如何实现两个整数变量数值的交换?
如:ia=3; ib=5; =>交换后:ia=5; ib=3;
// 方式一:性能低
ia = ia + ib; // 3 + 5 = 8;
ib = ia - ib; // 8 - 5 = 3;
ia = ia - ib; // 8 - 3 = 5;
// 方式二:性能高(因为是位运算),推荐
ia = ia ^ ib; // 先不算
ib = ia ^ ib; // ia ^ ib ^ ib = ia ^ 0 = ia;
ia = ia ^ ib; // ia ^ ib ^ ia = ia ^ ia ^ ib = 0 ^ ib = ib;