文章目录
Java概述
一、Java的特点
1. 强面向对象编程
Java与C++、Python等面向对象语言不同,Java采取“万物皆对象”的思想。其它面向对象语言可根据情况选择性的使用面向对象编程或面向过程编程,而Java为强面向对象语言,即是必须使用面向对象编程思想。
2. 安全性高
Java与C/C++相比,吸收了二者的优点,但去除了可能影响程序健壮性和安全的部分(如指针、内存管理等对内存的操作)。Java提供了一个相对安全的内存管理及访问机制,但同时也造成了性能的一定降低,其运行效率要比C/C++的运行效率略低
3. 跨平台性
C/C++实现跨平台原理为,在不同操作系统上,先将源文件由对应操作系统的编译器编译成可执行的二进制文件(不同的编译器由于硬件等因素的不同,编译之后的可执行文件也不同),编译后的文件不具有跨平台性。
Java实现跨平台的原理为,先将源文件编译为一个与平台无关的字节码文件(.class)。编译完成的字节码文件再在不同平台对应的JVM上运行。即是实现了“一次编译,到处运行”的思想。(JVM为JRE的一部分,是Java程序运行的基本环境,不同的操作系统有对应的JRE)
Java与C/C++相比,Java具有更好的跨平台性。在发布程序时,Java只需发布编译的字节码文件,而C/C++则需根据不同的环境发布对应的可执行文件。
二、Java的特性
1. 封装
将数据和对数据的操作行为封装在一起,使其构成一个独立的实体。只保留对外部的接口,不提供内部与外部的直接联系,具体对内部数据的操作封装在实体内部。
- **降低耦合度:**将不同实体类相互隔离开,可对实体进行独立的开发、测试、优化等操作
- **降低维护负担:**在系统维护时,不必关注于整个系统的运行,只需关注单个实体的运行情况。
- **提高复用性:**一个实体可在多个地方使用。
- **提高了模块的独立性:**当系统其中一个模块有bug时,并不会对其它模块造成影响。
2. 继承
对于不同的实体类的共同属性和操作进行抽取,封装为一个独立的实体类。被抽取的实体类可继承自该实体。
- **提高复用率:**子类可以使用父类的部分数据及方法。
- **多态的基础:**继承是多态的实现基础。
3. 多态
多态是在继承的基础上对父类的方法进行重写或增加新的方法,实现了在不同场景下提供针对性的方法。
- **可替代性:**可替代父类中已实现的方法。
- **可扩充性:**子类增加的方法不会对父类和其它子类有影响。
- **简化性:**简化了对于不同场景对代码的修改。
Java基础内容
一、Java及面向对象基础
1.JDK、JRE、JVM
- **JDK(Java Development Kit):**Java开发包。JDK为整个Java开发的核心,包含了JRE,Java开发工具(源码编译器javac,Java调试工具jconsole,打包工具jar等)。
- **JRE(Java Runtime Environment):**Java运行环境。包含了JVM,Java核心类库(util,long等)。
- **JVM(Java Virtual Machine):**Java虚拟机。JVM为实现Java跨平台的核心部分,包含了Java解释器(可将JDK编译出的.class文件解释为对应平台能执行的机器指令)。
JDK为编写Java程序所必需的,在程序编写完成后JDK可将源程序通过javac.exe工具编译成.class文件。JRE可为Java程序提供所运行的必需条件,并提供程序运行时所需要的所有类库。JVM为Java程序实际运行的平台,JVM可将JDK编译出的.class文件解释为对应操作系统的机器指令,进而实现跨平台的功能。
2.类的五大组成部分
-
**成员变量:**用于描述类或对象的属性信息,可为基础类型也可为引用类型。成员变量大致分为两种:静态成员变量、普通成员变量。
静态成员变量:
静态成员变量为该类所有对象的共有属性。每个实例均可对静态成员变量进行操作,没有实体对象也可对静态成员变量进行操作。普通成员变量:
普通成员变量为每个实例对象的特有属性,只有该对应的实例对象可以对该对象进行操作,其它对象不能对该属性进行操作。
-
**成员方法:**描述类或对象的行为。与成员变量相同,大致也分为:静态方法、普通方法。
静态方法:
该方法与静态成员变量相同,同属于类,而非具体的实例对象。普通方法:
该方法与普通成员变量相同,同属于具体的实例对象,其它对象不得调用。
-
**构造器:**构造器是类在创建实例对象时,必须执行的函数(没有返回值),由系统执行。
有参构造:
在使用有参构造时,可自行设置形参列表,根据传入的参数对构造出来的对象属性进行初始化。无参构造:
无参构造时,不需要传入参数。对象自动对属性进行一定的初始化。
构造器细节:
- 构造器只是完成对象的初始化,并没有创建对象。
- 构造器本质为函数,因此可以重载。
- 构造器名必须与类名相同。
- 类默认拥有无参构造。但当存在有参构造时,有参构造会自动将无参构造覆盖。存在有参构造时,应手动添加无参构造函数。
-
**代码块:**具体的代码逻辑。
-
**内部类:**将一个类定义在另一个类中或一个方法中。大致分为静态内部类、非静态内部类。见后续。
类中的静态区与非静态区:
静态区:静态区中的内容,属于类的部分。所有由此类实例的对象均可对静态区的内容进行操作。
非静态区:非静态区的内容,属于具体对象的部分。只有对应的对象能够操作。
3.抽象类
抽象类即是不能被实例化的类,其本质仍为一个类,可以有普通类的所有成员。抽象类需被非抽象子类继承后,方能通过该子类实例化。
-
定义格式
abstract class 类名字 { //类属性 //普通方法 //抽象方法 修饰符 abstract 返回值类型 方法名 (参数列表); }
实例:
public abstract class Animal { //类属性 public String name; //普通方法 public int fun1(){ return 1;} //抽象方法 public abstract void run(); }
在抽象类中可以有普通方法,有抽象方法的一定是抽象类。但抽象类不一定有抽象方法。
-
抽象类的继承
class Dog extends Animal{ @Override public void run(){ System.out.println("Dog is running"); } }
在继承抽象类时,子类必需实现抽象类中所有的抽象方法。
抽象类中可以含有构造方法,
- 抽象类的意义:为代码提供了一个模板设计模式,统一规范了代码。
抽象类不能使用private、final、static关键字来修饰。
4.接口
接口可视为一种方法的集合,接口中全部都是抽象方法。
-
定义格式
修饰符 interface 接口名称{ // 抽象方法,可省修饰符public //变量,省略修饰符public static final }
实例:
public interface interf1{ void run1(); int a=0; }
在JDK8之前接口中不存在变量,JDK8之后接口中可以含有变量。变量的属性为公共的、静态的。实现接口后,变量为全局变量。
-
接口的实现
[修饰符] class 类名 implements 接口1,接口2,接口3...{ }
实例:
public class A implements interf1,interf2{ @Override public void run1() { System.out.println("interf1 run()!"); } @Override public void run2() { System.out.println("interf2 run()!"); } }
在实现多个接口时,需将所有接口中的所有抽象方法全部实现。
- 接口注意事项
- 多接口同名抽象方法:在实现多个接口时,可能存在方法同名情况,此时只需在调用方法时,在方法前加上对应的接口名即可(如:interf1.run())。
- 优先级问题:当一个类实现接口并继承一个类后,存在同名方法时,会自动根据在类实现和继承顺序,自左向右调用对应方法。
- 接口的意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
5.抽象类与接口的对比
区别点 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 抽象方法和全局变量 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(JDK8:默认方法、静态方法) |
使用 | 继承(extends) | 实现(implements) |
关系 | 抽象类可实现多个接口 | 接口不能实现抽象类,但可继承接口 |
设计模式 | 模板模式 | 简单工厂、工厂方法、代理模式 |
局限 | 只能单继承 | 多实现(无局限) |
实际 | 作为模板使用 | 作为方法的标准 |
在接口和抽象类的使用时,为避免单继承的局限性应采取接口形式。
6.重载与重写
-
方法的重载:
在一个类中,同名方法根据各自不同的参数列表(参数个数和参数类型不同均可),实现方法的不同实现。重载是多态的一种体现。重载的方法之间属于平行关系。 -
方法的重写:
在子类中,将父类中的方法重新实现。方法名、参数列表必须与父类中的方法相同,具体实现代码不同。父类与子类中重写的方法属于垂直关系。 -
重载与重写的对比:
重写(Override) 重载(Overload) 实现范围 父类、接口 本类 方法名称 一致 一致 参数列表 不能修改 必须修改 返回类型 引用类型可修改(特定情况下) 可以修改 异常抛出 可减少、删除异常,但不能扩大异常 可以修改 重写注意事项:
-
子类不能重写父类中被final修饰的方法
-
子类必须重写父类中被abstract修饰的方法
-
子类所重写方法的访问修饰符一定大于等于父类中被重写的访问修饰符(public>protected>默认>private)。例如:
class Father{ int father(){ System.out.println("Father is running!"); return 1; } } class Son extends Father{ @Override public int father() { System.out.println("son is running father"); return 2; } }
-
子类重写的方法不一定是子类直属的父类,可能是父类的父类。
-
子类中重写方法的返回类型必须是父类中返回类型或是返回类型的子类。如:
class Father{ public Object father(){ System.out.println("Father is running!"); return "father"; } } class Son extends Father{ @Override public String father() { System.out.println("son is running father"); return "son"; } }
-
7. 流程控制
- **顺序控制:**程序自上而下逐行执行,中间没有任何判断和跳转。
-
**分支控制:**让程序有选择的执行(本质位if-else)。
-
单分支
if(条件表达式){ 执行代码块; }
-
-
双分支
if(条件表达式){ 执行代码块1; }else{ 执行代码块2; }
-
多分支
if(条件表达式1){ 执行代码块1; }else if(条件表达式2){ 执行代码块2; } ……else{ 执行代码块n; }
-
switch分支控制:
switch(表达式){ case 常量1:代码块1;break; case 常量2:代码块2;break; case 常量3:代码块3;break; …… default:语句块n;break; }
附:
- 若语句块中无break语句,程序将逐个执行接下来常量对应的代码块。
- case中的常量必须为整型常量。
- default对应的语句块,仅当表达式结果与case中的常量无对应时才执行。
-
for循环控制:
for(循环变量初始化;循环条件;循环变量迭代){ 执行代码块; }
附:
- 循环条件是返回一个boolean值的表达式。
- 循环初始值可以有多个初始化语句,单要求类型相同,用逗号隔开。
-
while循环控制:
while(循环条件){ 执行语句; 循环变量迭代; } do{ 执行语句; 循环变量迭代; }while(循环条件);
附:
- while与do-while的区别:do-while会先执行再判断(至少会执行一次),while先判断再执行(可能一次都不执行)。
-
**break和continue:**在循环语句(for、while、do-while)中使用,对循环进行判断。
break:
当执行该语句时,跳出整个循环结构。continue:
当执行该语句时,跳出本次循环进入下一次循环,而不跳出整个循环结构。
8. 关键字
关键字:
即是Java中已经被赋予其特殊含义,只能用于特定地方的特殊标识符。
- 各类型操作符:
访问控制 | 类、方法和变量修饰符 | 程序控制 | 异常处理 | 包 | 基本类型 | 变量引用 | 保留字 |
---|---|---|---|---|---|---|---|
private | abstract | break | try | import | boolean | super | goto |
protected | class | continue | catch | package | byte | this | const |
public | extends | return | throw | char | void | ||
final | do | throws | double | ||||
implements | while | finally | float | ||||
interface | if | int | |||||
native | else | long | |||||
new | for | short | |||||
static | instanceof | ||||||
strictfp | switch | ||||||
synchronized | case | ||||||
transient | default | ||||||
volatile | assert | ||||||
enum |
- 各关键字的使用:
final
:可用于修饰类、属性、方法、局部变量,使其无法被继承、修改、重写、重载等。- final不能修饰构造方法。
transient
:修饰属性,表示该属性不会被序列化。
9. 可变参数
可变参数:
运行将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。即是在调用方法时,可以传入不定个数且数据类型兼容的参数进行运算。
例如:
public static void main(String[] args) {
System.out.println(function1(5,6,7));
System.out.println(function1(5,6));
}
public static int function1(int... nums){//nums可认为是一个数组
int sum=0;
for (int i=0;i<nums.length;i++){
sum+=nums[i];
}
return sum;
}
可变参数细节:
- 可变参数的本质为一个数组,因此传入的形参可以为一个数组。
- 当函数的参数列表中含有可变参数和其它数据类型时,可变参数必须在形参列表的末尾。
- 一个形参列表中最多只能含有一个可变参数。
10. 访问修饰符
访问修饰符:
Java提供的四种用于控制方法、属性访问权限的符号。
-
各个访问修饰符
访问级别 访问修饰符 本类 同包 子类 不同包 公开 public √ √ √ √ 受保护 protected √ √ √ × 默认 无 √ √ × × 私有 private √ × × ×
11. main方法
main:
作为整个程序的入口。在类加载时,与类中普通的静态方法相同,被加载到方法区中。
public static void main(String[] args) {}
- main方法被虚拟机直接调用。调用时不必创建对象。
- main方法的本质也是类中的静态方法(可被重写)。
- 在main方法中可直接调用main方法所在类的静态成员。
二、运算符
运算符
:一种特殊的符号,用以表示数据的运算、赋值和比较等操作。
1. 算数运算符
- 运算符及其操作:
运算符 | 运算操作 | 范例 | 结果 |
---|---|---|---|
+ | 取正 | +7 | 7 |
- | 取负 | a=7;-a | -7 |
+ | 加 | 1+1 | 2 |
- | 减 | 1-1 | 0 |
* | 乘 | 1*2 | 2 |
/ | 除 | 2/1 | 2 |
% | 取模(取余) | 5%2 | 1 |
++ | 自增(前):先运算后取值; 自增(后):先取值后运算 | a=2; b=++a; b=a++ | b=3 b=2 |
– | 自减(前):先运算后取值; 自减(后):先取值后运算 | a=2 b=–a; b=a– | b=1 b=2 |
+ | 字符串相加 | “ab”+“cd” | “abcd” |
-
使用细则:
取模(%)运算本质:
取模运算的本质运算为a%b=a-a/b*b。如:-10%3=-10-(-10)÷3×3。
System.out.println(10%3);//输出1 System.out.println(-10%3);//输出-1 System.out.println(10%-3);//输出1 System.out.println(-10%-3);//输出-1
2. 关系运算符
关系运算符:
结果都是boolean型,其值只有true、false。
- 运算符及其操作
运算符 | 运算操作 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 8==7 | false |
!= | 不等于 | 8!=7 | true |
< | 小于 | 8<7 | false |
> | 大于 | 8>7 | true |
<= | 小于等于 | 8<=7 | false |
>= | 大于等于 | 8>=7 | true |
instanceof | 检测是否为某类对象或某类型的子类型 | “hap” instanceof String | true |
3. 逻辑运算符
逻辑运算符:
用于连接多个条件(多个关系表达式),最终结果的值为boolean。
- 运算符及其操作
运算符 | 运算操作 | 规则 |
---|---|---|
& | 逻辑与 | 全为真才为true,否则为false |
&& | 短路与 | 全为真才为true,否则为false |
| | 逻辑或 | 一个为真即为true,否则为false |
|| | 短路或 | 一个为真即为true,否则为false |
! | 取反 | 将false变为true,true变为false |
^ | 逻辑异或 | 当二者不同时,结果为true否则为false |
逻辑与(&)和短路与(&&):
其运算的结果是相同的。但其运算过程不同。
- 短路与(&&):如果第一个条件为false,则第二个条件不会进行判断。
- 逻辑与(&):无论第一个条件的结果是否为false,第二个条件都会进行判断。
逻辑或(|)和短路或(||):
其运算的结果是相同的。但其运算过程不同。
- 短路或:如果第一个条件为true,则第二个条件不会进行判断。
- 逻辑或:无论第一个条件是否为true,第二个条件均会进行判断。
public static void main(String[] args) {
if (f1() && f2()){ System.out.println("执行了f1(),未执行f2()"); }//输出:执行了f1(),未执行f2()
if (f1() & f2()){ System.out.println("执行了f1()和f2()"); }//输出:执行了f1()和f2()
if (f2() || f1()){ System.out.println("执行了f2(),未执行f2()"); }//输出:执行了f2(),未执行f2()
if (f2() | f1()){ System.out.println("执行了f1()和f2()"); }//输出:执行了f1()和f2()
}
public static boolean f1(){
boolean flag=false;
System.out.println("函数f1()的返回结果:"+flag);
return flag;
}
public static boolean f2(){
boolean flag=true;
System.out.println("函数f2()的返回结果:"+flag);
return flag;
}
- 判别结果
a | b | a&b | a&&b | a|b | a||b | !a | a^b |
---|---|---|---|---|---|---|---|
true | true | true | true | true | true | false | false |
true | false | false | false | true | true | false | true |
false | true | false | false | true | true | true | true |
false | false | false | false | false | false | true | false |
4. 赋值运算符
赋值运算符:
将某个运算后的结果赋值给指定的变量。
5. 三目运算符
三目运算符:
条件表达式 ? 表达式1: 表达式2;。其运算规则如下:
- 如果条件表达式为true,则执行表达式1并返回其结果。
- 如果条件表达式为false,则执行表达式2并返回其结果。
int a=10,b=20;
int result1=a>b ? ++a :++b;
int result2=a<b ? ++a :++b;
System.out.println(result1);//21
System.out.println(result2);//11
使用三元运算符时,需注意运算的结果需对应被赋值的变量类型(或可以自动转换)。
6. 运算符优先级
运算符优先级:
即是在一个复杂的表达式中,运算的顺序。
优先级 | 运算符类型 | 表达式中运算方向 | 运算符 |
---|---|---|---|
1 | “()”、“{}”、“;”、“.”、","等 | ||
2 | 单目运算符 | 右→左 | ++、–、~、! |
3 | 算数运算符 | 左→右 | *、/、% |
4 | 算数运算符 | 左→右 | +、- |
5 | 位运算符 | 左→右 | <<、>>、>>> |
6 | 关系运算符 | 左→右 | <、>、<=、>=、instanceof |
7 | 关系运算符 | 左→右 | ==、!= |
8 | 逻辑运算符 | 左→右 | & |
9 | 逻辑运算符 | 左→右 | ^ |
10 | 逻辑运算符 | 左→右 | | |
11 | 逻辑运算符 | 左→右 | && |
12 | 逻辑运算符 | 左→右 | || |
13 | 三目运算符 | 左→右 | ? : |
14 | 赋值运算符 | 右→左 | =、+=、/=、<<=、|=等 |
第一优先级的运算符为提升表达式优先级的符号。
单目运算符:
即是一次运算只作用于一个变量的运算符。优先级记忆:单目算数位关系,逻辑三目后赋值。
三、基本数据类型及常用的引用数据类型
1.基本数据类型与引用数据类型
- 8种基本数据类型:
数据类型 | 内存大小(比特位) | 包装类 | 表示范围 |
---|---|---|---|
boolean | 1 | Boolean | true/false |
char | 16 | Character | 0~65535 |
byte | 8 | Byte | -128~127 |
short | 16 | Short | -32768~32767 |
int | 32 | Integer | -2147483648~2147483647 |
long | 64 | Long | -9223372036854775808~9223372036854775807 |
float | 32 | Float | 1.4E-45~3.4028235E38 |
double | 64 | Double | 4.9E-324~1.7976931348623157E308 |
-
3种引用数据类型:
- **类:**由开发人员自定义的类型,内部由属性和方法组成。如Integer,Long等。
- **接口:**与类相同,但不可被直接实例化。如List等。
- **数组:**为一组相同数据类型的元素,在一块连续的内存组成。
引用类型与基本数据类型最大的不同就是,引用类型有方法,基本数据类型没有。
-
基本数据类型与引用数据类型的异同:
public static void main(String[] args) {
int a=1;
Person p=new Person(1.78,52);
System.out.println(a);//结果:1
System.out.println(p);//结果:Person@776ec8df
}
- 存储方式:
变量a存放在栈内存中,引用类型变量p,其变量p存放在栈内存中,p存放变量对象的引用。
非全局变量的基本数据类型存放在栈内存中,引用数据类型在栈内存中只存对象的引用(可理解为指针),其具体的内容存放在堆内存中。
-
传递方式:
- 值传递:基本数据类型(包含全局变量)在调用时,是直接将变量的值传递给调用方进行使用。
- 引用传递(址传递):引用数据类型在调用时,是将该对象的引用传递给调用方,调用方再根据引用找到该对象的具体内容。
基本类型中全局变量与局部变量的区别:
- 全局变量在类方法外声明,局部变量在方法内或方法的形参列表中声明。
- 全局变量在堆内存中存放,局部变量在栈内存中存放。
- 全局变量有初始值,局部变量无初始值需要手动初始化。
- 全局变量的声明周期随对象的建立而存在、对象的释放而消失。局部变量在使用时,系统会为该方法建立一个方法栈,局部变量存放在方法栈中,因此其生命周期随该方法栈变化。
-
**基本数据类型的自动类型转换:**在Java赋值或运算中,精度小的数据类型会自动转换为精度大的数据类型。
-
转换路线:
- char–>int–>long–>float–>double
- byte–>short–>int–>long–>float–>double
例如:
int num1='a'; double num2=80; System.out.println(num1);//输出97 System.out.println(num2);//输出80.0
-
转换中的细节:
-
精度大的数据类型赋值给精度小的数据类型存在错误,反之发生自动数据转换。
-
当多种数据类型混合运算时,Java会首先自动将所有数据转换为容量最大的那种类型,然后再运算。
int num1=10; float n2= num1+1.1;//错误,因1.1默认为double,故在运算是全部类型转换为double,所以double数据不能赋值给float。 double n3=num1+1.1;//正确
- char与(short、byte)之间不发生相互自动转换。但三种可以同时参与运算,运算时会先转为int类型。
byte num1=10; short num2=10; char num3=num1;//错误 char num4=3; System.out.println(num1+num2+num4);//输出23
- boolean类型数据不参与数据类型的自动转换
- 自动提升原则:即是表达式结果的类型自动提升为操作数中精度最大的数据类型。
byte num1=1; short num2=2; int num3=3; double num4=4; System.out.println(((Object)(num1+num2+num3+num4)).getClass().toString());//输出class java.lang.Double
-
-
-
**基本数据类型的强制类型转换:**程序员将数据的类型手动转换为想要的类型
int num1=(int)1.5;//精度损失 byte num2=(byte)2000;//数据溢出 System.out.println(((Object) num1).getClass().toString());//输出,class java.lang.Integer
- 当精度小的类型存放一个超出其范围的数据时,并使用了强制类型转换。会出现数据溢出现象。
- 强制类型转换符号"()"只会对最近的一个数据有效。
- 在进行赋值操作时编译器会进行的操作(1.判断范围、2.判断类型),编译器会先判断所赋的值是否在被赋值数据的类型范围内,而后才判断所赋的值的类型是否合理。
- 基本数据类型装箱为包装类型时,自动调用的valueof()方法。
2022.11.1补充:在进行隐式类型转时,进行补位操作时补位的值是符号位的值(即是:整数补0,负数补1)。
例如:
byte(1)==>int(1)
byte(1)
原码:00000000 00000001 反码:00000000 00000001 补码:00000000 00000001
转型过程中以补码形式运算,补位值为0
int(1)
转型后补码:00000000 00000000 00000000 00000001 其原码为:00000000 00000000 00000000 00000001
byte(-1)==>int(-1)
byte(-1)
原码:10000000 00000001 反码:11111111 11111110 补码:11111111 11111111
转型过程中补位值为1
int(-1)
转码后补码:11111111 11111111 11111111 11111111 其原码为:10000000 00000000 00000000 00000001
3. 常用的引用数据类型
数组
数组:
可以用于存放多个同一类型的数据。
-
数组的定义:数据类型 数组名[]=new 数据类型[大小]
int[] arr={1,2,3};//方式1 int arr1[]=new int[5];//方式2
数组使用细节:
- 数组内的所有元素对于基本数据类型而言,必须满足类型的自动转换兼容(如double可存放int)。对于引用数据类型而言,必须是同一类型。
- 数组在创建后,如未进行赋值,Java会自动赋默认值。
- int、short、byte、long–>0
- float、double–>0.0
- char–>\u0000
- boolean–>false
- String(及其它对象类型)–>null
- 数组角标自0开始
- 数组中的赋值机制采取的是引用传递方式。
-
数组的拷贝
-
浅拷贝:新的数组并没有在内存中创建新的数据,而是通过引用的方式,共享了原本的数据。
public static void main(String[] args) { int[] arr1={1,2,3}; int[] arr2=arr1; System.out.println(arr1);//输出[I@119d7047 System.out.println(arr2);//输出[I@119d7047 }
-
深拷贝:新的数组在内存中创建独立的数据,该数据内容与原数据内容相同。
public static void main(String[] args) { int[] arr1={1,2,3}; int[] arr2=new int[arr1.length]; for (int i=0;i< arr1.length;i++){ arr2[i]=arr1[i]; } System.out.println(arr1);//输出[I@119d7047 System.out.println(arr2);//输出[I@776ec8df }
-
-
多维数组
多维数组:
可理解为一维数组中的每个元素仍为数组类型的元素,逐层不断嵌套。-
二维数组
-
定义:
类型[][] 数组名=new 类型[大小][大小] int[][] arr={{1,2,3},{4,5,6},{7,8,9}};//方式1 int a[][]=new int[2][3];//方式2
-
内存分布:
-
-
> 附:
>
> 1. 二维数组中的一维数组的长度可相同也可不同。
Object
-
equal与==
- ==:既可以判断基本数据类型的数值,也可以判断引用数据类型的引用地址(即是是否为同一个对象)。
- equal:Object类的方法,只能判断引用类型。默认判断引用地址是否相等,子类往往会重写该方法,用于判断内容 是否相等。
-
hasCode方法:返回哈希码。
- 为了提高哈希结构容器的效率。
- 当多个引用指向同一个对象时,其哈希值相同。
- 当多个引用指向不同对象时,其哈希值不相同。
- 哈希值主要根据内部地址而来,但哈希值不能完全等价于哈希值。
-
finalize方法:当对象被回收时,系统自动调用该对象的finalize方法。用于对象资源的释放。
-
当对象没有被任何变量引用时,即可被JVM认为垃圾,会被垃圾回收器销毁。即是释放该对象的内存空间。
class A{ String name="测试finalize"; public A() { System.out.println("对象创建"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("自定义finalize方法"); } } public static void main(String[] args) { A a=new A(); a=null; System.gc();//主动调用垃圾回收器 System.out.println("结束"); }
实际开发中不会重写finalize方法。
-
String——StringBuilder——StringBuffer
- String的本质为char[],String不可修改指的是char[]指向的地址不可修改,而不是char[]内容不可修改
- StringBuffer和StringBuilder是对String的功能进行扩展,三者有很大相似地方。
-
String创建对象的区别
-
直接赋值
String str="Hello World";
内存分析:
-
> 首先在常量池中查找是否存在该字符串,如存在则直接指向该字符串;如不存在则在常量池中创建该字符串。
-
调用构造器
String str=new String("Hello World");
内存分析:
> 首先在堆内存中创建String对象,而后在常量池中查找是否存在该字符串,如存在则String对象指向该字符串,如不存在则创建该字符串。
-
StringBuffer
StringBuffer
:与String作用类似,但StringBuffer长度是可变的,是一个容器。- StringBuffer与String区别
- StringBuffer长度是可变,String的长度改变的本质为创建新的对象。
- StringBuffer中的char[]存放再堆内存中,String中的char[]存放在常量池。
- StringBuffer与String区别
-
StringBuilder
StringBuilder
:StringBuilder提供了与StringBuffer兼容的API,其速度比StringBuffer快,但不保证同步。常用于单线程。 -
String、StringBuilder、StringBuffer三者比较
- StringBuilder和StringBuffer非常相似,均代表可变字符串。
- String为不可变字符串,效率低,但复用率高。
- StringBuffer增删效率高,线程安全。
- StringBuffer效率最高,线程不安全。
日期类(Data、Calendar、LocalDate……)
-
Data
Date
:精确到毫秒,表示特定的瞬间,与其组合使用的有SimpleDateFormat(格式化日期类)。使用实例:
public static void main(String[] args) throws ParseException { Date date1=new Date();//获取当前系统时间 Date date2=new Date(9234567);//通过指定毫秒数得到时间 System.out.println(date1.getTime());//获取某个时间对应的毫秒数 SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");//指定格式 String format=sdf.format(date1);//format:将日期转换为指定格式字符串 String str="1996年01月01日 10:20:30 星期一"; Date parse=sdf.parse(str); }
-
Calendar
Calendar
:为抽象类,构造器为私有属性,可使用getInstance()获取。实例:
public static void main(String[] args) throws ParseException { Calendar calendar=Calendar.getInstance(); System.out.println(calendar); System.out.println("年:"+calendar.get(Calendar.YEAR)); System.out.println("月:"+(calendar.get(Calendar.MONTH)+1)); System.out.println("日:"+calendar.get(Calendar.DAY_OF_MONTH)); System.out.println("小时:"+calendar.get(Calendar.HOUR)); System.out.println("分钟:"+calendar.get(Calendar.MINUTE)); System.out.println("秒:"+calendar.get(Calendar.SECOND)); }
Calendar类没有专门的格式化方法,需开发人员自定义
-
LocalDate(日期)、LocalTime(时间)、LocalDateTime(时间日期)
LocalDate
:只包含日期,可以获取日期字段。LocalTime
:只包含时间,可以获取时间字段。LocalDateTime
:包含时间+日期,可以获取时间和日期字段。
四、类与对象
类:
将不同实例中的公共部分抽取出来形成一个模板。
对象:
即是类的具体体现。
例如:
public static void main(String[] args) {
Person person=new Person();
System.out.println(person.name);
int res=person.getSum(1,2);
System.out.println(res);
}
class Person{
String name="张三";
int age=15;
public int getSum(int num1,int num2){
int res=num1+num2;
return res;
}
}
1. 内存分部
内存分布要点:
- 在对象创建时,对象实际内容并不会在栈中创建,而是在堆内存创建实际内容。栈中创建一个对堆内存中的实际对象进行管理的变量(即是变量名)。
- 堆内存中,若对象属性为基本数据类型,直接赋值;若对象属性为引用数据类型,则为引用赋值。
- 对象的属性若暂未被赋值时,其值为默认值。
- 对象中的字符串属性会被存在常量池中。
2. 对象创建过程
- 加载类信息。主要加载的内容为属性信息、方法信息。(类信息只会加载一次,即在第一次创建该类的对象时在方法区中进行,后续创建该类的对象时不会进行)。
- 为对象分配内存空间
- 首先在堆内存中开辟内存空间,用以存放对象的实际内容。
- 而后在栈内存中开辟空间,用以存放对象在堆内存的引用,便于对对象的控制。
- 为对象属性进行默认赋值。(若属性为基本数据类型则直接赋值,若为引用数据类型则存放引用)。
- 设置对象头等参数。(一般包括对象哈希码、元数据信息等)。
- 执行构造函数,返回对象引用。
上述为无父类的对象创建过程。
- 先加载父类,再加载本类。
- 先创建父类,再创建本类。
3. 方法调用机制
- 在程序执行时,会先执行main方法,并在栈内存中创建main方法的方法栈。
- main方法执行到调用方法时,在栈内存中会开辟对应方法的方法栈。
- 被调用方法在其栈中执行。
- 被调用方法执行结束或返回结果。
- 程序执行时,JVM会在栈内存中开辟对应的独立空间用于方法的执行。
- 当方法执行完毕后,该方法在栈内存的空间会被销毁。
方法使用细节:
- 方法最多只能有一个返回结果。
- 方法返回结果与调用方法的接收类型必须兼容。
- 调用方法时传入的实参必须与方法形参列表中的数据类型兼容。
- 同一个类中的方法可以直接相互调用。
4. 面向对象三大特性
-
封装:
将对象中的数据和方法利用访问修饰符封装在一起保护在对象内部,外部访问只能通过被授权的方法进行访问。对象属性的访问接口常用setXxx()、getXxx()方法实现。class Person{ private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
-
继承:
当多个类存在相同属性和方法时,可将相同属性和方法抽象为一个单独的类,即是父类。class Person{ public String name; public void setName(String name) { this.name = name; } } class Man extends Person{ public boolean sex; public void setSex(boolean sex) { this.sex = sex; } public Man(boolean sex) { super(); this.sex = sex; } }
附:
- 子类继承了父类的所有属性和方法,但私有属性和方法子类不能直接访问。只能访问非私有的属性和方法。
- Java中子类只能继承自一个父类,即是单继承。
- Java中所有的类都是Object类的子类。
- 子类在构造时,会先调用父类的构造器完成父类的初始化。若父类没有无参构造器,则需使用super指定父类中具体的有参构造器。调用父类构造器时,会一直上溯到Object类。
- super调用时,只能放置在方法最前面。
继承的本质:
public static void main(String[] args) { Son son=new Son(); System.out.println(son.name);//大头儿子 System.out.println(son.age);//39 System.out.println(son.hobby);//旅游 } class GrandPa{ String name="大头爷爷"; String hobby="旅游"; } class Father extends GrandPa{ String name="大头爸爸"; int age=39; } class Son extends Father{ String name="大头儿子"; }
创建对象执行步骤:
子类访问某个属性或方法时,自本类逐步向上寻找父类中可被访问的第一个该属性或方法。
-
多态:
方法或对象具有多种形态。方法的多态:
方法的重载
方法的重写
**对象的多态:**本质为存在继承关系的对象,其编译类型与运行类型不一致的情况。
编译类型:
声明变量时,所使用的类型。运行类型:
实际为变量赋值时,所使用的类型。Animal animal=new Dog();//编译类型Animal,运行类型Dog
附:
- 对象的编译类型与运行类型可以不一致。
- 编译类型在定义时即确定,不可改变。
- 对象的运行类型可以改变。
- 编译时,根据编译类型确定对象可以调用哪些成员;运行时,根据运行类型确定被调用成员的运行结果。
-
向上转型:
其本质为父类的引用指向子类的对象。如:class Animal{ public void cry(){ System.out.println("Animal is crying"); } } class Dog extends Animal{ @Override public void cry() { System.out.println("Dog is crying"); } public void catchStolen(){ System.out.println("Dog catch stolen"); } } public static void main(String[] args) { Animal animal=new Dog(); animal.cry();//Dog is crying }
在遵守访问权限的前提下,变量可以调用父类(编译类型)的所有成员,不能调用子类(运行类型)的特有成员。
-
向下转型:
其本质为子类引用指向一个由父类引用强制转换为子类的引用。class Animal{ public void cry(){ System.out.println("Animal is crying"); } } class Dog extends Animal{ @Override public void cry() { System.out.println("Dog is crying"); } public void catchStolen(){ System.out.println("Dog catch stolen"); } } public static void main(String[] args) { Animal animal=new Dog();//编译类型Animal,运行类型Dog Dog dog=(Dog) animal;//编译类型Dog,运行类型Animal dog.catchStolen();//Dog catch stolen }
- 只能强转父类的引用,不能强转父类的对象。
- 在强转时,当前父类的引用必须指向当前目标类型(编译类型)的对象。
- 强转后可调用子类特有成员。
-
动态绑定机制:
本质为:
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定。
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
class A{ public int i=10; public int sum(){ System.out.println("父类sum()"); return getI()+10; } public int getI(){ System.out.println("父类getI()"); return i; } } class B extends A{ public int i=20; public int getI(){ System.out.println("子类getI()"); return i; } } public static void main(String[] args) { A a=new B(); System.out.println(a.sum());//调用父类sum(),子类getI() System.out.println(a.sum1());//调用子类sum1() }
当子类中不存在该方法,则继承机制生效,调用父类中该方法。当调用方法存在在子类时,无论父类中是否有该方法,均只会调用子类中的方法,
5. this与super
在面向对象的三大特性中,this和super两个关键字常被使用,用以子类与父类直接的关联。
-
this:
是自身的一个对象,表示对象本身。哪个对象调用this就指向哪个对象。用于:-
调用成员变量
class Son{ int num=2; public int getNum(){ return this.num; } }
-
调用构造函数
class Son{ int num=2; public Son(){ this(20); System.out.println("无参构造调用有参构造"); } public Son(int num){ this.num=num; } }
-
调用普通函数
class Son{ public Son(){ this.f1(); } public void f1(){ System.out.println("调用函数f1()"); } }
-
返回当前对象引用
class Son { public Object thisObject(){ return this; } }
-
-
super:
指向本类中满足super要求,关系最近的父类。用于:-
调用父类构造方法
class Father{ public Father() { System.out.println("调用父类构造方法"); } } class Son extends Father{ public Son() { super(); } }
-
访问父类成员属性
class Father{ int num=1; } class Son extends Father{ public Son() { System.out.println("访问父类成员属性num="+super.num); } }
-
调用父类方法
class Father{ public void fun(){ System.out.println("fun() is running"); } } class Son extends Father{ public Son() { super.fun(); } }
-
-
this与super的细节:
- this与super均需方在构造函数内的首行。
- this()和super()都指的是对象,所以,均不可以在static环境(static变量、static方法、static语句块 )中使用。
- this与super不能同时出现在构造函数中。
- 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
6. 静态变量及静态方法
静态变量及静态方法属于类而不属于某一个具体对象。
- jdk1.8之前,静态变量在加载时会被加载到方法区中的类加载区;jdk1.8之后,静态变量在加载时会被加载到堆内存中。
- 静态方法在加载时会和普通方法一同被加载到方法区中。
7. 代码块细节
代码块:
程序执行时的逻辑语句。分为静态代码块和普通代码块。
-
静态代码块:对类进行初始化,随类的加载而执行,只会执行一次。静态代码块只能调用静态成员。
class A{ static { System.out.println("A的静态代码块1"); } } public static void main(String[] args) { A a=new A(); A b=new A(); }
-
普通代码块:每创建一个对象就会执行一次。普通代码块可以调用所有成员。
class A{ { System.out.println("A的普通代码块1"); } } public static void main(String[] args) { A a=new A(); A b=new A(); }
-
代码块优先级:在创建对象时,存在静态代码块和普通代码块调用的优先级问题。
-
最先调用静态代码块和静态属性初始化。
-
其次调用普通代码块和普通属性初始化。
-
最后调用构造方法。
class A{ private static int n1=getN1();//静态属性初始化 private int n2=getN2();//普通属性初始化 {//普通代码块 System.out.println("A的普通代码块"); } static {//静态代码块 System.out.println("A的静态代码块"); } public static int getN1(){ System.out.println("getN1被调用"); return 1; } public int getN2(){ System.out.println("getN2被调用"); return 1; } public A() { System.out.println("A的构造函数被调用"); } } public static void main(String[] args) { A a=new A(); } /*输出 getN1被调用 A的静态代码块1 getN2被调用 A的普通代码块1 A的构造函数被调用 */
当同等优先级被执行时,按语句顺序执行。
-
-
继承关系中的优先级:父、子类中代码块存在优先级问题。
-
父类静态代码块和静态属性
-
子类静态代码块和静态属性
-
父类普通代码块和普通属性
-
父类构造方法
-
子类普通代码块和普通属性
-
子类构造方法
class A{ private static int n1=getN1();//静态属性初始化 private int n2=getN2();//普通属性初始化 {//普通代码块 System.out.println("父类的普通代码块"); } static {//静态代码块 System.out.println("父类的静态代码块"); } public static int getN1(){ System.out.println("父类的getN1被调用"); return 1; } public int getN2(){ System.out.println("父类的getN2被调用"); return 1; } public A() { System.out.println("父类的构造函数被调用"); } } class B extends A{ private static int n1=getN1();//静态属性初始化 private int n2=getN2();//普通属性初始化 {//普通代码块 System.out.println("子类的普通代码块"); } static {//静态代码块 System.out.println("子类的静态代码块"); } public static int getN1(){ System.out.println("子类的getN1被调用"); return 1; } public int getN2(){ System.out.println("子类的getN2被调用"); return 1; } public B() { System.out.println("子类的构造函数被调用"); } } public static void main(String[] args) { B b=new B(); } /* 父类的getN1被调用 父类的静态代码块 子类的getN1被调用 子类的静态代码块 子类的getN2被调用 父类的普通代码块 父类的构造函数被调用 子类的getN2被调用 子类的普通代码块 子类的构造函数被调用 */
普通代码块与构造方法的关系本质为,在构造方法内部除会隐式调用super外,还隐式调用了普通代码块。
-
类加载的时机:
- 创建对象实例时。
- 创建子类对象实例时,父类也会被加载。
- 使用类的静态成员时。
五、内部类
内部类:
一个类的内部又完整的嵌套了另一个类的结构,被嵌套的类称为内部类。结构大致为:
class Outer{//外部类
class Inner{}//内部类
}
1. 局部内部类
局部内部类:
具有类名,且定义在外部类的局部位置,如方法中。1
class Outer{
private int n1=10;
private int n2=20;
public void function1(){
class Inner{//局部内部类
private int n1=30;
public void function2(){
System.out.println("Inner n1="+n1);//30
System.out.println("Outer n2="+Outer.this.n2);//20
}
}
Inner inner=new Inner();
inner.function2();
}
}
public static void main(String[] args) {
Outer outer=new Outer();
outer.function1();
}
- 可以访问外部类的所有成员。
- 局部内部类不能添加访问修饰符,但可以被final修饰。
- 局部内部类仅在该局部位置作用域有效。
- 当内部类与外部类的成员重名时,默认次啊去就近原则。访问外部类格式(外部类名.this.成员)。
2. 匿名内部类
匿名内部类:
没有类名(开发人员无法使用,其在底层仍具有类名),且定义在外部类的局部位置,如方法中。
class Outer{
private int n1=10;
private int n2=20;
public void function1(){//基于接口实现匿名内部类
A a=new A(){//编译类型为接口A 运行类型为匿名内部类
private int n1=30;
@Override
public void function2() {
System.out.println("匿名内部类访问本类成员n1:"+n1);
System.out.println("匿名内部类访问外部类成员n2:"+Outer.this.n2);
System.out.println("Inner function2 is running");
}
};
a.function2();
System.out.println("匿名内部类的运行类型为:"+a.getClass());//Java底层自动为匿名内部类实现,并创建了实例。
}
public void function4(){//基于继承实现内部类
Father father=new Father(){//编译类型为Father类 运行类型为匿名内部类
@Override
public void function3(){
System.out.println("Inner function3 is running");
}
};
father.function3();
System.out.println("匿名内部类的运行类型为:"+father.getClass());//Java底层自动为匿名内部类实现,并创建了实例。
}
}
interface A{
void function2();
}
class Father{
public void function3(){}
}
public static void main(String[] args) {
Outer outer=new Outer();
outer.function1();
outer.function4();
}
- 匿名内部类可以访问外部类所有成员。
- 外部其它类不能访问内名内部类。
- 当匿名内部类与外部类的成员重名时,规则与局部内部类相同。
3. 成员内部类
成员内部类:
定义在外部类的成员位置,与外部类的普通成员使用相同。
class Outer{
private int n1=10;
private int n2=20;
public void function1(){
Inner inner=new Inner();
System.out.println("Outer function1 is running");
inner.function2();
}
class Inner{
private int n1=30;
public void function2(){
System.out.println("Inner function2 is running");
System.out.println("成员内部类访问外部类成员n2:"+Outer.this.n2);
System.out.println("成员内部类访问成员n1:"+n1);
}
}
}
public static void main(String[] args) {
Outer outer=new Outer();
outer.function1();
Outer.Inner inner=outer.new Inner();//外部其它类访问成员内部类
}
- 外部其它类可访问成员内部类。
- 当成员内部类与外部类的成员重名时,规则与局部内部类相同。
4. 静态内部类
静态内部类:
被static修饰,且定义在外部类的成员位置。与外部类的静态成员使用相同。
class Outer{
private static int n2=20;
static class Inner{
private int n3=30;
public void function2(){
System.out.println("Inner function is running");
System.out.println("静态内部类访问外部类成员n2:"+Outer.n2);
System.out.println("静态内部类访问本类成员n3:"+n3);
}
}
public void function1(){
Inner inner=new Inner();
System.out.println("Outer function1 is running");
inner.function2();
}
}
public static void main(String[] args) {
Outer outer=new Outer();
outer.function1();
Outer.Inner inner=new Outer.Inner();//外部其它类访问静态内部类
}
- 当静态内部类与外部类的成员重名时,规则与局部内部类相同。
六、枚举
枚举:
是一组常量集合,枚举本质为一个类,其中包含了一组有限的特定对象。
1. 枚举的定义
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
枚举类型其底层实现为:
public class Day {
public static final int MONDAY =0;
public static final int TUESDAY=1;
public static final int WEDNESDAY=2;
public static final int THURSDAY=3;
public static final int FRIDAY=4;
public static final int SATURDAY=5;
public static final int SUNDAY=6;
}
枚举类型其底层会自动将枚举值进行有序赋值,如上代码。
2. 枚举的使用
public static void main(String[] args) {
//创建枚举数组
Day[] days=new Day[]{Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY,Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY};
System.out.println(Day.MONDAY.ordinal());//枚举常量对应值
System.out.println(Day.MONDAY.name());//枚举常量
}
枚举常量也是一个类,其类型与枚举类型相同。
3. 枚举的使用细节
- 枚举不能被继承
- 枚举不能在外部创建对象(即不能使用new创建对象),枚举的构造方法默认为私有。
- 枚举可以继承接口
- 枚举与普通类基本相同,可以有成员变量。成员方法等
枚举的意义:
- 枚举可将常量组织起来,进行统一管理。
- 枚举可将参数控制在固定范围内,降低了非法数据传输的可能性。
七、注解
注解:
用于修饰包、类、方法、属性、构造器、局部变量等数据信息。注解不会影响程序逻辑,但会被编译或运行。
1. 标准注解
标准注解:
即是Java自带的标准注解,包括@Override、@Deprecated、@SuppressWarnings。
-
@Override:限定某个方法,是父类方法的重写。该注解只能用于方法。
class Father{ public void fly(){ System.out.println("Father is flying"); } } class Son extends Father{ @Override public void fly() { System.out.println("Son is flying"); } } public static void main(String[] args) { new Son().fly(); }
@Override只会在编译时,检查是否重写的父类的方法。
-
@Deprecated:修饰某个元素(类、方法、字段、包、参数等),表示该元素已经过时,不推荐使用,但仍可使用。
@Deprecated class A{ @Deprecated public void function(){ System.out.println("A function is running"); } } public static void main(String[] args) { A a=new A(); a.function(); }
-
@SuppressWarnings:抑制编译器警告,即当存在警告时,不会显示出来。该注解的位置即是注解的作用域。
@SuppressWarnings("all")//被抑制警告的类型 public static void main(String[] args) { List list=new ArrayList(); list.add("A"); list.add("B"); list.add("C"); System.out.println(list); }
-
抑制警告类型
类型 作用 all 抑制所有警告 boxing 抑制装箱、拆箱操作时候的警告 cast 抑制映射相关的警告 dep-ann 抑制启用注释的警告 deprecation 抑制过期方法的警告 fallthrough 抑制在switch中缺失breaks的警告 finally 抑制finally模块没有返回的警告 hiding 本地隐藏未显示的参数警告 incomplete-switch 忽略没有完整的switch语句 nls 忽略非nls格式的字符 null 忽略对null的操作 rawtypes 使用generics时忽略没有指定相应的类型 restriction 不支持或禁用的信息警告 serial 忽略在serializable类中没有声明serialVersionUID变量 static-access 抑制不正确的静态访问方式警告 synthetic-access 抑制子类没有按最优的方法访问内部类的警告 unchecked 抑制没有进行类型检查操作的警告 unqualified-field-acces 抑制没有权限访问的域的警告 unused 抑制没被使用过的代码额警告
-
2. 元注解
元注解:
用于修饰注解的注解。包含@Retention、@Documented、@Target、@Inherited四种。
@Retention
用于标明注解被保留的阶段。其参数值有:RetentionPolicy.SOURCE
:仅编译期RetentionPolicy.CLASS
:仅class文件RetentionPolicy.RUNTIME
:运行期
@Target
用于标明注解使用的范围。其参数值有:ElementType.TYPE
:类或接口ElementType.FIELD
:字段ElementType.METHOD
:方法ElementType.CONSTRUCTOR
:构造方法ElementType.PARAMETER
:方法参数
@Inherited
用于标明注解可继承(即是定义子类是否继承父类的注解)。@Documented
用于标明是否生成javadoc文档。
3. 自定义注解
自定义注解:
根据开发需要,利用元注解自定义特殊含有的注解。例如:
class A{
@MyAnnotation
public void function(){
System.out.println("A function is running");
}
}
@Target({METHOD,TYPE})
@interface MyAnnotation{ }//@interface注解类
public static void main(String[] args) {
new A().function();
}
八、异常处理
异常:
程序执行中发生的不正常情况。可分为Error
(Java虚拟机无法解决的严重问题。如:栈溢出)、Exception
(因编译错误或偶然的外在因素导致的一般性问题,可通过对应代码处理。如空指针异常)。
1. 异常框架体系图
常见异常:
- NullPointerException:空指针异常,当被使用引用指向空时抛出。如String st=null;。
- ArithmeticException:数学运算异常,当出现异常的运算条件时抛出。如:int i=1/0;。
- ArrayIndexOutOfBoundsException:数组下标越界异常,当访问的下标不在数组范围内抛出。如:int[] arr=new int[3]; arr[5]=1;。
- ClassCastExpection:类型转换异常,当对象轻质转换不兼容时抛出。
- NumberFormatException:数字格式不正确异常,当字符串不能转为某一格式的数字时抛出。
2. 异常处理机制
-
try-catch-finally
:开发人员将抛出的异常捕捉,自行处理。-
语法规范:
try { //可能产生异常的代码 } catch (异常类型 e) { //对异常进行处理 } finally { //无论异常是否发生均执行的代码 }
-
例:
public static void main(String[] args) { int a=10; int b=0; try { System.out.println(a/b); } catch (Exception e) { System.out.println("被除数为0异常"); } finally { System.out.println("执行完成"); } }
-
使用注意:
- 可以使用多个catch语句捕获不同异常,但要求父类异常在后,子类异常在前。
- 无论rty语句块中、catch语句块中是否有return语句,finally语句块均会执行。
-
-
throws
:将异常抛出,交由调用者处理。最顶级的处理者为JVM。JVM处理异常是将异常信息输出并退出程序。-
语法规范:
方法定义 throws 异常类型1,异常类型2……{}
-
例:
public void function() throws Exception{ int a=10; int b=0; System.out.println(a/b); }
-
使用注意:
- throws抛出的异常可以是产生异常的父类。
- 对于编译时异常,程序必须处理(throws或try-catch-finally)。
- 对于运行时异常,若未处理,默认为throws处理。
- 当方法重写时,子类方法抛出的异常必须与父类方法抛出的异常兼容(类型一致或为其子类)
-
3. 自定义异常
自定义异常
:当程序中出现的异常并没有在Throwable的子类中出现,则根据需要自定义该异常类进行处理。
- 例:
class MyException extends Exception{
public MyException(String message) {
super(message);
}
}
public static void main(String[] args) throws java.lang.Exception {
int age=120;
if (age>100){
throw new MyException("年龄不合理,请重写输入");
}
}
- 使用细节:
- 自定义异常通常继承自RuntimeException(即运行时异常)。
4. throws与throw
意义 | 位置 | 组合对象 | |
---|---|---|---|
throws | 处理异常的一种方式 | 方法声明处 | 异常类型 |
throw | 生成异常对象的关键字 | 方法体中 | 异常对象 |
九、集合类
1. 集合框架体系图
- 集合框架:
- 集合主要分为单列集合、双列集合。单列集合有Collection及其子类,双列集合有Map及其子类。
2. Collection
Collection
:为单列集合的顶级接口,没有实现类,通过其子类List、Set实现实例化。
-
Collection的常用方法
- add():添加元素
- remove():删除元素
- contains():查找元素是否存在
- size():获取元素个数
- isEmpty():判断是否为空
- clear():清空
……
-
元素遍历
-
Iterator(迭代器)
public static void main(String[] args) { Collection collection=new ArrayList(); for (int i=1;i<5;i++) collection.add(i); Iterator iterator = collection.iterator();//获取迭代器 while (iterator.hasNext()){ Object temp=iterator.next(); System.out.println(temp); } }
当迭代器结束后,迭代器指向最后一个元素。
-
增强for
- 语法规范
for(元素类型 元素名:集合名或数组名){ 访问元素 }
- 实例
public static void main(String[] args) { Collection collection=new ArrayList(); for (int i=1;i<5;i++) collection.add(i); for (Object temp:collection){ System.out.println(temp); } }
增强for的本质与Iterator相同,只是对其进行了简化。
-
3. List
List
:为Collection接口的子接口。
-
List的特点
- List集合类中的元素有序(添加顺序与存储顺序相同),且元素可重复。
- List集合中每个元素都有其对应的顺序索引。
-
ArrayList
- ArrayList使用细节
- ArrayList中可以添加null。
- ArrayList基本等同与Vector,虽然ArrayList执行效率高,但线程不安全。
- ArrayList底层机制
- ArrayList其底层实现是一个Object类型的数组elementData[]。
- 无参构造ArrayList时,其容量为0,首次添加扩容至容量为10,若再次扩容,则扩容为上次容量的1.5倍。
- 指定容量大小构造ArrayList时,若扩容则直接扩容为容量的1.5倍。
- ArrayList使用细节
-
Vector
Vector
:其底层为一个Object类型的数组。- Vector底层机制
- Vector线程安全。
- 构造Vector时,无参默认容量为10,无论有参无参扩容均为扩容至上次容量2倍。
- Vector底层机制
-
LinkedList
LinkedList
:其底层实现了双向链表及双端队列的特点。-
LinkedList使用细节
- LinkedList可以添加任意元素(包含null),且可重复。
-
LinkedList底层机制
-
LinkedList其底层为一个双向链表。
-
线程不安全,没有实现同步。
-
-
-
ArrayList与LinkedList
底层结构 增删效率 改查效率 ArrayList 可变数组 较低,数组扩容 较高 LinkedList 双向链表 较高,链表追加 较低
4. Set
Set:与List相同均为Collection的子接口。
-
Set的特点
- 无序(添加顺序与储存顺序不一致),且无索引。
- 不允许元素重复,最多只能有一个null。
-
HashSet
HashSet
:其底层实现为HashMap。- HashSet使用细节
- HashSet中可能存在两个元素的hash值相同。
- HashSet底层机制
- HashSet底层使用hash值进行存储。
- HashSet添加元素时,若根据hash值得到的索引值存在,则会调用equal比较,相同则不添加,不相同则添加。
- HashSet使用细节
-
LinkedHashSet
LinkedHashSet
:为HashSet的子类,并实现了Set接口。-
LinkedHashSet使用细节
- LinkedHashSet不允许添加重复元素。
- LinkedHashSet添加元素时,可能存在元素不同但hash值相同。
-
LinkedHashSet底层机制
-
LinkedHashSet底层为一个LinkedHashMap。
-
LinkedHashSet根据元素的hash值决定存储位置,同时使用链表维护元素次序。
-
-
5. Map
Map
:为双列集合用于保持具有映射关系的数据,其存储方式为K-V形式。
-
Map的特点
- Map中的key不允许重复,原因与HashSet相同。
- Map中的value可以重复。
- Map中的key可以为null,但只能有一个;value也可以为null。
- Map中的key与value之间存在单向一对一的关系。只能通过key找到对应的value。
- 为便于遍历,Map还会创建EntrySet集合(EntrySet<K,V>),类型为Entry,该类型存放的对象为k、v。
-
Map的常用方法
- put():添加。
- remove():根据K删除。
- get():根据K获取。
- size():获取元素个数。
- isEmpty():判断个数是否为0。
- containsKey():查找K是否查找。
-
Map的六种遍历方式
-
利用增强for遍历key
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Set keySet = map.keySet();//获取所有K for (Object key:keySet) System.out.println(key+"-"+map.get(key)); }
-
利用迭代器遍历key
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Set keySet = map.keySet();//获取所有K Iterator iterator = keySet.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key+"-"+map.get(key)); } }
-
利用增强for直接遍历value
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Collection values = map.values();//获取所有的value for (Object value:values) System.out.println(value); }
-
利用迭代器遍历value
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Collection values = map.values();//获取所有的value Iterator iterator = values.iterator(); while (iterator.hasNext()) { Object value = iterator.next(); System.out.println(value); } }
-
利用增强for遍历EntrySet
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Set entrySet = map.entrySet();//获取EntrySet进而获取k-v for (Object entry:entrySet){ Map.Entry temp=(Map.Entry)entry; System.out.println(temp.getKey()+"-"+temp.getValue()); } }
-
利用迭代器遍历EntrySet
public static void main(String[] args) { Map map=new HashMap(); map.put("1","A"); map.put("2","B"); map.put("3","C"); map.put("4","D"); map.put("5","E"); Set entrySet = map.entrySet();//获取EntrySet进而获取k-v Iterator iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry temp = (Map.Entry) iterator.next(); System.out.println(temp.getKey()+"-"+temp.getValue()); } }
-
-
HashMap
HashMap
:HashMap为Map接口种使用最高的类。- HashMap使用细节
- HashMap与HashSet一样,不保证映射的顺序,其底层以hash表的形式进行存储。
- HashMap线程不安全。
- HashMap底层机制
- HashMap底层实现为一个Node类型的数组,默认为null。
- HashMap扩容时并不会在填充满后扩容,当达到加载因子时就扩容,加载因子初始化为0.75。
- HashMap初始容量为16.
- HashMap每次扩容为之前容量的2倍。
- HashMap使用细节
-
HashTable
- HashTable使用细节
- HashTable中的K和V均不能为null。
- HashTable线程安全。
- HashTable底层机制
- HashTable扩容机制与HashMap机制相同,加载因子也为0.75。
- HashTable使用细节
-
Properties
Properties
:该类继承自HashTable,并实现了Map接口。- Properties的使用细节
- Properties主要用于对xxx.properties文件内容加载、读取、修改。
- Properties的使用细节
6. Collections
Collections
:是一个操作Set、List、Map等集合的工具类
-
Collections常用方法
-
reverse(List):反转List的元素顺序。
-
shuffle(List):对List集合元素进行随机排序。
-
sort(List):对List进行排序。
-
max(Collection):获取Collection排序的最大值。
-
min(Collection):获取Collection排序的最大值。
-
frequency(Collection,Object):获取Collection中指定元素Object的个数。
……
-
十、泛型
泛型
:其本质是为了参数化类型,即是编写模板代码用以适应任意类型,将代码中的形参类型进行规定。
泛型即是在创建类时,暂时不知道某个属性的类型,需要根据具体情况来确定。因此,先将该属性的类型泛化,不指定具体的类型。
1. 泛型语法规范
-
泛型基本使用
-
语法规范
class 类名<T>{ T a1; T a2; …… }
-
实例
class Person<T>{ T t;//T表示t属性的数据类型 public Person(T t) { this.t = t; } public T getT() { return t; } public void setT(T t) { this.t = t; } } public static void main(String[] args) { Person<String> stringPerson = new Person<String>("泛型"); Person<Integer> integerPerson = new Person<Integer>(1); System.out.println(stringPerson.getT()); System.out.println(integerPerson.getT()); }
-
-
泛型使用细节
- 泛型的类型根据创建实例对象时的类型确定。
- 泛型在编译时T变为对应的数据类型。
- 泛型只能指定引用数据类型,不能时基本数据类型。
- 泛型被指定类型后,传入的类型必须兼容(即是本类或其子类)。
- 泛型的默认类型是Object类型。
2. 自定义泛型
-
自定义泛型类
-
基本语法
class 类名<T,R……>{ 成员 }
-
实例
class A<T,R,M>{ String a1; T a2; R a3; M a4; }
-
使用细节
-
泛型的标识符可用多个。
-
使用泛型的数组不能初始化。
-
静态方法中不能使用类的泛型。
-
-
-
自定义泛型接口
-
基本语法
interface 接口名<T,R……>{ 成员 }
-
实例
interface A<U,R>{ R get(U u); void hi(R r); default R function(U u){ return null; } } interface B extends A<String,Integer>{ }//继承接口实现确定泛型 class C implements A<Integer,Short>{//实现接口确定泛型 @Override public Short get(Integer integer) { return null; } @Override public void hi(Short aShort) { } @Override public Short function(Integer integer) { return null; } }
-
使用细节
- 接口中,静态成员不能使用泛型。
- 泛型接口的类型,在继承接口或实现接口时确定。
-
-
自定义泛型方法
-
基本语法
修饰符 <T,R……> 返回类型 方法名(参数列表){}
-
实例
class A<M>{ public void run(){} public <T,R> void fly(T t,R r){ System.out.println(t+"----"+t.getClass()); System.out.println(r+"----"+r.getClass()); } } public static void main(String[] args) { A<String> a=new A<>(); a.fly(100,"Hello World"); }
-
使用细节
- 泛型方法可以定义在普通类中,也可以定义在泛型类中。
- 当泛型方法被调用时,其泛型类才被确定。
-
3. 泛型的继承与通配
-
泛型没有继承性
ArrayList<Object> arrayList=new ArrayList<String>();//错误
-
可使用通配符表示泛型
ArrayList<?> arrayList1=new ArrayList<>();//支持任意泛型类型 ArrayList<? extends A> arrayList2=new ArrayList<>();//支持A类及其子类 ArrayList<? super A> arrayList3=new ArrayList<>();//支持A类及其父类,不限定于直接父类
泛型的意义:
- 由于能将形参类型进行指定,因此增强了代码的健壮性、使代码更简洁。
- 可以在类声明是通过一个表识表示类中某个属性的类型、或是某个方法的返回值类型,后参数类型。
十一、多线程
1. 线程的基本使用
-
线程的创建方式
-
继承Thread类
-
语法规范
class 类名 extends Thread{ @Override//重写run()方法 public void run() {} }
-
实例
class A extends Thread{ @Override public void run() { while (true) { System.out.println("Hello World"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { new A().start(); }
-
-
实现Runnable接口
-
语法规范
class 类名 implements Runnable{ @Override//重写run()方法 public void run(){} }
-
实例
class A implements Runnable{ @Override public void run() { while (true) { System.out.println("Hello World"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Thread thread=new Thread(new A()); thread.start(); }
- 无论是继承Thread类还是实现Runnable接口,都需重写run()方法。
- 在启动线程时,必须调用start()方法,而不是直接调用run()方法。
- start()方法底层机制是,start()被调用后,start()再调用start()0,start()0是本地方法由JVM调用。可理解为start0创建线程后再调用了run()。
-
-
-
线程终止
-
自动退出:当线程认为完成后,自动退出。
-
通知方式终止线程:即是通过控制run()的运行进而控制线程的终止。如:
class A extends Thread{ private int count=0; private boolean loop=true; @Override public void run() { while (loop){ try { Thread.sleep(50); System.out.println("A is running"+" "+count); count++; } catch (InterruptedException e) { e.printStackTrace(); } } } public void setLoop(boolean loop) { this.loop = loop; } } public static void main(String[] args) { A a=new A(); a.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } a.setLoop(false); }
-
-
线程常用方法
-
setName():设置线程名
-
getName():获取线程名
-
start():线程开始执行,JVM底层调用该线程的start0方法
-
run():线程逻辑代码
-
setPriority():设置线程优先级,默认为5
-
getPriority():获取线程优先级
-
sleep():指定线程休眠时间
-
interrupt():中断线程
- 线程优先级范围为1-10,优先级逐渐升高
- interrupt并不会真正结束线程。因此一般用于中断正在休眠的线程,使线程结束休眠继续进行后续程序运行
-
2. 多线程机制
- 进程中可有多个线程
- 进程中起码包含一个主线程
- 线程可以再创建线程
- 当主线程结束,其它未结束线程仍可运行。
3. 线程调度
-
yield()
:线程礼让。线程让出CPU执行权,礼让时间不确定也不一定成功。class A extends Thread{ @Override public void run() { for (int i=0;i<20;i++){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getName()+" is running----------"+i); } } } public static void main(String[] args) throws Exception{ A a=new A(); a.start(); for (int i=0;i<20;i++){ Thread.sleep(500); System.out.println(Thread.currentThread().getName()+" is running----------"+i); if (i==5){ System.out.println("线程礼让"); Thread.yield(); System.out.println("礼让结束"); } } }
yield()执行成功与否取决于计算机资源是否紧张。
-
join()
:在某个线程前插入一个线程先执行,插入的线程执行完后才执行被插入的线程。class A extends Thread{ @Override public void run() { for (int i=0;i<20;i++){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getName()+" is running----------"+i); } } } public static void main(String[] args) throws Exception{ A a=new A(); a.start(); for (int i=0;i<20;i++){ Thread.sleep(500); System.out.println(Thread.currentThread().getName()+" is running----------"+i); if (i==5){ System.out.println("线程插队"); a.join(); System.out.println("插队结束"); } } }
-
线程守护
:- 用户线程:也称工作线程,当线程的认为执行完或通知方式结束。
- 守护线程:一般使为工作线程服务。当所有的用户线程结束,守护线程自动结束。常见的守护线程如:垃圾回收机制。
class A extends Thread{ @Override public void run() { int i=1; while (true){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getName()+" is running----------"+i); i++; } } } public static void main(String[] args) throws Exception{ A a=new A(); a.setDaemon(true);//将线程设置为守护线程 a.start();//先设置线程状态再启动线程 for (int i=0;i<10;i++){ Thread.sleep(500); System.out.println(Thread.currentThread().getName()+" is running----------"+i); } }
当用户线程结束,守护线程自动结束。
4. 线程同步机制
同步
:保证在某个时刻仅有一个线程访问临界资源。
-
synchronized
:-
同步代码块
语法规范
synchronized(对象){ 需要同步的代码 }
实例
class A implements Runnable{ public static int count=10; static Object o=new Object(); @Override public void run() { while (true) { synchronized (o) { int result = function(); if (result == 1) break; } } } public int function(){ if (count <= 0) { System.out.println("同步结束"); return 1; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" is running "+count--); return 0; } } public static void main(String[] args) { A a=new A(); Thread a1=new Thread(a); Thread a2=new Thread(a); Thread a3=new Thread(a); a1.start(); a2.start(); a3.start(); }
同步代码块实现同步时,需保证多个线程的对象锁是同一个对象,否则不会实现同步。
-
同步方法
语法规范
访问修饰符 synchronized 返回值类型 方法名(参数列表){}
实例
class A implements Runnable{ public static int count=10; @Override public synchronized void run() { while (true) { int result=function(); if (result==1) break; } } public int function(){ if (count <= 0) { System.out.println("同步结束"); return 1; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" is running "+count--); return 0; } } public static void main(String[] args) { A a=new A(); Thread a1=new Thread(a); Thread a2=new Thread(a); Thread a3=new Thread(a); a1.start(); a2.start(); a3.start(); }
-
十二、IO流
1. 文件操作
-
文件常用方法:
-
createNewFile():创建新文件。
-
delete():删除文件。
-
mkdir():创建目录。
-
getName():获取文件名字。
-
getAbsolutePath():获取文件绝对路径。
-
getParent():获取文件父级目录。
-
exists():判断文件是否存在。
-
isFile():判断是否是文件。
……
-
2. IO流的分类
-
流的分类
按操作数据单位分为:字节流(8bit),字符流
按数据流的流向分为:输入流,输出流
按流的角色分为:节点流,处理流
字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer Java中IO流所涉及的类,均是由以上四中类派生出来的。
-
IO流体系图
在使用IO流类时,使用完后需及时将流关闭。
3. 转换流
转换流
:即是InputStreamReader和OutputStreamWriter。可将字节流转为字符流。如:
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//3. 把 InputStreamReader 传入 BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(filePath), "gbk"));
String s = br.readLine();
System.out.println("读取内容=" + s);
br.close();
}
- 把 FileInputStream 转成 InputStreamReader
- 指定编码 gbk
- 把 InputStreamReader 传入 BufferedReader
public static void main(String[] args) throws IOException {
String filePath = "e:\\a.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("hi, hhh");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");
}
十三、反射
反射
:即是运行程序在运行时动态获取类的内部信息并对其对象进行动态操作。例如:
public class Cat {
private String name="tom";
public int age=10;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public void hi(){
System.out.println("hi"+" tom");
}
}
classfullpath=reflection.Cat
method=hi
public static void main(String[] args) throws Exception {
Properties properties=new Properties();
properties.load(new FileInputStream("D:\\IDEA破解\\JavaRenascence\\Renascence_demo01\\src\\reflection\\re.properties"));
System.out.println(classfullpath+" "+method);
Class a = Class.forName(classfullpath);//加载类,返回class类的对象
Object o = a.newInstance();
Method method1 = a.getMethod(method);
method1.invoke(o);
Field field = a.getField("age");
System.out.println(field.get(o));
Constructor constructor = a.getConstructor(String.class);
System.out.println(constructor);
}
1. 反射机制原理
反射机制流程如下:
-
Java源文件经过javac编译为class字节码文件。
-
字节码文件经过类加载器(ClassLoader),在堆内存中加载出对应的Class类对象。该对象中以数组的形式存放成员变量,成员方法等成员。
-
程序代码根据Class类对象执行对象创建语句。
-
在堆内存中创建对应对象。该对象与创建其的Class类对象相对应。
在反射中,把对象的所有成员(成员变量、成员方法、构造器等)均视为对象,从而进行调用。
反射机制能够完成的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
2. 反射机制中的主要类
-
Class
:代表一个类,Class对象标识某个类加载后在堆中的对象。例如:Class a = Class.forName(classfullpath);//加载类,返回class类的对象 Object o = a.newInstance();
-
Method
:代表类的方法。Method method1 = a.getMethod(method); method1.invoke(o);
-
Field
:代表类的成员变量。Field field = a.getField("age"); System.out.println(field.get(o));
getField()不能获取到对象的私有成员
-
Constructor
:代表类的构造器。Constructor constructor = a.getConstructor(String.class); System.out.println(constructor);
利用反射操作对象时,语法为:成员对象(方法、变量等)操作对象。
3. Class类
-
Class类常用方法
-
getClass():获取运行类型
-
getPackage():获取包
-
getName():获取全路径名
-
newInstance():创建新对象
-
getField():获取属性
……
public class Car { public String brand="宝马"; public int price=1248; public String color="red"; @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", price=" + price + ", color='" + color + '\'' + '}'; } }
public static void main(String[] args) throws Exception { String path="reflection.Car"; Class cls = Class.forName(path); System.out.println(cls); System.out.println(cls.getPackage().getName());//获取包名 System.out.println(cls.getName());//获取全类名 Car car = (Car) cls.newInstance();//通过cls创建对象实例 System.out.println(car); Field brand = cls.getField("brand");//通过cls获取属性 System.out.println(brand.get(car)); brand.set(car,"玛莎拉蒂");//通过反射堆属性赋值 System.out.println(brand.get(car)); }
-
-
Class类对象获取的方式
-
forName()
String path="reflection.Car"; Class cls1 = Class.forName(path);
多用于配置文件,读取类全路径,加载类
-
类名.class
Class cls2 = Car.class; System.out.println(cls2);
多用于参数传递,如通过反射获取对应的方法
-
对象.getClass()
Car car = new Car(); Class cls3 = car.getClass(); System.out.println(cls3);
用于已有实例对象获取Class对象
-
类加载器(4种)获取
String path="reflection.Car"; Car car = new Car(); ClassLoader classLoader = car.getClass().getClassLoader();//获取类加载器 Class cls4 = classLoader.loadClass(path); System.out.println(cls4);
-
-
Class类细节
-
Class类对象不是开发人员创建出来的,而是由系统创建的。
-
对于某个类的所有对象实例,均回指向创建该对象的Class类对象。
-
对于某个类的Class类对象,在内存中只有一份(因为类只加载一次),即是每个该类的实例对象均只指向同一个Class类对象。
-
Class类对象在堆内存中存放。
-
用于Class类对象的类型有:
-
外部类、成员内部类、静态内部类、局部内部类、匿名内部类
-
接口
-
数组
-
枚举
-
注解
-
基本数据类型
-
void
Class<String> stringClass = String.class;//外部类 Class<Serializable> serializableClass = Serializable.class;//接口 Class<Integer[]> aClass = Integer[].class;//数组 Class<Deprecated> deprecatedClass = Deprecated.class;//注解 Class<Thread.State> stateClass = Thread.State.class;//枚举 Class<Long> longClass = long.class;//基本数据类型 Class<Void> voidClass = void.class;//void Class<Class> classClass = Class.class;//Class
-
-
4. 类加载
静态加载
:编译时加载程序的相关类,依赖性强。
动态加载
:运行时加载代码需要的类,降低了依赖性。
-
类加载的时机
- 创建对象时(new)------------------------------静态加载
- 子类被加载时,父类同样也被加载------------------------------静态加载
- 调用类种的静态成员时------------------------------静态加载
- 通过反射------------------------------动态加载
-
类加载的过程
- 将Java源文件编译为字节码文件。
- 将字节码在运行时加载类。
- 加载阶段,将字节码从不同的数据源(class文件、jar包等)中加载到内存中,将类的class文件读入内存中并创建一个Class类对象。
- 链接,将类的二进制数据合并到JRE中
- 验证信息,对文件进行安全的校验(文件格式是否正确、元数据验证是否正确、字节码是否正确……)。可将验证关闭,以提高性能。
- 准备,对静态变量分配内存及默认初始化(0、null、false……)。
- 解析,JVM将常量池中的符号引用(变量引用)替换为直接引用(地址引用)。
- 初始化,根据代码中的内容对类进行初始化(主要为静态成员)
类加载后的内存分布
类加载时并不会受对象创建时的影响。
加载时,在堆中创建的Class类对象,即代表了实例对象的运行类型。
5. 通过反射对类操作
-
获取类结构信息的方法
-
getName():获取全类名。
-
getFields():获取类中所有public的属性,包括父类。
-
getDeclareFields():获取本类的所有属性。
-
getMethods():获取所有public方法,包括父类。
-
getDeclareMethods():获取本类所有方法。
-
getAnnotations():以Annotation[]形式返回注解信息。
……
-
-
通过反射创建对象
-
通过public的无参构造创建
Class cls=Class.forName("reflectino.Car"); Car car=(Car) cls.newInstance();
-
通过public的有参构造创建
Class cls=Class.forName("reflectino.Car"); Constructor<?> constructor=cls.getConstructor(String.class);//先获取对应构造器 Car car=(Car) constructor.newInstance("hello");
-
通过非public的有参构造创建
Class cls=Class.forName("reflectino.Car"); Constructor<?> constructor=cls.getDeclaredConstructor(String.class);//先获取对应构造器 constructor.setAccessible(true);//爆破,使用反射可以访问private成员。 Car car=(Car) constructor.newInstance("hello");
-
-
通过反射访问类中属性
Class cls=Class.forName("reflectino.Car"); Car car=(Car) cls.newInstance(); Field name=cls.getField("name");//获取name属性对象 name.set(car,"world");//设置属性 System.out.println(name.get(car));
-
通过反射访问类中方法
Class cls=Class.forName("reflectino.Car"); Car car=(Car) cls.newInstance(); Method name=cls.getMethod("f1",String.class);//获取f1方法对象 f1.setAccessible(true);//爆破,访问私有方法 f1.invoke(o,"Hello World");
在反射中,可以使用爆破(setAccessible(true))的方式对类的私有成员进行访问。
2022.9.18补充:Class类与普通类的关系类似与普通类与其对象的关系。普通类对其对象的抽象,对每个对象的所有组成部分(各个属性、方法)进行概括,以及对象创建、方法调用等相关操作。类似的Class类也是对普通类的抽象,对普通类的组成部分(类名、方法、属性等)进行概括,以及类的创建(运行时期)、属性获取等操作。
十四、网络编程
1. 网络基础
局域网
:覆盖范围最小的网络。
广域网
:覆盖范围最大的网络,万维网是最大的广域网。
-
IP地址
IP地址
:用于唯一标识网络中的每台计算机。-
IP地址的组成
IP地址=网络地址+主机地址
-
ipv4地址分类
类型 范围 A 0.0.0.0到127.255.255.255 B 128.0.0.0到191.255.255.255 C 192.0.0.0到223.255.255.255 D 224.0.0.0到239.255.255.255 E 240.0.0.0到247.255.255.255
-
-
域名及端口号
域名
:将ip地址映射为域名,从而便于记忆。端口号
:用于标识计算机中某一特定的网络程序。范围是065535,其中01024已经被占用了。 -
网络协议
- TCP协议(类似于打电话)
- 使用TCP协议前,须建立TCP连接,形成数据通道。
- 传输前,采用“三次握手”方式,因此是可靠的协议。
- TCP协议进行同学的两个应用进程:客户端、服务端。
- 连接中可进行大数据量的传输
- 传输完毕需释放已连接的资源,效率较低。
- UDP协议(类似于发邮件)
- 将数据、源、目的封装成数据包,不需要建立连接。
- 每个数据报的大小限制在64K内。
- 因无需连接,故是不可靠的协议。
- 发送数据结束后无需释放资源,速度快。
- TCP协议(类似于打电话)
2. 网络编程常用类
-
InetAddress
public static void main(String[] args) throws UnknownHostException { //1. 获取本机的InetAddress 对象 InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1 //2. 根据指定主机名 获取 InetAddress对象 InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S"); System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1 //3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应 InetAddress host2 = InetAddress.getByName("www.baidu.com"); System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4 //4. 通过 InetAddress 对象,获取对应的地址 String hostAddress = host2.getHostAddress();//IP 110.242.68.4 System.out.println("host2 对应的ip = " + hostAddress);//110.242.68.4 //5. 通过 InetAddress 对象,获取对应的主机名/或者的域名 String hostName = host2.getHostName(); System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com }
-
Socket
-
基于TCP的Socket网络通信
- 通信流程
-
实例
public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9999);//监听9999端口 Socket socket = serverSocket.accept();//阻塞中,等待连接 InputStream inputStream = socket.getInputStream();//获取输入流 byte[] buf=new byte[2048]; int readLen=0; while ((readLen = inputStream.read(buf)) != -1){//读取数据 System.out.println(new String(buf,0,readLen)); } inputStream.close();//关闭资源 socket.close(); serverSocket.close(); } }
public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 9999);//连接指定ip的端口 OutputStream outputStream = socket.getOutputStream();//获取输出流对象 outputStream.write("hello world".getBytes());//发送数据 outputStream.close();//关闭资源 socket.close(); } }
在Socket通信时 ,客户端与服务器端实际上时端口间的通信。客户端的Socket会随机占用一个端口与服务器端的指定端口进行通信。通信完成后自动释放该端口。
-
基于UDP的Socket网络通信
-
通信流程
在UDP通信中,客户端和服务器端二者混用,都可充当客户端和服务器端。
-
实例
public class Receiver { public static void main(String[] args) throws Exception { DatagramSocket socket = new DatagramSocket(9999);//指定端口接收数据 byte[] buf = new byte[1024];//准备接收数据 DatagramPacket packet = new DatagramPacket(buf, buf.length); System.out.println("接收端A 等待接收数据.."); socket.receive(packet);//阻塞等待中,接收数据 int length = packet.getLength();//拆包解析数据 byte[] data = packet.getData(); String s = new String(data, 0, length); System.out.println(s); data = "好的, 明天见".getBytes();//回复消息 packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 9998); socket.send(packet); socket.close();//关闭资源 } }
public class Send { public static void main(String[] args) throws Exception { //1.创建 DatagramSocket 对象,准备在9998端口 接收数据 DatagramSocket socket = new DatagramSocket(9998);//指定端口接收数据 byte[] data = "hello 明天吃火锅~".getBytes(); //将数据装包 DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 9999);//将数据包发送至指定ip的端口 socket.send(packet); byte[] buf = new byte[1024];//接收回复消息 packet = new DatagramPacket(buf, buf.length); int length = packet.getLength(); data = packet.getData(); String s = new String(data, 0, length); System.out.println(s); socket.close();//关闭资源 } }
-
-
Java补充内容
一、进制及进制转换
1. 各种进制
二进制:
0、1,满2进1,以0B或0b开头。如:0b1010。十进制:
0-9,满10进1。八进制:
0-7,满8进1,以数字0开头,如:01010。十六进制:
0-9及A(10)-F(15),满16进1,以0x或0X开头。如:0x10101。
public static void main(String[] args) {
int n1=0b1010;
int n2=1010;
int n3=01010;
int n4=0x10101;
System.out.println("二进制数n1="+n1);//10
System.out.println("十进制数n2="+n2);//1010
System.out.println("八进制数n3="+n3);//520
System.out.println("十六进制数n4="+n4);//65793
}
2. 进制转换
-
各进制转为十进制
- 二进制转十进制:自最低为(最右侧)开始,按位将该位的数乘2的(n-1)次方再相加。例如:
- 八进制转十进制:于二进制转十进制类似,只需将其权重改为8的幂次。例如:
- 十六进制转十进制:
-
十进制转为各进制
- 十进制转为二进制:将十进制数不断除2,直至商0为止,将每步所得余数倒置,即为对应的二进制数。例如:
- 十进制转为八进制:与十进制转为二进制规则基本相同,只需将除数变为8即可。
- 十进制转为十六进制:与十进制转为二进制规则基本相同,只需将除数变为16即可。
-
二进制转为八进制、十六进制
- 二进制转为八进制:由低位开始,将二进制数每三位为一组,将每组转为对应的八进制即可。例如:
- 二进制转十六进制:与二进制转为八进制类似,只需将二进制数每四位为一组,将每组转为对应的十六进制即可。例如:
-
八进制、十六进制转为二进制
- 八进制转为二进制:将八进制数的每一位,转为对应的一个三位的二进制数(位数不足是用0补充)即可。例如:
- 十六进制转为二进制:与八进制转二进制类似,只需将十六进制的每位数转为对应的四位二进制数即可。
二、原码、反码、补码及位运算
1. 原码、反码、补码
原码:
即是将十进制数字的绝对值转为二进制数字(8位),正数首位位0、负数首位为1.例如:
十进制 二进制
1 00000000 00000000 00000000 00000001
-1 10000000 00000000 00000000 00000001
反码、补码:
反码与补码只是在原码的基础上进行一定变换,便于计算机运算。变换规则有:
- 整数的原码、反码、补码均相同。
- 负数的反码=(原码符号位不变+其它为按位取反)。
- 负数的补码=(反码+1),负数的反码=(补码-1)。
- 0的反码、补码均是0。
例如:
原码 反码 补码
1 00000000 00000000 00000000 00000001 00000000 00000000 00000000 00000001 00000000 00000000 00000000 00000001
-1 10000000 00000000 00000000 00000001 11111111 11111111 11111111 11111110 11111111 11111111 11111111 11111111
附:
- Java中的数均是有符号位的。
- 计算机在进行运算时,均是以补码的形式进行运算。
- 计算机运算后的结果呈现均是以原码的形式。
2. 位运算符
位运算符:
包含&、|、~、^、>>、<<、>>>七种。位运算符的作用对象均为每一个bit位。
-
按位与(&):两位全为1,结果为1,否则为0。
System.out.println(2 & 3);//输出2
过程如下:
-
按位或(|):两位有一个为1,结果为1,否则为0。
System.out.println(2 | 3);//输出3
-
按位异或(^):两位一个为0一个为1,结果为1,否则为0。
System.out.println(2 ^ 3);//输出1
-
按位取反(~):0变1、1变0。
System.out.println(~-2);//输出1 System.out.println(~2);//输出-3
分析如下:
~-2
~2
在进行运算时,需注意补码所代表的正负性。根据其正负性分别获取其原码。
-
算术右移(>>):低位溢出,符号位不变,并用符号位补充溢出的高位。
System.out.println(1>>2);//输出0
分析如下:
-
算术左移(<<):符号位不变,低位补0。
System.out.println(-1<<2);//输出-4
分析如下:
-
逻辑右移(>>>):低位溢出,高位补0。
System.out.println(10>>>2);//输出2
三、递归思想
递归的本质是函数有限次嵌套调用自身函数的过程,用于解决差异性小的重复操作,使代码更简洁。
递归的三要素
-
递归内容:递归实际执行的重复操作。
-
递归结束条件:函数不断的调用自身只会需要有最终的结束条件,从而避免死循环。
-
递归执行条件:在进入递归函数后,再次进入调用该函数的条件。
例如:
public static void main(String[] args) { function1(5); } public static void function1(int num){ if (num==1||num==0){//递归结束条件 System.out.println("num="+num+", 递归结束"); }else {//递归执行条件 System.out.println("num="+num+", 递归继续执行");//递归内容 function1(num-1); } }
由于每次递归调用时,会在栈内存中开辟对应的方法栈。因此,当递归深度过高时,可能产生栈溢出的问题。
四、序列化及反序列化
序列化
:将数据结构或对象转换成二进制字节流的过程。
反序列化
:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程。
1. 序列化的原因及目的
原因
:当程序的数据结构和对象被存放在文件或通过网络传输到其它终端,则需要将程序原有的数据结构和对象重新恢复到原先状态。
目的
:按照一定的序列化规则,通过网络传输对象或将对象存储到文件系统、数据库、内存中。
2. Java中的序列化和反序列化
本质
:在Java中序列化的本质是将对象转变位二进制内容存放到byte[]数组中,再根据需求将byte[]保存到文件中、或通过网络传输到远程。
-
Serializable
Serializable
:Serializable是一个Java对象能进行序列化所必须实现的接口。该接口内没有定义任何方法,其作用仅仅是将实现该接口的类进行可序列化的标记。序列化:
public static void main(String[] args) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (ObjectOutputStream output = new ObjectOutputStream(buffer)) { output.writeInt(12345);// 写入int: output.writeUTF("Hello");// 写入String: output.writeObject(Double.valueOf(123.456));// 写入Object: } System.out.println(Arrays.toString(buffer.toByteArray())); }
反序列化:
public static void main(String[] args) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); try (ObjectInputStream input = new ObjectInputStream(...)) { int n = input.readInt(); String s = input.readUTF(); Double d = (Double) input.readObject(); } }
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
-
transient
transient
:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient 修饰的变量值不会被持久化和恢复。- 使用注意
- transient 只能修饰变量,不能修饰类和方法。
- transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。如int类型,序列化的结果为0。
- static变量因为不属于任何对象(Object),所以无论有没有transient 关键字修饰,均不会被序列化。
- 使用注意
3. 通用序列化方法
Java的序列化机制仅适用于Java,且存在安全性和兼容性问题。因此如需与其它语言进行数据交换,应采取通用的序列化方法。
-
通用的序列化方法
-
fastjson
-
JSON
-
jackjson
-
gson
……
-
五、正则表达式
1. 基本语法
String content = "私有地址(Private address)属于非注册地址,专门为组织机构内部使用。\n" +
"以下列出留用的内部私有地址\n" +
"A类 10.0.0.0--10.255.255.255\n" +
"B类 172.16.0.0--172.31.255.255\n" +
"C类 192.168.0.0--192.168.255.255";
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+\\.\\d+");//1. 先创建一个Pattern对象
Matcher matcher = pattern.matcher(content);//2. 创建一个匹配器对象
while (matcher.find()) {//3. 可以开始循环匹配
System.out.println(matcher.group(0));
}
-
元字符
元字符
:按功能大致分为限定符、选择匹配符、分组组合和反向引用符、特殊字符、字符匹配符、定位符。-
转义号(\\):在使用正则表达式检索特殊字符时,需使用转义符来进行转义。
public static void main(String[] args) { String content = "abc$(a.bc(123( )"; String regStr = "\\d{3}"; Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println(matcher.group(0)); } }
需要转义的符号有:‘.’、‘*’、‘+’、‘()’、‘$’、‘’、‘/’、‘?’、‘[]’、‘^’、‘{}’
-
字符匹配符
public static void main(String[] args) { String content = "a11c8abc _ABCy @"; String regStr = "[a-z]";//匹配 a-z之间任意一个字符 Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content) while (matcher.find()) { System.out.println("找到 " + matcher.group(0)); } }
符号 含义 示例 作用 [] 可接收的字符列表 [abcd] 可接受a、b、c、d中任意一个字符 [^] 不可接收的字符列表 [^abcd] 可接受除a、b、c、d之外的任意字符 - 连字符 A-Z 任意一个大写字母 -
选择匹配符(|):匹配某个字符串是是选择性的。
public static void main(String[] args) { String content = "hanshunping 韩 寒冷"; String regStr = "han|韩|寒"; Pattern pattern = Pattern.compile(regStr/*, Pattern.CASE_INSENSITIVE*/); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println( matcher.group(0)); } }
-
限定符:用于获取指定字符或组合练习出现次数
符号 含义 示例 作用 匹配输入 * 指定字符重复0次或n次 (abc)* 仅包含任意个abc的字符串 abc、abcabc + 指定字符重复1次或n次 m+(abc)* 以至少1个m开头,后接任意个abc的字符串 m、mabc ? 指定字符重复0次或1次 m+abc? 以至少1个m开头,后接任意个abc或ab的字符串 mab、mmabc {n} 只能输入n个字符 [abcd]{3} 由abcd中字母组成的任意长度为3 的字符串 abc、dbc {n,} 指定至少n个匹配 [abcd]{3,} 由abcd中字母组成的任意长度不小于3的字符串 aab、dbc {n,m} 指定至少n个但不多于m个匹配 [abcd]{3,5} 由ABC的中字母组成的任意长度不小于3,不大于5的字符串 abc、abcd -
定位符:用于规定匹配字符串的位置
符号 含义 示例 作用 匹配输入 ^ 指定起始字符 1+[a-z]* 以至少1个数字开头,后接任意个小写字母的字符串 123、6aa $ 指定结束字符 2\\-[a-z]+$ 以1个数字开头后连接字符“-”,并以至少1个小写字母结尾的字符串 1-a \\b 匹配目标字符串的边界 han\\b 边界指的是子串间由空格,或是目标字符串结束位置 hanshunpingsphan nnhan \\B 匹配目标字符串的非边界 han\\B 与\b含义相反 hanshunpingsphan nnhan
-
2. 分组
-
捕获分组
-
非命名捕获
:按照一定规则对匹配的字符串进行分组,对于匹配到的结果按顺序从1开始编号。其语法为**(pattern)**,例如:public static void main(String[] args) { String content = "hanshunping s7789 nn1189han"; String regStr = "(\\d\\d)(\\d\\d)";//匹配4个数字的字符串并分组 Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println(matcher.group(0)); System.out.println("第1个分组内容=" + matcher.group(1)); System.out.println("第2个分组内容=" + matcher.group(2)); } }
-
命名捕获
:按照一定规则对匹配的字符串进行分组,对于捕获的结果自定义分组中每个组的名字。其语法为**(?<name>pattern)**,例如:public static void main(String[] args) { String content = "hanshunping s7789 nn1189han"; String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";//匹配4个数字的字符串,并对分组进行命名g1、g2 Pattern pattern = Pattern.compile(regStr); Matcher matcher = pattern.matcher(content); while (matcher.find()) { System.out.println(matcher.group(0)); System.out.println("第1个分组内容[通过组名]=" + matcher.group("g1")); System.out.println("第2个分组内容[通过组名]=" + matcher.group("g2")); } }
在进行命名是,名字不能包含任何标点符号,且不能以数字开头。
-
-
非捕获分组
- (?:pattern):
- (?=pattern):
- (?!pattern):