# Java 概述
# Java 语言的主要特点
- 简单性
- 面向对象
- 跨平台
- 安全性
- 多线程
- 动态性
# 初始 Java 程序
Java 源文件以 “java” 结尾,此文件中最多只能有一个类被声明为 public,保存时源文件名需与 public 类名相同,如果文件中不存在 public 类,源文件名无要求。
一个源文件包含几个类就可以编译出几个.class 文件。
# 基本程序设计
# 常量
符号常量: final
修饰的变量,一旦赋值后不可更改。
# 类型转换
# 位运算
运算符 | 用法 | 说明 |
---|---|---|
~ | ~a |
按位取反 |
& | a & b |
按位与 |
| | a | b |
按位或 |
^ | a ^ b |
按位异或 |
<< | a << b |
左移 |
>> | a >> b |
右移 |
>>> | a >>> b |
无符号右移 |
# 流程控制
# 标准输入与 Scanner 类
Scanner in = new Scanner(System.in); | |
\\ ... | |
in.close(); |
返回值 | 方法 | 说明 |
---|---|---|
boolean | hasNext() |
判断是否还有输入 |
String | next() |
读取一个字符串 |
T | nextT() |
读取一个 T 类型的值 |
String | nextLine() |
读取一行字符串 |
boolean | hasNextT() |
判断下一个输入是否为 T 类型 |
boolean | hasNextT(int radix) |
判断下一个输入是否为 T 类型,radix 为进制 |
# 标准输出
System.out.println("Hello, World!"); |
printf
方法
标志 | 说明 |
---|---|
- | 左对齐 |
+ | 显示正负号 |
0 | 数字前补 0 |
# | 显示八进制或十六进制前缀 |
空格 | 正数前加空格 |
, | 数字以逗号分隔 |
( | 负数用括号括起来 |
格式化占位符
格式 | 说明 |
---|---|
%d | 整数 |
%f | 浮点数 |
%e | 科学计数法 |
%g | 通用浮点数 |
%h | 散列码 |
%s | 字符串 |
%c | 字符 |
%b | 布尔值 |
%t | 日期时间 |
%n | 换行 |
%x | 十六进制整数 |
%o | 八进制整数 |
%a | 十六进制浮点数 |
# 面向对象
# 面向对象编程的特性
# 封装
将对象的属性和行封装起来构成新的类型,并隐藏内部实现细节,只向用户提供对象的外部可调用操作
封装最大的好处是降低了软件系统的耦合程度。实现了代码的可重用性和可维护性。
# 继承
继承性是类与类之间的一种关系,通过继承,可以在无需重新编写原有类的情况下,实现代码的扩展和重用。
- 具有父类的全部属性和行为
- 能对继承的属性和行为进行修改和扩充。
- 极大提升了可重用性和可维护性
# 多态
多态性是指在父类中定义的属性和行为被子类继承之后,可以具有不同的数据类型或表现形式。
- 单个类:多态性在单个类中表现为方法重载:一个类可以有多个名字相同、形参列表不同的方法,在使用时由传递给它们的实参来决定使用哪个方法。
- 在多个类中主要表现为继承结构中的方法覆盖:父类和子类中具有相同的方法头,不同的代码实现,运行时再决定调用哪个方法。
# 对象与构造方法
要比较两个对象的内容是否相等,即对象实体值,必须在类里实现专门的方法,所有类的父类 Object
中有一个方法 equals()
。
因对象数组中每个元素都是对象,所以每个元素都需单独实例化(还需用 new
实例化每个元素)
# 方法重载
定义方法时使用相同的方法名,不同的形参列表,叫方法重载(overloading)方法重载是实现 “多态” 的一种方法。
- 形参列表不同是指参数个数不同,或者对应位置上参数类型不同。
- 重载方法返回类型、修饰符可以相同,也可不同,它不决定是否是重载方法
# 定位重载函数的顺序
原则:
- 查找同名方法,没有则报错
- 比较形参和实参的数目是否相等,如果多个方法符合条件,这些方法进入候选集
- 候选集中
- 如果对应位置上的每个参数类型完全匹配,为最佳方法,
- 如果无匹配的可以通过扩展转换找出最佳匹配方法,选择原则为:源类型与目标类型的距离越近越好。
# this
关键字
this
表示当前对象
当通过一个对象引用调用它的成员方法时,系统会将当前对象的别名 this
传递到被调方法中,所以, this
只能在成员方法中可见。
# 使用 this
访问对象成员
特别是局部变量和成员变量重名时,利用 this
可以限定某个变量是成员变量。
# 构造方法中,用 this
调用本类的另一构造方法
在一个构造方法中,调用另一个重载的构造方法:形式为: this([实参])
这条语句必须是构造方法的第一条语句,且只能出现一次。
# 返回当前对象
在方法中,利用 return this
可以返回当前对象,从而可以继续调用该类或其子类的成员。
# 参数传递
调用方法时,先将实参赋给形参,然后再执行操作。JAVA 传参数总是采用按值传递的方式,所谓值传递就是将实参值的副本传递给被调方法的形参。
# 基本数据类型
基本数据类型的传递是将实参的值传递给形参,形参的改变不会影响实参。
# 引用数据类型
- 如果形参一直指向同一个对象,那么通过形参改变对象的内容,会影响实参。
- 如果形参指向了另一个对象,那么通过形参改变对象的内容,不会影响实参。
# static
修饰符
- 与类相关的静态成员称为类变量或类方法,
- 与实例相关的普通成员称为实例变量或实例方法。
static
关键字可以修饰字段、方法、语句块和类(只能修饰内部类)
# static
修饰字段
static
字段也称类 / 静态数据,被类的所有对象共享。
当系统第一次准备使用该类时,系统会为该类的 static
字段分配内存空间,存储在方法区中。此时类变量开始生效,直到类被卸载。该类所占有的内存才垃圾回收机制回收。
static
变量被该类的所有对象所共享,只能是类一级的成员,不能声
明为方法的局部变量。而实例变量则是属于一个对象实例。
static
变量可以被同一类的其他方法直接访问。
其他类可以通过此 static
成员所属类的类名访问它,而无需先创建对象。
# static
修饰方法
静态方法是属于类的,而不是属于对象的。静态方法只能访问静态域和静态方法,不能访问实例域和实例方法。
静态方法属于定义它的类,而且无需创建对象就
- 可直接通过类名访问它。
- 通过对象引用(无论是否为
null
)调用,但实例方法必须通过非null
的对象引用调用。
静态方法不能使用 this
和 super
关键字,因为静态方法在对象创建之前就已经存在。
# static
修饰代码块
静态语句块不属于任何一个方法,当类被加载时,虚拟机会执行静态块中的语句,且在类型的生命周期中只执行一次。
所以,可以利用静态块在类的加载阶段做一些初始化操作,如初始化静态数据。
# 访问控制
修饰符 | 权限 | 修饰对象 | 可见性 | |||||
---|---|---|---|---|---|---|---|---|
类 | 字段 | 方法 | 同类 | 同包 | 不同包子类 | 不同包非子类 | ||
public |
公有 | ✔️ | ✔️ | ✔️ | * | * | * | * |
protected |
保护 | ✔️ | ✔️ | * | * | * | ||
默认 | 包访问 | ✔️ | ✔️ | ✔️ | * | * | ||
private |
私有 | ✔️ | ✔️ | * |
# 继承
- Java 只支持单继承,默认父类是
Object
- 父类中声明为
private
的字段和方法,子类不可见。 - 子类可以添加字段和方法,可以通过定义重名的属性隐藏父类属性(比较少用),也可以通过定义重名方法覆盖父类方法。
- 子类不会自动获得父类的构造方法,子类在构造方法中如果要调用父类的构造方法,可以使用
super
调用,而且该调用必须是子类构造方法的第一条语句。
# final
修饰符
# final
常量
final
修饰的常量只能赋值一次,之后不能再修改。它可以是局部常量也可以是成员常量。
- 局部常量必须在读取之前被赋值,
- 成员常量必须在声明时或者在构造方法中被初始化。
- 基本数据类型的常量值不能改变
- 引用类型不能指向其他对象,但可以改变对象的内容
final
常量的几个常见用法
- blank
final
s (空final
)
当由同一个类生成的不同对象希望可以有不同的final
字段值时,可以在定义该字段时只声明不赋值,通过构造方法对每个对象的final
字段进行赋值。 final
参数
final
修饰形参,如果修饰的是基本数据类型,表示形参在传入后值不变,如果修饰的是引用类型,表示形参的引用值被赋值后,就不会指向新的对象。static final
静态常量
是类一级的全局常量,只用于修饰字段而不能用于局部变量,它需要在类型被加载时就完成初始化操作。因此,一定要在定义时或者在static
块中就给定初始值。
# final
类
final
修饰的类不能被继承
# final
方法
final
修饰的方法不能被子类重写
# 类的进阶设计
# 对象类型转换
# 向上转型
使用向上转型的对象引用可以调用父类中的方法,但不能调用子类中扩展的方法。
# 向下转型
向下转型是将父类对象转换为子类对象,需要强制类型转换,但是在转换之前需要使用 instanceof
判断是否可以转换。
Father f = new Son(); | |
if (f instanceof Son) { | |
Son s = (Son) f; | |
} | |
// jdk 16 | |
if (f instanceof Son s) { | |
s.method(); | |
} |
# 方法重写
方法重写是指子类重新定义父类的方法,方法名、参数列表、返回类型必须与父类中被重写的方法一致。
- 访问权限:子类方法的访问权限不能小于父类方法的访问权限。
- 返回类型:子类方法的返回类型必须与父类方法的返回类型一致,或者是父类方法返回类型的子类。
- 异常:子类方法不能抛出比父类方法更多的异常,但可以抛出更少的异常,或者不抛出异常。
final
方法不能被重写;不能一个是static
方法,一个是实例方法。
# 动态绑定
Java 绑定规则如下:
- 被
final
、static
、private
修饰的方法执行静态绑定,与编译时类型的方法体进行绑定。 - 其余实例方法执行动态绑定,与对象的运行时类型的方法体进行绑定。
- 成员变量(包括静态变量和实例变量)执行静态绑定,与引用类型的成员变量绑定。
- 静态绑定:是指在程序执行前由编译器或连接程序绑定
- 动态绑定:是指在程序执行时由运行时 JVM 通过对象的类型指针找到对象的实际类型,然后调用相应的方法。
# 抽象方法
抽象方法是没有方法体的方法,只有声明,没有实现。
- 访问权限:抽象方法不能是
private
,因为private
方法不能被继承。 - 不能被
final
和static
修饰。
public abstract void foo(); |
# 抽象类
抽象类是包含抽象方法的类,抽象类不能被实例化,只能被继承。
- 抽象类可以包含零个到多个成员变量、普通方法,也可以含零个到多个抽象方法。
- 抽象类不能被
final
修饰。 - 一个类只要有一个方法是抽象方法,这个类就要定义成抽象类。
# 接口
接口是一种特殊的抽象类,接口中的方法都是抽象方法,接口中的成员变量都是 public static final
类型的常量。
public interface InterfaceName { | |
// 常量 | |
public static final int MAX = 100; | |
// 抽象方法 | |
public abstract void foo(); | |
} |
- 所有的方法都是公开的抽象方法,所以,方法可以省略
public
和abstract
关键字。 - 所有的字段都是公开的静态常量,所以,字段可省略
public static final
关键字。需要定义时赋初值。 - 类似抽象类,接口不能被实例化,只能被实现。
- 接口可以继承其他接口(不能是类),而且支持多继承,多个接口用
,
分割。这一点与类不同。
重写接口方法时,访问权限只能是 public
,因为接口中的方法默认是 public
的。
# 接口与抽象类的区别
抽象类与接口是进行抽象的两种机制,有相似性,也有区别。
- 语法上:抽象类用关键字
abstract class
定义,可定义成员变量和非抽象方法;接口用interface
定义,接口内只有公开的静态常量、成员方法都是公开的,且只能是抽象方法、默认方法或静态方法。 - 使用上:抽象类是用来被单继承的,而一个类可以实现多个接口。
- 设计上:抽象类作为父类,与子类之间存在 “is-a” 关系,即父子类本质上是一种类型。接口只能表示类支持接口的行,具有接口的功能。因此接口和实现类之间表示的是 “like-a” 关系。因此,在设计上,如果父子类型本质上是一种类型,那父类可设计成抽象类,如果子类型只是想额外具有一些特性,则可以将父类型设计成接口,而且这些接口不易过大,应该设计成多个专题的小接口。这也是面向对象设计的一个重要原则 — 接口隔离原则。
# 内部类
- 内部类仍是一个独立的类,在编译外部类时,内部类也会被编译成独立的
class
文件,文件名前面冠以外部类的类型和$
符号,如OuterClass$InnerClass
。 - 内部类是外部类的成员,因此,内部类可以访问外部类的成员,无论是否为
private
。如果内部类声明成static
,相应的,只能访问外部类的静态成员。 - 内部类可作为外部类的成员,也可作为方法的成员(局部内部类)。如果作为外部类的成员,则可以使用 4 种访问权限修饰符,如果作为方法成员则没有访问权限修饰符。
# 实例内部类
-
实例内部类是最常见的内部类,它是外部类的一个成员,可以访问外部类的所有成员。
-
在外部类的静态方法和外部类以外的地方创建内部类的实例,需要先创建外部类的实例,再通过外部类的实例创建内部类的实例。
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
-
在外部类中不能直接访问内部类的实例成员,需要通过内部类的实例访问。
-
在内部类中可以直接访问外部类的实例成员,如果内部类和外部类有同名成员,可以通过
OuterClass.this.member
访问外部类的成员。 -
内部类不能与外部类重名
# 静态内部类
静态内部类是 static
修饰的内部类,它是外部类的一个静态成员,它与所属的外部类而不是外部对象相关联。
-
通过外部类名直接访问静态内部类的静态成员
OuterClass.InnerClass inner = new OuterClass.InnerClass();
-
静态内部类中可定义静态成员和实例成员。外部类以外的其他类可通过类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则必须通过静态内部类的实例。
-
类似于类的静态方法,静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
-
接口中可以定义内部类,且默认是
static
内部类,这种类可以被某个接口的所有不同实现所共用。
# 局部内部类
- 局部内部类是指定义在方法内的内部类。其有效范围只在定义它的方法内。
- 局部内部类遵循如下规则:
- 局部内部类与局部变量一样,不能使用访问修饰符和
static
修饰符。 - 在局部内部类中可以访问外部类的所有成员。
- 在局部内部类中只可以读取方法常量,或初始化后不再改变的变量。
- 如果方法中的成员与外部类成员同名,则可用
OuterClass.this
访问外部类中的实例成员OuterClass.this.MemberName
或用OuterClass.MemberName
访问外部类的静态成员。
- 局部内部类与局部变量一样,不能使用访问修饰符和
# 匿名内部类
- 匿名类是指没有类名只有类体的内部类,如果程序定义某个类却只需要创建一个对象,这时可以考虑使用匿名内部类。
- 由于匿名类没有类名,所以创建匿名类的对象时需要用到该匿名类的父类或接口,而且匿名类的定义和对象创建是同时进行的,因此,在定义的同时,使用
new
语句来声明对象。
匿名内部类的特点:
- 匿名类和局部内部类一样,可以访问外部类的所有成员。其他局部内部类的特性也适用于匿名内部类。
- 匿名类没有名字,所以不能定义构造方法,但可定义非静态字段,重写父类型方法。
- 匿名内部类编译后对应的字节码文件名为:
外部类$数字序号(序号从1开始)
。 - 匿名类常用方式是向方法传参,当匿名内部类重写的父类(接口)只有一个方法时,建议使用
Lambda
表达式。
# 异常处理
# 异常类
- 运行时异常(
Runtime Exception
):是一种不检查异常,是程序员编程时的错误,如数组越界、空指针等。 - 受检异常(
Checked Exception
):是一种检查异常,是程序员无法控制的异常,如文件不存在、网络中断等。
# 常用异常类
异常类 | 描述 |
---|---|
ArithmeticException |
算术异常 |
ArrayIndexOutOfBoundsException |
数组下标越界异常 |
NullPointerException |
空指针异常 |
FileNotFoundException |
文件未找到异常 |
IOException |
输入输出异常 |
# 异常处理机制
- 声明异常
不捕获异常,而只是声明方法有可能抛出的异常,从而让该方法的上层调用方法捕获异常。 - 捕获处理异常
Java 运行时系统捕获异常并找到相应异常的捕获处理代码并运行,这一过程称为捕获处理异常。如果系统找不到捕获异常代码,程序将终止。
# 捕获处理异常
说明:
try
语句:发生异常时,将跳过try
块中异常点后面的语句,且异常处理需要更多的时间和资源。因此,应当仔细分析代码,尽量缩减try
代码块。catch
语句可以有零个或多个,finally
语句可以有零个或一个。catch
语句:设计捕获异常代码要注意其顺序,按照从 “特殊到一般” 的顺序来设计。将子类的catch
块放在前面,父类的catch
块放在后面。- 从 java7 开始,多个异常可以写在一个
catch
中,它们之间用竖线隔开,但需要注意,用|
操作符合并的异常不要出现互为父子的关系。 finally
语句是可选项。如果有该语句,无论是否捕获或处理异常,即使try
或者catch
块中包含break
或return
语句,finally
块里的语句也会被执行。finally
语句一般用来在最后做一些资源回收工作,比如在try
语句中打开了文件流,可以在finally
中确保文件被有效关闭。
方法 | 描述 |
---|---|
getMessage()->String |
返回异常的详细信息字符串 |
toString()->String |
返回异常类全名 + 异常信息 |
printStackTrace() |
打印异常的堆栈信息 |
getStackTrace()->StackTraceElement[] |
返回和异常对象相关的堆栈跟踪元素数组 |
# 带资源的 try
语句
可以被自动关闭的资源有一个前提,这个资源的类已经实现了 java.lang.AutoCloseable
接口,这个接口有一个方法: void close()
。
# throw
抛出异常和 throws
声明异常
# 抛出异常
检测到错误的程序可以创建一个合适的异常对象并抛出它,这就称为抛出一个异常。
Java 中有两种方法抛出异常。
- Java 运行时环境自动抛出异常:系统定义的
RuntimeException
类及其子类和Error
都可以由系统自动抛出。 - 语句
throw
抛出异常:用户程序想在一定条件下显式抛出异常,这必须借助于throw
语句抛出。Java 用throw
语句抛出异常。
# 声明异常
- 定义方法时,如果方法可能出现异常,但该方法不想或不能自己捕获处理这种异常,那就必须在声明方法时用
throws
声明可能发生的异常。 - 对于不受查异常(
Runtime Exception
和Error
),Java 不要求在方法头中显示声明,但是,其它异常就一定要在方法头中显示声明。
# 常用类
# Object
类
Object
类是所有类的父类,所以所有类都可以调用Object
类的方法。Object
类中的clone()
方法是浅拷贝,即只拷贝对象的引用,而不拷贝对象的内容。Object
类中的equals()
方法是比较两个对象的内容是否相等,如果没有重写equals()
方法,那么比较的是两个对象的引用是否相等。Object
类中的hashCode()
方法是返回对象的哈希码,哈希码是对象的内存地址经过哈希算法得到的一个整数,用于快速查找对象。Object
类中的toString()
方法是返回对象的字符串表示,如果没有重写toString()
方法,那么返回的是对象的哈希码。
# String
类
String
类是不可变的,一旦创建,它的值就不能被改变。
# 创建字符串
- 直接赋值:
String str = "Hello, World!";
- 通过构造方法:
String str = new String("Hello, World!");
- 通过字符数组:
char[] ch = {'H', 'e', 'l', 'l', 'o'}; String str = new String(ch);
- 通过字节数组:
byte[] b = {72, 101, 108, 108, 111}; String str = new String(b);
String str = "Hello, World!"; | |
String str1 = "Hello, World!"; | |
String str2 = new String("Hello, World!"); | |
String str3 = new String("Hello, World!"); | |
System.out.println(str == str1); // true | |
System.out.println(str == str2); // false | |
System.out.println(str2 == str3); // false |
# 字符串常用方法
方法 | 描述 |
---|---|
length()->int |
返回字符串的长度 |
charAt(int index)->char |
返回指定索引处的字符 |
indexOf(String str)->int |
返回指定子字符串在此字符串中第一次出现的索引 |
lastIndexOf(String str)->int |
返回指定子字符串在此字符串中最右边出现的索引 |
substring(int beginIndex, int endIndex)->String |
返回一个新的字符串,它是此字符串的一个子字符串 |
concat(String str)->String |
将指定字符串连接到此字符串的结尾 |
replace(char oldChar, char newChar)->String |
返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的 |
split(String regex)->String[] |
根据给定正则表达式的匹配拆分此字符串 |
trim()->String |
返回字符串的副本,忽略前导空白和尾部空白 |
endsWith(String suffix)->boolean |
测试此字符串是否以指定的后缀结束 |
startsWith(String prefix)->boolean |
测试此字符串是否以指定的前缀开始 |
format(String format, Object... args)->String |
使用指定的格式字符串和参数返回格式化的字符串 |
toLowerCase()->String |
使用默认语言环境的规则将此 String 中的所有字符都转换为小写 |
toUpperCase()->String |
使用默认语言环境的规则将此 String 中的所有字符都转换为大写 |
equals(Object obj)->boolean |
将此字符串与指定对象进行比较 |
# 正则表达式
# 字符类
字符 | 描述 |
---|---|
\d |
匹配一个数字字符 |
\D |
匹配一个非数字字符 |
\w |
匹配一个字母、数字或下划线字符,不包括 $ |
\W |
匹配一个非字母、数字或下划线字符 |
\s |
匹配一个空白字符 |
\S |
匹配一个非空白字符 |
. |
匹配除换行符 \n 之外的任何字符 |
\p{Lower} |
匹配一个小写字母 |
\p{Upper} |
匹配一个大写字母 |
\p{ASCII} |
匹配一个 ASCII 字符 |
\p{Alpha} |
匹配一个字母字符 |
\p{Digit} |
匹配一个数字字符 |
\p{Alnum} |
匹配一个字母或数字字符 |
\p{Punct} |
匹配一个标点字符 |
\p{Graph} |
匹配一个可打印字符,不包括空格 |
\p{Print} |
匹配一个可打印字符,包括空格 |
\p{Blank} |
匹配一个空格或制表符 |
\p{Cntrl} |
匹配一个控制字符 |
# 量词
字符 | 描述 |
---|---|
X? |
匹配 X 零次或一次 |
X* |
匹配 X 零次或多次 |
X+ |
匹配 X 一次或多次 |
X{n} |
匹配 X 恰好 n 次 |
X{n,} |
匹配 X 至少 n 次 |
X{n,m} |
匹配 X 至少 n 次,但不超过 m 次 |
# 运算符
字符 | 描述 |
---|---|
[] |
表示 [] 中的任意一个字符 |
\| |
表示 \| 两边的任意一个字符 |
^ |
1. 表示输入字符串的开始位置 \n 2. 在 [] 使用表示不接受 [] 中的字符 |
$ |
表示输入字符串的结束位置 |
() |
表示一个子表达式,可以获取供以后使用,用 $1 、 $2 等表示 |
&& |
表示交集 |
# 泛型集合
# 泛式数据结构
Collection
接口List
接口ArrayList
类LinkedList
类
Set
接口HashSet
类TreeSet
类
Queue
接口PriorityQueue
类
Map
接口HashMap
类TreeMap
类
# 集合遍历
List<String> list = new ArrayList<>(); | |
// 1. 使用迭代器遍历 | |
Iterator<String> it = list.iterator(); | |
while (it.hasNext()) { | |
System.out.println(it.next()); | |
} | |
// 2. 使用增强 for 循环遍历 | |
for (String str : list) { | |
System.out.println(str); | |
} | |
// 3. 使用 forEach 方法遍历 | |
list.forEach(str -> System.out.println(str)); | |
// 4. 使用 for 循环遍历 | |
for (int i = 0; i < list.size(); i++) { | |
System.out.println(list.get(i)); | |
} | |
Map<String, String> map = new HashMap<>(); | |
// 键值对遍历 | |
for (Map.Entry<String, String> entry : map.entrySet()) { | |
System.out.println(entry.getKey() + " : " + entry.getValue()); | |
} | |
// 键遍历 | |
for (String key : map.keySet()) { | |
System.out.println(key); | |
} | |
// 值遍历 | |
for (String value : map.values()) { | |
System.out.println(value); | |
} |
# Arrays
类(了解)
Arrays
类是java.util
包中的一个工具类,提供了一系列静态方法用于操作数组。
方法 | 描述 |
---|---|
asList(T... a)->List<T> |
将数组转换为 List |
binarySearch(T[] a, T key)->int |
对数组进行二分查找,需要先排序 |
copyOf(T[] original, int newLength)->T[] |
复制指定的数组,截取或用 null 填充(如果需要) |
sort(T[] a)->void |
对数组进行排序 |
fill(T[] a, T val)->void |
用指定的值填充数组 |
equals(T[] a, T[] a2)->boolean |
比较两个数组是否相等 |
compare(T[] a, T[] a2)->int |
比较两个数组的大小 |
stream(T[] array)->Stream<T> |
返回数组的流 |
# Collections
类(了解)
Collections
类是java.util
包中的一个工具类,提供了一系列静态方法用于操作集合。
方法 | 描述 |
---|---|
sort(List<T> list)->void |
对列表进行排序 |
reverse(List<T> list)->void |
反转列表中的元素 |
shuffle(List<T> list)->void |
随机排列列表中的元素 |
swap(List<T> list, int i, int j)->void |
交换列表中指定位置的元素 |
fill(List<? super T> list, T obj)->void |
用指定元素替换列表中的所有元素 |
copy(List<? super T> dest, List<? extends T> src)->void |
将所有元素从一个列表复制到另一个列表 |
max(Collection<? extends T> coll)->T |
返回集合中的最大元素 |
min(Collection<? extends T> coll)->T |
返回集合中的最小元素 |
binarySearch(List<? extends T> list, T key)->int |
对列表进行二分查找,需要先排序 |
disjoint(Collection<?> c1, Collection<?> c2)->boolean |
如果两个指定的集合没有相同的元素,则返回 true |
# IO 流
# File
类
File
类是java.io
包中的一个类,用于表示文件或目录的路径名。
# 文件流
创建 FileInputStream
的两种方式
File file = new File("test.txt"); | |
FileInputStream fis = new FileInputStream(file); | |
// ********** or ********** | |
FileInputStream fis2 = new FileInputStream("test.txt"); |
- 创建 FilelnputStream 实例对象时,指定的文件应当是存在和可读的。
- 创建 FileOutputStream 实例对象时,如果指定的文件己经存在,这个文件中原来内容将被覆盖清除,可以指定还不存在的文件名
# 文件拷贝
# FileInputStream
和 FileOutputStream
public static void copyFile(String src, String dest) { | |
try (FileInputStream fis = new FileInputStream(src); | |
FileOutputStream fos = new FileOutputStream(dest)) { | |
byte[] buffer = new byte[fis.available()]; | |
fis.read(buffer); | |
fos.write(buffer); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} |
# FileReader
和 FileWriter
public static void copyFile(String src, String dest) { | |
try (FileReader fr = new FileReader(src); | |
FileWriter fw = new FileWriter(dest)) { | |
char[] buffer = new char[1024]; | |
int len; | |
while ((len = fr.read(buffer)) != -1) { | |
fw.write(buffer, 0, len); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} |
# 缓冲流
- 缓冲流是对文件流的包装,提供了缓冲功能,可以提高读写文件的效率。
# BufferedInputStream
和 BufferedOutputStream
FileInputStream fis = new FileInputStream("test.txt"); | |
BufferedInputStream bis = new BufferedInputStream(fis); | |
FileOutputStream fos = new FileOutputStream("test.txt"); | |
BufferedOutputStream bos = new BufferedOutputStream(fos); |
# BufferedReader
和 BufferedWriter
public static void copyFile(String src, String dest) { | |
try (BufferedReader br = new BufferedReader(new FileReader(src)); | |
BufferedWriter bw = new BufferedWriter(new FileWriter(dest))) { | |
String line; | |
while ((line = br.readLine()) != null && !line.isEmpty()) { | |
bw.write(line); | |
bw.newLine(); | |
} | |
bw.flush(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} |
# 字节字符转换流
- 字节流和字符流之间的转换,可以使用
InputStreamReader
和OutputStreamWriter
。
# InputStreamReader
和 OutputStreamWriter
public static void main(String[] args) { | |
try (InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"), StandardCharsets.UTF_8); | |
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test2.bak"), StandardCharsets.UTF_8)) { | |
char[] buffer = new char[1024]; | |
int len; | |
while ((len = isr.read(buffer)) != -1) { | |
osw.write(buffer, 0, len); | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} |
# 线程
# 线程概念
# 程序
程序是一段静态代码,是指令与数据的集合。通常是外存上保存的可执行的二进制文件。
# 进程 (Process)
进程是程序的一次运行活动。它对应从代码加载、执行到结束的一个过程,进程可以申请和拥有系统一整套资源,是系统进行资源分配和调度的基本单位。
# 线程 (Thread)
线程是进程中能够独立执行的执行序列。一个进程可以产生多个线程,线程也有创建、存活到消亡的生命周期,每个线程都有独立的运行栈和程序计数器,是 CPU 调度的最小单位。
# 进程与线程关系
一个进程中的所有线程共享相同的地址空间和这个进程所拥有的操作系统资源。
# 线程的创建
# 继承 Thread
类
public class MyThread extends Thread { | |
@Override | |
public void run() { | |
System.out.println("MyThread is running..."); | |
} | |
} | |
public class Main { | |
public static void main(String[] args) { | |
MyThread myThread = new MyThread(); | |
myThread.start(); | |
} | |
} |
# 实现 Runnable
接口
public class MyRunnable implements Runnable { | |
@Override | |
public void run() { | |
System.out.println("MyRunnable is running..."); | |
} | |
} | |
public class Main { | |
public static void main(String[] args) { | |
MyRunnable myRunnable = new MyRunnable(); | |
Thread thread = new Thread(myRunnable); | |
thread.start(); | |
} | |
} |
# 实现 Callable
接口和 FutureTask
类
public class MyCallable implements Callable<String> { | |
@Override | |
public String call() throws Exception { | |
return "MyCallable is running..."; | |
} | |
} | |
public class Main { | |
public static void main(String[] args) { | |
MyCallable myCallable = new MyCallable(); | |
FutureTask<String> futureTask = new FutureTask<>(myCallable); | |
new Thread(futureTask).start(); | |
try { | |
System.out.println(futureTask.get()); | |
} catch (InterruptedException | ExecutionException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
# 图形用户界面
# JFrame
类
JFrame
是Swing
中的顶层容器,是一个窗口,可以包含其他GUI
组件。- 每个
GUI
组件只能被添加到一个容器中。 - 创建
JFrame
对象后,默认情况下布局管理器是BorderLayout
Swing
中事件处理和绘画代码都在一个单独的线程中执行。该线程确保了事件处理器都能串行的执行,并且绘画过程不会被事件打断。因此,在main
方法或其他启动界面的线程,尽量使用SwingUtilities.invokeLater(Runnable doRun)
启动线程来操作界面。
# JFrame
的常用方法
方法 | 描述 |
---|---|
getContentPanel()->Container |
返回 JFrame 的内容面板 |
setVisible(boolean b)->void |
设置窗口是否可见 |
setTitle(String title)->void |
设置窗口标题 |
setBounds(int x, int y, int width, int height)->void |
设置窗口的位置和大小 |
resize(int width, int height)->void |
设置窗口的大小 |
setLocation(int x, int y)->void |
设置窗口的位置 |
add(Component comp)->void |
添加组件到窗口中心 |
add(Component comp, Object constraints)->void |
添加组件到窗口中心,并设置布局约束 |
pack()->void |
调整窗口大小,以适应其子组件的首选大小 |
hide()->void |
隐藏窗口 |
setDefaultCloseOperation(int operation)->void |
设置窗口关闭操作 |
# 布局管理器
java.awt
包中的布局管理器- 容器中的组件在容器中的大小和位置是由容器的布局管理器(
layoutmanager
)来布置的,这样能根据不同的屏幕自动进行排版。 - 每个容器中都有默认的布局管理器,缺省的布局管理器为:
- 所有窗口:
BorderLayout
(文件对话框除外) - 所有面板(包括
Applet
):FlowLayout
但容器组件可以通过setLayout
方法设置或取消容器的布局管理器,其语法格式为:
- 所有窗口:
void setLayout(LayoutManager mgr) |
# FlowLayout
FlowLayout
是java.awt
包中的布局管理器,将容器中的组件按照添加的顺序从左到右排列,当一行排满时,自动换行。- 构造方法:
FlowLayout()
、FlowLayout(int align)
、FlowLayout(int align, int hgap, int vgap)
align
:对齐方式,有FlowLayout.LEFT
、FlowLayout.CENTER
、FlowLayout.RIGHT
hgap
:水平间距,vgap
:垂直间距
# BorderLayout
BorderLayout
是java.awt
包中的布局管理器,是JFrame
的默认布局管理器。BorderLayout
将容器分为 5 个区域:NORTH
、SOUTH
、WEST
、EAST
、CENTER
。BorderLayout
的构造方法:BorderLayout(int hgap, int vgap)
,hgap
和vgap
分别是水平和垂直间距。add(Component comp, Object constraints)
方法可以设置组件的位置,constraints
是一个字符串,可以是NORTH
、SOUTH
、WEST
、EAST
、CENTER
# GridLayout
GridLayout
是java.awt
包中的布局管理器,将容器分为行和列,每个单元格中放置一个组件。GridLayout
的构造方法:GridLayout(int rows, int cols, int hgap, int vgap)
,rows
和cols
分别是行数和列数,hgap
和vgap
分别是水平和垂直间距。
# CardLayout
CardLayout
是java.awt
包中的布局管理器,可以在容器中放置多个组件,但只能显示一个组件。CardLayout
的构造方法:CardLayout(int hgap, int vgap)
,hgap
和vgap
分别是水平和垂直间距。CardLayout
添加组件的方法:addLayoutComponent(Component comp, Object constraints)
,constraints
是一个字符串,用于标识组件。CardLayout
显示组件的方法:show(Container parent, String name)
,parent
是容器,name
是组件的标识。
# GridBagLayout
GridBagLayout
是java.awt
包中的布局管理器。
# 事件处理
# 事件处理机制
应用委托模型编写事件处理程序一般包含以下几个步骤:
- 确定事件源应关注的事件。
- 设计处理各事件的事件监听器类。
- 创建事件监听器对象。
- 调用事件源的
addXxxListener
方法事件源注册事件监听器
# 事件源
能够产生事件的组件都可以成为事件源,例如按钮、菜单、文本框等。
- 事件源通常提供注册和注销事件监听器(Event Listener)的方法,注册 / 注销监听器的方法:
事件源对象.addXXXListener(监听器对象)
事件源对象.removeXXXListener(监听器对象)
# 事件对象
事件对象通常由用户操作触发,由 Java 虚拟机产生并传播的对象。
例如:点击按钮产生的事件(ActionEvent),按下某个键产生的事件(KeyEvent),关闭窗口产生的事件(WindowEvent)
# 事件监听器
事件监听器(Event Listener)用于接收和处理事件的对象。
Java 中采用委托模型的方式处理事件。即事件产生以后,不是由事件源处理事件,而是将事件委托给第三方对象 —— 事件监听器来处理。
事件监听器能够工作必须满足两个要求:
- 事件监听器实现了处理某种事件的接口方法;
- 需要将事件监听器注册到事件源中,从而与事件源建立关联。
根据事件源产生事件的类型和需要,可以为事件源注册一个或多个监视器,又称为注册监视器。注册监视器的方法: 事件源对象.addXXXListener(监视器)
(XXX 为对应的事件类型)。
# 事件处理方法
事件处理方法是事件监听器接口中的方法,用于处理事件。
# 事件和事件分类
- 低级事件:低级事件是指基于组件和容器的事件,当一个组件上发生事件,如鼠标的进入、点击、拖放等,或组件的窗口开关等时,触发了组件事件。如
ComponentEvent
(组件事件:组件尺寸的变化、移动)、ContainerEvent
(容器事件:组件增加、移动)、FocusEvent
(焦点事件:焦点的获得和丟失)、KeyEvent
(键盘事件:键按下、释放)、MouseEvent
(鼠标事件:鼠标单击、移动)、WindowEvent
(窗口事件:关闭窗口、窗口闭合、图标化)等。 - 高级事件(也称语意事件):高级事件是基于语义的事件,它可以不和特定的动作相关联。是用来描述用户操作所产生的结果,低级事件是高级事件的基础。如
ActionEvent
(动作事件:按钮按下,TextField
中按Enter
键)、AdjustmentEvent
(调节事件:在滚动条上移动滑块以调节数值)、ItemEvent
(项目事件:选择项目,不选择 “项目改变”)、TextEvent
(文本事件:文本对象改变)等。
# 事件监听器
事件监听器的实现,有两种方法:
- 实现监听器接口:
implements XXXListener
- 在事件源和事件监听器对象中进行约定的接口。
- 事件监听器接口的名称与事件类的名称是相对的,例如:
KeyEvent
事件类的监听器接口名为KeyListener
- 扩展监听适配类:
extends XXXAdapter
,JDK 中也提供了大多数事件监听器接口的最简单的实现类,称之为事件适配器(Adapter
) 类.
事件类型 | 监听接口 | 接口中的方法 | 适配器类 |
---|---|---|---|
ActionEvent | ActionListener | actionPerformed(ActionEvent) | 无 |
AdjustmentEvent | AdjustmentListener | adjustmentValueChanged(AdjustmentEvent) | 无 |
ItemEvent | ItemListener | itemStateChanged(ItemEvent) | 无 |
TextEvent | TextListener | textValueChanged(TextEvent) | 无 |
MouseEvent | MouseListener | mouseClicked(MouseEvent) | MouseAdapter |
mouseEntered(MouseEvent) | |||
mouseExited(MouseEvent) | |||
mousePressed(MouseEvent) | |||
mouseReleased(MouseEvent) | |||
MouseEvent | MouseMotionListener | mouseDragged(MouseEvent) | MouseMotionAdapter |
mouseMoved(MouseEvent) | |||
KeyEvent | KeyListener | keyPressed(KeyEvent) | KeyAdapter |
keyReleased(KeyEvent) | |||
keyTyped(KeyEvent) | |||
FocusEvent | FocusListener | focusGained(FocusEvent) | FocusAdapter |
focusLost(FocusEvent) | |||
WindowEvent | WindowListener | windowActivated(WindowEvent) | WindowAdapter |
windowClosed(WindowEvent) | |||
windowClosing(WindowEvent) | |||
windowDeactivated(WindowEvent) | |||
windowDeiconified(WindowEvent) | |||
DocumentEvent | DocumentListener | changedUpdate(DocumentEvent) | 无 |
removeUpdate(DocumentEvent) | |||
insertUpdate(DocumentEvent) |
实现接口需要实现接口中的所有方法,而适配器类中的方法都是空方法,可以选择性的实现。
# 回调与事件监听器的实现
- 回调是一种编程模式,是指在一个函数中调用另一个函数。
- 在委托事件处理模型中,类 A 相当于事件源,类 B 相当于事件监视器,实现上述目的的设计方式有下面两类。
- 用闭包(closure)类实现事件监视器。可以用事件源的内部类的方式创建事件监视器;也可以把事件源和事件监视器类合而为一;如果监视器只有一个函数,还可以用 Lambda 表达式简写监视器。
- 用外部类实现事件监视器。这时监视器 B 为了调用事件源 A 的回调方法,需要给监视器 B 传入事件源 A 的引用。
# 事件源和事件监听器合二为一
public class MyFrame extends JFrame implements ActionListener { | |
private JButton button; | |
public MyFrame() { | |
button = new JButton("Click me"); | |
button.addActionListener(this); | |
add(button); | |
pack(); | |
setVisible(true); | |
} | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
System.out.println("Button clicked"); | |
} | |
public static void main(String[] args) { | |
new MyFrame(); | |
} | |
} |
# 匿名内部类实现事件监听器
public class MyFrame extends JFrame { | |
private JButton button; | |
public MyFrame() { | |
button = new JButton("Click me"); | |
button.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
System.out.println("Button clicked"); | |
} | |
}); | |
add(button); | |
pack(); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
new MyFrame(); | |
} | |
} |
# Lambda 表达式实现事件监听器
public class MyFrame extends JFrame { | |
private JButton button; | |
public MyFrame() { | |
button = new JButton("Click me"); | |
button.addActionListener(e -> System.out.println("Button clicked")); | |
add(button); | |
pack(); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
new MyFrame(); | |
} | |
} |
# 外部类实现事件监听器
public class MyFrame extends JFrame { | |
private JButton button; | |
public MyFrame() { | |
button = new JButton("Click me"); | |
button.addActionListener(new MyActionListener()); | |
add(button); | |
pack(); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
new MyFrame(); | |
} | |
} | |
class MyActionListener implements ActionListener { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
System.out.println("Button clicked"); | |
} | |
} |
# 网络编程
# TCP/IP 通信
# 创建 Socket
对象
方法 | 描述 |
---|---|
Socket(String host, int port) |
创建一个流套接字并将其连接到指定主机上的指定端口号 |
Socket(InetAddress address, int port) |
创建一个流套接字并将其连接到指定 IP 地址的指定端口号 |
Socket() |
创建一个未连接的套接字 |
getInputStream()->InputStream |
返回此套接字的输入流 |
getOutputStream()->OutputStream |
返回此套接字的输出流 |
close()->void |
关闭此套接字 |
connect(SocketAddress endpoint)->void |
连接到服务器 |
getInetAddress()->InetAddress |
返回此套接字连接的远程主机的地址 |
getPort()->int |
返回此套接字连接的远程端口号 |
getLocalAddress()->InetAddress |
返回此套接字绑定到的本地地址 |
getLocalPort()->int |
返回此套接字绑定到的本地端口号 |
getRemoteSocketAddress()->SocketAddress |
返回此套接字连接的远程地址 |
# 打开 IO
流
ServerSocket
类的构造方法和方法
构造方法 | 描述 |
---|---|
ServerSocket(int port) |
创建绑定到指定端口的服务器套接字 |
ServerSocket(int port, int backlog) |
创建绑定到指定端口的服务器套接字,并指定最大连接数 |
ServerSocket(int port, int backlog, InetAddress bindAddr) |
创建绑定到指定端口的服务器套接字,并指定最大连接数和本地地址 |
accept()->Socket |
侦听并接受到此套接字的连接 |
close()->void |
关闭此套接字 |
bind(SocketAddress endpoint)->void |
将服务器套接字绑定到指定的端口 |
ServerSocket server = null; | |
try { | |
server = new ServerSocket(8888); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
Socket socket = null; | |
try { | |
//accept () 方法会阻塞,直到有客户端连接, | |
// 返回一个 Socket 对象同客户端进行交互 | |
socket = server.accept(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} |
# C/S 模式一对一 TCP 通信
- 服务器端
public class Server { | |
public static void main(String[] args) { | |
ServerSocket server = null; | |
Socket socket = null; | |
BufferedReader is = null; | |
PrintWriter os = null; | |
Scanner sin = null; | |
try { | |
String line; | |
server = new ServerSocket(8888); | |
socket = server.accept(); | |
is = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |
os = new PrintWriter(socket.getOutputStream()); | |
sin = new Scanner(System.in); | |
System.out.println("Client:" + is.readLine()); | |
line = sin.nextLine(); | |
while (!line.trim().equals("bye")) { | |
os.println("Server:" + line); | |
os.flush(); | |
System.out.println("Client:" + is.readLine()); | |
line = sin.nextLine(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (server != null) { | |
server.close(); | |
} | |
if (socket != null) { | |
socket.close(); | |
} | |
if (is != null) { | |
is.close(); | |
} | |
if (os != null) { | |
os.close(); | |
} | |
if (sin != null) { | |
sin.close(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} |
- 客户端
public class Client { | |
public static void main(String[] args) { | |
Socket socket = null; | |
Scanner sin = null; | |
PrintWriter os = null; | |
BufferedReader is = null; | |
try { | |
socket = new Socket("127.0.0.1", 8888); | |
sin = new Scanner(System.in); | |
os = new PrintWriter(socket.getOutputStream()); | |
is = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |
String line; | |
line = sin.nextLine(); | |
while(!line.trim().equals("bye")) { | |
os.println(line); | |
os.flush(); | |
System.out.println("Server:" + is.readLine()); | |
line = sin.nextLine(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (socket != null) { | |
socket.close(); | |
} | |
if (sin != null) { | |
sin.close(); | |
} | |
if (os != null) { | |
os.close(); | |
} | |
if (is != null) { | |
is.close(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |
# C/S 模式多对一 TCP 通信
- 服务器端
public class Server { | |
static int clientNum = 0; | |
static ArrayList<Socket> clients = new ArrayList<>(); | |
public static void main(String args[]) throws IOException { | |
ServerSocket server = null; | |
boolean listening = true; | |
try { | |
server = new ServerSocket(8888); | |
} catch (IOException e) { | |
System.err.println("Could not listen on port: 8888."); | |
System.exit(-1); | |
} | |
while (listening) { | |
Socket socket = server.accept(); | |
clients.add(socket); | |
new ServerThread(socket, clientNum).start(); | |
clientNum++; | |
} | |
server.close(); | |
} | |
} |
- 服务器线程
class ServerThread extends Thread { | |
private Socket socket = null; | |
private int clientNum = 0; | |
public ServerThread(Socket socket, int clientNum) { | |
super("ServerThread"); | |
this.socket = socket; | |
this.clientNum = clientNum + 1; | |
} | |
public void run() { | |
BufferedReader is = null; | |
PrintWriter os = null; | |
Scanner sin = null; | |
try { | |
String line; | |
is = new BufferedReader(new InputStreamReader(socket.getInputStream())); | |
os = new PrintWriter(socket.getOutputStream()); | |
sin = new Scanner(System.in); | |
String outStr = is.readLine(); | |
System.out.println("Client " + clientNum + ":" + outStr); | |
line = sin.nextLine(); | |
while(!(outStr == null)) { | |
os.println(line); | |
os.flush(); | |
outStr = is.readLine(); | |
System.out.println("Client " + clientNum + ":" + outStr); | |
line = sin.nextLine(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} finally { | |
try { | |
if (socket != null) { | |
socket.close(); | |
} | |
if (is != null) { | |
is.close(); | |
} | |
if (os != null) { | |
os.close(); | |
} | |
if (sin != null) { | |
sin.close(); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |