运算符
运算符优先级
当多个运算符出现在一个表达式中,谁先谁后呢?这就涉及到运算符的优先级别的问题。在一个多运算符的表达式中,运算符优先级不同会导致最后得出的结果差别甚大。
例如,(1+3)+(3+2) * 2,这个表达式如果按加号最优先计算,答案就是 18,如果按照乘号最优先,答案则是 14。
再如,x = 7 + 3 * 2;这里x得到13,而不是20,因为乘法运算符比加法运算符有较高的优先级,所以先计算3 * 2得到6,然后再加7。
下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。
类别 | 操作符 | 关联性 |
---|---|---|
后缀 | () [] . (点操作符) | 左到右 |
一元 | expr++ expr– | 左到右 |
一元 | ++expr --expr + - ~ ! | 右到左 |
乘性 | * /% | 左到右 |
加性 | + - | 左到右 |
移位 | >> >>> << | 左到右 |
关系 | > >= < <= | 左到右 |
相等 | == != | 左到右 |
按位与 | & | 左到右 |
按位异或 | ^ | 左到右 |
按位或 | 丨 | 左到右 |
逻辑与 | && | 左到右 |
逻辑或 | 丨丨 | 左到右 |
条件 | ?: | 右到左 |
赋值 | = + = - = * = / =%= >> = << =&= ^ =丨= | 右到左 |
逗号 | , | 左到右 |
数据类型
基本数据类型
数据类型 | 占字节数 | 取值范围 | 缺省默认值 |
---|---|---|---|
byte(字节型) | 1 | [-27~27-1]、[-128~127] | 0 |
short(短整型) | 2 | [-215~215-1]、[-32768~32767] | 0 |
int(整数型) | 4 | [-231~231-1]、[-2147483648~2147483647] | 0 |
long(长整型) | 8 | [-263~263-1] | 0L |
float(单精度) | 4 | [-231~231-1]、[1.4E-45~3.4028235E38] | 0.0F |
double(双精度) | 8 | [-263~263-1] | 0.0 |
boolean(布尔型) | 1 | true、false | false |
char(字符型) | 2 | [0~216-1]、[0~65535] | ‘\u0000’ |
引用数据类型
引用数据类型默认值为 null
方法
关于数据结构
定义
数据结构通常是: 存储数据的容器,而该容器可能存在不同的结构。
常见的数据结构有哪些
数组、链表、图、二叉树、栈、队列…
算法
和数据结构通常出现在一起的是: 算法
算法: 排序算法、查找算法、…
方法重载(Overload)
定义
如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载(Overload)。
具体条件
在同一个类中
- 方法名相同
- 参数列表不同
- 参数的个数不同
- 参数的类型不同
- 参数的顺序不同
- 注意
- 方法重载和方法的返回值类型无关
- 方法重载和方法的修饰符列表无关
- main 方法也可以被重载
实例
System.out.println()
使用了方法重载
jdk/lib/src.zip/java.base/java/io/PrintStream.java - line 889
/* Methods that do terminate lines */
/**
* Terminates the current line by writing the line separator string. The
* line separator string is defined by the system property
* {@code line.separator}, and is not necessarily a single newline
* character ({@code '\n'}).
*/
public void println() {
newLine();
}
/**
* Prints a boolean and then terminate the line. This method behaves as
* though it invokes {@link #print(boolean)} and then
* {@link #println()}.
*
* @param x The {@code boolean} to be printed
*/
public void println(boolean x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints a character and then terminate the line. This method behaves as
* though it invokes {@link #print(char)} and then
* {@link #println()}.
*
* @param x The {@code char} to be printed.
*/
public void println(char x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints an integer and then terminate the line. This method behaves as
* though it invokes {@link #print(int)} and then
* {@link #println()}.
*
* @param x The {@code int} to be printed.
*/
public void println(int x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints a long and then terminate the line. This method behaves as
* though it invokes {@link #print(long)} and then
* {@link #println()}.
*
* @param x a The {@code long} to be printed.
*/
public void println(long x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints a float and then terminate the line. This method behaves as
* though it invokes {@link #print(float)} and then
* {@link #println()}.
*
* @param x The {@code float} to be printed.
*/
public void println(float x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints a double and then terminate the line. This method behaves as
* though it invokes {@link #print(double)} and then
* {@link #println()}.
*
* @param x The {@code double} to be printed.
*/
public void println(double x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints an array of characters and then terminate the line. This method
* behaves as though it invokes {@link #print(char[])} and
* then {@link #println()}.
*
* @param x an array of chars to print.
*/
public void println(char[] x) {
if (getClass() == PrintStream.class) {
writeln(x);
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes {@link #print(String)} and then
* {@link #println()}.
*
* @param x The {@code String} to be printed.
*/
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
/**
* Prints an Object and then terminate the line. This method calls
* at first String.valueOf(x) to get the printed object's string value,
* then behaves as
* though it invokes {@link #print(String)} and then
* {@link #println()}.
*
* @param x The {@code Object} to be printed.
*/
public void println(Object x) {
String s = String.valueOf(x);
if (getClass() == PrintStream.class) {
// need to apply String.valueOf again since first invocation
// might return null
writeln(String.valueOf(s));
} else {
synchronized (this) {
print(s);
newLine();
}
}
}
方法递归
定义
方法递归指在一个方法的内部调用自身的过程
注意
-
当递归时程序没有结束条件,一定会发生:栈内存溢出错误(
StackOverflowError
),所以递归必须要有结束条件。JVM发生错误后只有一个结果,就是退出JVM。
-
假设递归结束条件是对的,是合法的,递归有时也会出现栈内存溢出错误。因为有可能递归的太深,栈内存不够用了(因为一直在压栈)。
-
在实际的开发中,不建议轻易算则递归,尽量用循环代替。因为循环的效率高,耗费内存少;而递归耗费的内存比较大。另外,递归的使用不当,会导致JVM死掉(但在极少数情况下,不用递归程序没法实现)。
-
在实际的开发中,假设有一天真正遇到了:StackOverflowError 怎么解决?
第一步:
先检查递归的结束条件对不对。如果不对,必须修改到正确为止。
第二步:假设递归条件没问题怎么办?
手动调整JVM的栈内存初始化大小,可以将占空间的内存调大些。
第三步:调整了大小,运行时还是出现这个错误?
只能继续扩大栈内存初始化大小。
-Xss<size> 设置 Java 线程堆栈大小
面向对象
类和对象
什么是类
类实际上在现实世界当中是不存在的,是一个抽象的概念,是一个模板。是我们人类大脑进行“思考、总结、抽象”的一个结果(主要是因为人类的大脑不一般才有了类的概念)。
类本质上是现实世界当中某些事物具有共同特征,将这些共同特征提取出来形成的概念就是一个“类”,“类”就是一个模板。
什么是对象
对象是实际存在的个体(真实存在的个体)。
类的定义
语法
[修饰符列表] class 类名 {
类体;
}
类体 = 属性 + 方法(描述动作/行为)
关于属性
属性对应的是“数据”,数据在程序中只能放到变量中,属性在代码上以“变量”的形式存在(描述状态)。
类的实例化(对象的创建)
什么是实例变量
对象又被称为实例,实例变量实际上就是:对象级别的变量。
语法
类名 变量名 = new 类名();
对象和引用的区别
对象是:通过new出来的,在堆内存中存储。
引用是:是变量,且该变量中保存了内存地址指向了堆内存当中的对象的。
内存图
属性是引用类型时
构造方法
什么是构造方法
构造方法是一个比较特殊的方法,通过构造方法可以完成对象的创建,以及实例变量的初始化。换句话说:构造方法是用来创建对象,并且同时给对象的属性赋值。(注意:实例变量没有手动赋值的时候,系统会赋默认值。)
关于构造方法
- 当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法,这个无参数的构造方法叫做缺省构造器。
- 而当一个类中手动的提供了构造方法,那么系统将不再默认提供无参数构造方法。建议将无参数构造方法手动的写出来,这样一定不会出问题。
- 构造方法是支持方法重载的。
- 通常在构造方法体当中给属性赋值,完成属性的初始化。
语法
[修饰符列表] 构造方法名([形式参数列表]){
构造方法体;
}
注意
- 修饰符列表目前统一写:public。千万不要写public static。
- 构造方法名和类名必须一致。
- 构造方法不需要指定返回值类型,也不能写void,写上void表示普通方法,就不是构造方法了。
空指针异常(NullPointerException
)
只有在空引用
访问实例
相关的时,才会出现空指针异常。
static
关键字
定义
-
使用static修饰: 都是
静态
的,类
相关的,不需要new对象,访问时采用“类名.
”的方式访问-
注意: 静态的建议使用“
类名.
”来访问,但使用“引用.
”也行但不建议使用“
引用.
”访问,因为容易产生困惑,误以为其是实例的 -
局部变量 -> 静态变量
-
方法 -> 静态方法
-
-
不用static修饰: 都是
实例
的,实例
相关的,需要new对象,访问时采用“引用.
”的方式访问- 局部变量 -> 实例变量
- 方法 -> 实例方法
静态变量
静态变量在类加载时初始化,不需要new对象,就会开出静态变量的所需内存空间。
静态变量存储在方法区。
什么时候使用静态方法/实例方法?
当这个方法体当中,直接访问了实例变量,这个方法一定是实例方法。
开发中,大部分情况下,如果是工具类的话,工具类当中的方法一般都是静态的。(静态方法有一个优点,是不需要new对象,直接来用类名调用,极其方便。工具类就是为了方便,所以工具类中的方法一般都是 static的。)
代码块
静态代码块
语法
static {
java 语句;
}
什么时候执行?
在类加载的时候执行,而且只执行一次。
注意
静态代码块在类加载时执行,而且在main方法执行之前执行。
静态代码块一般时按照自上而下的顺序执行。
作用
- 静态代码块不是那么常用。
- 静态代码块这种语法机制是一个特殊的时机: 类加载时机。
具体业务: 在类加载的时候记录日志。
实例代码块
语法
{
java语句;
}
什么时候执行?
只要是构造方法执行,必然在构造方法执行之前自动执行。
作用
- 时机: 对象构建时机。
final
关键字
final 怎么用?
-
final表示最终的,不可变的。
-
final可以修饰类
final修饰的类无法被继承。
-
final可以修饰方法
final修饰的方法无法被重写。
-
final可以修饰变量
- final修饰的局部变量,一旦赋值就不能重新赋值。
- final修饰的引用,只能永远指向一个对象,无法再指向其它对象,这意味着被final修饰的对象在当前方法执行结束前不会被垃圾回收器回收,直到当前方法结束才会释放空间。
- final修饰的实例变量(不常用,原因如下),不能由系统分配默认值(不能只声明不赋值),必须手动赋值(可以在变量声明时也可以在构造方法中)。
- final修饰的静态变量(也就是
static final
联合修饰的变量),被称为常量,常量一般都是公开的(被public
修饰,因为它不能被改变所以不需要封装),常量名建议全部大写,每个单词间用下划线连接。
this
关键字
this 是什么?
this是一个变量,是一个引用,this保存当前对象的内存地址,指向自身。所以,严格意义上来说,this代表的就是“当前对象”,this存储在堆内存当中对象的内部。
this 怎么用
- this 可以使用在实例方法中,也可以使用在构造方法中。
- this 不能使用在静态方法中。
- this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。
- this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的构造方法,目的是代码复用。
super
关键字
super 怎么用(对比 this)
- super 可以使用在实例方法中,也可以使用在构造方法中。
- super 不能使用在静态方法中。
- super. 大部分情况下可以省略,
- super() 这种语法只能出现在构造方法第一行,表示当前构造方法调用父类的构造方法,目的是代码复用。
内存图
封装
作用
- 保证内部结构的安全。
- 屏蔽复杂,暴露简单。
实现方法
- 使用private关键字修饰属性
- 属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,可以在set方法中设立关卡来保证数据的安全性。
继承
语法
class 子类名 extends 父类名 {
类体;
}
作用
- 基本作用: 子类继承父类,代码可以得到复用。
- 主要作用: 因为有了继承关系,才有了后期的方法覆盖和多态机制。
特性
-
B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。
-
java 中的继承只支持单继承,不支持多继承,C++ 中支持多继承,这也是 Java 体现简单性的一点,换句话说,Java 中不允许这样写代码:
class B extends A,C{ }
-
虽然 java 中不支持多继承,但有的时候会产生间接继承的效果
例如:class C extends B,class B extends A
也就是说,C 直接继承 B,其实 C 还间接继承 A。 -
java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问(父类中 private 修饰的不能在子类中直接访问,可以通过间接的手段来访问)。
-
子类继承父类后在子类定义的构造器一开始会默认调用父类的无参构造器。
-
java 中的类没有显示的继承任何类,则默认继承 Object 类,Object类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有 Object 类型中所有的特征。
-
继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类。
什么时候使用继承?
凡是采用“is a”能描述的,都可以继承。例如:
Cat is a Animal:猫是一个动物
Dog is a Animal:狗是一个动物
CreditAccount is a Account:信用卡账户是一个银行账户
…
方法覆盖(Override)
定义
如果在子类中定义一个方法,其名称、返回值类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖(Override)了父类的方法。
方法覆盖(Override)又被叫做方法重写(Overwrite)
具体条件
-
有继承关系的两个类。
-
具有相同方法名、返回值类型、参数列表。
- 关于返回值类型相同:
- 对于基本数据类型来说: 返回值类型必须相同
- 对于引用数据类型来说: 重写后方法的返回值类型可以是重写前方法的子类(可以变小, 但是意义不大)
- 关于返回值类型相同:
-
访问权限不能更低。
-
抛出异常不能更多。
方法重载和方法覆盖的区别
- 方法重载发生在同一个类当中 : 方法覆盖发生在具有继承关系的父子类之间
- 方法重载是一个类中,方法名相同,参数列表不同 : 方法覆盖是具有继承关系的父子类,并且覆盖之后的方法必须和之前的方法一致(方法名一致、参数列表一致、返回值类型一致)
注意
- 方法覆盖只是针对于方法,和属性无关。
- 私有方法无法覆盖。 => 私有方法不能覆盖
- 构造方法不能被继承,所以构造方法也不能被覆盖。
- 方法覆盖只是针对于“实例方法”,“静态方法”覆盖没有意义。 => 静态方法不谈覆盖
多态
转型的概念
向上转型(upcasting)
子 —> 父(自动类型转换)
向下转型(downcasting)
父 —> 子(强制类型转换,需要加强制类型转换符)
注意
java中允许向上转型,也允许向下转型,但无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。
自动/强制类型转换是对于基础数据类型的说法,而引用数据类型只分向上/向下转型。
什么是多态?
多种形态,多种状态。
分析:a2.move();
java程序分为编译阶段和运行阶段。
- 先来分析编译阶段:
- 对于编译器来说,编译器只知道a2的类型是Animal,所以编译器在检查语法的时候,会去Animal.class字节码文件中找move()方法,找到了,绑定上move()方法,编译通过,静态绑定成功(编译阶段属于静态绑定)。
- 再来分析运行阶段:
- 运行阶段的时候,实际上在堆内存中创建的java对象是Cat对象,所以move的时候,真正参与move的对象是Cat,所以运行阶段会动态执行Cat对象的move()方法。这个过程属于运行阶段绑定(运行阶段绑定属于动态绑定)。
多态表示多种形态:编译的时候一种形态,运行的时候另一种形态。
多态中成员访问特点
- 方法调用:编译看左边,运行看右边。
- 变量调用:编译看左边,运行看左边。(多态侧重行为多态)
实例
class Animal {
public void move() {
System.out.println("Animal is moving");
}
}
class Cat extends Animal {
public void move() {
System.out.println("Cat is walking");
}
public void catchMouse() {
System.out.println("Cat is catching a mouse");
}
}
class Bird extends Animal {
public void move() {
System.out.println("Bird is flying");
}
}
public class Test{
public static void main(String[] args) {
// 类的继承 方法覆盖
Animal a1 = new Animal();
a1.move(); // out: Animal is moving
Cat c1 = new Cat();
C1.move(); // out: Cat is walking
Bird b1 = new Bird();
B1.move(); // out: Bird is flying
// 多态
Animal a2 = new Cat(); // 向上转型
a2.move(); // out: Cat is walking
Animal a3 = new Bird(); // 向上转型
a3.move(); // out: Bird is flying
Animal a5 = new Cat();
// a5.catchMouse(); // 编译报错: 找不到符号
/*
因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
*/
Cat x = (Cat)a5; // 向下转型
x.catchMouse(); // out: Cat is catching a mouse
}
}
多态的定义
父类型引用指向子类型对象,包括编译阶段和运行阶段。
编译阶段:静态绑定父类的方法。
运行阶段:动态绑定子类型对象的方法。
也就是多种形态 —— 多态。
什么时候必须使用向下转型
?
当需要访问的是子类对象中特有的方法时,必须且有必要进行向下转型。
向下转型的风险
类型转换异常(ClassCastException
)
Animal a6 = new Bird();
// Cat y = (Cat)a6; // 运行报错: java.lang.ClassCastException:类型转换异常。
// y.catchMouse();
怎么避免?
新的运算符: instanceof
作用
instanceof可以在运行阶段动态判断引用指向的对象的类型
语法
boolean 接收变量 = 引用 instanceof 类型;
多态的作用
降低程序的耦合度,提高程序扩展力
软件开发七大原则之一: OCP(开闭原则)
对扩展开放,对修改关闭。
在软件的扩展过程当中,修改的越少越好。
实例
public class Master{
public void feed(Dog d) {}
public void feed(Cat c) {}
}
以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高),导致扩展力很差。
public class Master {
public void feed(Pet p) {}
}
以上的代码中表示:Master和Dog以及Cat的关系脱离了,Master类关注的时Pet类。
这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。
抽象类
定义
抽象类是一种引用数据类型,类和类之间有共同特征,将这些具有共同特征的类再进一步抽象化就形成了抽象类。
语法
[修饰符列表] abstract class 类名 {
类体;
}
抽象方法
定义
被abstract修饰的方法是抽象方法,抽象方法是没有实现的方法,是没有方法体的方法。
语法
[修饰符列表] abstract 返回值类型 抽象方法名([形式参数列表]);
特点
- 没有方法体,以“;”结尾。
- 修饰符列表中有abstract关键字。
注意
- 抽象类不能实例化。
- 理论上:被
abstract
修饰的类不能被实例化。 - 思想上:类本身的定义就是不存在的,而抽象类是多个类的共同特征抽象化得来的产物,自然不能实例化。
- 理论上:被
- 抽象类不能是“最终的”。
- 理论上:被
abstract
修饰的类不能再被final
修饰,反之亦然(错误:非法的修饰符组合:abstract 和 final)。 - 思想上:抽象类既然不能实例化那它的出现本身就是用来被继承的,而“最终的”类不能被继承,产生了矛盾。
- 理论上:被
- 抽象类的子类可以是抽象类。
- 抽象类有构造方法。这个构造方法是供其子类使用的,因为抽象类的子类可以用
super()
调用其构造方法。 - 抽象类中不一定要有抽象方法,但抽象方法一定要出现在抽象类中。
- 一个**
非抽象类
**继承了抽象类
必须实现
(又称覆盖/重写)其中所有的抽象方法
。否则编译器报错:(错误:SubClass不是抽象的,并且未覆盖AbstractClass中的抽象方法AbstractMethod())
接口
定义
接口是一种引用数据类型,接口时完全抽象的(对比抽象类是半抽象),或者也可以说——接口是特殊的抽象类。
语法
[修饰符列表] interface 接口名{
接口体;
}
特性
- 接口支持多继承,一个接口可以继承多个接口。
- 接口中只包含两部分内容:常量、抽象方法。
- 接口中的所有元素都是
public
修饰的(都是公开的)。 - 接口中的抽象方法定义时
public abstract
可以省略不写。 - 接口中的常量定义时
public static final
可以省略不写。 - 接口在编译后也会生成 .class 字节码文件。
接口的实现
语法
class 类名 implements 接口名{
类体;
}
注意
- 一个**
非抽象类
**继承了接口
必须实现
(又称覆盖/重写)其中所有的抽象方法
,且实现的方法都要被public修饰(原因见方法覆盖的具体条件)。否则编译器报错:(错误:SubClass不是抽象的,并且未覆盖Interface中的抽象方法AbstractMethod())。 - 一个类可以同时实现多个接口。
- 继承和实现同时存在时,extends关键字在前,implements关键字在后。
- 实现接口可以使用多态(父类型引用指向子类型)。
抽象类和接口的区别
-
- 抽象类是半抽象的
- 接口时完全抽象的
-
- 抽象类中有构造方法
- 接口中没有构造方法
-
- 接口之间支持多继承
- 类之间只能单继承
-
- 一个类可以同时实现多个接口
- 一个抽象类只能继承一个类(单继承)
-
- 接口中只允许出现常量和抽象方法
注意
- 接口不能创建对象。
- 一个类实现多个接口,多个接口中有相同的静态方法不冲突。
- 一个类继承了父类,又同时实现了接口,且父类中和接口有同名方法时,默认用父类的。
- 个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。
- 一个接口继承多个接口是没有问题的,如果多个接口中存在规范冲突则不能多继承。
内部类
定义
内部类即为定义在一个类中的类,里面的类可以理解为寄生,外部类可以理解为宿主。
public class Outer{
// 内部类
public class Inner{
// ...
}
}
作用
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用
private
protected
等修饰,封装性可以做更多控制。
静态内部类
- 有
static
修饰,属于外部类本身。 - 其特点与使用与普通类完全一致,类所有的成分它都有,只是位置在外部类里。
定义
public class Outer{
// 静态内部类
public static class Inner{
// ...
}
}
创建对象
语法:外部类名.内部类名 对象名 = new 外部类名.内部类构造器();
范例:Outer.Inner in = new Outer.Inner();
注意
- 静态内部类可以直接范围外部类的静态成员。
- 静态内部类不能直接访问外部类的实例成员。
成员内部类
- 无
static
修饰,属于外部类的对象。 - JDK16之前,成员内部类中不能定义静态成员,JDK16开始支持.
定义
public class Outer{
// 成员内部类
public class Inner{
// ...
}
}
创建对象
语法:外部类名.内部类名 对象名 = new 外部类构造器().new 内部类构造器();
范例:Outer.Inner in = new Outer().new Inner();
注意
- 成员内部类可以直接范围外部类的静态成员。
- 成员内部类可以直接访问外部类的实例成员。
- 在成员内部类中访问其所在外部类的对象:
外部类名.this
;
局部内部类
- 基本没用。
- 局部内部类放在方法、代码块、构造器等执行体中。
- 局部内部类编译后生成的类文件名为:外部类名$N内部类名.class。
定义
public class Outer{
public static void main(String[] args) {
// 局部内部类
class Inner{
// ...
}
}
}
匿名内部类
- 本质上是一个没有名字的局部内部类,定义在方法、代码块等中。
- 作用:方便创建子类对象,最终目的为了简化代码编写。详解
- 匿名内部类时一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个类的子类类型。
- 匿名内部类编译后生成的类文件名为:外部类名$N.class。
- 匿名内部类可以作为方法的实参进行传输。
定义即对象创建
new 类名|抽象类名|接口名() {
重写方法;
};
// =========== 实例 ===========
Employee e = new Employee() {
public void work() {
}
};
e.work();
包机制
包
语法
package 包名;
package语句只允许出现在java源代码中的第一行
命名规范
一般来说采用:公司域名倒叙 + 项目名 + 模块名 + 功能名
使用时
使用了package的java程序
编译:
javac -d . java文件
- -d 带包编译
- . 编译后生成的.class文件放到当前目录下
运行
java 包名.类名
导包
语法
import 完整类名;
import 包名.*;
import语句只允许出现在package语句之后 class声明语句之前
访问控制权限
访问权限修饰符
public
(公开)、protected
(受保护)、默认、private
(私有)
访问控制修饰符 | 本类 | 同包 | 子类 | 任意位置 |
---|---|---|---|---|
public | ⭕ | ⭕ | ⭕ | ⭕ |
protected | ⭕ | ⭕ | ⭕ | ❌ |
默认 | ⭕ | ⭕ | ❌ | ❌ |
private | ⭕ | ❌ | ❌ | ❌ |
访问权限修饰符可以修饰:
- 属性(4个都能用)
- 方法(4个都能用)
- 类(
public
和 默认 能用) - 接口(
public
和 默认 能用) - 内部类(4个都能用)
Java API
String
类
概述
java.lang.String
类代表字符串,String 类定义的变量可以用于指向字符串对象,然后操作字符串。
特点
String 被称为不可变字符串类型,它的对象在创建后不能被更改。
- String 变量每次的修改其实都是长生并指向了新的字符串对象。
- 原来的字符串对象是没有改变的,所以称为不可变字符串。
创建字符串对象
直接使用 “” 定义(推荐)
String name = "GabrielxD";
通过 String 类的构造器创建对象
构造器 | 说明 |
---|---|
public String() |
创建一个空白字符串对象,不含任何内容 |
public String(String original) |
根据传入的字符串内容,来创建字符串对象 |
public String(char[] chs) |
根据字符数组的内容,来创建字符串对象 |
public String(byte[] chs) |
根据字节数组的内容,来创建字符串对象 |
区别
- 以 “” 方式给出的字符串对象,在字符串常量池中存储,而且相同内容跟只会在其中存储一份。
- 通过构造器new对象,每new一次都会产生一个新对象,放在堆内存中。
常用方法
方法 | 说明 |
---|---|
boolean equals(Object anObject) |
将此字符串与指定的对象进行比较 |
boolean equalsIgnoreCase(String anotherString) |
将此 String 与另一个 String 比较,忽略了大小写 |
API文档:String (Java SE 11 & JDK 11 )
ArrayList
类
概述
ArrayList 是集合中的一种,支持索引,元素可以重复。
创建 ArrayList 集合对象
构造器 | 说明 |
---|---|
public ArrayList() |
创建一个空的集合对象 |
泛型
- 集合都是支持泛型的。
- 用于约束集合在编译阶段只能操作某种数据。
- 语法:
ArrayList<E> list = new ArrayList<E>();
- 实例:
ArrayList<Integer> list = new ArrayList<>();
// JDK 1.7 开始,泛型后面的类型声明可以忽略不写 - 注意:集合和泛型都不支持基本数据类型,只支持引用数据类型。
常用方法
方法 | 说明 |
---|---|
boolean add(int index, E element) |
将指定元素插入此列表中的指定位置 |
void add(int index, E element) |
将指定元素插入此列表中的指定位置 |
E get(int index) |
返回此列表中指定位置的元素 |
int size() |
返回此列表中的元素数 |
E remove(int index) |
删除此列表中指定位置的元素 |
boolean remove(Object o) |
从该列表中删除指定元素的第一个匹配项(如果存在) |
E set(int index, E element) |
用指定的元素替换此列表中指定位置的元素 |
API文档:ArrayList (Java SE 11 & JDK 11 )
Object
类
概述
Object 类是一切类的父类,所有对象(包括数组)都实现此类的方法。
常用方法
方法 | 说明 |
---|---|
String toString() |
返回对象的字符串表示形式,默认返回为其在堆内存中的地址信息:类的权限名@内存地址 |
boolean equals(Object obj) |
指示某个其他对象是否“等于”此对象,默认比较地址是否相同 |
API文档:Object (Java SE 11 & JDK 11 )
注意
- 父类
toString()
方法的存在意义就是为了被子类重写,以便返回对象内容信息而非地址信息。 equals()
方法同上,以便子类自定制比较规则。
Objects
类
常用方法
方法 | 说明 |
---|---|
static equals(Object a, Object b) |
比较两个对象,底层会先进行非空判断,从而避免空指针异常 |
static isNull(Object obj) |
返回 true 如果提供的参考是 null ,否则返回 false |
API文档:Objects (Java SE 11 & JDK 11 )
StringBuilder
类
概述
StringBuilder 是一个可变字符串类,可以将其看成是一个对象容器。
作用
提高字符串操作效率,如拼接、修改等。
StringBuilder 构造器
构造器 | 说明 |
---|---|
public StringBuilder() |
创建一个空白的可变字符串对象,不包含任何内容 |
public StringBuilder(String str) |
创建一个指定字符串内容的可变字符对象 |
常用方法
方法 | 说明 |
---|---|
StringBuilder append(参数) |
将参数的字符串表示形式追加到StringBuilder对象中,并返回修改后的对象 |
StringBuilder reverse() |
反转可变字符串对象 |
int length() |
返回对象长度 |
String toString() |
转换为String对象 |
API文档:StringBuilder (Java SE 11 & JDK 11 )
Math
类
概述
包含执行基本数字运算的方法,Math类没有提供公开的构造器。
常用方法
方法 | 说明 |
---|---|
static int abs(int a) |
获取参数绝对值 |
static double ceil(double a) |
向上取整 |
static double floor(double a) |
向下取整 |
static int round(float a) |
四舍五入 |
static int max(int a, int b) |
取两数中最大值 |
static int min(int a, int b) |
取两数中最小值 |
static double pow(double a, double b) |
返回a的b次幂的值 |
static double random() |
返回值为double类型的随机值,范围[0.0, 1.0) |
API文档:Math (Java SE 11 & JDK 11 )
System
类
概述
工具类。
常用方法
方法 | 说明 |
---|---|
static void exit(int status) |
终止当前运行的Java虚拟机,非零表示异常终止 |
static long currentTimeMillis() |
以毫秒为单位返回当前时间 |
static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) |
将指定源数组中的数组从指定位置开始复制到目标数组的指定位置 |
API文档:System (Java SE 11 & JDK 11 )
BigDecimal
类
概述
封装浮点型数据。
BigDecimal 构造器
构造器 | 说明 |
---|---|
public BigDecimal(String val) |
包装字符串成为 BigDecimal 对象 |
public BigDecimal(double val) |
包装浮点数成为 BigDecimal 对象(存在精度问题,不建议使用) |
常用方法
方法 | 说明 |
---|---|
static BigDecimal valueOf(double val) |
转换一个 double 类型对象成 BigDecimal |
BigDecimal add(BigDecimal b) |
加法 |
BigDecimal substract(BigDecimal b) |
减法 |
BigDecimal multiply(BigDecimal b) |
乘法 |
BigDecimal divide(BigDecimal b) |
除法 |
BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) |
除法,指定精确位数,舍入模式 |
double doubleValue() |
将 BigDecimal 对象转换为 double |
Date
类
概述
获取系统时间。
Date 构造器
构造器 | 说明 |
---|---|
public Date() |
创建当前时间的 Date 对象 |
public Date(long time) |
创建对应时间戳的 Date 对象 |
常用方法
方法 | 说明 |
---|---|
void setTime(long time) |
将 Date 对象设置为时间戳对应的时间点 |
long getTime(long time) |
获取 Date 对象的时间戳值 |
SimpleDateFormat
类
概述
格式化 Date 对象或时间戳,把字符串时间形式转换为时期对象。
SimpleDateFormat构造器
构造器 | 说明 |
---|---|
public SimpleDateFormat() |
创建一个 SimpleDateFormat 对象,使用默认格式 |
public SimpleDateFormat(String apttern) |
构建一个 SimpleDateFormat 对象,使用指定格式 |
常用方法
方法 | 说明 |
---|---|
final String format(Date date) |
将日期格式化成日期/时间字符串 |
final String format(Object time) |
将时间戳格式化成日期/时间字符串 |
Date parse(String source) |
解析字符串为 Date 对象 |
Calendar
类
概述
创建日历对象,抽象类。
常用方法
方法 | 说明 |
---|---|
int get(int field) |
取日期中的某个字段信息 |
void set(int field, int value) |
修改日历的某个字段信息 |
void add(int field, int amount) |
为某个字段添加/减少指定值 |
final Date getTime() |
获取当前 Date 对象 |
long getTimeInMillis() |
获取当前时间戳 |
包装类
基本数据类型的对应引用类型。
实现了一切皆对象。
集合和泛型不支持基础数据类型,只能使用包装类。
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
自动装箱:基本数据类型的数据和变量可以直接赋值给包装类型的变量。
自动拆箱:包装类型的数据和变量可以直接赋值给基本数据类型的变量。
作用
- 包装类的变量默认值可以是
null
,容错率更高。 - 可以把基本数据类型装成字符串类型(
toString()
)。 - 路由把字符串类型数字装成真实数据类型(
valueOf()
、parseInt()
……)。
正则表达式
正则表达式在字符串方法中的使用(java.lang.String)
方法 | 说明 |
---|---|
boolean matches(String regex) |
判断此字符串是否与给定的正则表达式匹配。 |
String replaceAll(String regex, String replacement) |
按照正则表达式替换对应字符串 |
String[] split(String regex) |
安装正则表达式分割字符串 |
其他应用:爬虫
Arrays
类
概述
数组操作工具类。
常用方法
方法 | 说明 |
---|---|
static String toString(Object[] a) |
返回指定数组内容的字符串表示形式 |
static void sort(Object[] a) |
将指定的对象数组按升序排序 |
static void sort(T[] a, Comparator<? super T> c) |
将指定的对象数组按自定义比较器进行排序 |
binarySearch(Object[] a, Object key) |
二分搜索 |
集合
概述
是存储对象数据的一种容器。
特点
- 大小不固定,启动后可以动态变化。
- 类型也可以选择不固定。
- 适合做元素的增删操作。
常见数据结构
Collection 单列集合
单列集合:每个元素(数据)只包含一个值。
特点
- Collection 单列集合
- List 系列集合:有序、可重复、有索引
ArrayList
、LinkedList
:有序、可重复、有索引
- Set 系列集合:不重复、无索引
HashSet
:无序、不重复、无索引LinkedHashSet
:有序、不重复、无索引TreeSet
:排序、不重复、无索引
- List 系列集合:有序、可重复、有索引
- 集合支持泛型
- 集合和泛型只支持引用类型
常用 API
方法 | 说明 |
---|---|
boolean add(E e) |
把给定的对象添加到当前集合中 |
void clear() |
清空集合 |
boolean remove(E e) |
删除集合中给定元素,默认删除第一个 |
boolean contains(Object obj) |
判断集合中是否包含给定对象 |
boolean isEmpty() |
判断集合是否为空 |
int size() |
获取集合大小 |
Object[] toArray() |
把集合中元素存储到数组中并返回 |
集合的遍历
迭代器遍历
迭代器在 Java 中的代表是 Iterator
,迭代器是集合的专用遍历方式(继承了 Iterable 接口)。
Collection 获取迭代器
方法 | 说明 |
---|---|
Iterator<E> iterator() |
返回集合中的迭代器对象,该迭代器默认指向当前集合的0索引 |
Iterator 中常用方法
方法 | 说明 |
---|---|
boolean hasNext(E e) |
返回当前位置是否有元素存在 |
E next() |
获取当前位置元素,并同时将迭代器对象移向下一个位置 |
实例
Iterator<Integer> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
增强 for 循环
- 自 JDK 5 之后出现,内部原理为一个 Iterator 迭代器,遍历集合时相当于是迭代器的简化写法。
- 既可以遍历集合也可以遍历数组。
- 实现 Iterable 接口的类才可以使用迭代器和增强 for 循环(Collection 接口已经继承了 Iterable 接口)。
用法
for (Object item : Object[]|Collection<E>) {
// use item
}
Lambda 表达式
得益于 JDK 8 的 Lambda 表达式,提供了一种更简单、直接的遍历集合的方式。
Iterable API
方法 | 说明 |
---|---|
default void forEach(Consumer<? super T> action) |
遍历集合(对 Iterable 每个元素执行给定操作,直到处理 Iterable 所有元素或操作引发异常) |
用法
col.forEach(item -> {
// use item
});
集合的并发修改异常问题
从集合中遍历找出某个元素并删除的时候可能出现一种并发修改异常问题(ConcurrentModificationException)
List<String> list = new ArrayList<>();
list.add("aa"); list.add("bb"); list.add("bb"); list.add("cc"); list.add("dd"); // 准备数据
// 删除列表中所有 "bb"
// a.使用迭代器遍历删除
// Iterator<String> it = list.iterator();
// while(it.hasNext()) {
// String item = it.next();
// if (item.equals("bb")) {
// list.remove(item);
// }
// }
// Exception in thread "main" java.util.ConcurrentModificationException
// 解决方法:使用迭代器中的方法删除
while(it.hasNext()) {
String item = it.next();
if (item.equals("bb")) {
it.remove(); // 删除当前遍历到的元素
}
}
System.out.println(list); // [aa, cc, dd]
// b.使用增强for循环删除
// for (String item : list) {
// if (item.equals("bb")) list.remove(item);
// }
// Exception in thread "main" java.util.ConcurrentModificationException
// c.使用forEach删除
// list.forEach(item -> {
// if (item.equals("bb")) list.remove(item);
// });
// Exception in thread "main" java.util.ConcurrentModificationException
// d.使用for循环删除
// for (int i = 0; i < list.size(); i++) {
// if (list.get(i).equals("bb")) list.remove(i);
// }
// System.out.println(list); // [aa, bb, cc, dd] 删除失败
// 解决方法1:反向遍历
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).equals("bb")) list.remove(i);
}
System.out.println(list); // [aa, cc, dd]
// 解决方法2:每删一个索引--
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("bb")) {
list.remove(i);
i--;
}
}
System.out.println(list); // [aa, cc, dd]
List 系列集合
有序、可重复、有索引
特有常见方法
方法 | 说明 |
---|---|
void add(int index, E element) |
在此集合中的指定位置插入指定的元素 |
E remove(int index) |
删除指定索引处的元素,返回被删除的元素 |
E set(int index, E element) |
修改指定索引处的元素,返回被修改的元素 |
E get(int index) |
返回指定索引处的元素 |
原理
- ArrayList 底层是基于数组实现的,查询元素慢,增删相对慢。
- LinkedList 底层基于双链表实现,查询元素慢,增删相对慢。
LinkedList 特有常见方法
方法 | 说明 |
---|---|
void addFirst(E element) |
在此列表开头插入指定的元素 |
void addLast(E element) |
在此列表末尾追加指定的元素 |
E getFirst() |
返回此列表中第一个元素 |
E getFirst() |
返回此列表中最后一个元素 |
E removeFirst() |
从此列表中删除并返回第一个元素 |
E removeLast() |
从此列表中删除并返回最后个元素 |
Set 系列集合
不重复、无索引
HashSet 集合
无序、不重复、无索引
原理
底层采取哈希表存储的数据。
哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
- JDK 8 之前底层使用数组+链表组成。
- JDK 8 开始地城使用数组+链表+红黑树组成。
哈希值
-
是 JDK 根据对象的地址,按照某种规则算出来的 int 类型的数值。
-
Object 类 API
-
方法 说明 int hashCode()
返回对象哈希值
-
-
特点:同一个对象多次调用
hashCode()
返回的哈希值是相同的。
默认情况下,不同对象的哈希值是不同的。
哈希表的详细流程
- 创建一个默认长度16,默认加载因为0.75的数组,数组名table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为 null ,如果是 null 直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
- 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
LinkedHashSet 集合
有序 (保证存储和取出元素顺序一致)、不重复、无索引
原理
底层数据结构采用哈希表+双链表
双链表用于记录存储顺序
TreeSet 集合
不重复、无索引、可排序 (默认按照元素大小升序)
原理
底层基于红黑树的数据结构实现排序。
增删改查性能都较好。
注意
TreeSet 集合一定要排序,可将元素按照指定规则进行排序。
默认排序规则
- 对于数值类型:如 Integer、Double,默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型:默认无法排序,会在运行阶段报错。
结论:若想使用 TreeSet 存储自定义类型,需要指定排序规则
自定义排序规则
-
让自定义的类实现 Comparable 接口并重写里面的
compareTo
方法来定制比较规则。 -
TreeSet 集合有参数构造器,可以设置 Comparator 接口对应的比较器对象,来定制比较规则。
构造器 说明 public TreeSet(Comparator<? super E> comparator)
构造一个新的空树集,根据指定的比较器进行排序。
注意:当比较器返回0时两个元素将会被认为重复,会只保留一个。
如果 TreeSet 集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
实例
TreeSetDemo.java
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Apple> ts = new TreeSet<>((a1, a2) -> Double.compare(a2.getPrice(), a1.getPrice()));
ts.add(new Apple("富士山", 9.9, 500));
ts.add(new Apple("绿苹果", 25.9, 800));
ts.add(new Apple("黄苹果", 18.9, 400));
ts.add(new Apple("青苹果", 14.9, 500));
System.out.println(ts);
}
}
Apple.java
public class Apple implements Comparable<Apple> {
private String name;
private Double price;
private int weight;
public Apple(String name, Double price, int weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Apple o) {
return this.weight - o.weight >= 0 ? 1 : -1;
}
}
Collections
类
概述
java.utils.Collections
是集合工具类。
其并不属于集合,是用来操作集合的工具类
常用 API
方法 | 说明 |
---|---|
static <T> boolean addAll(Collection<? super T> c, T... elements) |
给集合批量添加元素 |
static void shuffle(List<?> list) |
打乱 List 集合元素的顺序 |
static <T extends Comparable<? super T>> sort(List<T> list) |
将集合中元素默认排序,或按照元素的 CompareTo 方法指定的规则排序 |
static <T> void sort(List<T> list, Comparator<? super T> c) |
将集合中元素按照指定比较器的规则排序 |
Map 双列集合(映射)
双列集合:每个元素(数据)包含两个值(一对键值对)。
特点
- Map 双列集合
HaspMap
: 键无序、不重复、无索引,值不做要求LinkedHashMap
: 键无序、不重复、无索引,值不做要求
HashTable
Properties
- …
TreeMap
: 键排序、不重复、无索引,值不做要求
- Map 集合的特点都是由键决定的。
- Map 集合的键是无序的,不重复的,无索引的,值不做要求。
- Map 集合后面重复的键对应的值会覆盖前面重复键的值。
- Map 集合的键值对都可以为 null。
常用 API
方法 | 说明 |
---|---|
V put(K key, V value) |
添加元素 |
V get(Object key) |
根据键获取值 |
V remove(Object key) |
根据键删除键值对元素 |
void clear() |
清空映射 |
boolean containsKey(Object key) |
判断映射中是否包含给定值为键的元素 |
boolean containsValue(Object value) |
判断映射中是否包含给定值为值的元素 |
boolean isEmpty() |
判断映射是否为空 |
int size() |
获取映射大小 |
Set<K> keySet() |
返回所有键组成的集合 |
Collection<V> values() |
返回所有值组成的集合 |
映射的遍历
键找值遍历
先获取 Map 集合的全部键的 Set 集合。
遍历键的 S儿童、 集合,然后通过键提取对应值。
实例
Set<String> keys = map.keySet();
for (String k : keys) {
System.out.println(k + "=" + map.get(k));
}
键值对遍历
先把 Map 集合转换成 Set 集合,Set 集合中每个元素都是键值对实体类型。
遍历 Set 集合然后提取键以及值。
Map 获取映射条目(键值对对象)
方法 | 说明 |
---|---|
Set<Map.Entry<K, V>> entrySet() |
获取所有键值对对象的集合 |
Map.Entry 中常用方法
方法 | 说明 |
---|---|
K getKey() |
获得键 |
V getValue() |
获得值 |
实例
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
Lambda 语法
Map API
方法 | 说明 |
---|---|
default void forEach(BiConsumer<? super K,? super V> action) |
遍历映射(对此映射中的每个条目执行给定操作,直到处理完所有条目或操作引发异常) |
用法
map.forEach((k, v) -> {
// use key&value
});
HashMap 映射
原理
HashSet
基于 HashMap
实现,LinkedHashSet
基于 LinkedHashMap
实现。
故底层原理相同,都是哈希表+链表+红黑树,详情见上 HashSet 集合与 LinkedHashSet 的原理。
TreeMap 映射
不重复、无索引、可排序 (默认按照元素大小升序)
原理
TreeSet
基于 TreeMap
实现,底层都是基于红黑树实现的。
详细用法与上 TreeSet 基本一致
Properties 映射
一般不会被当作集合用,因为 HashMap 更好用。
作用
- Properties 代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。4
- 属性文件:后缀是 .properties 结尾的文件,里面的内容都是 key=value,后续做系统配置信息。
构造器
构造器 | 说明 |
---|---|
public Properties() |
创建一个没有默认值的空属性列表 |
常用 API
方法 | 说明 |
---|---|
void load(InputStream inStream) |
从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) |
从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) |
将此属性列表(键和元素对)写入此 Properties 表中,以适合于使用 load(InputStream) 方法的格式写入输出字节流 |
void store(Writer writer, String comments) |
将此属性列表(键和元素对)写入此 Properties 表中,以适合于使用 load(Reader) 方法的格式写入输出字符流 |
Object setProperty(String key, String value) |
保存键值对(类似 put) |
String getProperty(String key) |
使用此属性列表中指定的键搜索属性值 (类似 get) |
Set<String> stringPropertyNames() |
所有键的名称的集合(类似 keySet) |
实例
public class Test {
public static void main(String[] args) throws Exception {
Properties prop = new Properties();
prop.setProperty("root", "root");
prop.setProperty("gabrielxd", "123456");
prop.setProperty("admin", "123456");
System.out.println("prop: " + prop);
prop.store(new FileWriter("file-io-app/assets/pwd.properties"), "passwords");
Properties prop1 = new Properties();
prop1 .load(new FileReader("file-io-app/assets/pwd.properties"));
System.out.println("prop1: " + prop1);
}
}
不可变集合
概述
不可被修改的集合。
集合的数据项在创建的时候提供,而且在整个生命周期中都不可改变,否则报错。
为什么需要不可变集合?
如果某个数据不能被篡改,把它防御性地拷贝到不可变集合是个很好的时间。
或者当集合对象被不可信的库调用时,不可变形式是安全的。
创建
List、Set、Map 接口中都存在 of 方法,可以创建不可变集合
方法 | 说明 |
---|---|
static <E> List<E> of(E... elements) |
创建一个具有指定元素的 List 集合对象 |
static <E> Set<E> of(E... elements) |
创建一个具有指定元素的 Set 集合对象 |
static <K, V> Map<K, V> of(E... elements) |
创建一个具有指定元素的 Map 映射对象 |
其它语法/特性
Lambda 表达式
概述
Lambda 表达式是自JDK 8以来的一中新语法格式
作用
简化匿名内部类的代码写法
语法
(parameters) -> expression
或者
(parameters) -> {
statements;
}
- **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
- **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
- **可选的大括号:**如果主体只有一个语句,就不需要使用大括号。
- **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
注意
Lambda 表达式基本上只能简化函数式接口的匿名内部类的写法形式。
函数式接口
是有且仅有一个抽象方法构成的接口。
通常会在接口上加上 @FunctionalInterface
注解,标记该接口必须是满足函数式接口。
泛型
概述
- 是 JDK 5 中引入的特性,可以在编译阶段约束操作的数据类型,应进行检查。
- 集合以西的全部接口和实现类都是支持泛型使用的。
- 泛型只能支持引用数据类型。
好处
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型类
定义类时同时定义了泛型的类就是泛型类。
语法
修饰符 class 类名<泛型变量> {}
范例: public class MyArrayList<T> {}
此处泛型变量T可以随便写为任意标识,常见的如 E、T、K、V 等。
作用
编译阶段可以指定数据类型,类似于集合的作用。
把出现泛型变量的地方全部替换成传输的真实数据类型。
泛型方法
定义方法时同时定义了泛型的方法就是泛型方法。
语法
修饰符 <泛型变量> 方法返回值 方法名称(形参列表) {}
范例:public <T> void show(T t) {}
作用
方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
把出现泛型变量的地方全部替换成传输的真实数据类型。
泛型接口
使用了泛型定义的接口就是泛型接口。
语法
修饰符 interface 接口名称<泛型变量> {}
范例:public interface Data<E> {}
实例
public static <T> T[] func(Collection<? entends T> o, T... elements) {
return elements;
}
作用
泛型接口可以让实现类选择当前功能需要操作的数据类型
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
通配符
- 通配符:
?
?
可以在“使用泛型”的时候代表一切类型。- E T K V 是在定义泛型的时候使用的。
泛型的上下限
? extends Car
:?
必须是 Car 或者其子类 – 泛型上限? super Car
:?
必须是 Car 或者其父类 – 泛型下限
可变参数
概述
是 JDK 5 中引入的特性,可变参数用在形参中可以接受多个数据。
可变参数在方法内部本质上就是一个数组。
语法
修饰符 方法返回值 方法名称([参数, ]数据类型... 参数名称) {}
作用
传输参数非常灵活方便。可以不传输参数,可以传一个或多个,也可以传输一个数组。
实例
public class Test {
public static void main(String[] args) {
System.out.println(sum()); // 不传参数
System.out.println(sum(1)); // 传一个参数
System.out.println(sum(1, 3, 5, 4)); //传多个参数
System.out.println(sum(new int[]{1, 3, 2, 6, 8})); // 传数组
}
public static int sum(int... nums) {
int sum = 0;
System.out.println(Arrays.toString(nums)); // 收到的是数组
for (int num : nums) sum += num;
return sum;
}
}
注意
- 一个参数列表中可变参数只能有一个。
- 可变参数必须放在形参列表的最后面。
Stream 流
概述
JDK 8 以后,得益于 Lambda 语法带来的函数式编程,引入了一个全新的 Stream 流概念。
目的:用于简化集合和数组操作的 API。
类似于 JS 中的数组迭代 API + 箭头函数。
获取 Stream 流
集合
java.util.Collection
方法 | 说明 |
---|---|
default Stream<E> stream() |
回去当前集合的 Stream 流 |
数组
java.util.Arrays
方法 | 说明 |
---|---|
static <T> Stream<T> stream(T[] array) |
获取当前数组的 Stream 流 |
java.util.stream.Stream
方法 | 说明 |
---|---|
static <T> Stream<T> of (T... values) |
获取当前数组 / 可变数据的 Stream 流 |
常用 API
中间方法
方法 | 说明 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) |
对流中的数据进行过滤 |
Stream<T> limit(long maxSize) |
获取前几个元素 |
Stream<T> skip(long n) |
跳过前几个元素 |
Stream<T> distinct() |
去重 |
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) |
合并 a、b 两个流为一个 |
<R> Stream<R> map(Function<? super T,? extends R> mapper) |
对流中的数据进行加工 |
- 中间方法也称非终结方法,即调用完成后返回新的 Stream 流对象可以继续使用,支持链式编程。
- 在 Stream 流中无法直接修改集合、数组中元素。
终结方法
方法 | 说明 |
---|---|
void forEach(Consumer<? super T> action) |
遍历流中的数据 |
long count() |
返回流中的元素个数 |
收集 Stream 流
java.util.stream.Stream
方法 | 说明 |
---|---|
R collect(Collector collector) |
开始收集 Stream 流,指定收集器 |
具体收集方式
java.util.stream.Collectors
方法 | 说明 |
---|---|
static <T> Collector toList() |
收集到 List 集合中 |
static <T> Collector toSet() |
收集到 Set 集合中 |
static Collector toMap(Function keyMapper, Function valueMapper) |
收集到 Map 映射中 |
实例
Collection<Integer> col = new ArrayList<>();
Collections.addAll(col, 1, 2, 3, 4, 5, 6); // 准备数据
Stream<Integer> stm = col.stream().filter(n -> n > 2).map(n -> ++n); // 获取流
List<Integer> list = stm.collect(Collectors.toList()); // 收集流
System.out.println(list);
评论区