文章目录
- Java基础
- Java进阶
- final关键字
- 抽象类
- 接口
- 访问控制权限修饰符
- Object类
- 内部类[^6]
- 数组
- String类
- String类的构造方法
- String类中的常用方法
- char charAt(int index)
- int compareTo(String anotherString)
- boolean contains(CharSequence s)
- boolean endsWith(String suffix)
- boolean equalsIgnoreCase(String anotherString)
- byte[] getBytes()
- int indexOf(String str)
- boolean isEmpty()
- int length()
- int lastIndexOf(String str)
- String replace (CharSequence target, CharSequence replacement)
- String[] split(String regex)
- boolean startsWith(String prefix)
- String substring(int beginIndex)
- String substring(int beginIndex, int endIndex)
- char[] toCharArry()
- String toLowerCase()
- String toUpperCase()
- String trim()
- valueOf()
- StringBuffer类
- StringBuilder类
- 基础类型对应的8个包装类
- Date类
- 数字类
- 枚举
- 异常
- 集合
- 泛型
- 增强for循环(foreach)
- IO流
- File类
- 多线程
- 反射机制
- 注解
Java基础
Java的加载与执行原理:
标识符1
命名规则
- 标识符只能由数字、字母(包括中文)、下划线_、美元符号$组成,不能包含有其他符号;
- 标识符不能以数字开头;
- 关键字不能做标识符,例如public、class、static……
- 标识符严格区分大小写,大写字母与小写字母不同,而在Mysql中不严格区分大小写;
命名规范
- 见名知意;
- 遵循驼峰命名方式;
- 类名和接口名首字母大写,后面每个单词首字母大写
- 变量名,方法名:首字母小写,后面每个单词首字母大写
- 所有常量名全部大写,单词与单词之间用下划线衔接;
数据类型
数据类型 | 占用字节数 | 取值范围 | 缺省默认值 | ||
基本数据类型 | 整数型 | byte | 1 | [-128, 127] | 0 |
short | 2 | [-32768, 32767] | 0 | ||
int | 4 | [-2147483648, 2147483647] | 0 | ||
long | 8 | [-263, 263-1] | 0L | ||
浮点型 | float | 4 | [-2147483648, 2147483647] | 0.0f | |
double | 8 | [-263, 263-1] | 0.0 | ||
布尔型 | boolean | true, false | 1 | false | |
字符型 | char | 2 | [0, 65535] | '\u0000' | |
引用数据类型 | String | null |
字符型
字符编码与解码
字符编码是人为定义的一套转换表,在字符编码中规定了一系列的文字对应的二进制(例如utf-8)。
字符编码与字符解码一定要用同一种编码方式,这就是出现乱码的原因之一。
转义字符
含义
java语言中“ \ ”负责转义。
“ \ ” 将其后紧跟的字符(英文字母)进行转义。\n表示换行,\t表示制表符tab。
作用
在java中要打印 “java” 时,如果使用 system.out.println(" “2java”2 ") 的话会报“No documentation found.(未找到文档)”的错误。此时就需要使用转义字符“ \ ”将“ " ”进行转义,system.out.println(“\“java\””) 这样就能达到目的了。
整数型
整数型 字面量 有4中表现形式:
- int a = 10(十进制,表示10)
- int b = 010(八进制,表示8)
- int c = 0x10(十六进制,表示16)
- int d = 0b10(二进制,表示2)
在任何情况下,整数型的“字面量”默认被当作int来处理。如果希望该整型 字面量 被当作long处理,需要在 字面量 后加上 L/l。
类型转换
- 小容量可以直接转换给大容量,称为自动类型转换;
例: long a = 200; 此处的200被当作int类型来处理,a 变量是long类型,200为int类型,200自动转换成long类型。 - 大容量不能直接赋值给小容量,需要使用强制类型转换。但加了强制类型转换符后,运行时会损失精度。
例:int b = 100L; 100L 为long类型,而需要的是一个int类型,会报“类型不兼容 ”的错误,需将代码改写成int b = (int)100L,这就是强制类型转换。
整数型中的结论
- 当一个整数直接赋值给char类型变量的时候,会自动转换成char字符型,最终的结果是一个字符。
- 当一个整数没有超过byte,short, char的取值范围的时候,这个整数可以直接赋值给byte,short,char类型的变量。
例:char a = 97; 打印结果为 a,因为97为int类型,int类型为大容量,char为小容量,但是97在char的取值范围之内,所以不需要进行强制类型转换。
public static void main(String[] args){
char c = 'a';
byte b = 1;
short s = 2;
System.out.println(c + b + s); // 打印结果为100
}
- 上述代码片说明,byte,short,char做混合运算时,先各自转换成int类型再进行运算。
注:若是short s2 = c + b + s;,则会报“ 类型不兼容 ”的错误,因为“c + b + s”的返回值是int类型。 - 多种数据类型做混合运算时,最中的结果类型是“最大容量”对应的类型。“char + byte + short”除外。
例:int类型的 字面量 和long类型的 字面量 做运算时,返回值是long类型。 - 整数型做除法取整。
例:double a = 3 / 4; 返回结果为“ 0.0 ”。改为double a = 3.0 / 4后,返回结果为“ 0.75 ”。
浮点型
- float(单精度浮点型),double(双精度浮点型),double更精确。
例:10.0 / 3,如果采用float来储存的话,结果为3.33333
而采用double来存储的话,结果为3.3333333333333
注:任意一个浮点型都比整数型容量大。虽然float占4个字节,long占8个字节,但float的容量大于long的容量。 - 任何一个浮点型数据默认被当作double来处理如果想让这个浮点型 字面量 被当作float来处理,在 字面量 后加上F/f。
布尔型
值只有true和false,使用在逻辑判断中,通常放到条件的位置上。
运算符
运算符 | 具体符号 |
算术运算符 | +、-、*、/、%(求余数)、++(自加1【单目】)、--(自减一【单目】) |
关系运算符 | >、>=、<、<=、==、!= |
逻辑运算符 | &(逻辑与)、|(逻辑或)、!(逻辑非)、&&(短路与)、||(短路或) |
赋值运算符 | =、+=、-=、*=、/=、%=、^=、&=、|=、<<=、>>= |
位运算符 | &(按位与)、|(按位或)、^(按位异或)、~(按位取反【单目】)、<<(左移)、>>(带符号右移)、>>>(无符号右移) |
条件运算符 | 布尔表达式?表达式1:表达式2(三目) |
字符串连接运算符 | + |
其他运算符 | instanceof、new |
算数运算符
public static void main(String[] args){
int m = 20;
int n = m++;
System.out.println(n); // 20
System.out.println(m); // 21
int a = 30;
int b = ++a;
System.out.println(b); // 31
System.out.println(a); // 31
}
如上代码片所示,当“ ++ ”出现在变量后时,会先进行赋值运算,再自加1;
当“ ++ ”出现在变量前时,会先进行自加1运算,再赋值。
关系运算符
所有的关系运算符的运算结果都是布尔类型。
逻辑运算符
逻辑运算符的两边要求都是布尔类型,并且最终的运算结果也是布尔类型。
逻辑与“ & ”和逻辑或“ | ”
逻辑与“ & ”只要有一边是false,结果就是false,只有两边同时为true,结果才是true。
逻辑或“ | ”只要有一边是true,结果就是true,只有两边同时为false,结果才是false。
短路与“ && ”和短路或“ || ”
短路形象:以短路与“ && ”为例,逻辑“ & ”只有当两边同时为true的时候结果为true,其余任何情况都为false,短路与“ && ”的执行顺序是从左向右,假设“ && ”左边的表达式的结果为false,那么结果必定是false,右边的表达式没有意义再执行下去,所以右边的表达式就不执行,这种现象就称为短路现象。
短路与“ && ”和短路或“ || ”的效率更高,编写代码时要尽量使用。
赋值运算符
基本赋值运算符
基本赋值运算符“ = ”右边的优先级比左边高,先执行右边的表达式,然后再赋值3。
扩展赋值运算符
public static void main(String[] args){
byte m = 0;
byte n = 0;
m += 1;
n = n + 1;
}
“ m += 1;” 与“ n = n + 1; ”相似,但不完全相同。如上代码片所示,n为byte类型,在进行“ n = n + 1; ”时,会先将右边看成int类型进行运算,然后再赋值,而左边为byte类型,将大容量转换成小容量,系统会报错。
条件运算符(三目运算符)
语法格式
布尔表达式 ? 表达式1 : 表达式2
执行原理
布尔表达式的结果为true时,表达式1的执行结果作为整个表达式的返回结果。
布尔表达式的结果为false时,表达式2的执行结果作为整个表达式的返回结果。
字符串连接运算符
当 + 运算符的任意一边为字符串类型时,这个 + 就会进行字符串的拼接操作,拼接完成之后的结果还是一个字符串。
方法
定义方法的意义
public static void main(String[] args){
int a = 100;
int b = 200;
int c = a + b;
System.out.println(a + "+" + b + "=" c);
int d = 300;
int e = 400;
int f = d + e;
System.out.println(d + "+" + e + "=" f);
int g = 500;
int h = 600;
int i = g + h;
System.out.println(g + "+" + h + "=" i);
}
如上代码片,对于一个Java程序来说,没有方法,代码无法得到复用。这就需要定义一个方法来实现某一功能,使用该功能时,只需要调用该方法即可。
方法的语法机制
[修饰符列表4] 返回值类型 方法名(形式参数列表5){
方法体;
}
修饰符列表不是必须的,是可选的。
JVM 内存结构
JVM 中主要有三内存空间:栈,堆,方法区。
栈:在方法被调用的时候,该方法需要的内存空间在 栈 中分配,在开辟的内存空间中存储方法的局部变量。
堆:凡是通过new运算符创建的对象都存储在堆内存当中,new运算符的作用就是在堆内存中开辟一片空间。
方法区:类加载器,将硬盘上的“ xxx.class ”字节码文件装载到 JVM 的时候,会将字节码文件存放到方法区当中。也就是说方法区中存储的是代码片段。(因为类需要加载,所以方法区最先有数据)。
栈
栈帧永远指向的是栈顶部的元素。进栈(也叫压栈,入栈,push),出栈(也叫弹栈,pop)。调用方法时就是压栈,方法执行结束后,该方法所占用的内存空间就会被释放,此时发生弹栈。
栈数据结构的特点:先进后出,后进先出。
注:处于栈顶的元素具备活跃权
方法重载(overload)
为什么使用方法重载
在同一个类当中,如果“ 功能1 ”和“ 功能2 ”的功能是相似的,使用方法重载能复用代码。
方法重载的条件
- 在同一个类当中;
- 方法名相同
- 参数列表不同(包括参数的个数,类型,顺序不同)
方法递归
含义
方法自己调用自己
注意
方法递归一定要有结束条件,不然会发生栈内存溢出的错误。
面向对象
使用面向对象的方式贯穿整个系统的话,包括三部分:
- OOA:面向对象分析(Object-Oriented Analysis)
- OOD:面向对象设计(Object-Oriented Design)
- OOP:面向对象编程(Object-Oriented Programming)
面向对象编程的三个特征: - 封装
- 继承
- 多态
类的定义
[修饰符列表] class 类名{
类体(属性 + 方法)
属性在代码中以变量的形式存在(描述状态)
方法描述动作/行为
}
对象的创建
new 类名();
类是模板,通过一个类,是可以创建N个对象的。
构造方法
构造方法是用来创建对象的,并且同时给对象的属性赋初值。当一个类没有提供构造方法时,系统会默认提供一个无参数的构造方法。(被称为缺省构造器)
构造方法的创建
[修饰符列表] 构造方法名(形式参数列表){
构造方法体;
(通常在构造方法中给属性赋值,完成属性的初始化)
}
注:构造方法名和类名必须一致;构造方法不需要指定返回类型;手动定义了有形参的构造方法后,默认的无参构造方法会消失,需要自己重新写上。
封装
作用
- 保证内部结构的安全;
- 屏蔽复杂,暴露简单;
用法
使用private关键字将属性封装起来,再对外提供set,get方法对属性进行操作。在set方法中设置条件来限制对属性的赋值。
继承
作用
基本作用:子类继承父类,代码可以得到复用。
主要作用:有了继承 机制,才能有多态 机制。
特性
- 子类继承父类,除构造方法不能继承外,剩下的都可以继承,但是私有的属性无法在子类中直接访问;
- 类没有显示继承任何类,则默认继承Object类,Object类是Java语言提供的根类,也就是说,一个类与生俱来就有Object的特征;
- 父类的改变会立即影响子类的变化。
什么时候使用继承
能采用“ is a ”描述的就使用继承。
多态
向上转型可理解为自动类型转换;向下转型可理解为强制类型转换,需要加强制类型转换符。
无论是向上转型还是向下转型一定要满足继承关系
向下转型
在子类中有父类中没有的方法的情况下,要使用多态就要向下转型。并需要使用instanceof运算符来判断是否可以进行向下转型。
方法覆盖
当子类继承父类以后,当继承过来的方法无法满足子类业务需求时,子类可以对这个方法进行重写。
方法覆盖的条件
- 两类必须有继承关系。
- 重写后的方法和之前的方法具有相同的返回值类型,相同的方法名,相同的形式参数列表5。
- 访问权限不能更低,可以更高。
- 重写之后的方法不能比之前方法抛出的异常更多,可以更少。
方法覆盖的注意事项
- 私有方法无法覆盖。
- 构造方法无法被继承,所以无法覆盖。
- 方法覆盖值针对实例方法,静态方法覆盖无意义。
多态的作用
满足软件开发的 OCP 原则,降低耦合度,提高程序的扩展力。
面向抽象编程,不建议面向具体编程。
this与super
- this能出现在实例方法和构造方法中;
- this的语法是“ this. ”、“ this() ”;
- this不能使用在静态方法中;
- this. 大部分情况下是可以省略的
- this. 在区分局部变量和实例变量时不能省略;
- this() 只能出现在构造方法的第一行,通过当前构造方法去调用本类中的其他构造方法。如下代码片所示:
public static void main(String[] args){
class Date{
private int day;
private int month;
private int year;
public Date(){
/*this.year = 1949;
this.month = 10;
this.day = 1;*/
this(1949, 10, 1);
}
public Date(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
}
}
注释的无参的构造函数和有参的构造函数的有重复,将其改写为“ this(1949, 10, 1); ”简化了代码。如果不需要进行初始化赋值的话,直接写“ this(); ”即可。
- super能出现在实例方法和构造方法中;
- super的语法是“ super. ”、“ super() ”;
- super不能使用在静态方法中;
- super. 大部分情况下是可以省略的
- 子类和父类有同名属性或方法,并且想在子类中访问父类中的同名属性或方法时,super. 不能省略
- super() 只能出现在构造方法的第一行,通过当前构造方法去调用父类中的构造方法。目的是创建子类对象时,先初始化父类型特征。如下代码片:
public static void main(String[] args){
}
class Animal{
private String name;
private int age;
public Animal(){
}
public Animal(int age, String name){
this.age = age;
this.name = name;
}
}
class Cat extends Animal{
private String master;
public Cat(){
}
public Cat(int age, String name, String master){
/*this.age = age;
this.name = name;*/
super(age, name);
this.master = master;
}
}
子类Cat中的有参构造方法中注释掉的内容错误,因为age和name是父类Animal中的私有属性,在子类中无法访问,而改写成“ super(age, name); ”就可以了。
Java进阶
final关键字
- final修饰的类无法继承;
- final修饰的方法无法覆盖;
- final修饰的变量只能赋一次值;
- final修饰的引用一旦指向某个对象,则不能再指向其他对象,但引用指向的对象内部数据是可以修改的;
- final修饰的实例变量必须手动初始化,不能采用系统默认值。
- final修饰的实例变量一般和static联合使用,称为常量。
例:public static final PI = 3.1415936;
抽象类
- 抽象类的定义:public abstract class 类名;
- 抽象类无法实例化,无法创建对象,所以抽象类是用来被子类继承的;
- final 和 abstract不能联合使用,这两个关键字是对立的;
- 抽象类的子类可以是抽象类,也可以是非抽象类;
- 抽象类虽然无法实例化,但是有构造方法,这个构造方法是给子类使用的;
- 抽象类中不一定有抽象方法,抽象方法一定出现在抽象类中;
- 抽象方法的定义:public abstract void doSome();
- 一个非抽象的类继承抽象类,必须将抽象类中的抽象方法进行覆盖/重写/实现。
接口
- 接口也是一种引用“数据类型”,编译之后也是一个字节码文件;
- 接口时完全抽象的(抽象类是半抽象), 接口是特殊的抽象类;
接口的基础语法
- 接口定义:[修饰符列表] interface 接口名{};
- 接口支持多继承,一个接口可以继承多个接口;
- 接口中只包含两部分内容,一部分是常量;一部分时抽象方法
- 接口中所有元素都是public修饰的;
- 接口中的抽象方法定义时,public abstract修饰符可以省略;
- 接口中的方法都是抽象方法,所以不能有方法体;
- 接口中的常量中public static final 可以省略;
- 使用接口时,可以使用多态(父类型引用指向子类型对象)。
接口的作用
面向抽象编程,不面向具体编程。降低耦合度,提高扩展力。
类型与类型之间的关系
- 凡是满足“ is a ”关系的都能使用继承;
- 凡是满足“ has a ”关系的表示“关联关系”,关联关系通常以“属性”的形式存在;
- 凡是满足“ like a ”关系的表示“实现关系”,实现关系通常是类实现接口。
抽象类与接口的区别
- 抽象类中有构造方法,接口中无构造方法;
- 一个类可以实现多个接口,一个抽象类只能继承一个类
访问控制权限修饰符
访问控制权限 | ||||
访问控制修饰符 | 本类 | 同包 | 子类 | 任意位置 |
public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
protected | 可以访问 | 可以访问 | 可以访问 | 不可访问 |
默认 | 可以访问 | 可以访问 | 不可访问 | 不可访问 |
private | 可以访问 | 不可访问 | 不可访问 | 不可访问 |
访问控制权限修饰符修饰对象 | ||||
访问控制权限修饰符 | 属性 | 方法 | 类 | 接口 |
public | 可修饰 | 可修饰 | 可修饰 | 可修饰 |
protected | 可修饰 | 可修饰 | 不可修饰 | 不可修饰 |
默认 | 可修饰 | 可修饰 | 可修饰 | 可修饰 |
private | 可修饰 | 可修饰 | 不可修饰 | 不可修饰 |
Object类
Object类中的toString()方法
源码中的toString方法的默认实现是:类名@对象的内存地址转换为十六进制的形式。
toString()方法的存在是用来重写的,将子类中的toString()方法重写成简洁的,详实的,易阅读的形式。
public static void main(String[] args){
User u1 = new User("admin", "123456");
System.out.println(u1);
}
class User{
private String username;
private String password;
public User(String username, String password){
this.username = username;
this.password = password;
}
public String toString(){
return "username: " + this.username + ", password: " + this.password;
}
}
如上代码片所示,若没有重写toStiring()方法,“ System.out.println(u1); ”在打印时默认调用u1.toString,故打印出的结果是“ u1@对象的内存地址转换为十六进制的形式”。重写toString()方法以后,“ System.out.println(u1); ”的打印结果就是“ username: admin, password: 123456”。
Object类中的equals()
判断两个基本数据类型相等,使用“ == ”来判断,但当使用“ == ”来判断两个Java对象是否相等。如下代码片所示:
public static void main(String[] args){
MyDate md1 = new MyDate(2022, 6, 23);
MyDate md2 = new MyDate(2022, 6, 23);
System.out.println(md1 == md2); // 打印结果A
System.out.println(md1.equals(md2)); // 打印结果B
}
class MyDate{
private int year;
private int month;
private int day;
public MyDate(){
}
public MyDate(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
// 未重写前的equals()方法
/*public boolean equals(Object obj){
return (this == obj);
}*/
// 重写后的equals()方法
public boolean equals(Object obj){
if(obj == null || !(obj instanceof MyDate)){
return false;
}
if(this == obj){
return true;
}
MyDate md = (MyDate)obj;
return this.year == md.year && this.month == md.month && this.day == md.day;
}
}
如“ 打印结果A所示 ”直接将“ md1 ”和“ md2 ”用“ == ”比较,结果为false,其底层原理实际上是将“ md1 ”的内存地址和“ md2 ”的内存地址进行比较,两个的内存地址显然不一样,所以“ 打印结果A ”为false。但要求是两个对象的日期一样时,就认为这两个对象相等,用“ == ”不符合要求。默认的equlas() 方法实际上也是将两个对象用“ == ”比较,没有意义,所以要进行重写,重写后,“ 打印结果B ”为true。
Object类中的finalize()方法
源代码:
protected void finalize() throws Throwable{ }
- finalize()只有一个方法体,里面额米有代码,且是protected修饰的;
- 这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法,finalize()方法只需要重写,系统会自动调用;
- 当一个Java对象即将被垃圾回收器回收时,垃圾回收器负责调用finalize()方法;
- 类似静态代码块,静态代码块在类加载时调用。
Object类中的hashCode()方法
该方法返回的是哈希码:实际上就是一个Java对象的内存地址 ,经过哈希算法,得出的一个值,所以hashCode()方法的执行结果可以等同看作一个Java对象的内存地址。
内部类6
内部类分为实例内部类,静态内部类 和局部内部类。
如下代码片所示:
class Test{
// 有static修饰,且在Test类的内部,所以为静态内部类
static class Inner1{
}
// 无static修饰,且在Test类的内部,所以为实例内部类
class Inner2{
}
public void dosome(){
// 局部变量
int i = 100;
// 在方法体的内部,且在Test类的内部,所以为局部内部类
class Inner3{
}
}
}
匿名内部类
匿名内部类属于局部内部类的一种,因为这个类没有名字而得名。一般出现在实参列表中 ,可以new接口。
数组
一维数组
- 数组为引用数据类型,不属于基本数据类型,数组的父类是Object;
- 数组中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据;
- 数组存储在堆内存当中;
- 数组中如果存储的是“ 对象 ”,实际上存储的是对象的内存地址;
- 数组一旦创建,其长度不可变;
- 数组对象有length属性,用来获取数组中元素的个数;
- 数组要求数组中元素的类型统一,比如int类型的数组只能存储int类型,person类型的数组只能存储person类型;
- 数组中元素的内存地址都是连续的;
- 数组中首个元素的内存地址作为整个数组对象的内存地址
- 数组的优点:查找/检索效率高。
缺点:随机删除或增加元素效率低,不能存储大数据量(在内存空间上很难找到一块特别大的连续的内存空间)
初始化一维数组
静态初始化:
int[] array = {1, 2, 3, 4, 5};
动态初始化:
int[] array = new int[5];
数组拷贝
System类提供了一个 arraycopy() 的方法用来进行数组拷贝,该方法有五个参数。
System.arraycopy(拷贝源, 拷贝源的起始位置, 拷贝目标, 拷贝目标的起始位置, 要拷贝元素的个数),如下代码片所示:
public static void main(String[] args){
int[] src = {1, 2, 3, 4, 5}
int[] dest = new int[10];
System.arraycopy(src, 0, dest, 0, src.length);
}
将src数组中的全部数据拷贝到dest数组中。
注:这里拷贝的是拷贝对象的地址;原来的 拷贝源 的内存地址会被释放。
数组扩容
数组拷贝是为数组扩容服务的,因为数组的长度一旦确定就无法改变,当数组满了以后,就需要进行数组扩容。其原理是,先创建一个大容量数组,然后将小容量数组中的数据拷贝到大容量数组中。
二维数组
二维数组是一个特殊的一维数组,可以理解为一维数组的每一个元素都是一个一维数组。
初始化二维数组
静态初始化:
int[][] array = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
注:array.length是二维数组的长度,如上代码片中的array.length就是3,array[0].length为4。
动态初始化:
int[][] array = new int [3][4];
Arrays工具类
- 通过查找帮助文档,选择合适的数组操作方法。
- 工具类当中的代码大部分都是静态的。
sort()方法
调用Arrays.sort(数组名),可以直接对传进去的数组进行从小到大排序。
binarySearch()方法
二分法查找法,查找数组中是否存在某个元素,若存在则返回“数组下标(索引)”,若不存在则返回“-1”,二分法查找建立在排序之上。
String类
- String表示字符串类型,不属于基本数据类型;
- 使用双引号括起来的,都是String对象。例如"abc" , “中国”, “!!!”;
- 双引号括起来的是字符串,是不可变的,相当于"abc"不可变,不能变成"abcd";
- 字符串都是存储在“方法区”的“字符串常量池”当中
字符串也可以“ new ”出来,如下代码片:
String s = new String("ab");
这实际是在堆中开辟了一个空间,用来储存“ab”在字符串常量池中的地址。
在比较字符串是否相等时,尽量不要使用“ ==”,而要使用“ equals ”,因为字符串可能时new出来的。
String类的构造方法
public static void main(String[] args){
byte[] bytes = {97, 98, 99};
String s1 = new String(bytes);
System.out.println(s1); // 打印结果:abc
String s2 = new String(bytes, 1, 2); // 此处的构造方法为String(数组, 数组下标,要转换成字符串的数组长度)
System.out.println(s2); // 打印结果:bc
}
String类中的常用方法
char charAt(int index)
找出指定位置的字符。
public static void main(String[] args){
char c = "大学牲".charAt(1);
System.out.println(c); // 打印结果:学
}
int compareTo(String anotherString)
字符串之间比较大小不能用<, >,应使用compareTo。
public static void main(String[] args){
int reasult1 = "abc".compareTo("abc")
System.out.println(result1); // 打印结果:0,前后一致
int result2 = "abcd".compareTo("abce")
System.out.println(result2); // 打印结果:-1,前小后大
int result3 = "abce".compareTo("abcd")
System.out.println(result3); // 打印结果:1,前大后小
}
boolean contains(CharSequence s)
判断字符串中是否包含某个字符串。
public static void main(String[] args){
System.out.println("HelloWorld.java".contains(".java")); // 打印结果:true
System.out.println("http://www.baidu.com".contains("https://")) // 打印结果:false
}
boolean endsWith(String suffix)
判断字符串是否以某个字符串结尾。
public static void main(String[] args){
System.out.println("HelloWorld.java".endsWith(".java")); // 打印结果:true
System.out.println("JavaTest.txt".endsWith(".java")); // 打印结果:false
}
boolean equalsIgnoreCase(String anotherString)
判断两个字符是否相等,并且忽略大小写。
public static void main(String[] args){
System.out.println("abC".equalsIgnoreCase("ABc")); // 打印结果:true
}
byte[] getBytes()
将字符串转换成字节数组
public static void main(String[] args){
byte[] bytes = "abcdef".getBytes();
for(int i = 0; i < bytes.length; i++){
System.out.println(bytes[i]); // 打印结果: 97
// 98
// 99
// 100
// 101
// 102
}
}
int indexOf(String str)
找出某个子字符串在当前字符串下第一次出现的索引
public static void main(String[] args){
System.out.println("我们都是大学牲".indexOf("大学牲")); // 打印结果:4
}
boolean isEmpty()
判断某个字符串是否为“空字符串”。
public static void mian(String[] args){
String s1 = "";
String s2 = "abcd";
System.out.println(s1.isEmpty()); // 打印结果:true
System.out.println(s2.isEmpty()); // 打印结果:false
}
int length()
判断字符串长度。
public static void main(String[] args){
System.out.println("abc".length()); // 打印结果:3
}
int lastIndexOf(String str)
找出某个子字符串在当前字符串下最后一次出现的索引
public static void main(String[] args){
System.out.println("abcdefcd".lastIndexOf("cd")); // 打印结果:6
}
String replace (CharSequence target, CharSequence replacement)
public static void main(String[] args){
String newString = "http://www.baidu.com".replace("http://", "https://");
System.out.println(newString); // 打印结果:https://www.baidu.com
}
String[] split(String regex)
public static void main(String[] args){
String[] ymd = "2022-6-23".split("-");
for (int i = 0; i < ymd.length; i++) {
System.out.println(ymd[i]); // 打印结果:2022
// 6
// 23
}
}
boolean startsWith(String prefix)
判断某个字符串是否以某个字符串开始
public static void main(String[] args){
System.out.println("http://www.baidu.com".startsWith("http")); // 打印结果:true
System.out.println("http://www.baidu.com".startsWith("https")); // 打印结果:false
}
String substring(int beginIndex)
截取字符串
public static void main(String[] args){
System.out.println("http://www.baidu.com".substring(7)); // 打印结果:www.baidu.com
}
String substring(int beginIndex, int endIndex)
截取字符串,beginIndex包括,endIndex不包括
public static void main(String[] args){
System.out.println("http://www.baidu.com".substring(7, 10)); // 打印结果:www
}
char[] toCharArry()
将字符串转换成char数组
public static void main(String[] args){
char[] chars = "我是大学牲".toCharArray();
for(int i = 0; i < chars.length; i++){
System.out.println(chars[i]); // 打印结果:我
// 是
// 大
// 学
// 牲
}
}
String toLowerCase()
将字符串转换成小写
public static void mian(String[] args){
System.out.println("ABCDefgHiJKl".toLowerCase()); // 打印结果:abcdefghijkl
}
String toUpperCase()
将字符串转换成大写
public static void main(String[] args){
System.out.println("ABCDefgHiJKl".toLowerCase()); // 打印结果:ABCDEFGHIJKL
}
String trim()
去除字符串前后空白。
public static void main(String[] args){
System.out.println(" hello world ".trim());// 打印结果:helloworld
}
valueOf()
该方法时String方法中唯一一个静态方法,将非字符串转换成字符串
public static void main(String[] args){
System.out.println(String.valueOf(123));// 打印结果:123
}
StringBuffer类
StringBuffer的作用及用法
在开发中通常会遇到频繁的字符串拼接,但字符串是不可变的,字符串拼接会占用大量方法区字符串常量池空间。如下代码片所示:
public static void main(String[] args){
String s1 = "a";
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3); // 打印结果:abc
}
虽然最后打印结果为想要的abc,但是中间过程却创建了a,ab,abc三个字符串。
为缓解字符串常量池的压力,拼接字符串可以使用StringBuffer类。StringBuffer使用“append()”方法进行拼接,会避免该问题。
查看String和StringBuffer的底层代码:
// String类的底层代码:
private final byte[] value;
// StirngBuffer类的底层代码:
byte[] value;
如上代码片所示:“ String ”类和“ StringBuffer ”类的底层都是byte数组,但是“ String ”类由fianl修饰,这就是String类不可变的原因。
使用StringBuffer类进行扩容如下代码片所示:
public static void main(String[] args){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
System.out.println(stringBuffer); // 打印结果:abc
}
这样只创建了一个字符串“abc”
StringBuffer的构造方法
- StringBuffer(),默认初始容量为16;
- StringBuffer(int capacity),指定一个初始容量;
- StringBuffer(String str),指定初始值。
注:在创建StringBuffer类时可以预估容量并指定其容量,这样可以减少扩容次数,提高效率。
StringBuilder类
StringBuilder与StringBuffer的区别:
StringBuffer有synchronized修饰,而StringBuilder没有synchronized修饰,所以StringBuffer是线程安全的,StringBuilder不是线程安全的。
基础类型对应的8个包装类
基本数据类型 | 包装类型 | 父类型 |
byte | Byte | Number |
short | Short | |
int | Integer | |
long | Long | |
float | Float | |
double | Double | |
boolean | Boolean | |
char | Character |
包装类的作用
当一个方法的参数列表为Object类型的参数,而却需要传一个基本数据类型的实参时,就需要用到包装类。如下代码片所示:
public static void doSome(Object obj){
System.out.println(obj);
}
public static void main(String[] args) {
Integer i = new Integer(2);
doSome(i);
}
自动装箱和自动拆箱
自动装箱:
自动拆箱:
Integer类的常用方法
- static int parseInt(String str)
将string类型转换为Int类型。
int value = Integer.parseInt("123") // 将“123”转换成int类型
注:如果写成Integer.parseInt(“大学牲”); 会报“ NumberFormatException ”的错误。
- static String toBinaryString(int i)
将十进制转换成二进制字符串。
String binaryString = Integer.toBinaryString(2);
- static String toHexString(int i)
将十进制转换成十六进制字符串。
String hexString = Integer.toHexString(2);
- static String toOctalString(int i)
将十进制转换成八进制字符串。
String octalString = Integer.toOctalString(2);
int,Integer和String类型之间的转换
Date类
Date的作用即用法
Date用来获取当前系统的时间
Date nowTime = new Date();
System.out.println(nowTime);
日期格式化
SimpleDateFormat是java.text包下的类,专门负责日期格式化。如下代码片演示:
Date nowTime = new Date();
System.out.println(nowTime);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String nowTimeStr = sdf.format(nowTime);
System.out.println(nowTimeStr);
字符串转换成日期
String date = "2022.06.25 14:40:25";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
try {
Date d = simpleDateFormat.parse(date);
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
注:指定的格式要与字符串的格式一致
System.currentTimeMillis()方法
获取自1970年一月一日 00:00:00 000到当前系统的总毫秒数。
long nowTimeMillis = System.currentTimeMillis();
System.out.println(nowTimeMillis);
数字类
数字格式化
“ DecimalFormat ”类负责数字的格式化。
- #代表任意数字
- , 代表千分位
- .代表小数点
DecimalFormat decimalFormat1 = new DecimalFormat("###,###.##");
String s1 = decimalFormat1.format(12314);
System.out.println(s1); // 打印结果:12,314
DecimalFormat decimalFormat2 = new DecimalFormat("###,###.000"); // .000表示自动补零
String s2 = decimalFormat2.format(123456);
System.out.println(s2); // 打印结果为:123,456.000
BigDeciaml
BigDecimal属于大数据,精度极高。其求和,求积等运算操作需要调用方法,如下代码片所示:
BigDecimal bigDecimal1 = new BigDecimal(100);
BigDecimal bigDecimal2 = new BigDecimal(2);
BigDecimal bigDecimal3 = bigDecimal1.add(bigDecimal2); // 求和运算
System.out.println(bigDecimal3); // 打印结果:102
BigDecimal bigDecimal4 = bigDecimal1.multiply(bigDecimal2); // 求积运算
System.out.println(bigDecimal4); // 打印结果:200
Random()
随机生成一个int类型取值范围之内的数:
Random random = new Random();
int a = random.nextInt();
System.out.println(a);
随机生成一个指定范围内的随机数:
Random random = new Random();
int a = random.nextInt(100);
System.out.println(a);
注:随机生成在0到100之间的数,不包括100!!!
枚举
- 枚举是引用数据类型
- 枚举的定义:
enum 枚举名{
枚举值1, 枚举值2
}
// 例如:
enum season{
SPRING, SUMMER, AUTUMN, WINTER
}
- 返回的结果只有两种情况建议使用boolean,结果超过两种,并且可以一个一个列举出来的,采用枚举类型。例如,季节,颜色,星期等。
- 具体取值使用 枚举名.枚举值,如下代码片所示
season.SPRING
season.SUMMER
异常
异常以类的形式存在,每一个异常类都可以创建异常对象。
public static void main(String[] args) {
int a = 1;
int b = 0;
int c = a/b; // 系统报错Exception in thread "main" java.lang.ArithmeticException: / by zero
System.out.println(c);
}
如上代码片:因为被除数为0,所以系统会报Exception in thread “main” java.lang.ArithmeticException: / by zero的错误。实际上,底层new了一个异常对象,如下代码片所示:
ArithmeticException ae = new ArithmeticException("/ by zero");
编译时异常
要求在编写程序阶段必须预先对这些异常进行处理,如果不处理就会报错。
发生概率高。
运行时异常
在编写程序阶段,可以预先处理,也可以不管。
发生概率低。
注:所有的异常都是在程序的运行阶段发生,因为异常的实质就是对象,而只有在程序运行时才会new对象。
异常的两种处理方式
throws
将异常上抛,谁调用有该异常的方法谁就得对异常进行处理,如果不处理就会报错。如下代码片所示:
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
try catch
将异常捕捉并进行处理。
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!");
}
public static void main(String[] args){
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注意:
- 如果异常没有捕捉,采用上报的方式,此方法后的代码不会执行;
- try catch 捕捉异常之后,后续代码可以执行。
finally
finally子句与try catch一起用,finall子句中的代码块是最后执行的,并且一定会执行
finally通常使用在关闭IO流中,子句代码块负责实现流的关闭。
异常的两个常用方法
getMessage()
获取简单的描述信息
NullPointerException e = new NullPointerException("空指针异常!!!");
String msg = e.getMessage();
System.out.println(msg); //打印结果:空指针异常!!!
printStackTrace()
打印异常堆栈信息
自定义异常
- 编写一个类继承Exception或者RuntimeException;
- 提供两个构造方法,一个无参的,一个带有String参数的。
public class MyException extends Exception{
public MyException(){
}
public MyException(String s){
super(s);
}
}
注:在创建异常对象时记得抛出!
集合
- 数组就是一个集合,集合实际上是一个容器,可以】来容纳其他类型的数据。
- 集合不能直接存储Java对象和基本数据类型,集合中存储的都是Java对象的内存地址。
- 每一个不同的集合,底层会对应不同的数据结构,往不同的集合中存储元素,等于数据放到了不同的 数据结构 当中。
- Collection 和 Map 都实现了 Iterable 接口,通过 Collection 和 Map 调用其继承接口 Iterable 中的Iterator()方法产生了一个 Iterator 接口
Collection
List
List继承了Collection接口,List是有序7可重复,存储元素有下标的集合
ArrayList
- ArrayList采用了数组数据结构,但不是线程安全的;
- ArrayList的初始化容量时10;
- 构造方法:new ArrayList(), new ArratList(int initialCapacity), new ArrayList(Collection c)
- ArrayList扩容容量为原来的1.5倍。
LinkedList
- LinkedList采用了 双向链表 数据结构。
- 链表的优点:随机增删效率高
- 链表的缺点:检索效率低
- 链表无初始化容量
Vector(不常用)
- Vector采用了数组数据结构,且是线程安全的。
- 初始化容量为10;
- 扩容容量为原来的2倍。
Set
Set继承了Collection接口,Set是无序8不可重复,存储元素无下标的集合
HashSet
底层实际上new了一个HashMap集合,向HashSet中存储元素实际上是存储到HashMap集合中了,HashMap是一个哈希表数据结构。
TreeSet
底层实际上new了一个TreeMap集合,向TreeSet中存储元素实际上是存储到TreeMap集合中了,TreeMap是一个二叉树数据结构。
TreeSet集合会按照元素的大小自动排序。
注:自定义类型无法自动排序,需要实现Comparable接口,并重写compareTo方法。
二叉树
迭代器
迭代器对象Iterator中有两个方法:
- boolean hasNext() 如果仍有元素可以迭代,返回true
- Object next() 返回迭代的下一个元素
迭代器的用法如下代码片所示:
public static void main(String[] args){
Collection c = new ArrayList<>();
c.add("a");
c.add("b");
c.add(100);
c.add(new Object());
c.add("大学牲");
Iterator iterator = c.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
注:当集合的结构发生改变时(比如在迭代的时候删除或增加了元素),迭代器必须重新获取,否则会出现异常
Map
- Map集合以key和value键值对的方式存储元素
- key和value都是引用数据类型
- key和value存储的都是对象的内存地址
- 所有Map集合的key的特点:无序不可重复
- Map的key和Set集合存储的元素特点相同
- key起主导作用,value时key的附属品
Map集合中的常用方法
- V put(K key, V value); 向Map集合中添加键值对
- V get(K key, V value); 通过key获取value
- Collection values; 获取集合中所有的value,返回一个Collection集合
- Set keySet(); 获取Map集合所有的key,返回一个Set集合
- Set<Map.Entry<K, V>> entrySet(); 将Map集合转换成Set集合
HashMap
- HashMap底层是哈希表数据结构,但不是线程安全的。
- HashMap的初始化容量是16,并且当数组容量达到75%的时候开始扩容
哈希表数据结构
- 哈希表数据结构是数组和链表的结合体。其实就是一个数组,每个数组的元素是一个单向链表。
- 哈希表会在底层调用“ hashCode() ”方法得出哈希值,通过哈希算法转换成数组下标。
- 哈希表还会调用“ equals() ”方法,将要查找的键值和元素的键值进行比较
- HashMap的初始化容量必须是2的倍数
Hashtable
底层是哈希表数据结构,且是线程安全的
Properties
线程安全的,在存储元素时以key和value的形式存储,并且key和value只支持String类型。Properties也被称为属性类。
setProperty(); 存储数据
getProperty(int key); 通过key值取出数据
TreeMap
二叉树数据结构
- 遵循左小右大的存放规则
- 有三种遍历方式:1.前序遍历:根左右;2.中序遍历:左根右;3.后序遍历:左右根。
泛型
含义及用法
使用List之后,表示List集合中只能存储Aniaml类型的数据,具体如下代码片所示:
List<Animal> myList = new ArrayList<Animal>();
作用
在不使用泛型,迭代集合中的对象并调用对象的具体方法时,由于迭代返回的类型是Object类,需要进行频繁的向下转型,才能调用类中特有的方法。使用泛型,使集合中只能存储一种类型的数据,就免去了频繁的向下转型这一步骤。
自动类型推断(钻石表达式)
有了自动类型推断之后,泛型的写法可以写成如下代码片所示:
List<Animal> myList = new ArrayList<>();
自定义泛型
自定义泛型是“ <> ”里写的是一个标识符,一般使用和。T是type是类型,E是element是元素。使用如下代码片所示:
public static void main(String[] args){
Generic1<String> generic1 = new Generic1<>();
generic1.doSome("abc");
Generic2<String> generic2 = new Generic2<>();
generic2.get();
}
}
class Generic1<E>{
public void doSome(E e){
System.out.println(e);
}
}
class Generic2<T>{
public T get(){
return null;
}
在创建Generic1对象是指定了其泛型为String类型,所以在调用其 doSome() 方法时,必须传一个String类型的参数,否则会报错;同样在创建Generic2对象时,指定了 get() 方法的返回值为String类型。
注:如果定义了泛型为不使用,则会默认为Object。
增强for循环(foreach)
foreach使遍历数组更简单,其语法为:
for (元素类型 变量名 : 数组或集合){
System.out.println(变量名);
}
具体如下代码片所示:
int[] array = {1, 2, 3, 4, 5};
for (int data : array){
System.out.println(data);
}
IO流
分类
- 输入流
往内存中去,叫做输入(Input),或者叫做读。 - 输出流
从内存中出来,叫做输出(Output),或者叫做写。 - 字节流
一次读取一个字节(byte),等同于一次读取8个二进制位。这种流可以读取文本文件,视频,声音,图片等 - 字符流
一次读取一个字符,只能读取普通文本文件。
典型的4种IO流: - InputStream 字节输入流
- OutputStream 字节输出流
- Reader 字符输入流
- Writer 字符输出流
注:1.只要类名以Stream结尾的都是字节流,以Reader / Writer结尾的都是字符流
2.调用完输出流后,一定要使用flush()方法进行刷新。
3.所有的流一定要使用close()关闭。
文件专属流
FileInputStream
FileInputStream的创建如下代码片所示:
public static void main(String[] args){
FileInputStream fis = null;
try {
fis = new FileInputStream("文件路径");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis == null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读取数据,如下代码片所示:
public static void main(String[] args){
FileInputStream fis = null;
try {
fis = new FileInputStream("文件路径");
while(true){
int readDate = fis.read();
if (readDate == -1){
break;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis == null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用循环语句进行数据读取。
FileOutputStream
创建文件并向文件中写入数据如下代码片所示:
public static void main(String[] args){
FileOutputStream fos = null;
try {
fos = new FileOutputStream("newFile");
fos.write(1);
fos.write(2);
fos.write(3);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos == null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注:文件不存在时会自动新建。
上述代码写入数据的原理是将文件内的数据先清空,再写入数据,这样做的风险大,可以进行如下代码片的修改:
public static void main(String[] args){
FileOutputStream fos = null;
try {
fos = new FileOutputStream("newFile", true);
fos.write(1);
fos.write(2);
fos.write(3);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos == null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在调用构造方法时传入一个true,就会以追加的方式向文件中添加数据。
FileReader
FileWriter
缓冲流
BufferedReader
BufferedReader是带有缓冲区的字符输入流。使用普通的流,在读取数据时,是一个一个字符读取的,效率较低,所以需要自定义byte数组来一次性读取多个字符来提高效率。而带有缓冲区的流则不需要自定义数组。
BuffereddReader中有一个“ readLine() ”方法,可以直接读取一行字符。如下代码片演示:
public static void main(String[] args){
FileReader fr = null;
BufferedReader br = null;
try {
fr = new FileReader("");
br = new BufferedReader(fr);
br.readLine();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br == null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedReader()的构造方法中需要传入一个流,被传入的流就是节点流,外部这个负责包装的流叫做包装流或者叫处理流。
注:在关闭流的时候,只需要关闭外层的包装流即可。
转换流
将字节流转换成字符流。
InputStreamReader
public static void main(String[] args){
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream("");
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (br == null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上代码片所示,将fis字节流转换成isr字节流。相对于fis和isr而言,fis属于节点流,isr属于包装流,相对于isr和br而言,isr属于节点流,br属于包装流。所以只需要关闭最外层的包装流即可,即只需关闭br流。
OutputStreamWriter
数据流
DataOutputStream
public static void main(String[] args){
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
fos = new FileOutputStream("文件名");
dos = new DataOutputStream(fos);
byte b1 = 100;
short s = 200;
int i = 300;
long l = 400;
float f = 500.0F;
double d = 600.0;
boolean b2 = false;
char c = 'a';
dos.writeByte(b1);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(b2);
dos.writeChar(c);
dos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (dos == null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上代码片所示,该流可将数据以及数据类型一并写入文件中且生成的文件不可轻易访问。
DataInputStream
public static void main(String[] args) {
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream("用DataOutputStream写入数据的文件名");
dis = new DataInputStream(fis);
byte b1 = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean b2 = dis.readBoolean();
char c = dis.readChar();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (dis == null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上代码片所示,FileOutputStream写入的数据只能使用FileInputStream读出,并且读的顺序要与写的顺序保持一致才能取出。
标准输出流
标准输出流不需要手动close()
PrintStream
默认输出到控制台
public static void main(String[] args) {
System.out.println("Hello World");
PrintStream ps= System.out;
ps.println("Hello World");
}
如上代码片所示,Syste.out实际上返回的是一个PrintStream类型,并且默认输出到控制台。
指定标准流的输出方向
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("log");
PrintStream ps = new PrintStream(fos);
System.setOut(ps);
System.out.println("在" + new Date() + "执行了程序");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
使用System.out进行设置,使输出到log文件当中,该方法一般用来写日志文件。
对象专属流
序列化(serialize):Java对象存储到文件中,将Java对象的状态保存下来的过程
反序列化(deserialize):将硬盘上的数据重新恢复到内存当中,恢复成Java对象
ObjectInputStream
使用ObjectInputStream进行序列化,如下代码片所示,将User类序列化:
public static void main(String[] args) {
User u = new User();
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("文件名");
oos = new ObjectOutputStream(fos);
oos.writeObject(u);
oos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos == null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注:1.参加序列化和反序列化的的对象,必须实现 Serializable 接口
2.序列化多个对象需采用序列化LIst集合,如果直接序列化多个对象的话,在序列化第二个对象时会报错
3.在不需要序列化对象的某个属性时,只需在前面加个transient关键字,如下代码片所示:
private transient String name;
ObjectOutputStream
使用ObjectOutputStream进行反序列化,如下代码片所示:
public static void main(String[] args) {
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("需要反序列化的文件名");
ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally{
if (ois == null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
序列化版本号
当修改了原先类中的属性时,再次进行反序列化就会报错,这是因为序列化版本号的存在。
当一个类实现了 Serializable 接口之后,会自动生成一个序列化版本号 。Java虚拟机识别一个类首先通过类名,如果类名一致,再区分序列化版本号。
修改类中的属性后,其默认的序列化版本也会发生改变,JVM就认为修改前后不是一个类,就会报错。
因此在实现了 Serializable 接口后建议自定义一个序列化版本号,这样,JVM就不会自动生成了,自定义如下所示:
private static final long serialVersionUID = 1L;
IO和Properties联合使用
以后经常改动的数据可以单独写到一个文件中,使用程序动态读取。以后只需要修改文件的内容,Java代码不需要改动。
File类
- File类不能完成程序的读和写。
- FIle是文件个目录路径名的抽象形式
- 一个File对象对应的可能是目录,也可能是文件
File类中的常用方法
FIle的构造方法:File(String pathname)
boolean exists()
判断文件或目录是否存在
boolean createNewFile()
如果不存在当前名称的文件,创建一个当前名称的文件
boolean mkdir()
创建一个当前名称的目录
boolean mkdirs()
上述常用方法,如下代码片所示:
public static void main(String[] args) {
File f1 = new File("D:\\file");
/*if (! f1.exists()){ // 判断file文件是否存在,如果不存在执行代码块
try {
f1.createNewFile(); // 以文件形式创建
} catch (IOException e) {
e.printStackTrace();
}
}*/
if (!f1.exists()){
f1.mkdir(); // 以目录的形式创建
}
File f2 = new File("D:\\a\\b\\c\\d");
if (!f2.exists()){
f2.mkdirs(); // 以多重目录的形式创建,a目录下有b目录,b目录下有c目录,c目录下有d目录
}
}
创建一个当面路径下不存在的所有目录
String getParent()
获取文件的父路径,以String类型的形式返回。
File getParentFile()
获取文件的父路径,以File类型的形式返回。
String getAbsolutePath()
获取文件的绝对路径。
多线程
进程与线程
- 进程是一个应用程序,线程是一个进程中的执行场景/执行单元。一个进程可以启用多个线程。
- 进程A和进程B的内存独立不共享。
- 线程A和线程B,堆内存和方法区内存共享
- 线程A和线程B的栈内存独立,一个线程一个栈
- 线程之间互不干扰
注:run()方法中的异常不能throws,只能try catch,因为run方法的父类没有抛出任何异常,子类不能比父类抛出更多的异常。
实现线程的方式
- 编写一个类,直接继承Thread,重写run方法;
创建线程对象使用new,启动线程使用对象名.start()9,如下代码片所示:
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < ; i++) {
System.out.println("主线程---->" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程----->" + i);
}
}
}
- 编写一个类实现Runnable接口,重写run()方法,继承Runnable的类不是线程类,是一个可运行的类,需要将可运行的对象封装成一个线程对象,如下代码片所示:
public class ThreadTest {
public static void main(String[] args) {
MyRunner mr = new MyRunner();
Thread t = new Thread(mr);
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程----->" + i);
}
}
}
class MyRunner implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("支线线程---->" + i);
}
}
}
线程对象的生命周期
- 新建状态
刚new出来的线程对象 - 就绪状态
就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺时间片的权力(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run()方法,run()方法的执行标志着线程进入运行状态。 - 运行状态
run()方法开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完以后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间片以后,会重新进入run()方法接着上一次的代码继续向下执行。 - 阻塞状态
当遇到阻塞事件(如sleep()方法,接受用户从键盘输入内容)时,会进入阻塞状态,阻塞状态的线程会放弃之前所占有的CPU时间片,而之前的时间片没了会再次进入就绪状态,所以在阻塞解除以后会再次进入就绪状态 - 死亡状态
run()方法结束后进入死亡状态。
获取当前线程对象
static Thread currentThread()
Sleep()
静态方法,参数是毫秒,让当前线程进入休眠,就是阻塞状态。
注:传的参数时毫秒!!!
终止线程睡眠
interrupt()方法终止线程的睡眠,不是终端线程的执行。该方式依靠了Java的异常处理机制。
终止线程
- 强制终止线程
调用stop()方法(已过时),该方法将线程直接“杀死”,容易丢失数据。 - 合理终止线程
打一个布尔标记,如下代码片所示:
public class ThreadTest {
public static void main(String[] args) {
MyRunner mr = new MyRunner();
Thread t = new Thread(mr);
t.setName("t");
t.start();
try {
Thread.sleep(5000); // 调用sleep()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
mr.mark = false; // 在此处想终止线程,于是将布尔标记改成false
}
}
class MyRunner implements Runnable{
boolean mark = true; // 打的布尔标记
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (mark){ // 当布尔标记位true时执行以下代码块
System.out.println(Thread.currentThread().getName() + "---->" + i); // 调用currentThread()方法和getName()方法获取当前对象的名字
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
return;
}
}
}
}
注:在什么时候想终止线程,就在什么时候将布尔标记改为false
与线程调度有关的方法
- 实例方法
- void setPriority(int newPriority),设置线程的优先级,优先级较高的,抢占的CPU时间片多一些。
int getPriority(),获取线程优先级
最低优先级:1
默认优先级:5
最高优先级:10 - void join(),合并线程,如下代码片所示
public class ThreadTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
try {
mt.join(); // 当前线程进入阻塞状态,mt线程执行,知道mt线程结束,当前线程才可以继续
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread{
}
- 静态方法
static void yield(),让位方法。暂停当前正在执行的线程对象,并执行其他线程。
注:yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
线程安全
在多线程并发的时候,有共享数据,并且修改共享的这个数据时,就存在线程安全问题。(典型案例:银行账户)
解决线程安全问题的方法:线程同步,实际上就是让线程排队执行,而不是并发。
- 异步线程模型(多线程并发)
线程A和线程B各自执行,互不干扰,不用排队 - 同步线程模型
在执行线程A时必须等待线程B执行结束,线程之间需要排队,互相等待
使用sychronized(){ } 解决线程安全问题,如下所示:
sychronized(共享对象){
需要同步的代码块
}
注:1.sychronized后的 () 传入的数据至关重要,这个数据必须是多线程共享的对象,才能达到多线程排队的效果
2.同步代码块越小,效率越高
例如:假设有A, B, C, D四个线程,只需要A,B两个线程排队,就需要在 () 内传入A,B共享的数据,而这个数据C,D不共享。
注:1.每个对象都有一把“锁”,其实这把锁就是标记,sychronized()的执行原理实际上是线程遇到了sychronized,就会去找 () 里共享对象的对象锁,找到之后占有这把锁,然后执行{ }里的同步代码块内容,在程序执行过程中一直占有着这把锁,知道代码块执行完毕,这把锁才会释放,线程B才能占用,并执行同步代码块。
2.sychronized出现在实例方法上,锁的一定是this,这种方法不灵活
死锁机制
开发中如何对待线程安全
- 尽量使用局部变量代替“实例变量”和“静态变量”
- 如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享
守护线程(后台线程)
- 垃圾回收器就是一个守护线程。一般守护线程是一个死循环,只要所有的用户线程结束,守护线程自动结束。
- 调用setDaemon方法,将线程设置为守护线程。
定时器(Timer类)
- 间隔特定时间,执行特定程序
- 操作数据备份时,要将定时器设定为守护线程,在特定时间进行备份,当程序不在操作时,就没必要备份。
实现线程的的三种方式
- 实现callable接口
- 方法优点:可以获取线程的执行结果
- 方法缺点:效率较低,在使用get方法获取对应线程的执行结果时,当前线程受阻
wait和notify
- wait和notify不是线程对象的方法,时任何一个对象都有的方法,这两个方法是Object类自带的
- 如下代码片所示:
Object obj = new Object();
obj.wait();
表示让正在obj对象上活动的线程进入等待状态,直到被唤醒为止
- 如下代码片所示:
Object obj = new Object();
obj.notify;
表示唤醒正在obj上等待的线程。
反射机制
- 通过反射机制可以操作字节码文件。
反射机制中重要的类
- java.lang.Classs:代表整个字节码,代表一个类型
- java.lang.reflect.Method:代表字节码中的方法字节码
- java.lang.reflect.Constructor:代表字节码中的构造方法字节码
- java.lang.reflect.Field:代表字节码中的属性字节码
获取字节码
- Class.forName()
1.静态方法
2.方法的参数是一个字符串
3.字符串需要一个完整的类名
4.完整的类名必须带有包名。Java.lang包也不可以省略
如下代码片所示:
public static void main(String[] args) {
try {
Class c1 = Class.forName("java.lang.String"); // c1代表String类
Class c2 = Class.forName("java.util.Date"); // c2代表Date类
Class c3 = Class.forName("java.lang.Integer"); // c3代表Integer类
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
注:如果只想让一个类的静态代码块执行,可以使用forName方法。
- getClass()
1.java中任何一个对象都有getClass()方法
2.getClass方法用来获取对象的类型
如下代码片所示:
public static void main(String[] args) {
String s = "abc";
Class c1 = s.getClass(); // 代表String类型
Date time = new Date();
Class c2 = time.getClass(); // 代表Date类型
}
- class属性
任何一种类型都有都有.class属性
如下代码片所示:
public static void main(String[] args) {
Class c1 = String.class; // 代表String类型
Class c2 = Integer.class; // 代表Integer类型
Class c3 = Date.class; // 代表Date类型
}
通过反射实例化对象
使用newInstance方法实例化对象,如下代码片所示:
public static void main(String[] args) {
try {
Class c = Class.forName("java.util.Date");
Object obj = c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
如上,newInstance方法会调用Date这个类的无参构造方法,完成对象的创建。所以必须保证类的无参构造方法存在,否则会报错。
获取绝对路径
为了可移植性,需要获取类的绝对路径,获取绝对路径如下代码片所示:
String path = Thread.currentThread().getContextClassLoader().getResource("文件名").getPath();
注:获取绝对路径的这个文件必须在类路径10下
资源绑定器
资源绑定器可以替代之前复杂的IO和Properties联合使用,如下代码片所示:
ResourceBundle rb = ResourceBundle.getBundle("属性文件的文件名");
String className = rb.getString("key的名字");
注:使用该方法时,绑定的文件必须是属性文件,也就是以properties结尾的文件,并且这个属性文件必须在类路径下,在写路径名时,properties后缀不用写
获取Field
Field[ ] getFileds()
获取类中所有public修饰的Field,并以Field数组的形式返回
Filed[ ] getDeclaredFields()
获取类中所有的Field,并以Field数组的形式返回
getType()
获取Field的类型
getModifiers()
获取Field的修饰符的代号
通过反射机制访问对象属性
如下代码片所示:
public static void main(String[] args) {
try {
Class c = Class.forName("类所在的文件路径");
Object obj = c.newInstance();
Field f = c.getDeclaredField("属性名");
f.set(obj, "要赋的值");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
如上,set方法是给obj对象的 f 属性赋指定的值。
- 当访问私有属性时,需使用setAccessible()方法来打破封装,如下代码片所示:
public static void main(String[] args) {
try {
Class c = Class.forName("类所在的文件路径");
Object obj = c.newInstance();
Field f = c.getDeclaredField("私有属性的属性名");
f.setAccessible(true);
f.set(obj, "要赋的值");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
可变长的参数
可变长度参数如下代码片所示:
public static void doSome(int... i){
System.out.println("我是一个可以传可变长的参数的方法");
}
public static void main(String[] args) {
doSome();
doSome(1);
doSome(1, 2);
doSome(1, 2, 3);
}
如上,doSome是一个可以传可变长的参数的方法。
- 可变长的参数要求的参数个数是0~N个
- 可变长度参数在参数列表中必须在最后一个位置上,且可变长度参数只能有一个
- 可变长度参数可以当作一个数组来看待
获取Method
Method[ ] getDeclaredMethods()
获取所有的方法,并以Method数组的形式返回
getName()
获取方法的方法名
getReturnType()
获取方法的返回值类型
Class[ ] getParameterTypes()
获取方法的参数列表
通过反射机制调用对象方法
如下代码片所示:
public static void main(String[] args) {
try {
Class c = Class.forName("文件名");
Object obj = c.newInstance();
Method m = c.getDeclaredMethod("方法名", 参数名.class, 参数名.class);
Object vale = m.invoke(obj, "输入的实参", "输入的实参");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
如上,invoke方法调用的是obj对象的m方法,并传入了指定参数。
获取Constructor
获取父类和父接口
注解
- 也可以叫做注释,英文单词Annotation
- 注解Annotation是一种引用数据类型
- 自定义注解的语法格式为:
[修饰符列表] @interface 注解类型名{
}
如下代码片所示:
public @interface MyAnnotation{
}
- 注解的使用格式是:@注解类型名
- 注解可以出现在类上,属性上,方法上,变量上等;注解还可以出现在注解类型上
- 用“ @Deprecated ”注释的程序元素表示不推荐使用这样的元素
- 用“ @Override ”表示一个方法声明打算重写父类中的另一个方法声明
元注解
- 元注解是用来标注注解类型的注解
- 常见的元注解有Target,Retention
Target注解:
1.Target注解是用来标注注解类型的注解
2.用来标注“被标注的注解 ”只能出现在指定位置上
@Target(ElementType.METHOD):表示“被标注的注解 ”只能出现在方法上
Retention注解:
1.Retention注解用来标注注解类型的注解
2.用来标注“被标注的注解 ”最终保存到哪里
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中
@Retention(RetentionPolicy.CLASS):表示该注解只被保留在class文件中
@Retention(RetentionPolicy.RUNTIME):表示该注解只被保留在class文件中,并且可以被反射机制所读取
注解中指定属性
如下代码片所示:
public @interface MyAnnotation {
String name();
}
如上,定义了一个名为MyAnnotation的注解,并在里面添加了String类型的name
调用该注解如下代码片所示:
public static void main(String[] args) {
@MyAnnotation(name = "s")
int a = 1;
}
在调用时要采用“ @MyAnnotation(属性名 = 属性值) ”的方法
注:1.可以使用default给属性指定默认值,在调用时可以不用对该属性赋值,如下代码片所示:
public @interface MyAnnotation {
String name() default "";
}
给name属性指定默认值为空,在调用时就可以不用在赋值了。
2.属性是value时可以省略,如下代码片所示:
自定义注解:
public @interface MyAnnotation {
String value();
}
调用注解:
public static void main(String[] args) {
@MyAnnotation("s")
int a = 1;
}
调用时不必再写@MyAnnotation(value =“s”),可将value省略,写成@MyAnnotation(“s”),当属性为其他时则必须加上属性名。
元注解的使用
- 只允许这个类用在类和方法上如下代码块所示:
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
String value();
}
- 允许这个注解被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
标识符用来标识类名,方法名,变量名,接口名,常量名………
凡是自己有权力命名的都是标识符。 ↩︎将右边表达式的结果传给左边的变量。 ↩︎
Java中定义了public、protected、private、abstract、static、final、synchronized等修饰词 ↩︎
在类的内部又定义了一个新的类,被称为内部类。 ↩︎
存进去是这个顺序,取出来还是这个顺序。 ↩︎
存进去是这个顺序,取出来不是这个顺序。 ↩︎
start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈内存空间,这段代码完成以后瞬间就结束了,线程就启动成功了。启动成功的线程会自动调用run()方法并且run()方法在分支栈的栈底,和main()是平级的 ↩︎
在src下的都是类路径下,src是类的根路径 ↩︎