1. Java语言概述
1.1 基础常识
- 软件:一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件。
- 人机交互:
- 图形化界面 (Graphical User Interface GUI)
- 命令行方式(Command Line Interface CLI)
- dos命令:Disk Operating System 命令
1.2 Java语言概述
Java:
- 跨平台的纯面向对象的语言。(强制面向对象)
- 分布式:支持Internet应用开发
- Robust:强类型机制,异常处理,垃圾的自动回收。Java丢弃指针
- 安全:安全防范机制
- 支持多线程
1.3 Java程序运行机制
Java特点:
- 面向对象:
- 两个基本概念:类、对象
- 三个特征:封装、继承、多态
- 健壮性,完善性:
- 跨平台性 JVM:Java编写的应用程序可以在不同的系统平台运行。原理:在操作系统,安装JVM(Java Virtual Machine),由JVM负责Java程序的运行。
Java核心机制:
- 虚拟机 JVM (Java Virtual Machine): 虚拟计算机,具有指令集并使用不同的存储区域,负责执行指令,管理数据、内存、寄存器。Write once, run anywhere
- 垃圾回收 GC (Garbage Collection):
- C/C++ 需要手动编写代码回收。优点:能够在内存不再使用时快速回收。缺点:不主动回收则一直不回收
- Java 垃圾回收是自动,开了一个系统集线程自动检测不再使用的内存,并回收。优点:自动,不会忘记回收。缺点:回收不及时。
1.4 Java语言环境的搭建
- JDK (Java Development Kit): Java开发工具包。包含Java开发工具(编译工具java.exe,打包工具jar.exe等),也包括了JRE。
- JRE (Java Runtime Environment):Java运行环境。包括JVM和Java程序所需的核心类库等。如果想运行一个开发好的Java程序,计算机中只需要安装JRE。
简单来说,使用JDK的开发工具完成的Java程序,交给JRE运行。
1.5 HelloWorld
步骤:
- 将Java代码编写到扩展名为.java的文件中
- 通过Javac命令对该java文件进行编译
- 通过Java命令对生成的.class文件进行运行
class Test{
public static void main (String[] args){
System.out.print("Hello World!");
}
}
1.6 小结第一个程序
- Java源文件以“.java”为扩展名。源文件的基本组成部分是类(class)
- Java应用的执行入口是main()方法,有固定书写格式:
public static void main(String[] args){} - 严格区分大小写
- 由一条条语句构成,每一句以“;”结尾
- 括号成对出现
1.7 常见问题及解决方法
- 声明为public的主类必须与文件名一致
- 编译错误去指定行改错
1.8 注解
- 提高代码阅读性
- 调试程序的重要方式
- 单行 //
- 多行/* */
1.9 集成开发环境 IDE(Integrated Development Environment)
包含文本编辑工作,自动编译,简化运行,随时进行代码的调试
2. 基本语法
2.1 关键字
- 定义:被java赋予了特殊含义,用作专门用途的字符串(单词)
- 特点:全部小写。特有,事先定义好。
- 作用:在关键地方使用的关键单词,表达关键含义。
- 保留字:目前尚未使用,但以后版本有可能作为关键字。避免使用。例:byValue, cast, future, operator, var, const...
2.2 标识符
2.2.1 标识符
- 定义:Java对各种变量,方法和类等要素命名时使用的字符序列。凡是可以自己取名字的地方都是标识符
- 规则:
- 由26个英文字符大小写,0-9,_或$组成
- 数字不可以开头
- 不可以使用关键字和保留字,但可以包括关键字和保留字
- Java严格区分大小写,长度无限制
- 不能包含空格
- 尽量以实际含义取名
2.2.2 Java命名规范
- 包名:全小写 xxxyyyzzz
- 类名,接口名,所有单词首字母大写 XxxYyyZzz
- 变量名,方法名:第一个单词首字母小写,第二个单词开始每个单词首字母大写 xxxYyyZzz
- 常量名:所有字母都大写。多单词每个单词用下划线链接 XXX_YYY_ZZZ
2.3 变量
- 变量的概念:
- 内存中的一个储存区域
- 该区域由自己的名称(变量名)和类型(数据类型)
- 每个变量必须先声明,后使用
- 该区域的数据可以在同一类型范围内不断变化
- 注意⚠️:
- 变量的作用域:一对{}之间有效
- 初始化值
- 定义变量的格式:数据类型 变量名 = 变量值。例:
int x = 1; - 变量是通过使用变量名来访问这块区域的
2.3.1 基本数据类型
8种基本数据类型,除了基本数据类型,都是引用数据类型
- 整数类型:byte、short、int、long
| 类型 | 占用储存空间 | 表达数据范围 |
|---|---|---|
| byte | 1 Byte 字节= 8 bit 位 | -128 ~ 127 |
| short | 2 Byte | - ~ - 1 |
| int | 4 Byte | - ~ - 1 |
| long | 8 Byte | - ~ - 1 |
long类型赋值时,需要在数字后面加一个l。例:long x = 3l
- 浮点类型:float、double
| 类型 | 占用存储空间 | 表达数据范围 | 精度 |
|---|---|---|---|
| float单精度 | 4 Byte | - ~ | 7位有效数字| |
| double双精度| 8 Byte | - ~ | 16位有效数字 |
float类型赋值时,需要在数字后面加f或F。例:float x = 5.21f
- 字符类型: char
- 2 Byte
- 用单引号括起来的,单个字母,数字,符号。例:
char c = 'a' - char类型可以运算,因为有对应的Unicode码。ASCII码中,
‘a’的值97 - Java也支持转义字符
- 布尔类型: boolean
- 适用于逻辑运算,一般用于程序流程控制
- 只能是true或false,不能是null
- 引用类型: String类
- 字符串:由0到多个字母数字符号组成的串,用双引号括。例:
String str = "Hello World"; - 值null可以赋值给任何引用类型(类、接口、数组)的变量,用来表示这个引用类型中保存的地址为空。所以String可以用null赋值
- String类是一个典型的不可变类,String对象创造出来就不可能被改变。创建出的字符串存放在数据区(字符串常量池),保证每个字符串常量只有一个,不会产生多个副本。例:
String s1 = "hello"和String s2 = "hello"只会存在一个hello,两个变量会引用同一个地址。
2.3.2 基本数据类型转换
2.3.2.1 自动类型转换
- 容量小的类型自动转换为容量大的数据类型。数据类型大小排序(由小到大),以及转换过程:
例:
byte b = 1; int m = b;合法。但是int i = 1; byte m = i;编译错误。
- 多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后计算
- byte,short,char 之间不能相互转换,三者在计算时,都转换成int
- 当把任何基本类型的值和字符串值进行连接运算时(+),基本类型的值将自动转化为String类型。
2.3.2.2 强制类型转化
- 自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时将上强制转换符
(),但可能造成精度降低或溢出(overflow),谨慎使用。int i = 1; byte b = (byte)i; - 通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换为基本类型。例:
String a = "43"; int i = Integer.parseInt(a); - boolean 不可以转换为其他数据类型
2.4 运算符
运算符是一种特殊的符号,用来表示数据的运算,赋值和比较等
2.4.1 算数运算符
注意:
- int除以int,结果也是int,舍弃小数部分。例:
int x = 3510; x/1000; x = 3 - 对负数取模,可以把模数的负号忽略不计。例:
5 % -2 = 1。但是被模数的负号不可以忽略。例-5 % 2 = -1。取模的运算结果不一定总是正数。 - "+"除字符串相加功能外,还能把非字符串转换成字符串。例:
System.out.println("5+5="+5+5)。打印结果是5+5=55。因为字符串与任何类型连接,其他类型都会转换成字符串。 - 注意单引号和双引号区别。
System.out.println('*' + '*');//output 93。因为单引号是char类型,相加会转换成ASCII码计算
System.out.println("*" + '*');//output **。因为双引号是String类型。相加都转换成String
2.4.2 赋值运算符
符号: =
- 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。
- 支持连续赋值。
i0 = i1 = i2 = 1 - 扩展:+=、-=、*=、/=、%=。注:字符串的+=,是字符串的拼接。
2.4.3 比较运算符(关系运算符)
- 比较运算符的结果都是boolean类型。只能是true或者false
- 相等比较要用双等 “==”
2.4.4 逻辑运算符
| 符号 | 意思 |
|---|---|
| & | 逻辑与 |
| | | 逻辑或 |
| ! | 逻辑非 |
| && | 短路与 |
| || | 短路或 |
| ^ | 逻辑异或| |
- 逻辑运算符用于连接boolean型表达式,在Java中不可以写成
3<x<5。需要写成x>3 & x<6 - “&”:左边无论真假,右边都进行运算
- “&&”:如果左边为真,右边参与运算。如果左边为假,右边不参与运算
- "||":同理,左边为真,右边不参与运算
- “^”:相同为假,不同为真
2.4.5 位运算符
进制:
- 十进制转二进制:辗转相除
- 负数:取对应正数的二进制取反,然后+1(补码)
- 正数的>>>和>>相同。
- 🈚️<<<
2.4.6 三目运算符
格式:(条件表达式)?表达式1:表达式2。true执行表达式1,false执行表达式2.
int k = i > 0 ? 1 : 0
2.4.7 运算符优先级
- 上一行的运算符总优先于下一行。
- 综合性指计算机执行语句时的运算顺序
2.5 程序流程控制
2.5.1 顺序结构
从上到下执行
2.5.2 分支结构
- if - else
- if(){}
- if(){} else{}
- if(){} else if(){} else{}
- switch:
switch(变量){
case 常量1:
语句1;
break;
case 常量2:
语句2;
break;
case 常量N:
语句N;
break;
default:
语句;
break;
}
int i = 1;
switch(i){
case 1:
System.out.printlm("Monday");
break;
case 2:
System.out.printlm("Tuesday");
break;
default:
System.out.printlm("Unknown");;
break;
}
String str = "a";
switch(str){
case "a":
System.out.printlm("A");
break;
case "b":
System.out.printlm("B");
break;
default:
System.out.printlm("Unknown");;
break;
}
- switch(表达式)中表达式的返回值必须是:byte、short、char、int、枚举、String
- case子句中的值必须是常量,且所有case子句中值不同
- default子句是可任选的,当没有匹配的case时,执行default
- break用来在执行完一个cae分支后,使程序跳出switch语句块。如果没有break,程序会顺序执行到switch结尾
2.5.3 循环结构
循环语句的四个组成部分:
- 初始化 initial statement
- 循环条件 text expression
- 循环体 body statement
- 迭代 alter statement
- for循环
for(初始化表达式;布尔值测试表达式;更改表达式){语句} - while循环
初始化语句
while(布尔测试表达式){语句;更改语句}
-
do/while循环
-
嵌套循环
-
特殊流程控制语句
- break:用于终止某个语句块的执行,终止当前所在的循环
- continue:跳过某个循环语句块的一次执行。结束当前这次循环,直接进入下一次循环。
- return:结束整个方法。break只结束当前循环。
2.6 数组
-
一维数组:
type[] var。例:int[] nums -
初始化
- 动态初始化:数组声明且为数组元素分配空间与复制的操作分开进行。
int[] arr = new int[3] - 静态初始化:在定义数组的同时就为数组元素分配空间并赋值。
int[] arr = {1,2,3} - 默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。例:数字类型默认值是0,对象类型默认值是null。
- 动态初始化:数组声明且为数组元素分配空间与复制的操作分开进行。
-
数组元素的引用
- 定义并用运算符new为数组分配空间之后,才可以引用数组中的每个元素
- 引用方式:数组名[数组元素下标]。例:
arr[3]。数组元素下表可以是整型常量或整型表达式。数组元素下标从0开始,长度为n的数组合法下标的取值范围0~n-1. - 每个数组都有一个属性length指明长度。例:
arr.length获取arr的长度。数组一旦初始化,长度不能改变。
-
二维数组
- 初始化:
int[][] arr = new int[2][3]。两行三列数组
- 初始化:
-
数组中的常见算法
- 最大值、最小值、总和、平均数、复制:遍历数组
- 数组的反转:two pointers
- 数组元素的排序
||时间复杂|空间复杂|稳定性| |:---:|:---:|:---:|:---:| |选择|O()|O(1)|No| |冒泡|O()|O(1)|Yes| |插入|O()|O(1)|Yes| |归并|O(N logN)|O(N)|Yes| |快排|O(N logN)|O(logN)|No| |堆|O(N logN)|O(1)|No|
-
常见问题
- 数组下标越界:ArrayIndexOutOfBoundsException。访问到数组中不存在的脚标
- 空指针异常:NullPointerException。引用没有指向实体,却在操作实体中的元素
3. 面向对象
3.1 面向对象与面向过程
- 面向过程 Procedure Oriented
- 面向对象 Object Oriented。强调功能行为。三大特征:
- 封装 Encapsulation
- 继承 Inheritance
- 多态 Polymorphism
3.2 Java语言的基本元素:类和对象
- 类
定义类步骤:
- 定义类(修饰符、类名)
- 编写类的属性(修饰符、属性类型、属性名、初始化值)
- 编写类的方法(修饰符、返回值类型、方法名、形参)
修饰符 class 类名{//类名每个单词首字母都大写
属性声明;
方法声明;//方法名除了第一个单词,所有单词首字母大写
}
//example
public class Person{
public int age;
public String name;
public void showAge(){
System.out.print(age);
}
}
- 对象
- 使用new + 构造器创建一个新的对象
- 使用“对象名.对象成员”的方式访问对象成员(包括属性和方法)
Person person = new Person(); //声明一个Person类的变量,实例化Person类
person.name = "Joey";//给person对象的name属性赋值
person.age = 21;
person.showAge();//对象的方法调用
3.3 类的成员之一:属性 Field
修饰符 类型 属性名 = 初始值
public class Person{
private int age;
public String name = "Joey";
System.out.print(age);
System.out.print(name);
}
Person p = new Person();
String str = p.name;
//int i = p.age; 错误,age是private,并不能调用
- 修饰符private私有:该属性只能由该类的方法访问。不能通过对象.属性的方式调用。age只能在Person这个类中调用
- 修饰符public公有:该属性可以被该类以外的方法访问。name既可以在Person这个类中调用,也可以在类外面被调用
3.3.1 变量的分类
- 成员变量: 在方法体外,类体内声明
- 实例变量:必须在类实例化成对象后,才能使用
- 类变量 static:不需要类实例化成对象,就能使用。可以直接通过
类名.属性的方式直接调用
public class Person{ public static String sex = "Male"; public name = "Joey"; } System.out.print(Person.sex);//不需要实例化 Person p = new Person; System.out.print(p.name);//需要实例化 - 局部变量:在方法体内部声明
- 形参:
- 方法局部标量:
- 代码块局部变量
区别
- 成员变量:
- 定义在类中,整个类都可以访问
- 分为实例变量和类变量,实例变量存在于对象所在的堆内存中
- 有默认初始化值
- 权限修饰符可以根据需要,任意选择(public/private)
- 局部变量:
- 定义在局部范围内,如:方法内、代码块等。且只能在局部范围内中使用。
- 存在于栈内存中
- 作用的范围结束,变量空间会自动释放
- 没有默认初始化值,每次必须显式初始化
- 声明时不指定权限修饰符
3.4 类的成员之二:方法 Method
- 类或对象行为特征的抽象,也叫函数。不能独立存在,所有方法必须定义在类里。
修饰符 返回值类型 方法名(参数列表){
方法题语句;
}
//for instance
public class Person{
private int age;
public void setAge(int i){
age = i;
}
public int getAge(){
return age;
}
}
- 方法的调用
-
方法只有被调用才会运行。以下是方法调用的流程:
-
定义方法时,方法的结果应该返回给调用者,交给调用者处理。没有返回值用void。
-
方法中只能调用方法,不可以在方法内定义方法。
-
同一类中方法可以互相调用,不用new实例化对象。
3.5 对象的创建和使用
-
对象的产生 当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型。
成员变量类型 初始值 byte 0 short 0 int 0 long 0L float 0.0F double 0.0D char '\u0000'(表示空) boolean false 引用类型 null -
一个类可以创建多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,互不干扰
-
匿名对象:不定义对象的句柄,直接调用这个对象的方法。例
new Person().showAge()。如果对一个对象只需要进行一次方法调用,就可以使用匿名对象。经常将匿名对象作为实参传递给一个方法调用。
类的访问机制
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。但static方法不能访问非static的成员变量
- 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
3.6 方法进阶
3.6.1 方法重载 overload
- 在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可。
- 与返回值类型无关,只看参数列表,且参数列表必须不同(参数个数或参数类型)。调用时,根据方法参数列表的不同区别。
//返回两个整数的和
int add(int x int y){
return x + y;
}
//返回三个整数的和
int add(int x, int y, int z){
return x + y + z;
}
//返回两个小数的和
int add(double x, double y){
return x + y;
}
3.6.2 可变个数的形参
- 可变参数:方法参数部分指定类型的参数个数是可变的
- 声明方法:方法名(参数的类型名...参数名)
public static void test(int a, String...books) - 可变参数方法的使用与方法参数部分使用数组是一致的
- 方法的参数部分有可变形参,需要放在形参的最后
3.6.3 方法的参数传递
-
方法,必须有其所在类或者对象调用才有意义。若方法有参数:
- 形参:方法声明时的参数
- 实参:方法调用时实际传给形参的参数值
-
实参传入: Java里方法的参数传递方式只有值传递。将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
-
形参是基本数据类型:把实参的值复制到形参上。
-
形参是引用对象:把实参在栈内存中的值(引用对象在堆内存中的地址)复制到形参上。所以形参改变数据时,实参的数据也改变。因为指向同一个堆中的地址。
-
-
补充:JVM内存
- 基本数据类型,值保存在栈中
- 引用对象,值保存在堆中,栈中存的是对象在堆中的地址
3.7 面向对象特征之一:封装和隐藏
3.7.1 封装和隐藏
使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。Java通过将数据声明为private,再提供公共的public方法:getXXX()和setXXX()实现对该属性的操作,以实现:
- 隐藏一个类中不需要对外提供的实现细节
- 使用者只能通过事先制定好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作
- 便于修改,增强代码的可维护性
3.7.2 四种访问权限修饰符
Java权限修饰置于类的成员 定义前,用来限定对象对该类成员对访问权限
| 修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
|---|---|---|---|---|
| private | yes | |||
| 缺/省 | yes | yes | ||
| protected | yes | yes | yes | |
| public | yes | yes | yes | yes |
对于class的权限修饰只可以用public和default(缺省)
- public类可以在任意地方被访问。(一个Java文件中可以写多个class,但是只有一个是public,其他都是default)
- default类只可以被同一个包内部的类访问
3.8 类的成员之三:构造器(构造方法)Constructor
new对象,实际就是调用类的构造器
-
构造器的特征:
- 具有与类相同的名称
- 不声明返回值类型(与声明void不同)
- 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回
-
构造器作用:创建对象;给对象进行初始化
-
根据参数不同,构造器分为
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参,有参)
-
注意
- Java中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可以被子类继承
5.构造器重载,参数列表不用。使对象的创建更加灵活,方便创建不同的对象。
3.9 几个关键字
3.9.1 package
包package: 指明该文件中定义的类所在的包(若缺失该语句,则指定为无名包)package 顶层包名.子包名
- 包对应于文件系统的目录,package语句中,用"."来知名包(目录)的层次
- 包通常用小写单词,类名首字母通常大写
- 等同于文件夹的概念,可以有多级(包中包)
3.9.2 import
为使用定在在不用包中的Java类,需用improt语句来引入指定包层次下所需要的类或者全部类(.*)。import 包名.[子包名...].<类名|*>
JDK中主要的包
- java.lang:包括Java语言的核心类。如String、Math、Integer、System和Thread,提供常用操作
- java.net:网络相关
- java.io:输入输出功能
- java.util:实用工具类。如定义系统特征、接口的集合框架类、使用与日期日历相关的函数
- java.text:格式化相关
- java.sql:JDBC
3.9.3 this
- 含义
- 在方法内部使用,即这个方法所属对象的引用
- 在构造器内部使用,表示该构造器正在初始化的对象
- this表示当前对象,可以调用类的属性、方法和构造器
- 当形参与成员变量重名时,如果在方法内部需要使用成员变量,必须添加this来表明该变量是类成员。
- 在任意方法内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性
- this可以作为一个类中,构造器相互调用的特殊格式。注意:使用this()必须放在构造器的首行。使用this调用本类中其他的构造器,保证至少有一个构造器是不用this的。(实际就是构造器不能自己调用自己)
- 使用:当方法内需要用到调用该方法的对象时,使用this。
3.10 JavaBean
JavaBean是Java语言写成的可重用组件。符合以下标准:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法。
用户可以使用JavaBean将功能、处理、值、数据库访问和任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
4. Java类设计
4.1 面向对象特征之二:继承
- 为什么要有继承:多个类中存在相同属性和行为时,将这些内容抽取到单独一个类(父类)中,那么多个类(子类)无需定义这些属性和行为,只要继承父类即可。子类 is a 父类。
- 语法:
class Subclass extends Superclass{} - 作用:
- 提高代码复用性
- 让类与类之间产生关系,提供多态的前提
- 不要仅为了获取其他类中某个功能而去继承。需要有逻辑关系
- 规则:
- 子类继承父类,继承了父类的方法和属性
- 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法
- 在Java中,继承使用关键字是“extends”,即子类不是父类的子集,而是对父类的扩展
- 子类不能直接访问父类中private的成员变量和方法
- 单继承
- Java只支持单继承,不允许多重继承。即一个子类只能有一个父类
- 但一个父类可以派生出多个子类
4.2 方法的重写 override
- 定义:在子类中可以根据需要对从父类继承来的方法进行改造。在程序执行时,子类的方法将覆盖父类的方法
- 要求
- 重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型。(只重写方法体的代码)
- 重写方法不能使用比被重写方法更严格的访问权限
- 重写和被重写的方法需同时为static或同时为非static
- 子类方法抛出的异常不能大于父类被重写方法的异常
4.3 四种访问权限修饰符
| 修饰符 | 类内部 | 同一个包 | 子类 | 任何地方 |
|---|---|---|---|---|
| private | yes | |||
| 缺省 | yes | yes | ||
| protected | yes | yes | yes | |
| public | yes | yes | yes | yes |
- 如果子类和父类在同一个包下,那么对于父类的成员修饰符只要不是private,那就都可以使用
- 如果子类和父类不在同一个包下,子类只能使用父类中protected和public修饰的成员
4.4 关键词 super
-
在Java中使用super来调用父类中的指定操作。
- 访问父类中定义的属性
- 调用父类中定义的成员方法
- 在子类构造方法中调用父类的构造器:子类中所有的构造器默认都会访问父类中空参数的构造器。当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或super(参数列表)语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行。如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。
注意:
- 尤其当子父类出现同名成员时,可以用super区分
- super的追溯不仅限于直接父类
- super和this用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
-
调用父类构造器
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行
- 如果子类构造器中既未显示调用父类或本类的构造器,且父类中又没有无参构造器,则编译出错
-
this 和 super的区别
| 区别点 | this | super | |
|---|---|---|---|
| 1 | 访问属性 | 访问本类中的属性,如果本类没有该属性则从父类中继续查找 | 访问父类中的属性 |
| 2 | 调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
| 3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在构造器的首行 |
| 4 | 特殊 | 表示当前对象 | 无 |
4.5 对象实例化过程
4.5.1 简单类对象的实例化过程
4.5.2 子类对象的实例化过程
4.6 面向对象特征之三: 多态 Polymorphism
-
多态的两种体现:
- 方法的重载(overload)和重写(overwrite)
- 对象的多态性 -- 可以直接应用在抽象类和接口上。 Java引用变量有两个类型,编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。若编译时类型和运行时类型不一致,就出现多态
-
对象的多态:在Java中,子类的对象可以代替父类的对象使用
- 一个变量只能由一种确定的数据类型
- 一个引用类型变量可能指向(引用)多种不同类型的对象。例:
//Person 是 Student的父类 Person p = new Person(); Person s = new Student(); //以上的正常情况 Person e = new Student();//父类的引用对象可以指向子类的实例子类可以看作是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。把子类的对象,给父类类型的变量引用。 一个引用类型如果声明的为父类的类型,但实际引用的是子类的对象,那么该变变量就不能再访问子类中添加的属性和方法。
-
虚拟方法调用 Virtual Method Invocation
//正常调用
Person p = new Person();
p.getinfo()
Student s = new Student();
s.getinfo()
//虚拟方法调用(多态情况戏)
Person e = new Student();
e.getinfo() //调用Student类的getinfo()方法
编译时e是Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getinfo()方法。 -- 动态绑定
-
多态小结
- 前提:
- 需要存在继承或者实现关系
- 要有覆盖操作
- 成员方法:成员方法的多态性,也就是动态绑定,必须存在于方法的重写之上
- 编译时:要查看引用变量所属的类中是否有所调用的方法。
- 运行时:调用实际对象所属的类中的重写方法
- 成员变量:
- 不具备多态性,只看引用变量所属的类
- 子类继承父类:
- 若子类重写了父类的方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中
- 对于实例变量,不存在这样的想象。即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
- 多态例子:
- 方法声明的形参类型为父类的类型,可以使用子类的对象作为实参调用该方法
- 前提:
-
instanceof:检验某个对象是否为类的子类
x instanceof A: 检验x是否为类A的对象,返回值为boolean。
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误
- 如果x属于类A的子类B, x instanceof A 值也是true
4.7 Object类、包装类
4.7.1 Object类
- Object类是所有Java类的根父类(基类)。
- 所有在类的声明中未使用extends关键词指明其父类,则默认父类为Object类。
public class Person{} //等价于 public class Person extends Object{} //Object obj 代表所有类型 method(Object obj){...}//可以接收任何类作为参数 - Object类中的主要方法(所有类都可以执行Object中的方法)
| 方法名称 | 类型 | 描述 | |
|---|---|---|---|
| 1 | public Object() | 构造 | 构造方法 |
| 2 | public boolean equals(Object obj) | 普通 | 对象比较(引用对象) |
| 3 | public int hashCode() | 普通 | 取得Hash码的值 |
| 4 | public String toString() | 普通 | 打印对象的内存地址 |
4.7.2 对象类型转换 casting
- 基本数据类型的casting:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
- 强制类型转换:可以把大的数据类型强制转换成小的数据类型
- 对Java对象的强制类型转换成为造型
- 从子类到父类的类型转换可以自动进行
Student s = new Student(); Person p = s;- 从父类到子类的类型转换必须通过casting实现
Person p = new Person(); Student s = (Student) p;- 无继承关系的引用类型间的转换是非法的
4.7.3 ==与equals()
- ==:
- 基本类型比较值:只要两个变量的值相等,返回true
- 引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,返回true
- 用==比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译错误
- equals(): 所有类都继承了Object,也就获得了equals()方法,还可以重写
- 只能比较引用类型,其作用与==相同,比较是否指向同一个对象
obj1.equals(obj2) - 特殊类:File,String,Date及包装类(Wrapper Class)来说,比较类型及内容而不考虑引用的是否是同一个对象:因为这些类中重写了Object类中的equals()方法
- 如果想改变某一个类的equals,不想用equals来比较对象的内存地址,就需要重写equals方法
String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1 == s1);//false 因为地址不一样 System.out.println(s1.equals(s2));//true 因为内容一样 - 只能比较引用类型,其作用与==相同,比较是否指向同一个对象
4.7.4 String对象的创建
- 字面量创建对象时,只在常量池创建一个对象
- new的时候,常量池有对象,堆中也有对象。浪费内存
4.7.4 包装类 Wrapper
针对八种基本定义相应的引用类型。有了类的特点,就可以调用类中的方法
| 基本数据类型 | 包装类 |
|---|---|
| boolean | Boolean |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| char | Character |
| float | Float |
| double | Double |
-
基本数据类型包装成包装类的实例:装箱
- 通过包装类的构造器实现
int i = 500; Integer t = new Integer(i);-还可以通过字符串参数构造包装类对象
Float f = new Float("1.23"); Long l = new ("asdf")//编译不报错,但运行报错:NumberFormatException ,因为里面不是数字 -
获得包装类对象中包装的基本类型变量:拆箱 调用.xxxValue()方法
boolean b = Bobj.booleanValue(); -
JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配
Integer i = 112;//自动装箱 int i2 = i1;//自动拆箱 Boolean b1 = ture;//自动装箱 boolean b = new Boolean("false");//自动拆箱 -
字符串转基本类型
- 通过包装类的构造器实现:
int i = new Integer("12");- 通过包装类的ParseXxx(String s)静态方法:
Float f = Float.parseFloat("12.1"); -
基本类型转字符串
- 调用字符串重载的valueOf()
String fstr = String.valueOf(2.34f);- 更直接的方式:
String intstr = 5 + ""
5. 高级类特性
5.1 关键字:static
-
使用范围:修饰属性、方法、代码块、内部类
-
被修饰后的成员具备一下特点:
- 随类的加载而加载:类加载之后,静态的方法或者属性就能用了。直接使用
类名.方法调用 - 优先于对象存在:不用new就能用
- 修饰的成员,被所有对象共享
- 访问权限允许时,可不创建对象,直接被类调用
- 随类的加载而加载:类加载之后,静态的方法或者属性就能用了。直接使用
-
实例变量和类变量(class variable)
- 没有用static修饰的变量,叫做实例变量 instance variable。 它属于类的每一个对象,不能被同一个类的不同对象共享。
- 用static修饰的变量,叫做类变量。不用实例化,直接使用
类名.属性名就可以使用,是类的一部分,被所有这个类的实例化对象所共享。
-
类方法 class method
- 在static方法内部只能访问类的static属性,不能访问类的非static属性。
- 因为不需要实例就可以访问static方法,所以static方法内部不能有this和super
- 重载的方法需要同时为static的或者非static的
-
类属性、类方法的设计思想
- 类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些类属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
- 如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
-
单例(Singleton)设计模式: 只存在一个对象的实例。
5.2 main方法
public static void main(String[] args){
}
JVM需要调用类的main()方法,所以访问权限是public。JVM在执行main()方法时不必创建对象,所以该方法是static的。该方法接受一个String类型的数组参数,该数组保存执行Java命令时传递给所运行的类的参数。
5.3 类的成员之四:初始化块(代码块)
对Java对象进行初始化。
程序的执行顺序:
- 声明成员变量的默认值
- 显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
- 构造器再对成员进行赋值操作
例:
public
public class Person{
String name;//第一执行
public Person(){
this.name = "Mike";//第三执行
}
//非静态代码块
{
System.out.print("name");//第二执行
}
//静态代码块。通常用于初始化static属性
static{
}
}
- 非静态代码块:没有static修饰
- 可以有输出语句
- 可以对类的属性声明进行初始化操作
- 可以调用静态和非静态的变量或方法
- 若有多个非静态的代码块,按照从上到下的顺序依次执行
- 每次创建对象的时候,都会执行一次。且优先于构造器执行
- 静态代码块:static修饰
- 可以有输出语句
- 可以对类的属性声明进行初始化操作
- 不可以对非静态的属性初始化。即L不可以调用非静态的属性和方法
- 若有多个静态的代码块,按照从上到下的顺序一次执行
- static代码块执行先于非static代码块
- static代码块只执行一次
5.4 关键字:final
在Java声明类、属性和方法时,可以使用关键字:final修饰。表示最终。
- final标记的类不能被继承。提高安全性和程序的可读性。
String类、System类、StringBuffer类 - final标记的方法不能被子类重写。
Object类中的getClass() - final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。final标记的成员变量必须在声明的同时或在每个构造方法中或代码块中显式赋值,然后才能被使用。
final double PI = 3.14;
5.5 抽象类 abstract class
抽象类用来模型化那些父类无法确定全部实现,而是由其子类提供具体的实现的对象的类。
- 用abstract关键字来修饰一个类时,这个类叫做抽象类
- 用abstract修饰一个方法时,这个方法叫抽象方法
- 含有抽象方法的类必须被声明为抽象类
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写的全部的抽象方法,仍为抽象类
- 不能用abstract修饰属性、私有方法、构造器、静态方法、final的方法
- final:不能被继承,没有子类
- 构造器:抽象类可以有构造方法,只是不能直接创建抽象类的实例对象。
- 模版方法设计模式 Template Method:抽象类作为多个子类的通用模版。
5.6 更彻底的抽象:接口 interface
Java不支持多重继承,即一个类只能有一个父类。但接口可以解决这个问题。interface是抽象方法和常量值的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量的方法的定义,而没有常量和方法的实现。
class SubClass implements InterfaceA{}//实现接口类
一个类可以实现多个接口,接口也可以继承其他接口。
- 接口的特点
- 用interface定义
- 所有成员变量都默认是由public static final修饰
- 所有方法都默认是由public abstract修饰的
- 没有构造器
- 采用多层继承机制
public interface Runner{
int ID = 1;
void start();
public void run();
void stop();
}
//上下等价。写上默认是下面的格式
public interface Runner{
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
-
接口的实现
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍是抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 定义Java类的语法格式:先extends,后implements
<modifier> class <name> [extends <superclass>] [implements<interface>[,<interface>]*]{ <declarations>* } -
为什么要有接口:
-
抽象类是对一类事物的高度抽象,其中既有属性也有方法。接口是对方法的抽象,是对一系列动作的抽象。
-
工厂方法 Factory Method
5.7 类的成员之五:内部类
一个类的定义位于另一个类的内部。前者称为内部类,后者称为外部类。
解决Java不能多层继承的问题。
5.8 面向对象总结
6. 异常处理
6.1 异常
异常:在Java语言中,将程序执行中发生的正常行为称为异常。可以分为两类:
- Error:JVM系统内部错误、资源耗尽等严重情况
- Exception:其他因编程错误或偶然的外在因素导致的一般性问题,例:数组越界、空指针访问、试图读取不存在的文件、网络连接中断等。
- 程序员通常处理exception,对error无能为力。
6.2 异常处理
Java采用异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁并易于维护。
6.2.1 抓抛模型
抓抛模型(Throw - Catch):Java程序执行过程中,如果出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(Throw)异常。如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(Catch)异常。 如果一个异常回到main(),且main()也不处理,则程序运行终止。
try{//用try{}括住一段有可能出现异常的代码段,如果前面的代码出现异常,就不会执行后面的
...
}catch(Exception e){//当不知道捕获的是什么类型的异常时,可以直接用所有异常的父类Exception
...
}finally{//最终会执行的代码,可有可无
}
- getMessage():得到有关异常时间的信息
- printStackTrace():跟踪异常时间发生时执行堆栈的内容
6.2.2 声明抛出异常
如果一个方法中的语句执行时可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这种异常进行处理,而由该方法的调用者负责处理。
在方法声明中用throws子句可以声明抛出异常的列表,throws后面的异常类型可以时方法中产生的异常类型,也可以是他的父类。
public void readFile(String file) throws FileNotFoundException{
......
//读取文件可能产生FileNotFoundException类型异常
FileInputStream fis = new FileInputStream(file);
}
7. Java集合
7.1 集合特点和分类
Java集合存放在java.util包中,是一个用来存放对象的容器。
- 集合只能存放对象。如果存了一个基本数据类型int,会自动转换为Integer类后存入。
- 集合存在的是多个对象的引用,对象本身还是放在堆内存中
- 集合可以存放不同种类,不限数量的数据类型。
Java集合可以分为Set、List、Map三大体系
- Set:无序不可重复的集合
- List:有序,可重复的集合
- Map:具有映射关系的集合
7.2 HashSet
-
按Hash算法来储存集合中的元素,因此具有很好的存取和查找的性能。
-
特点:
- 不能保证元素的排列顺序:存放位置由hashCode()决定
- 不可重复:hashCode不相同
- 不是线程安全的
- 元素可以是null
3. 遍历set: for each
for (Object obj : set){//意思是把set的每一个值取出来,赋值给obj,直到循环set的所有值
System.out.println(obj);
}
-
HashSet集合判断两个元素相等的标准:两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
如果两个对象通过equals()方法返回true,这两个对象hashCode值也应该相同
-
泛型: 让set中只存在一种类型
Set set = nre HashSet();//可以存任何类型
Set<Object> set = new HashSet<Object>();//与上面等价
Set<String> set1 = new HashSet<String>();//指定String为集合的泛型,只能存String类型
7.3 TreeSet
TreeSet是 SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet支持两种排序方法:自然排序和定制排序。默认自然排序。(排序时,集合内元素必须类型相同,加入泛型限制。)
-
自然排序:调用集合元素的compareTo(Obejct obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列。
-
定制排序:提供Comparator接口的实现类对象。由该Comparator对象负责集合元素的排序逻辑。
7.4 ArrayList
List代表一个元素有序、且可重复的集合,集合中的每个元素都有其对应的顺序索引。
- List允许使用重复元素,可以通过索引来访问指定位置的集合元素
- List默认按元素的添加顺序设置元素的索引
- List集合里添加了一些根据索引来操作集合元素的方法
ArrayList是线程不安全的。
7.5 Map
Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组值用于保存Map里的Key,另一组用于保存Map里的Value。Key和Value都可以是任何引用类型的数据。
Key不允许重复。Key和Value之间存在单向一对一关系,即通过指定的Key总能找到唯一的,确定的Value。
遍历Map
Set<String> keys = map.KeySet();
for(String key : keys){
}
HashMap是线程不安全的。且不能保证其中key-value对的顺序。
7.6 操作集合的工具类:Collections
Collections是一个操作set、list和map等集合的工具类。
Collections中提供了大量方法对集合元素进行排序、查修和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法:
-
排序操作:
- reverse(list):反转list中元素的顺序
- sort(list):根据元素的自然顺序对指定List集合元素按升序排序
- sort(list, Comparator):根据指定Comparator长生的顺序对List记得元素排序
- swap(list, int, int):将指定list集合中的i处元素和j处元素进行交换
-
查找、替换
- Object max(Collection):根据元素的自然顺序,返回集合中最大元素
- Object max(Collection, Comparator)
- Object min(Collection)
- int frequency(Collection, Object):返回指定集合中指定元素的出现次数
- boolean replaceAll(List list, Object oldVal, Object newVal):替换list中所有旧值
-
同步控制: synchronizedXxx()方法
8. 泛型 Generic <>
8.1 为什么要有泛型
解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识表示类中的某个属性的类型或者是某个方法的返回值及参数类型。这样在类声明或实例化时,只要指定好需要的具体类型即可。Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,让代码更健壮,简洁。
Java中的泛型,只在编译阶段有效。泛型信息不会进入到运行阶段。
8.2 泛型的使用
- 泛型类
- 对象实例化时不指定泛型,默认为Obejct
- 泛型不同的引用不能相互赋值
- 泛型方法
- 泛型接口
8.3 通配符 ?
不确定集合中的元素具体的数据类型,只用?表示所有类型
public void test(List<?> list){
}
有限制的通配符
9. 枚举 & 注解
9.1 枚举类
在某些情况下,一个类的对象是有限且固定的。例如季节类,只能有4个对象。
-
手动实现枚举类:
- private修饰构造器
- 属性使用private final修饰
- 把该类的所有实例都是用public static final修饰
-
enum 定义枚举类
-
枚举类方法
9.2 注解 Annotation
代码中的特殊标记,可以在编译,类加载,运行时被读取,并执行相应的处理。
可以像修饰符一样被使用,用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明。
基本Annotation:
- @Override:限定重写父类方法,该注释只能用于方法
- @Deprecated:用于表示某个程序元素(类,方法等)已过时
- @SuppressWarnings:抑制编译器警告
自定义Annotation:使用@interface关键字
10. IO
11. Java反射 Reflection
Reflection是被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。Java反射前提:已经加载过这个类。就可以通过类名来寻找到这个类的所有相关信息。
Java反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
11.1 class类
在Object类中定义了以下的方法,此方法将被所有子类继承:public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓的反射从程序的运行结果来看就是可以通过对象的反射求出类的名称。
Class类是对所有类的高度抽象,可以描述所有类。Class本身也是一个类。通过class可以完整地得到一个类中的完整结构。
Class类的常用方法
实例化Class类对象
-
已知具体的类,通过类的class属性获取该方法。最安全可靠,程序性能最高
Class c0 = Person.class//通过类名.class创建指定类的Class实例 -
已知某个类的实例,调用该实例的getclass()方法获取Class对象
Class c1 = p.getClass(); -
已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class c2 = Class.forName("java.lang.String");//forName(包名.类名)全路径
11.2 通过反射调用类的完整结构
Field、Method、Constructor、Superclass、Interface、Annotation
实现的全部接口,所继承的父类,全部的构造器,全部的方法,全部的field
- 实现的全部接口
public Class<?>[] getInterfaces() - 所继承的父类
public Class<? Super T> getSuperclass() - 全部构造器
public Constructor<T>[] getConstructors()获取public的构造器。public Constructor<T>[] getDeclaredConstructors()获取所有构造方法
11.3 用反射的构造方式创建对象
11.4 反射机制获取类的方法
11.5 反射机制获取类的属性和包
11.6 反射机制调用指定方法
11.7 反射机制调用指定属性
11.8 Java动态代理
Proxy:专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
动态代理步骤:
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法,以完成代理的具体操作
- 创建被代理的类以及接口
- 通过Proxy的静态方法
newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)创建一个Subject接口代理 - 通过SUbject代理调用RealSubject实现类的方法
12. 线程
12.1 基本概念
- 程序 program:是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
- 进程 process:是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。(几核CPU,就代表同一个瞬时时间能处理任务数)
- 线程 thread:进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个程序可以同一时间执行多个线程,就是支持多线程的。多线程:一个进程(一个程序运行时),可以分化为并行执行的多个线程(多个子程序)。
- 何时需要多线程:程序需要同时执行两个及以上的任务。程序需要实现一些需要等待的任务,如用户输入,文件读写,网络操作,搜索等。需要一些后台运行的程序时。
12.2 多线程的创建与启动
Java的JVM允许程序运行多个线程,通过java.lang.Thread类实现。
-
Thread类特性
- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。想要在开启的多线程中运行的代码逻辑,写在run()方法里。
- 通过该Thread对象的start()方法来调用这个线程。用来启动线程,本质上就是运行run()方法。
-
构造方法
- Thread() :创建新的Thread对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target):指定创建线程的目标对象,它实现了runnable接口中的run方法
- Thread(Runnable target, String name):创建新的Thread对象
-
异步执行: 开启了线程之后run方法中运行的代码与主程序中start之后的程序是并行的,没有先后顺序。
-
创建线程的两种方式
- 继承Thread类:线程代码存放Thread子类run方法中。重写run方法
- 定义自类继承Thread类
- 子类中重写Thread类中的run()方法
- 创建Thread子类对象,即创建了线程对象
- 调用线程对象start方法:启动线程,调用run方法
- 实现Runnable接口:线程代码存在接口的子类run方法中。实现run方法
- 定义子类,实现Runnable接口
- 子类中重写Runnable接口中的run方法
- 通过Thread类含参构造器创建线程对象
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
- 实现Runnable接口好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
- 继承Thread类:线程代码存放Thread子类run方法中。重写run方法
-
多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,增强用户体验
- 提高计算机系统CPU的利用率
- 改善程序结构。将长而复杂的进程分为多个线程,独立运行,利于理解和修改
12.3 Thread类的相关方法
- void start():启动线程,并执行对象的run()方法
- run():线程在被调度时执行的操作
- String getName():返回线程的名称
- void setName(String name):设置该线程名称
- static currentThread():返回当前线程
- static void yield():线程让步。暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
- static void sleep(long millis):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重新排队。抛出InterruptedException异常
- stop():强制线程生命期结束
- boolean isAlive():返回boolean,判断线程是否还活着
12.4 线程的生命周期
- 完整的生命周期经历五种状态
12.5 线程的同步与死锁
12.5.1 线程的同步和锁
多线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法。就会出现问题。
解决思路:一个线程整体执行完这个方法,另一个线程再执行。直接在方法上synchronized同步锁。在普通方法加同步锁,锁住的是整个对象,不是某一个方法。
使用方法
- 普通方法加同步锁,锁当前方法对应的对象,当前对象的所有加了同步锁的方法共用一个同步锁
- 静态方法加synchronized,对于所有的对象都是使用一个锁
- synchronized可以放在方法声明中,表示整个方法为同步方法
public synchronized void show (String name){
}
- 锁代码块:
synchronized(对象){
}
12.5.2 死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。
解决方法:
- 专门的算法、原则,比如加锁顺序一致
- 尽量减少同步资源的定义,尽量避免锁未释放的场景
12.6 线程通信
- wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll():唤醒正在排队等待同步资源的所有线程结束等待