版权说明:
本笔记非原著,是基于微信公众号【开发学习笔记】(微信号:bxkuaie_com),的简化版本
(本笔记已获原著转载权)
一、Java基础
01、为什么学习Java
跨平台语言,一次编写,到处运行
内置垃圾收集,不必考虑内存管理
Java虚拟机拥有工业级的稳定性和高度优化的性能
Java拥有最广泛的开源社区支持,各种高质量组件随时可用
02、Java简介
Java最早是由SUN公司(已被Oracle收购)的詹姆斯·高斯林(高司令,人称Java之父)在上个世纪90年代初开发的一种编程语言,最初被命名为Oak,目标是针对小型家电设备的嵌入式应用,结果市场没啥反响。谁料到互联网的崛起,让Oak重新焕发了生机,于是SUN公司改造了Oak,在1995年以Java的名称正式发布,原因是Oak已经被人注册了,因此SUN注册了Java这个商标。随着互联网的高速发展,Java逐渐成为最重要的网络编程语言。
Java介于编译型语言和解释型语言之间。编译型语言如C、C++,代码是直接编译成机器码执行,但是不同的平台(x86、ARM等)CPU的指令集不同,因此,需要编译出每一种平台的对应机器码。解释型语言如Python、Ruby没有这个问题,可以由解释器直接加载源码然后运行,代价是运行效率太低。而Java是将代码编译成一种“字节码”,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果。当然,这是针对Java开发者而言。对于虚拟机,需要为每个平台分别开发。为了保证不同平台、不同公司开发的虚拟机都能正确执行Java字节码,SUN公司制定了一系列的Java虚拟机规范。从实践的角度看,JVM的兼容性做得非常好,低版本的Java字节码完全可以正常运行在高版本的JVM上。
SUN给Java又分出了三个不同版本:
Java SE:Standard Edition(标准版,包含标准的JVM和标准库
)
Java EE:Enterprise Edition(企业版,它只是在Java SE的基础上加上了大量的API和库
,以便方便开发Web应用、数据库、消息服务等)
Java ME:Micro Edition(针对嵌入式设备的“瘦身版”
,Java SE的标准库无法在Java ME上使用)
1995 版本1.0
2004 版本1.5 / 5.0
2004 版本1.0
2014 版本1.8 / 8.0
JDK
:Java Development Kit
JRE
:Java Runtime Environment
03、安装JDK
系统环境变量Path=%JAVA_HOME%\bin;
命令行输入java -version
java
:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
javac
:这是Java的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);
jar
:用于把一组.class文件打包成一个.jar文件,便于发布;
javadoc
:用于从Java源码中自动提取注释并生成文档;
jdb
:Java调试器,用于开发阶段的运行调试。
04、第一个程序HelloWorld
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
这个定义被称为class
(类),这里的类名是Hello
,大小写敏感,class用来定义一个类,public
表示这个类是公开的,public、class都是Java的关键字,必须小写,Hello是类的名字,按照习惯,首字母H要大写。而花括号{}
中间则是类的定义
public static void main(String[] args){
...
}
Java程序总是从main方法
开始执行。
方法是可执行的代码块,一个方法除了方法名main
,还有用()
括起来的方法参数
,这里的main方法有一个参数,参数类型是String[],参数名是args
,public、static
用来修饰方法,这里表示它是一个公开的静态方法,void
是方法的返回类型,而花括号{}
中间的就是方法的代码。
05、开发工具IDE介绍
06、类名要求和注释
类名要求
类名以英文大写字母开头,后接字母,数字和下划线的组合
。
方法名命名和class一样,但是首字母小写
07、变量和数据类型
变量分为两种:基本类型
的变量和引用类型
的变量
整数类型:byte
,short
,int
,long
浮点数类型:float
,double
字符类型:char
布尔类型:boolean
计算机内存的最小存储单元是字节(byte
),一个字节就是一个8位二进制数,即8个bit
。它的二进制表示范围从00000000~11111111
,换算成十进制是0~255
,换算成十六进制是00~ff
整型
对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数
)
byte
:-128 ~ 127
short
: -32768 ~ 32767
int
: -2147483648 ~ 2147483647
long
: -9223372036854775808 ~ 9223372036854775807
浮点型
对于float
类型,需要加上f
后缀。
float
:3.4x1038,
double
:1.79x10308
布尔类型
理论上存储布尔类型只需要1 bit
,但是通常JVM内部会把boolean
表示为4字节整数
字符类型
字符类型char
表示一个字符。Java的char类型除了可表示标准的ASCII
外,还可以表示一个Unicode
字符:
注意char
类型使用单引号'
,且仅有一个字符,要和双引号"
的字符串类型区分开。
常量
定义变量的时候,如果加上final
修饰符,这个变量就变成了常量:常量在定义时进行初始化后就不可再次赋值
,常量名通常全部大写
。
var关键字
略
StringBuilder 与 StringBuffer
StringBuffer:线程安全
StringBuilder:线程不安全
。
StringBuffer 的所有公开方法都是 synchronized 修饰的,而 StringBuilder 并没有 StringBuilder 修饰。
StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串
。
StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串
。
StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景
,如果是单线程场合 StringBuilder 更适合
。
变量的作用范围
定义变量时,要遵循作用域最小化原则
,尽量将变量定义在尽可能小的作用域,并且,不要重复使用变量名。
08、整数运算
四则运算
四则运算规则和初等数学一致
求余运算使用%
特别注意:整数的除法对于除数为0
时运行时将报错
,但编译不会报错。
溢出
计算结果超出了范围,就会产生溢出
,而溢出不会出错,却会得到一个奇怪的结果
还有一种简写的运算符,即+=
,-=
,*=
,/=
自增/自减
++
运算和--
运算,可以对一个整数进行加1
和减1
的操作:
注意++
写在前面和后面计算结果是不同的,++n
表示先加1再引用n,n++
表示先引用n再加1。
移位运算
对整数7
左移1位
将得到整数14
,左移两位
将得到整数28
左移29
位时,由于最高位变成1,因此结果变成了负数
int n = 7; // 00000000 00000000 00000000 00000111 = 7
int a = n << 1; // 00000000 00000000 00000000 00001110 = 14
int b = n << 2; // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912
还有一种不带符号的右移运算,使用>>>
,它的特点是符号位跟着动,因此,对一个负数进行>>>右移,它会变成正数,原因是最高位的1变成了0
:
int n = -536870912;
int a = n >>> 1; // 01110000 00000000 00000000 00000000 = 1879048192
int b = n >>> 2; // 00111000 00000000 00000000 00000000 = 939524096
int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1
对byte
和short
类型进行移位时,会首先转换为int
再进行位移。
仔细观察可发现,左移实际上就是不断地×2,右移实际上就是不断地÷2
。
位运算
位运算是按位进行与
、或
、非
和异或
的运算。
与运算
的规则是,必须两个数同时为1,结果才为1:
或运算
的规则是,只要任意一个为1,结果就为1:
非运算
的规则是,0和1互换:
n = ~0; // 1
n = ~1; // 0
异或运算
的规则是,如果两个数不同,结果为1,否则为0:
n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0
对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算
。例如:
int i = 167776589; // 00001010 00000000 00010001 01001101
int n = 167776512; // 00001010 00000000 00010001 00000000
System.out.println(i & n); // 167776512
上述按位与运算实际上可以看作两个整数表示的IP地址10.0.17.77
和10.0.17.0
,通过与运算,可以快速判断一个IP是否在给定的网段内。
运算优先级
()
! ~ ++ --
* / %
+ -
<< >> >>>
&
|
+= -= *= /=
记不住也没关系,只需要加括号就可以保证运算的优先级正确。
类型自动提升与强制转型重要性
在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。
例如,shor
t和int
计算,结果总是in
t,原因是short首先自动被转型为int:
short s = 1234;
int i = 123456;
int x = s + i; // s自动转型为int
short y = s + i; // 编译错误!
也可以将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用(类型)
例如,将int
强制转型为short
:
int i = 12345;
short s = (short) i; // 12345
要注意,超出范围的强制转型会得到错误的结果
,原因是转型时,int
的两个高位字节直接被扔掉,仅保留了低位的两个字节:
int i1 = 1234567;
short s1 = (short) i1; // -10617
System.out.println(s1);
int i2 = 12345678;
short s2 = (short) i2; // 24910
System.out.println(s2);
因此,强制转型的结果很可能是错的。
09、浮点数运算
浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算
。
浮点数0.1
在计算机中就无法精确表示,因为十进制的0.1
换算成二进制是一个无限循环小数
,无论使用float还是double,都只能存储一个0.1的近似值。但是,0.5这个浮点数又可以精确地表示。因此,浮点数运算会产生误差
double x = 1.0 / 10;
double y = 1 - 9.0 / 10;
// 观察x和y是否相等:
System.out.println(x);
System.out.println(y);
由于浮点数存在运算误差,所以比较两个浮点数是否相等常常会出现错误的结果。正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数
:
// 比较x和y是否相等,先计算其差的绝对值:
double r = Math.abs(x - y);
// 再判断绝对值是否足够小:
if (r < 0.00001) {
// 可以认为相等
} else {
// 不相等
}
Math.abs() 返回参数的绝对值
类型提升
如果参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型:
int n = 5;
double d = 1.2 + 24.0 / n; // 6.0
需要特别注意,在一个复杂的四则运算中,两个整数的运算不会出现自动提升
的情况。例如:
double d = 1.2 + 24 / 5; // 5.2
计算结果为5.2
,原因是编译器计算24 / 5
这个子表达式时,按两个整数进行运算,结果仍为整数4
。
溢出
整数运算在除数为0
时会报错,而浮点数运算在除数为0
时,不会报错,但会返回几个特殊值:
NaN
表示Not a Number
Infinity
表示无穷大
-Infinity
表示负无穷大
例如:
double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -Infinity
在实际运算中很少碰到
强制转换
可以将浮点数强制转型为整数。在转型时,浮点数的·小数部分会被丢掉·。如果转型后超过了整型能表示的最大范围,将·返回整型的最大值·。例如:
int n1 = (int) 12.3; // 12
int n2 = (int) 12.7; // 12
int n2 = (int) -12.7; // -12
int n3 = (int) (12.7 + 0.5); // 13
int n4 = (int) 1.2e20; // 2147483647
如果要进行四舍五入,可以对浮点数加上0.5再强制转型
:
double d = 2.6;
int n = (int) (d + 0.5);
System.out.println(n);
10、布尔运算
对于布尔类型boolean
,永远只有true
和false
两个值。
比较运算符:>
,>=
,<
,<=
,==
,!=
与运算 &&
或运算 ||
非运算 !
关系运算符的优先级从高到低依次是:
!
>
,>=
,<
,<=
==
,!=
&&
||
短路运算
布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。
因为false && x
的结果总是false
,无论x
是true
还是false
,因此,与运算在确定第一个值为false
后,不再继续计算,而是直接返回false
。
对于||
运算,只要能确定第一个值为true
,后续计算也不再进行,而是直接返回true
:
三元运算符
b ? x : y
会首先计算b
,如果b
为tru
e,则只计算x
,否则,只计算y
。此外,x和y的类型必须相同,因为返回值不是boolean
,而是x
和y
之一。
三元运算 b ? x : y后面的类型必须相同,三元运算也是“短路运算”,只计算x或y。
int n = -100;
int x = n >= 0 ? n : -n;
上述语句的意思是,判断n >= 0
是否成立,如果为true
,则返回n
,否则返回-n
。这实际上是一个求绝对值的表达式。
11、字符和字符串
字符和字符串是两个不同的类型
字符串类型
String
是引用类型,我们用双引号"..."
表示字符串
转义字符
需要用"
是,需要转义字符\
\"
表示字符"
\'
表示字符'
\\
表示字符\
\n
表示换行符
\r
表示回车符
\t
表示Tab
\u####
表示一个Unicode编码的字符
字符串连接
"hello" + 3 +4 //hello34
3 + 4 + "hello" //7hello
不可变特性
String s = "hello";
System.out.println(s); // 显示 hello
s = "world";
System.out.println(s); // 显示 world
其实变的不是字符串,而是变量s的“指向”
空值null
空值null
和空字符串""
,空字符串是一个有效的字符串对象,它不等于null
String s1 = null; // s1是null
String s2; // 没有赋初值值,s2也是null
String s3 = s1; // s3也是null
String s4 = ""; // s4指向空字符串,不是null
12、数组类型
定义一个数组类型的变量,使用数组类型“类型[]
”
int[] ns = new int[5];
ns[0] = 68;
ns[1] = 79;
ns[2] = 91;
ns[3] = 85;
ns[4] = 62;
new int[5]
表示创建一个可容纳5
个int
元素的数组
数组一旦创建后,大小就不可改变
数组索引从0
开始 索引范围是0~4
int[] ns = new int[] { 68, 79, 91, 85, 62 };
也可以在定义数组时直接指定初始化的元素,这样就不必写出数组大小,而是由编译器自动推算数组大小
还可以进一步简写为:
int[] ns = { 68, 79, 91, 85, 62 };
数组所有元素初始化为默认值,整型都是0
,浮点型是0.0
,布尔型是false
可以用数组变量.length
获取数组大小
字符串数组
13、输入和输出
println
表示输出并换行
print
表示输出
格式化输出
格式化输出使用System.out.printf()
,通过使用占位符%?
,printf()
可以把后面的参数格式化成指定格式:
double d = 3.1415926;
System.out.printf("%.2f\n", d); // 显示两位小数3.14
System.out.printf("%.4f\n", d); // 显示4位小数3.1416
%d
格式化输出整数
%x
格式化输出十六进制整数
%f
格式化输出浮点数
%e
格式化输出科学计数法表示的浮点数
%s
格式化字符串
由于%
表示占位符,因此,连续两个%%
表示一个%字符本身。
占位符本身还可以有更详细的格式化参数.下面的例子把一个整数格式化成十六进制,并用0补足8位:
int n = 12345000;
System.out.printf("n=%d, hex=%08x", n, n); // 注意,两个%占位符必须传入两个数
输入
通过import
语句导入java.util.Scanner
,必须放到Java源代码的开头
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}
14、if判断
15、switch多重选择
16、while循环
17、do-while循环
18、for循环
19、break和continue
20、遍历数组
21、数组排序
22、多维数组
23、命令行参数
二、面向对象编程
24、面向对象编程基础
class是一种对象模版,它定义了如何创建实例,它定义了如何创建实例,因此,class本身就是一种数据类型
instance是对象实例,instance是根据class创建的实例,可以创建多个instance,每个instance类型相同,但各自属性可能不相同
字段(field) 方法(method)
25、方法
不允许传入null和空字符串
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("invalid name");
}
this.name = name.strip(); // 去掉首尾空格
IllegalArgumentException不合法的参数异常
StringUtils.isEmpty
(String str)判断某字符串是否为空,为空的标准是str==null或str.length()==0
StringUtils.isBlank
(String str)判断某字符串是否为空或长度为 0 或由空白符 (whitespace) 构成
String.trim()
可以去除字符串前后的“半角”空白字符
String.strip()
可以去除字符串前后的“全角和半角”空白字符
使用方法(method)
修改private字段调
用方法的语法是 实例变量.方法名(参数)
例如:ming.setName("Xiao Ming");
定义方法
定义方法的语法是:
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
private
private方法不允许外部调用,可以调用private方法
例:
public int getAge() {
return calcAge(2019); // 调用private方法
}
// private方法:
private int calcAge(int currentYear) {
return currentYear - this.birth;
}
calcAge()是一个private方法,外部代码无法调用,但是,内部方法getAge()可以调用它。
this
局部变量和字段重名
,那么局部变量优先级更高,就必须加上this
可变参数
用 类型...
定义,可变参数相当于数组类型
参数绑定
基本类型参数的传递,是调用方值的复制
。双方各自的后续修改,互不影响
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象
。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)
26、构造方法
创建实例的时候,实际上是通过构造方法来初始化实例的
构造方法的名称就是类名
普通方法相比,构造方法没有返回值(也没有void)
,调用构造方法,必须用new
操作符。
默认构造方法
任何class都有构造方法
一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法
我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
没有在构造方法中初始化字段时,引用类型的字段默认是null
,数值类型的字段用默认值,int
类型默认值是0,布尔类型默认值是false
:
在Java中,创建对象实例的时候,按照如下顺序进行初始化
- 先初始化字段
- 执行构造方法的代码进行初始化
多构造方法
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)
:
例:
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this(name, 18); // 调用另一个构造方法Person(String, int)
}
public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
}