Java基础知识复习&梳理
一、基础知识
1、Java特点
-
简单
-
面向对象(类、接口、继承等)
-
分布式
-
健壮的(强类型机制、异常处理、垃圾的自动收集等)
-
安全的
-
体系结构中立的
-
可移植的
-
解释型
-
高性能
-
多线程
-
动态的(适应于动态变化的环境)
2、环境搭建
①Windows上安装开发环境
下载JDK
下载链接🔗:https://www.oracle.com/java/technologies/downloads/
安装JDK的时候也会安装JRE,一并安装就ok
配置环境变量
添加系统变量JAVA_HOME(jdk安装路径)、Path(%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;)、CLASSPATH(.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; )
注意:如果使用 1.5 以上版本的 JDK,可以不用设置 CLASSPATH 环境变量;如果设置记得路径前面有个"."
测试JDK是否安装成功
cmd中键入命令:java -version、java、javac
②Linux上安装开发环境
环境变量PATH应该设定为指向Java二进制文件安装的位置。如果设置遇到困难,请参考shell文档。
例如,假设你使用bash作为shell,你可以把下面的内容添加到你的 .bashrc文件结尾: export PATH=/path/to/java:$PATH
二、Java基础语法
1、数据类型
①内置数据类型
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | '\u0000' |
String (or any object) | null |
boolean | false |
②引用数据类型
- 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是null。
③Java常量
用final修饰常量,声明方式和变量类似:
final double PI = 3.1415927;
PS:通常使用大写字母表示常量
- 当使用字面量的时候,前缀 0 表示 8 进制,而前缀 0x 代表 16 进制
④类型转换
低 ------------------------------------> 高
🔺byte,short,char—> int —> long—> float —> double
-
不能对boolean类型进行类型转换。
-
不能把对象类型转换成不相关类的对象。
-
在把精度大的类型转换为精度小的类型时必须使用强制类型转换。
-
转换过程中可能导致溢出或损失精度。
-
浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入。
2、流程控制
=各语句执行顺序的语句
基本的流程结构:顺序结构、分支结构(选择结构)、循环结构
-
注释:- 单行注释://开头,至该行行尾
- 多行注释:/*开头,*/结束
- 文件注释:/**开头,*/结束——用于描述类,数据和方法
三、数组
1、声明数组变量
dataType[] arrayRefVar; // 首选的方法
或
dataType arrayRefVar[]; // 效果相同,但不是首选方法
2、创建数组
arrayRefVar = new dataType[arraySize];
上面的语法语句做了两件事:
①使用dataType[arraySize]创建了一个数组
②把新创建的数组的引用赋值给变量arrayRefVar
另外,还可以使用以下的方式创建数组:
dataType[] arrayRefVar = {value0, value1, ..., valuek};
索引:从0到arrayRefVar.length-1
3、数组的For-Each循环
for(type element: array)
{
System.out.println(element);
}
4、多维数组
多维数组的动态初始化:
- 直接为每一维分配空间:
type[][] typeName = new type[typeLength1][typeLength2];
- 从最高维开始,分别为每一维分配空间
String[][] s = new String[2][];
s[0] = new String[2];
s[1] = new String[3];
s[0][0] = new String("Good");
s[0][1] = new String("Luck");
s[1][0] = new String("to");
s[1][1] = new String("you");
s[1][2] = new String("!");
5、Arrays类
java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
具有以下功能:
- 给数组赋值:通过 fill 方法。
- 对数组排序:通过 sort 方法,按[升序][]。
- 比较数组:通过 equals 方法比较数组中元素值是否相等。
- 查找数组元素:通过 binarySearch 方法能对[排序好的数组][]进行二分查找法操作。
序号 | 方法和说明 |
---|---|
1 | public static int binarySearch(Object[] a, Object key) 用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。 |
2 | public static boolean equals(long[] a, long[] a2) 如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
3 | public static void fill(int[] a, int val) 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
4 | public static void sort(Object[] a) 对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。 |
四、面向对象
1、方法
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
主方法:
public static void main(String[] args) {
...
}
构造方法:
- 与所在的类名字相同
- 构造方法没有返回值
- 用来初始化对象
- 无论是否自定义构造方法,所有的类都有构造方法(默认构造方法的访问修饰符和类的访问修饰符相同)
可变参数:
typeName... parameterName
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。
2、继承
class 父类 {
}
class 子类 extends 父类 {
}
3、重载
方法的重载:指一个类的两个方法拥有相同的名字但是有不同的参数列表
- 被重载的方法必须改变参数列表(参数个数或类型不一样)
- 被重载的方法可以改变返回类型
- 被重载的方法可以改变访问修饰符
- 被重载的方法可以声明新的或更广的检查异常
- 方法能够在同一个类中或者在一个子类中被重载
- 无法以返回值类型作为重载函数的区分标准
4、重写
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
- 参数列表与被重写方法的参数列表必须完全相同
- 返回类型与被重写方法的返回类型可以不同,但必须是父类返回类型的派生类(比如,子类)
- 访问权限不能比父类中被重写的访问权限更低
- 父类的成员方法只能被它的子类重写
- 声明为final的方法不能被重写
- 声明为 static 的方法不能被重写,但是能够被再次声明
- 子类和父类在同一个包中,那么子类可以重写父类的所有方法,除了声明为private和final的方法
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以
- 构造方法不能被重写
- 如果不能继承一个类,则不能重写该类的方法
· super关键字
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}
public class TestDog{
public static void main(String args[]){
Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法
}
}
5、多态
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现。
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
- 继承
- 重写
- 父类引用指向子类对象:Parent p=new Child();
· 虚函数
虚函数的存在就是为了多态。
Java中纯虚函数的形式:abstract void print();
重写与重载的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
6、封装
实现Java封装的步骤
-
修改属性的可见性来限制对属性的访问(一般限制为private),例如:
public class Person { private String name; private int age; }
// 这段代码中,name和age属性的访问权限都为private,只有本类才能访问,其他类访问不了,实现了信息的隐藏。
-
为每个值属性创建赋取值方法,实现对私有属性的访问,例如:
public class Person{ private String name; private int age; public int getAge(){ //对age取值 return age; } public String getName(){ //对name取值 return name; } public void setAge(int age){ //对age赋值 this.age = age; } public void setName(String name){ //对name赋值 this.name = name; } }
this关键字解决实例变量和局部变量之间的冲突。
五、抽象类+抽象方法
抽象类
-
抽象类必须被继承才能被使用
-
抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
-
一个类只能继承一个抽象类,而一个类却可以实现多个接口
-
定义抽象类:abstract class
-
抽象类中不一定包含抽象方法
抽象方法
- 抽象方法只包含一个方法名,而没有方法体
- 抽象方法的方法名后面直接跟一个分号,而不是花括号
- 如果一个类包含抽象方法,那么该类必须是抽象类
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类
- 构造方法、类方法(用static修饰的方法)不能声明为抽象方法
六、接口
声明格式:
interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
-
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法
-
接口无法被实例化
-
接口类型可用来声明一个变量-->成为一个空指针,或被绑定在一个以此接口实现的对象
-
△接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字
-
△接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字
-
接口中的方法都是公有的
接口与类的相似点:
- 一个接口可以有多个方法
- 接口文件保存在.java文件中,文件名使用接口名;接口的字节码文件保存在.class文件中
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中
接口与类的区别:
-
接口不能用于实例化对象
-
接口没有构造方法
-
接口中的所有方法必须是抽象方法,会被隐式指定为[public abstract][]
-
接口不能包含成员变量,除了static和final变量
-
接口中的变量会被隐式指定为[public static final][]
-
接口不是被类继承了,而是要被类实现
-
接口支持多继承
抽象类与接口的区别:
- 抽象类中的方法可以有方法体,但接口中的方法不行
- 接口中的成员变量只能是public static final类型
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口
接口的实现:
...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...
-
当类实现接口时,类要实现接口的所有方法,否则类必须是抽象的类
-
一个类只能继承一个类,但是能实现多个接口
-
一个接口能继承另一个接口:[extends][](允许多继承)
标记接口:没有任何方法的接口(能使实现接口的类通过多态性变成一个接口类型)
七、枚举
枚举类使用 [enum][] 关键字来定义,各个常量使用逗号 [ , ][] 来分割
实例:
enum Color
{
RED, GREEN, BLUE;
}
public class Test
{
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
- 内部类中使用枚举
public class Test
{
enum Color
{
RED, GREEN, BLUE;
}
// 执行输出结果
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
}
}
每个枚举都是通过 Class 在内部实现的,且所有的枚举值都是 **public static final **的。
以上的枚举类Color转化在内部类实现:
class Color
{
public static final Color RED = new Color();
public static final Color BLUE = new Color();
public static final Color GREEN = new Color();
}
- 迭代枚举元素
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
for (Color myVar : Color.values()) { //迭代枚举元素
System.out.println(myVar);
}
}
}
- 在switch中使用枚举类
enum Color
{
RED, GREEN, BLUE;
}
public class MyClass {
public static void main(String[] args) {
Color myVar = Color.BLUE;
switch(myVar) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
- 枚举类默认继承的方法
- **values() **返回枚举类中所有的值(返回值为对象数组)。
- ordinal()方法可以找到每个枚举常量的索引,就像数组索引一样。
- valueOf()方法返回指定字符串值的枚举常量。
枚举类成员
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
实例:
enum color{
red{
public void display() {
System.out.println("red------"+"红色");
}
},
blue{
public void display() {
System.out.println("blue------"+"蓝色");
}
},
whilt{
public void display() {
System.out.println("whilt------"+"白色");
}
},
blank{
public void display() {
System.out.println("blank------"+"黑色");
}
},
pink{
public void display() {
System.out.println("pink------"+"粉红色");
}
};
private color() {
System.out.println("独属于color的构造方法!");
}
public void checkInfo() {
System.out.println("该枚举类的类型为:"+color.class);
}
public abstract void display();
}
public class Enum {
public static void main(String[] args) {
color test=color.blue; //注意构造方法的调用
test.checkInfo();
test.display();
System.out.println("遍历枚举的值");
for (color temp : color.values()) {
temp.display();
}
}
}
输出:
独属于color的构造方法!
独属于color的构造方法!
独属于color的构造方法!
独属于color的构造方法!
独属于color的构造方法!
该枚举类的类型为:class color
blue------蓝色
遍历枚举的值
red------红色
blue------蓝色
whilt------白色
blank------黑色
pink------粉红色
八、常用类
1、String类
- 创建字符串
-
最简单的方式:
String str = "Runoob";
-
用构造方法创建字符串:
String str2 = new String("Runoob");
- String创建的字符串存储在公共池中,而new创建的字符串对象在堆上
String s1 = "Runoob"; // String 直接创建 String s2 = "Runoob"; // String 直接创建 String s3 = s1; // 相同引用 String s4 = new String("Runoob"); // String 对象创建 String s5 = new String("Runoob"); // String 对象创建
-
提供一个字符数组参数来初始化字符串:
public class StringDemo{
public static void main(String args[]){
char[] helloArray = { 'r', 'u', 'n', 'o', 'o', 'b'};
String helloString = new String(helloArray);
System.out.println( helloString );
}
}
- 创建格式化字符串:
我们知道输出格式化数字可以使用 printf() 和 format() 方法。
String 类使用静态方法 format() 返回一个String 对象而不是 PrintStream 对象。
String 类的静态方法 format() 能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。
实例:
System.out.printf("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
"is %s", floatVar, intVar, stringVar);
或
String fs;
fs = String.format("浮点型变量的值为 " +
"%f, 整型变量的值为 " +
" %d, 字符串变量的值为 " +
" %s", floatVar, intVar, stringVar);
- String方法
SN(序号) | 方法描述 |
---|---|
1 | char charAt(int index) 返回指定索引处的 char 值。 |
2 | int compareTo(Object o) 把这个字符串和另一个对象比较。 |
3 | int compareTo(String anotherString) 按字典顺序比较两个字符串。 |
4 | int compareToIgnoreCase(String str) 按字典顺序比较两个字符串,不考虑大小写。 |
5 | String concat(String str) 将指定字符串连接到此字符串的结尾。 |
6 | boolean contentEquals(StringBuffer sb) 当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。 |
7 | [static String copyValueOf(char] data) 返回指定数组中表示该字符序列的 String。 |
8 | [static String copyValueOf(char] data, int offset, int count) 返回指定数组中表示该字符序列的 String。 |
9 | boolean endsWith(String suffix) 测试此字符串是否以指定的后缀结束。 |
10 | boolean equals(Object anObject) 将此字符串与指定的对象比较。 |
11 | boolean equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较,不考虑大小写。 |
12 | [byte] getBytes() 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 |
13 | [byte] getBytes(String charsetName) 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 |
14 | [void getChars(int srcBegin, int srcEnd, char] dst, int dstBegin) 将字符从此字符串复制到目标字符数组。 |
15 | int hashCode() 返回此字符串的哈希码。 |
16 | int indexOf(int ch) 返回指定字符在此字符串中第一次出现处的索引。 |
17 | int indexOf(int ch, int fromIndex) 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索。 |
18 | int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。 |
19 | int indexOf(String str, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。 |
20 | String intern() 返回字符串对象的规范化表示形式。 |
21 | int lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。 |
22 | int lastIndexOf(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 |
23 | int lastIndexOf(String str) 返回指定子字符串在此字符串中最右边出现处的索引。 |
24 | int lastIndexOf(String str, int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 |
25 | int length() 返回此字符串的长度。 |
26 | boolean matches(String regex) 告知此字符串是否匹配给定的正则表达式。 |
27 | boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等。 |
28 | boolean regionMatches(int toffset, String other, int ooffset, int len) 测试两个字符串区域是否相等。 |
29 | String replace(char oldChar, char newChar) 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 |
30 | String replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 |
31 | String replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 |
32 | [String] split(String regex) 根据给定正则表达式的匹配拆分此字符串。 |
33 | [String] split(String regex, int limit) 根据匹配给定的正则表达式来拆分此字符串。 |
34 | boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始。 |
35 | boolean startsWith(String prefix, int toffset) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。 |
36 | CharSequence subSequence(int beginIndex, int endIndex) 返回一个新的字符序列,它是此序列的一个子序列。 |
37 | String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。 |
38 | String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。 |
39 | [char] toCharArray() 将此字符串转换为一个新的字符数组。 |
40 | String toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 |
41 | String toLowerCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。 |
42 | String toString() 返回此对象本身(它已经是一个字符串!)。 |
43 | String toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 |
44 | String toUpperCase(Locale locale) 使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。 |
45 | String trim() 返回字符串的副本,忽略前导空白和尾部空白。 |
46 | static String valueOf(primitive data type x) 返回给定data type类型x参数的字符串表示形式。 |
47 | contains(CharSequence chars) 判断是否包含指定的字符系列。 |
48 | isEmpty() 判断字符串是否为空。 |
2、日期时间
①Date类
java.util 包提供了 Date 类来封装当前的日期和时间。 Date 类提供两个构造函数来实例化 Date 对象。
第一个构造函数:使用当前日期和时间来初始化对象
Date()
第二个构造函数:接收一个参数,该参数是从1970.1.1起的毫秒数
Date(long millisec)
Date对象的方法:
序号 | 方法和描述 |
---|---|
1 | boolean after(Date date) 若当调用此方法的Date对象在指定日期之后返回true,否则返回false。 |
2 | boolean before(Date date) 若当调用此方法的Date对象在指定日期之前返回true,否则返回false。 |
3 | Object clone( ) 返回此对象的副本。 |
4 | int compareTo(Date date) 比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。 |
5 | int compareTo(Object obj) 若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。 |
6 | boolean equals(Object date) 当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。 |
7 | long getTime( ) 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。 |
8 | int hashCode( ) 返回此对象的哈希码值。 |
9 | void setTime(long time) 用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。 |
10 | String toString( ) 把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。 |
- SimpleDateFormat类格式化日期
SimpleDateFormat 允许你选择任何用户自定义日期时间格式来运行。
实例:
import java.util.*;
import java.text.*;
public class DateDemo {
public static void main(String[] args) {
Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(dNow));
}
}
输出:
当前时间为: 2018-09-06 10:16:34
- 日期和时间的格式化编码
字母 | 描述 | 示例 |
---|---|---|
G | 纪元标记 | AD |
y | 四位年份 | 2001 |
M | 月份 | July or 07 |
d | 一个月的日期 | 10 |
h | A.M./P.M. (1~12)格式小时 | 12 |
H | 一天中的小时 (0~23) | 22 |
m | 分钟数 | 30 |
s | 秒数 | 55 |
S | 毫秒数 | 234 |
E | 星期几 | Tuesday |
D | 一年中的日子 | 360 |
F | 一个月中第几周的周几 | 2 (second Wed. in July) |
w | 一年中第几周 | 40 |
W | 一个月中第几周 | 1 |
a | A.M./P.M. 标记 | PM |
k | 一天中的小时(1~24) | 24 |
K | A.M./P.M. (0~11)格式小时 | 10 |
z | 时区 | Eastern Standard Time |
' | 文字定界符 | Delimiter |
" | 单引号 | ` |
- Java休眠(sleep):
Thread.sleep(1000*3); //休眠3秒
②Calendar类
- 创建一个代表系统当前日期的Calendar对象
Calendar c = Calendar.getInstance(); //默认是当前日期
- 创建一个指定日期的Calendar对象
//创建一个代表2009年6月12日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2009, 6 - 1, 12);
- Calendar类对象字段类型
常量 | 描述 |
---|---|
Calendar.YEAR | 年份 |
Calendar.MONTH | 月份 |
Calendar.DATE | 日期 |
Calendar.DAY_OF_MONTH | 日期,和上面的字段意义完全相同 |
Calendar.HOUR | 12小时制的小时 |
Calendar.HOUR_OF_DAY | 24小时制的小时 |
Calendar.MINUTE | 分钟 |
Calendar.SECOND | 秒 |
Calendar.DAY_OF_WEEK | 星期几 |
- 用set()方法只设定某个字段:
public void set(int field,int value)
比如,只设置对象c1的年份为2008年,其他的所有数值会被重新计算:
c1.set(Calendar.YEAR,2008);
- add方法
比如,把c1对象的日期减去10,也就是c1表示为10天前的日期,其它所有的数值会被重新计算:
c1.add(Calendar.DATE, -10);
- Calendar类对象信息的获得(get):
// 获得年份
int year = c1.get(Calendar.YEAR);
// 获得月份
int month = c1.get(Calendar.MONTH) + 1;
九、集合类
-
集合可以动态保存多个对象
-
有一系列操作对象的方法:add、remove、set、get等
-
添加、删除元素比较方便
-
集合接口
序号 | 接口描述 |
---|---|
1 | Collection接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。 |
2 | List接口 List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。 |
4 | SortedSet 继承于Set保存有序的集合。 |
5 | Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。 |
6 | Map.Entry 描述在一个Map中的一个元素(键/值对)。是一个 Map 的内部接口。 |
7 | SortedMap 继承于 Map,使 Key 保持在升序排列。 |
8 | Enumeration 这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代 |
- 一些标准集合类:
序号 | 类描述 |
---|---|
1 | AbstractCollection 实现了大部分的集合接口。 |
2 | AbstractList 继承于AbstractCollection 并且实现了大部分List接口。 |
3 | AbstractSequentialList 继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问。 |
4 | LinkedList 该类实现了List接口,允许有null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个List,则必须自己实现访问同步,解决方法就是在创建List时候构造一个同步的List。例如:List list=Collections.synchronizedList(newLinkedList(...)); LinkedList 查找效率低。 |
5 | ArrayList 该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的,在多线程的情况下不要使用。ArrayList 增长当前长度的50%,插入删除效率低。 |
6 | AbstractSet 继承于AbstractCollection 并且实现了大部分Set接口。 |
7 | HashSet 该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。 |
8 | LinkedHashSet 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。 |
9 | TreeSet 该类实现了Set接口,可以实现排序等功能。 |
10 | AbstractMap 实现了大部分的Map接口。 |
11 | HashMap HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。 |
12 | TreeMap 继承了AbstractMap,并且使用一颗树。 |
13 | WeakHashMap 继承AbstractMap类,使用弱密钥的哈希表。 |
14 | LinkedHashMap 继承于HashMap,使用元素的自然顺序对元素进行排序. |
15 | IdentityHashMap 继承AbstractMap类,比较文档时使用引用相等。 |
1、集合框架体系
(背下来!!!
2、Collection(单列集合)
-
Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
①Collection实现子类可以存放多个元素,每个元素可以是Object
②有些可以存放重复的元素,有些不可以
③List是有序的,Set是无序的
④Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
- Collection接口遍历元素
①使用Iterator(迭代器)
Iterator iterator = coll.iterator();//得到一个集合的迭代器 while(iterator.hasNext){ //hasNext():判断是否还有下一个元素 Object next = iterator.next(); //next():指针下移,并返回下移后集合位置上的元素(返回Object对象)(必须先调用hasNext()) } //以上的while循环代码可以用快捷键“itit+回车”生成 //显示所有快捷键的快捷键:ctrl+j
迭代完迭代器指向最后一个元素,如果希望再次迭代,需要重置迭代器
iterator = coll.iterator();
②for循环增强
只能用于遍历集合或数组,底层也是Iterator
for(元素类型 元素名 : 集合名或数组名){ 访问元素 } //快捷键:I
- List
- 元素有序
- 有索引(从0开始)
- 三种遍历:Iterator、增强for、普通for
(1)ArrayList
-
可以加入所有的元素,包括null
-
是由数组来实现数据存储的
-
基本等同于Vector,除了ArrayList是线程不安全的(没有synchronized)(执行效率高),在多线程情况下,不建议使用ArrayList
-
ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData; //transient短暂的,瞬间的,表示该属性不会被序列化
当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第1次添加,则扩容elementData为10(如果是指定大小的构造器,则初始值为指定的大小);如需要再次扩容,则扩容elementData为1.5倍
(2)Vector
-
Vector底层也是一个对象数组
protected Object[] elementData;
-
Vector是线程同步的,即线程安全的,Vector类的操作方法带有synchronized
-
Vector的扩容倍数:如果是无参,默认是10,满后,按2倍扩容;如果指定大小,则每次直接按2倍扩容
(3)LinkedList
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任何元素(元素可以重复),包括null
- 线程不安全,没有实现同步
// 如何选择ArrayList和LinkedList:(均为线程不安全的)
-
如果改查的操作多,选择ArrayList(大部分情况下会选择ArrayList)
-
如果增删的操作多,选择LinkedList
- Set
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,最多只能包含一个null
- 取出的顺序虽然和添加的顺序不一致,但是是固定的
- 遍历方式:①迭代器 ②增强for ③不能通过索引遍历
(1)HashSet
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值-->转化成索引值
- 找到存储数据表table,看这个索引位置是否已经存放有元素
- 如果没有,直接加入;如果有,调用 equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD (默认是8),并且 table的大小>=MIN_TREEIFY_CAPACITY (默认64),就会进行树化(红黑树)
- HashSet扩容机制
第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)=16*0.75=12
如果table数组使用到了临界值12,就会扩容到16*2=32,新的临界值就是32*0.75=24,一次类推
在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD (默认是8),并且 table的大小>=MIN_TREEIFY_CAPACITY (默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
(2)LinkedHashSet
- LinkedHashSet是HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的主次,这使得元素看起来是以插入顺序保存的(取出的顺序和放入的顺序一样)
- 不允许添重复元素
- 添加第一次时,直接将数组table扩容到16,存放结点的类型是LinkedHashMap
Node[])
(3)TreeSet
-
底层是TreeMap
-
特点:可以排序。当使用无参构造器创建时,仍然是无序的;使用有参构造器,传入比较器(匿名内部类)并指定排序规则
(构造器把传入的比较器对象,赋给了TreeSet底层的TreeMap的属性this.comparator
TreeSet treeSet = new TreeSet(new Comparator(){ @Override //按首字母排序 public int compare(Object o1, Object o2){ return ((String)o1.compareTo((String)o2)); } }); treeSet.add("jack"); treeSet.add("mike"); treeSet.add("tom"); treeSet.add("amy");
3、Map(双列集合)
- Map接口实现类的特点(JDK8)
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
- Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复(当有相同的key时,就等价于替换),原因和HashSet一样;但value可以重复
- Map的key和value都可以为null,但key只能有一个null,value可以有多个null
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
- Map存放数据的key-value是存放在一个HashMap$Node中的,又因为Node实现了Entry接口,所以也可以说一对k-v就是一个Entry
- Entry接口中,key放在一个Set中,value放在一个Collection中
- HashMap$Node implements Map.Entry
- Map接口常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键获得值
- size:获取元素个数
- isEmpty:判断个数是否为0
- clear:清除
- containsKey:查找键是否存在
- Map接口的遍历
先取出所有的Key,通过Key取出对应的value
Map map = new HashMap(); Set keyset = map.keySet(); // (1)增强for for(Object key : keyset){ System.out.println(key + "-" + map.get(key)); } // (2)迭代器 Iterator iterator = keyset.iterator(); while(iterator.hasNext()){ Object key = iterator.next(); System.out.println(key + "-" + map.get(key)); }
把所有的values取出
Collection values = map.values(); // 这里可以使用所有的Collections使用的方法 // (1)增强for // (2)迭代器 // (3)不能使用普通for
通过EntrySet来获取k-v
Set entrySet = map.entrySet(); // (1)增强for for(Object entry : entrySet){ //将entry转成Map.Entry Map.Entry m = (Map.Entry)entry; System.out.println(m.getKet()+"-"+m.getValue()) } // (2)迭代器 Iterator iterator = entrySet.iterator(); while(iterator.hasNext()){ Object next = iterator.next(); //next.getClass()为HashMap$code System.out.println() }
-
HashMap
- 最常用的实现类
- 没有实现同步,因此是线程不安全的
- 扩容机制和HashSet一样
-
HashTable
- key和value都不能为null,否则会抛出NullPointerException
- HashTable是线程安全的,HashMap是线程不安全的
- 底层有数组HashTable$Entry[] 初始化大小为11
- HashTable扩容机制
-
LinkedHashMap
-
TreeMap
-
Properties
- 继承自Hashtable类并且实现了Map接口(k-v)
- Properties还可以用于从 xxx.properties 文件中,加载数据到Properties类对象,并进行读取和修改(xxx.properties文件通常作为配置文件)
- key和value都不能为null
- 如果有相同的key,value会被替换
4、Collections工具类
- 介绍
1)Collections是一个操作Set、List和Map等集合的工具类
2)Collections中提供了一系列静态方法对集合元素进行排序、查询和修改等操作
- 排序操作(均为static方法)
1)reverse(List):反转List中元素的顺序
2)shuffle(List):对List集合元素进行随机排序
3)sort(List):根据元素的自然顺序对指定List集合元素按升序排序
4)sort(LIst, Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序
5)swap(List, int, int):将指定list集合中的 i 处元素和 j 处元素进行交换
- 查找、替换
1)Object max(Collection)
2)Object max(Collection, Comparator)
3)Object min(Collection)
4)Object min(Collection, Comparator)
5)int frequency(Collection, Object):返回指定集合中指定元素的出现次数
6)void copy(List dest, List src):将src中的内容复制到dest中
7)boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
5、*红黑树
6、集合选择:
(集合家庭作业4和集合家庭作业5
十、泛型
泛型标记符:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的 java 类型
实例:
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element ); //注意是%s
}
System.out.println();
}
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。
// 比较三个值并返回最大值
public static <T extends Comparable<T>> T maximum(T x, T y, T z) //只接受Comparable类的子类
{
T max = x; // 假设x是初始最大值
if ( y.compareTo( max ) > 0 ){
max = y; //y 更大
}
if ( z.compareTo( max ) > 0 ){
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
- 泛型类
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("菜鸟教程"));
System.out.printf("整型值为 :%d\n\n", integerBox.get());
System.out.printf("字符串为 :%s\n", stringBox.get());
}
}
十一、注解
1、注解的定义
注解通过 @interface 关键字进行定义。
public @interface TestAnnotation {
}
2、元注解
元注解是修饰注解的注解。
@Retention:解释说明了注解的存活时间
- [RetentionPolicy.SOURCE][] 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- [RetentionPolicy.CLASS][] 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- [RetentionPolicy.RUNTIME][] 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
//指定了 TestAnnotation 可以在程序运行周期被获取到
@Documented:将注解中的元素包含到 Javadoc 中去
@Target:指定了注解运用的地方
- ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给一个包进行注解
- ElementType.PARAMETER 可以给一个方法内的参数进行注解
- ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Inherited:如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
//注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解
@Repeatable:注解的值可以同时取多个
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
//@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解(容器注解=用来存放其他注解的地方,本身也是个注解)
3、注解的属性
-
注解只有成员变量,没有方法
-
注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
这个注解中有id和msg两个属性,在使用时应该对其赋值。
注解赋值:在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性默认值:默认值需要用 default 关键值指定
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
当注解属性只有一个value时,应用这个注解时可以直接将属性值填写到括号内。
注解没有属性的情况下,应用注解时括号可以省略。
4、JDK内置的基本注解类型
@Override:限定某个方法,是重写父类方法,该注解只能用于方法
@Deprecated:用于表示某个程序元素(类、方法等)已过时
- 被@Deprecated修饰的元素表示不再推荐使用,但仍然可以使用
- 可以修饰方法、类、字段、包、参数等等
- 可以做版本升级的过渡使用
@SuppressWarnings:抑制编译器警告
-
在{""}中,可以写入希望抑制的警告信息(该注解类中有String[]属性),比如:
@SuppressWarnings({"all"}) public static void main(String[] args){ List list = neew ArrayList(); list.add("jack"); list.add("tom"); list.add("mary"); int i; System.out.println(list.get(1)); }
-
作用范围与放置的位置有关
十二、异常处理
- 执行过程中所发生的异常分为两大类:
- Error(错误):Java虚拟机无法解决的严重问题
- Exception:①运行时异常(程序运行时发生的异常);②编译时异常(编程时编程器检查出的异常)
- 异常体系图
- 常见的运行时异常
- NullPointerException空指针异常
- ArithmeticException数学运算异常
- ArrayIndexOutOfBoundsException数组下标越界异常
- ClassCastException类型转换异常
- NumberFormatException数字格式不正确异常(字符串转换成一种数值类型)
- 常见的编译异常
- SQLException:操作数据库时,查询表可能发生异常
- IOException:操作文件时发生的异常
- FileNotFoundException:文件不存在
- ClassNotFoundException:类不存在
- EOFException:操作文件,到文件末尾,异常
- IllegalArguementException:参数异常
- 异常处理的方式:
-
try-catch-finally:程序员在代码中捕获的异常,自行处理
try{ //可能有异常的代码块 }catch(Exception e){ //捕获到异常才执行 }finally{ //不管try代码块有没有抛出异常,始终要执行finally(通常将释放资源的代码放在finally中) }
-
throws:将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
-
对于编译异常,程序中必须处理,try-catch或者throws
-
对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
-
子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型,要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
-
-
自定义异常:
public class CustomException{ public static void main(String[] args){ int age = 180; //要求在18~120之间,否则抛出一个自定义异常 if(!(age >= 18 && age <= 120)){ //这里可以通过构造器设置信息 throw new AgeException("年龄需要在18~120之间"); } System.out.println("你的年龄范围正确。") } } //一般情况下,自定义异常是继承RuntimeException(运行时异常) class AgeException extends RuntimeException{ public AgeException(String message){ //构造器 super(message); } }
十三、多线程
进程:指程序的一次执行过程,or正在运行的一个程序。是动态过程,有它自身的产生、存在和消亡的过程。
线程:由进程创建(线程也可以创建线程),是进程的一个实体。一个进程可以拥有多个线程。
单线程:同一个时刻,只允许执行一个线程。
多线程:同一个时刻,可以执行多个线程。
并发:同一个时刻,多个任务交替执行。(单核cpu实现的多任务就是并发)
并行:同一个时刻,多个任务同时执行。(多核cpu可以实现并行)
- 创建线程的两种方式:
-
继承Thread类,重写run方法(Thread类实现了Runnable接口)
public class Thread01{ public static void main(String[] args){ //创建Cat对象,可以当作线程使用 Cat cat = new Cat(); cat.start(); //启动线程Thread-0,如果这里是调用run方法,则不会开启Thread线程,主线程main阻塞 //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行 System.out.println("主线程继续执行"+Thread.currentThread().getName()); //线程名main for(int i=0;i<10;i++){ System.out.println("主线程 i="+i); //让主线程休眠 try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } } } } class Cat extends Thread{ int times = 0; @Override public void run(){ while(true){ //该线程每隔1秒,在控制台输出“喵喵” System.out.println("喵喵"+(++times)+" 线程名="+Thread.currentThread().getName()); //让该线程休眠1秒 try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } if(times == 80){ break; } } } }
- 当一个类继承了Thread类,该类就可以当作线程使用
- start()开启线程的原理:(真正实现多线程的是start0()方法,不是run()方法)
- 实现Runnable接口,重写run方法
-
Java是单继承的,当一个类已经继承了某个父类时,就不能再使用继承Thread类的方法来创建多线程了
public class Thread02{ public static void main(String[] args){ Dog dog = new Dog(); //dog.start()会报错,Runnable中没有start方法 //创建Thread对象,把dog对象(实现Runnable),放入Thread Thread thread = new Thread(dog); //静态代理 thread.start(); } } class Dog implements Runnable{ int count = 0; @Override public void run(){ while(true){ System.out.println("汪汪"+(++count)+" 线程名="+Thread.currentThread().getName()); //休眠1秒 try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } if(count == 80){ break; } } }
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用
- 通知线程退出
public class ThreadExit{
public static void main(String[] args){
T t1 = new T();
t1.start();
//让main线程修改loop -> t1退出run方法,从而终止t1进程
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
t1.setLoop(false);
}
}
class T extends Thread{
private int count = 0;
private boolean loop = true;
@Override
public void run(){
while(loop){
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("T 运行中。。。"+(++count));
}
}
public void setLoop(boolean loop){
this.loop = loop;
}
}
- 线程常用方法
- setName //设置线程名称
- getName //返回该线程名称
- start //使该线程开始执行
- run //调用线程对象run方法
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep //休眠
- interrupt //中断线程,没有真正结束线程,一般用于中断休眠
- yield //线程的礼让。让出cpu,让其他线程执行,但礼让时间不确定,所以也不一定礼让成功
- join //线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有任务
- getState //获取线程的状态
- 守护线程
public class JoinThread extends Thread {
public static void main(String[] args) {
int count = 0;
TDaemon td = new TDaemon();
td.setDaemon(true); //将子线程设为守护线程,那么main线程结束之后子线程自动结束
td.start();
while (count < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hi.."+(++count));
}
}
}
class TDaemon extends Thread {
@Override
public void run() {
for (;;) { //无线循环
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello..");
}
}
}
- 线程的状态
Thread.State中的状态:
- NEW: 尚未启动的线程处于此状态
- RUNNABLE: 在Java虚拟机中执行的线程处于此状态(分为Ready和Running)
- BLOCKED: 被阻塞等待监视器锁定的线程处于此状态
- WAITING: 正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED: 已退出的线程处于此状态
- 线程同步机制--Synchronized
保证数据在任何同一时刻,最多只有一个线程访问。(当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作)
-
同步代码块
synchronized(对象){ //得到对象的锁(互斥锁),才能操作同步代码 //需要被同步的代码 }
-
synchronized还可以放在方法声明中,表示整个方法为同步方法(此时的互斥锁在this对象上)
public synchronized void m(String name){ //需要被同步的代码 }
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)[默认为this]
synchronized(this){}
或
Object object=new Object();
synchronized(object){} //object相当于一个媒介
-
同步方法(静态的)的锁为当前类本身[默认为当前类.class]
class Name implements Runnable{
public static void m(){
synchronized(Name.class){}
}
}
- 释放锁
释放锁的情况:
- 当前线程的同步方法、同步代码执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的情况:
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
十四、IO流
十五、反射
-
反射机制解决问题
// 从re.properties文件中读取“classfullpath=com.edu.Cat method=hi” Properties properties = new Properties(); properties.load(new FileInputStream("src\\re.properties")); String classfullpath = properties.get("classfullpath").toString(); String methodName = properties.get("method").toString(); //(1)加载类,返回Class类型的对象cls Class cls = Class.forName(classfullpath); //(2)通过cls得到加载的类com.edu.Cat的对象实例 Object o = cls.newInstance(); //Cat类的对象 //(3)通过cls得到加载的类com.edu.Cat的methodName“hi”的方法对象 Method method1 = cls.getMethod(methodName); //Cat类的方法hi //(4)通过方法对象method1实现调用方法 method1.invoke(o); //★方法.invoke(对象)
-
反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等等),并能操作对象的属性及方法(反射在设计模式和框架底层都会用到)
-
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。(这个对象就像一面镜子,透过这个镜子看到类的结构,所以称之为"反射")
-
反射机制的作用
- 在运行时判断任意一个对象所属的类(对象→类)
- 在运行时构造任意一个类的对象(类→对象)
- 在运行时得到任意一个类所具有的成员变量和方法(类→成员变量、方法)
- 在运行时调用任意一个对象的成员变量和方法(对象→成员变量、方法)
- 生成动态代理
-
反射优缺点
- 优点: 可以动态的创建和使用对象(也是架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
- 缺点:使用反射基本是解释执行,执行速度慢
-
反射相关的主要类
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
- 反射调用优化-关闭访问检查
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
1、Class类
- Class类的特点
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的(通过ClassLoader类加载Class对象)
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
- Class对象是存放在堆的
- 类的字节码二进制数据(元数据),是放在方法区的(包括方法代码,变量名,方法名,访问权限等等)
- Class常用方法(这里以Car类为例)
获取到Car类对应的Class对象
class<?> cs = class.forName(classAllPath); //<?> 表示不确定的Java类型
输出cls
System.out.println(cls); //显示cLs对象,是哪个类的CLass对象 com.Car System.out.println(cs.getclass()); //输出cLs运行类型 java.lang.class
得到包名
System.out.println(cls.getPackage().getName()); //包名 com
得到全类名
System.out.println(cls.getName());
通过cLs创建对象实例
Car car = (Car)cls.newInstance(); System.out.println(car); //car.toString()
通过反射获取属性
Field brand = cls.getField("brand"); //不能获取private属性 System.out.printn(brand.get(car)); //宝马
通过反射给属性赋值
brand.set(car,"奔驰"); System.out.println(brand.get(car)); //奔驰
得到所有的属性(字段)
Field[] fields = cls.getFields(); for (Field f : fields) { System.out.println(f.getName()); //名称 }
- 获取Class类对象的方式
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName() 获取,可能抛出ClassNotFoundException
Class cls1 = Class.forName("java.lang.Cat");
应用场景:多用于配置文件,读取类全路径,加载类
前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
Class cls2 = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
Car car = new Car(); Class cls3 = car.getClass();
应用场景:通过创建好的对象,获取Class对象
通过类加载器 [4种] 来获取类的class对象
//(1)先得到类加载器 car ClassLoader classLoader = car.getClass().getClassLoader(); //(2)通过类加载器得到Class对象 Class cls4 = classLoader.loadClass(classAllPath);
▲以上的cls1、cls2、cls3、cls4其实是同一个对象
基本数据(int,char,boolean,float,double,byte,long,short)可以通过.class得到Class类
Class cls = 基本数据类型.class;
基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class type = 基本数据类型.TYPE;
▲以上的cls和type是同一个对象
- 有Class对象的类型
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface
- 数组
- enum(枚举)
- annotation(注解)
- 基本数据类型
- void
2、类加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
🔺举例:
- 当创建对象时(new) //静态加载
- 当子类被加载时,父类也被加载 //静态加载
3. 调用类中的静态成员时 //静态加载- 通过反射 //动态加载
-
类加载过程图(🔺重要!!!)
加载阶段
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象
连接阶段-验证
目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
包括:文件格式验证(是否以魔数 oxcafebabe开头/class文件头)、元数据验证、字节码验证和符号引用验证。
可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段-准备
JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所使用的内存都将在方法区中进行分配。
class A{ //属性-成员变量-字段 //在类加载的链接阶段-准备中,属性是如何处理 //1.n1 是实例属性,不是静态变量,因此在准备阶段,是不会分配内存 //2.n2 是静态变量,分配内存 n2 是默认初始化 0,而不是20 //3.n3 是static final 是常量,他和静态变量不一样,因为一且赋值就不变 n3 = 30 public int n1 = 10; public static int n2 = 20; public static final int n3 = 30; }
连接阶段-解析
- 虚拟机将常量池内的符号引用(相对地址)替换为直接引用(内存地址)的过程
初始化
到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行
<clinit>()
方法的过程。
<clinit>()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。(直接使用类的静态属性也会导致类的加载;如果是new一个类,那么是先加载静态代码块,再加载类的构造器)虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()
方法完毕。
3、通过反射获得类的结构信息
- 第一组:java.lang.Class类
getName:获取全类名
getSimpleName:获取简单类名
getFields:获取所有public修饰的属性,包含本类以及父类的
getDeclaredFields:获取本类中所有属性
getMethods:获取所有public修饰的方法,包含本类以及父类的
getDeclaredMethods:获取本类中所有方法
getConstructors: 获取所有public修饰的构造器,只包含本类
getDeclaredConstructors:获取本类中所有构造器
getPackage:以Package形式返回包信息
getSuperClass:以Class形式返回父类信息
getInterfaces:以Class[]形式返回接口信息
getAnnotations:以Annotation[] 形式返回注解信息
- 第二组:java.lang.reflect.Fieid类
- getModifiers:以int形式返回修饰符 [说明: 默认修饰符 是0,public 是1 ,private 是 2 ,protected 是 4static 是 8 , final 是 16]
- getType:以Class形式返回类型
- getName:返回属性名
- 第三组: java.lang.reflect.Method类
- getModifiers:以int形式返回修饰符 [说明: 默认修饰符 是0,public 是1 ,private 是 2 ,protected 是 4,static 是8,final 是 16]
- getReturnType:以Class形式获取返回类型
- getName:返回方法名
- getParameterTypes:以Class[]返回参数类型数组
- 第四组:java.lang.reflect.Constructor类
- getModifiers: 以int形式返回修饰符
- getName:返回构造器名 (全类名)
- getParameterTypes:以Class[]返回参数类型数组
class cls = Class.forName("className");
cls.getName(); //这里的方法可替换成以上任意一个
- 通过反射创建对象(利用各种构造器)
class User{
private int age = 10;
private String name = "初始名";
public User(){ //public 无参
}
public User(String name){ //public 有参
this.name = name;
}
private User(int age, String name){ //private 有参
this.age = age;
this.name = name;
}
public String toString(){
return "User [age=" + age + ", name=" + name + "]";
}
}
public static void main(String[] args){
//先获取到User类的Class对象
Class userClass = Class.forName("thePath.User");
//1.通过public的无参构造器创建实例
Object obj1 = userClass.newInstance();
System.out.println(obj1);//User [age=10, name=初始名]
//2.通过public的有参构造器创建实例
//2.1 先得到对应的构造器
Constructor constructor1 = userClass.getConstructor(String.class);
//2.2 创建实例,并传入参数
Object obj2 = constructor1.newInstance("张三");
System.out.println(obj2);//User [age=10, name=张三]
//3.通过非public的有参构造器创建实例
//3.1 得到private的构造器对象
Constructor constructor2 = userClass.getDeclaredConstructor(int.class, String.class);//使用getConstructor方法则会报错,因为对应的构造器不是public的
//3.2 创建实例
constructor2.setAccessible(true);//【🔺暴力破解】so使用反射可以访问private构造器
Object obj3 = constructor2.newInstance(100, "李四");
System.out.println(obj3);//User [age=100, name=李四]
}
- 通过反射访问类中的属性(也可以是private和static属性)
class Student {
public int age;
private static String name;
public Student(){
}
public String toString(){
return "Student [age=" + age + ", name=" + name + "]";
}
}
public static void main(String[] args){
//先得到Student类对应的Class对象
Class stuClass = Class.forName("thePath.Student");
//创建对象
Object o = stuClass.newInstance();
System.out.println(o.getClass());//class thePath.Student (这一步表明o的运行类型是Student)
//使用反射操作age属性(public属性)
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作o的属性
System.out.println(o);//Student [age=88, name=null]
System.out.println(age.get(o));//返回age属性的值88
//使用反射操作name属性(private属性)
Field name = stuClass.getDeclaredField("name");//使用getField方法则会报错,因为name属性不是public的
name.setAccessible(true);//【🔺暴力破解】,so使用反射可以操作private属性
name.set(o, "张三");//也可以使用【🔺name.set(null, "张三");】,因为name属性是static的
System.out.println(o);//Student [age=88, name=张三]
System.out.println(name.get(o));//获取name属性的值:张三
System.out.println(name.get(null));//同上,不过使用null代替o,要求name属性必须为static
}
- 通过反射访问类中的方法()
class Boss{
public int age;
private static String name;
public Boss(){
}
private static String say(int n, String s, char c){ //private static 方法
return n + " " + s + " " + c;
}
public void hi(String s){ //public 方法
System.out.println("hi " + s);
}
}
public static void main(String[] args){
//得到Boss类的Class对象
Class bossCls = Class.forName("thePath.Boss");
//创建对象
Object o = bossCls.newInstance();
//调用public的hi方法
Method hi = bossCls.getMethod("hi", String.class); //或者调用getDeclaredMethod("hi", String.class)
hi.invoke(o, "张三");//hi 张三
//调用private static的say方法
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);//【🔺暴力破解】,so使用反射可以操作private方法
say.invoke(o, 100, "张三", "男");//也可以使用【say.invoke(null, 100, "张三", "男");】,因为say方法是static的
//🔺在反射中,如果方法有返回值,统一返回Object(即用Object对象接收返回值),但是运行类型[.getClass()]和方法中定义的返回值是一样的
}
【参考资料】:菜鸟教程 https://www.runoob.com/java/java-tutorial.html;
韩顺平零基础30天学会Java https://www.bilibili.com/video/BV1fh411y7R8。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!