Java Study Node
一、绪论
1 基础常识
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzhVm40j-1630855915473)(E:%5C%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3%5CJavaImg%5Cjava%E5%8F%91%E5%B1%95%E5%8F%B2.png)]
空指针不能直接打印输出
常用DOS命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6PkkO9e-1630855915474)(E:%5C%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3%5CJavaImg%5C%E5%B8%B8%E7%94%A8DOS%E5%91%BD%E4%BB%A4.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2iMrNGi1-1630855915475)(E:%5C%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3%5CJavaImg%5Cimage-20210614231201696.png)]
2 数据类型
大数
如果基本的整数和浮点数精度都不能满足需求,那么可以使用java.math
包中的两个很有用的类BigInteger
和BigDecimal
这两个类可以处理包含任意长度数字序列的数值。BigInteger
类实现任意精度的整数计算,BigDecimal
类实现任意精度的浮点数运算。
使用静态的valueOf
方法可以将普通的数值转换为大数
BigInteger a = BigInteger.valueOf(100);
不能使用人们熟悉的的算术运算符(如**+和-**)处理大数,而需要使用大数类中的add
和multiply
方法
BigInteger a = BigInteger.valueOf(100);
BigInteger b = BigInteger.valueOf(50);
BigInteger c = a.add(b);
System.out.println(c);
数组
定义方法
int[] a = new int[100];
int a[] = new int[100];
数组简单赋值
int a[] = {1,2,3,4,5,6};
如果想要获得数组中元素个数,可以使用array.length
for(int i=0;i<a.length;i++){
System.out.println(a[i]);
}
如果希望将一个数组中的所有值拷贝到另一个新的数组中去,就要使用Arrays类的copyOf
方法
int a[]= new int[100];
for (int i=0;i<10;i++){
a[i]=i;
}
int b[] = Arrays.copyOf(a,a.length);
System.out.println(Arrays.toString(b));
for each强循环
java中有一种功能很强的循环结构,可以用来依次处理数组(或者其他元素集合)中的每个元素,而不必考虑指定下标值
语句格式
for(variable : collection) statement
变量 : 数组or实现了Iterable接口的类对象
for(int element:a)
System.out.println(element);
for each循环语句的循环变量将会遍历数组中的每个元素,而不是下标值。
3 函数方法
3.1 String字符串API
使用方法
String 变量名 = 字符串值
函数属性 | 说明 |
---|---|
boolean equalsIgnoreCase(String anotherString) | 如果字符串与other相等(忽略大小写),返回True |
boolean equals(Object other) | 判断两个字符串是否相同 |
boolean empty() | 判断字符串是否为空串 |
int length() | 返回字符串长度 |
String toLowerCase() | 把所有的字符串转换称小写字母 |
String toUpperCase() | 把所有的字符串转换称大写字母 |
String trim() | 去除字符串前导和尾随的空格 |
String repeat(int count) | 将当前字符串重复count次 |
char charAt(int index) | 返回某索引处的字符 |
String concat(String str) | 将指定字符串连接到此字符串的结尾。等价于“+” |
String substring(int beginIndex) | 返回一个新字符串,是此字符串从beginIndex开始截取到最后的一个子字符串 |
String substring [int beginIndex,int endIndex) | 返回一个新字符串,是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串 |
boolean endsWith(String suffix) | 测试此字符串是否以指定的后缀结束 |
boolean startsWith(String prefix) | 测试此字符串是否以指定的前缀开始 |
boolean startsWith(String prefix,int toffset) | 测试此字符串是否以指定索引开始的子字符串是否以指定前缀开始 |
boolean contains(CharSequence s) | 当且仅当此字符串包含指定的char值序列时,返回true |
int indexOf(String str) | 返回指定子字符串在此字符串中第一次出现的索引,未找到返回-1 |
int indexOf(String str,int fromIndex) | 返回指定子字符串在此字符串中第一次出现的索引,从指定的索引开始 |
int lastIndexOf(String str) | 返回指定子字符串在此字符串中最后边出现处的索引,未找到返回-1 |
int lastIndexOf(String str,int fromIndex) | 返回指定子字符串在此字符串中最后边出现处的索引,从指定的索引开始反向搜索 |
String replace( char OldString ,char newString) | 返回一个用newString代替oldString中的所有字符的字符串。 |
String replace(charSequence target,charSequence replacement) | 使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串 |
String replaceAll(String regex,String replacement) | 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串 |
String replaceFirst(String regex,String replacement) | 使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串 |
boolean matches(String regex) | 告知此字符串是否匹配给定的正则表达式 |
String[] split(String regex) | 根据给定正则表达式的匹配拆分此字符串 |
String[] split(String regex,int limit) | 根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中 |
int compareTo(String other)属性
如果A compareTo B,若A的首字符在B的后面,返回一个正数;
若A的首字符在B之前,返回一个负数;
返回的数是字符串A和B之间的字母差值。
若两个字符串相等则返回0。"" 和 “compareTo()” 和 “equals()" 函数的区别
如果等号的两边是基本数据类型,比如int,double,那么等号就用来单纯的比较他们的数值大小;
如果等号两边放的是两个对象,那么就会比较他们在内存当中的地址。
compareTo()会返回数字,如果两个字符串内容相同,会返回0,字符串a大于字符串b,会返回相差的ASCII码的正数,字符串a小于字符串b,会返回相差的ASCII码的负数。
equals是先用等号()直接来比较两个对象在内存当中的地址,如果相等会直接返回true,如果这两个对象的地址不一样,就会考虑这两个对象是不是String类型的,如果是String类型的,那先比较两个字符串长度是否一样,如果长度不一致,那100%不相等,直接返回false。长度一致则逐个比较
注compareTo()会返回二者的差值,即返回的是一个数字;而equals就简单一些,只返回true或者false。
3.2 系统输入Scanner
使用方法
Scanner 变量名 = new Scanner(System.in);
函数属性 | 函数说明 |
---|---|
int nextInt() | 等待输入一个整数 |
double nextDouble() | 读取一个浮点数或整数的字符序列 |
String nextLine() | 读取下一行的内容 |
String next() | 读取输入的下一个单词(以空格作为分隔符) |
3.3 Random随机数
作用:用于产生一个随机数
使用步骤
- 导包
import java.util.Random;
- 创建对象
Random r = new Random();
- 获取随机数
int number = r.nextInt(100); //获取数据的范围:【0,10)
4 属性
对属性可以赋值的位置:
- 默认初始化
- 显示初始化/在代码块中赋值
- 构造器中初始化
- 有对象以后,可以通过“对象.属性”或“对象.方法”的方式进行赋值
二、IDEA
快捷键
快速生成main()方法:
psvm
,回车快速生成输出语句:
sout
,回车内容提示,代码补全:
ctrl+alt+space
单行注释:选中代码,
ctrl+/
,再来一次,就是取消多行注释:选中代码,
ctrld+shift+/
,再来一次,就是取消格式化代码:
ctrl+alt+L
快捷生成部分代码
/*生成if判断语句*/
boolean flag = true;
if(flag){
//flag.if
}
if(!flag){
//flag.else
}
/*生成for/while循环语句*/
for (int i =0;i<10;i++){
//fori
}
while(flag){
//flag.while
}
/*var关键字*/
String s = new String(); //new String().var
People people = new People(); //new People().var
/*try异常*/
try { //int num= 10/0;.try
int num =10/0;
} catch (Exception e) {
e.printStackTrace();
}
/*List集合*/
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
for (Integer integer : list) {
//list.for
}
for (int i = 0; i < list.size(); i++) {
//list.fori
}
for (int i = list.size() - 1; i >= 0; i--) {
//list.forr
}
三、面向对象
四种权限修饰符
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
(缺省) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
对于Class的权限修饰只可以用public
和default
public类可以在任意地方被访问
default类只可以被同一个包内部的类访问
封装性
继承性
优点:
- 减少代码冗余,提高了代码的复用性
- 便于功能的扩展
- 为之后多态性的使用,提供了前提
特点:
1.Java只支持单继承和多层继承,不允许多重继承
- Java中的单继承:一个子类只能有一个父类
- 一个父类可以派生出多个子类
方法的重写
定义
:子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作。
应用
:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
重写方法的声明:
:
权限修饰符 返回值类型 方法名() throws 异常的类型{
//方法体
}
super关键字
super可以用来调用:属性
、方法
、构造器
- 我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略“super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显示的使用“super.属性”的方式,表明调用的是父类中声明的属性
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显示的使用“super.方法”的方式,表明调用的是父类中被重写的方法
super调用构造器
- 我们可以在子类的构造器中显示的使用“super(形参列表)"的方式,调用父类中声明的指定的构造器
- ”super(形参列表)"的使用,必须声明在子类构造器的首行
- 我们在类的构造器中,针对于“this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
- 在构造器的首行,没显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的时父类中空参的构造器:super()
- 在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
Object父类
Object类时所有Java类的根父类
Object类中的主要结构
NO. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容“是否相等。
equals方法
因为equals()方法只能适用于引用数据类型,通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容“是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写equals()方法:
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1.equals(cust2)); //比较两个对象是否相同
public boolean equals(Object obj){
if(this == obj){ //判断地址值是否相同
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer)obj;
return this.age == cust.age && this.name.equals(cust.name); //比较两个对象的每个属性是否都相同
}
return false;
}
toString方法
当我们输出一个对象的引用时,自动调用toString()方法
像String、Date、File、包装类等都重写了Object类中的toString()方法。
多态性
对象的多态性:父类的引用指向子类的对象
如 Person people = new Man();
多态的使用:
在编译期,只能调用父类中声明的方法,但在运行期,当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法
总结:编译看左边,执行看右边
对象的多态性只适用于方法,不适用于属性
总结:编译运行都看左边
使用前提
- 必须要有类的继承关系
- 子类对父类方法的重写
instanceof操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型
使用情景:
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof判断,
一旦返回true,就进行向下转型;返回false则不向下转型
包装类
针对八种基本数据类型定义相应的引用类型——包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
基本数据类型-->包装类:调用包装类的构造器
public void test1{
int num =10;
Integer ig1 = new Integer(num);
System.out.println(ig1.toString());
}
包装类-->基本数据类型:调用包装类的xxxValue()
public void test2{
Integer ig2 = new Integer(10);
int i1 = ig2.intValue();
System.out.println(i1+1);
}
自动装箱与自动拆箱
public void test3{
int num1 =10;
Integer i1 = num1; //自动装箱:基本数据类型-->包装类
int num2 = i1; //自动拆箱:包装类-->基本数据类型
}
基本数据类型、包装类-->String类型:调用String重载的valueOf(Xxx xxx)
public void test4{
int num =10;
String str1 = num + ""; //方式1:做连接运算
float f = 12.3f;
String str2 = String.valueOf(f); //方式2:调用String的valueOf()
}
String类型-->基本数据类型、包装类:调用包装类的parseXXX(String s)
public void test5{
String str = "123";
int num = Integer.parseInt(str);
System.out.println(num+1);
}
Integer m = 1;
Integer n =1;
System.out.println(m==n);//true
Integer a =128;
Integer b=128;
System.out.println(a==b); //false
//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内,可以直接使用数组中的元素,不用再去new了。
//目的:提高效率
代码块
代码块的作用:用来初始化类、对象
代码块如果有修饰,只能使用static
静态代码块和非静态代码块格式
static{
System.out.println("我是静态代码块");
}
{
System.out.println("我是非静态代码块");
}
静态代码块和非静态代码块区别
静态代码块:1.内部可以有输出语句
2.随着类的加载而执行,且只执行一次
3.作用:初始化类信息
4.静态代码块的执行优先于非静态代码块
非静态代码块:1.内部可以有输出语句
2.随着对象的创建而执行,每创建一次就执行一次非静态代码块
3.作用:可以在创建对象时,对对象属性进行初始化
关键字
static关键字
如果想要某些特定的数据在内存空间里只有一份,需要使用static关键字声明
static可以修饰:属性、方法、代码块、内部类
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,每个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时显示的是修改过的。
static修饰属性的其他说明
- 静态变量随着类的加载而加载,可以通过“类.静态变量”方式进行调用
- 静态变量的加载要早于对象的创建
- 由于类只会加载一次,则静态变量在内存中也会只存在一份:存在方法去的静态域中。
静态方法(类变量) 非静态方法(实例变量)
类 yes no
对象 yes yes
static注意点
- 静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
- 在静态方法内不能使用this关键字、super关键字
开发中如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同
开发中如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上设置为static的
final关键字
final可以用来修饰的结构:类、方法、变量
final用来修饰一个类:此类不能被其他类所继承
final用来修饰方法:表明此方法不可以被重写
final用来修饰变量:此时的“变量”就变成了常量
final修饰属性可以考虑的位置有:显示初始化、代码块中初始化、构造器中初始化
abstract抽象类
abstract可以用来修饰的结构:类、方法
abstract不能用来修饰私有方法、静态方法、final方法、final类
abstract修饰类:抽象类
抽象类不能实例化
抽象类中一定有构造器,便于子类实例化时调用
开发中都会提供抽象类的子类,让子类对象实例化,完成相关操作
abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法体。如:public abstract void eat();
若子类重写了父类中的所有抽象方法后,此子类方可实例化;若子类没有重写了父类中的所有抽象方法,表明该子类也是一个抽象类,需要使用abstract关键字修饰
抽象类的匿名子类
Worker worker = new Worker();
method(worker); //非匿名类非匿名对象
method(new Worker()); //非匿名类匿名对象
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
}; //匿名类非匿名对象
method(p);
method(new Person(){ //匿名类匿名对象
@Override
public void eat() {
System.out.println("吃好吃的东西");
}
@Override
public void breath() {
System.out.println("呼吸新鲜空气");
}
});
static | 可以修饰 属性、方法、代码块、内部类 |
---|---|
final | 可以修饰 类、方法、变量 |
abstract | 可以修饰 类、方法;不能用来修饰私有方法、静态方法、final方法、final类 |
interface接口
接口使用interface定义,在Java中,接口和类是并列的两个结构。
如何定义接口中的成员:
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final的
抽象方法:public abstract的
JDK8:除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
1.接口中定义的静态方法,只能通过接口来调用
2.通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时仍调用的是重写以后的方法
3.如果子类继承父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法。——>类优先原则
4.如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。——>接口冲突
5.如何在子类的方法中调用父类、接口中被重写的方法
public void myMethod(){
method(); //调用自己定义的重写方法
super.method(); //调用的是父类中声明的方法
CompareA.super.method(); //调用接口中的默认方法
CompareB.super.method();
}
接口中不能定义构造器,意味着接口不可以实例化
在Java开发中,接口通过让类去实现(implements)的方式来使用
若实现类覆盖了接口中的所有抽象方法后,则实现类方可实例化;若实现类没有覆盖接口中的所有抽象方法,表明实现类仍为一个抽象类
接口与接口之间可以继承,且可以多继承
接口中定义的静态方法,只能通过接口来调用
通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写后的方法
内部类
java中允许将一个类A声明在另一个类B内,则类A就是内部类,类B成为外部类
内部类分为成员内部类和局部内部类
成员内部类:1.作为外部类的成员:
调用外部类结构;可以被static修饰;可以被4中不同的权限修饰
2.作为一个类:
类内可以定义属性、方法、构造器等;
可以被final修饰,表示此类不可被继承;可以被abstract修饰
如何创建成员内部类的对象?
Person.Dog dog = new Person.Dog(); //创建Dog实例(静态成员内部类)
//创建Bird实例(非静态的成员内部类)
Person p = new Person();
Person.Bird bird = p.new Bird();
四、异常
在Java语言中,将程序执行中发生的不正常情况称为“异常”
Java异常分为两类:Error和Exception
Error:Java虚拟机无法解决的严重问题,一般不编写针对性的代码进行处理。
Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护
异常处理机制
程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
try-catch-finally
try {
// 可能出现异常的代码
}catch (异常类型1 变量名1){
// 处理异常的方式1
}catch (异常类型2 变量名2){
// 处理异常的方式2
}catch (异常类型3 变量名3){
// 处理异常的方式3
}
...
finally{
// 一定会执行的代码
}
执行流程
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没写finally的情况),继续执行其后的代码。
finally中声明的是一定会被执行的代码。像数据库连接、输入输出流、网络编程socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时资源的释放,就需要声明在finally中。
throws+异常类型
throws+异常类型
写在方法的声明处,知名此方法执行时,可能会抛出的异常类型。
import java.io.File;
import java.io.IOException;
public void method() throws IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.println((char)data);
data = fis.read();
}
fis.close();
}
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
区别
try-catch-finally:真正的将异常给处理到了
throws的方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉
如何选择?
如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catc-finally方式处理
五、多线程
基本概念
程序
(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程
(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。一一生命周期
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程
(thread): 进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)
多线程的创建
- 创建一个继承于Thread类的子类
- 重写
- 创建Thread类的子类对象
- 通过此对象调用start()方法 ①启动当前线程 ②调用当前线程的run()
【注】:不能直接通过调用run()的方式启动线程
线程只能调用一次,不能重复调用
方式一:继承Thread类
//遍历100以内的偶数
class MyThread extends Thread{ //①创建一个继承于Thread类的子类
@Override
public void run() { //②重写
for (int i = 0; i < 100; i++) {
if (i%2 == 0 ){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread(); //③创建Thread类的子类对象
t1.start(); //④对象调用start()方法
}
}
方式二:实现Runnable方法
//1.创建一个实现了Runnable接口的类
class MyThread1 implements Runnable{
@Override
public void run() { //2.实现类去实现Runnable接口中的抽象run()方法
for (int i = 0; i < 100; i++) {
if (i%2 == 0) {
System.out.println(i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类对象
MyThread1 myThread = new MyThread1();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(myThread);
//5.通过Thread类的对象调用start()
t1.start();
}
}
JDK5.0新增两种线程创建方式:Callable接口和线程池
方式三:实现Callable接口
与使用Runnable相比,Callable功能更强大。具体表现为:
- 相比run()方法,可以有返回值
- 方法可以抛异常
- 支持泛型的返回值
- 需要借助FutureTask类
FutureTask是Future接口的唯一实现类
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum =0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class CallableThread {
public static void main(String[] args) {
//3.创建Callable接口实现类对象
NumThread nt = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(nt);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callab实现类重写的Call()的返回值
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式四:线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以有效避免频繁创建销毁、实现重复利用。
优点
- 提高相应速度(减少了新线程创建的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要反复创建)
- 便于线程管理
- √ corePoolSize:核心池的大小
- √ maximunPoolSize: 最大线程数
- √ keepAliveTime :线程没有任务时最多保持多长时间后会终止
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性
//ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//service1.setCorePoolSize(15);
//service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口的实现类
service.execute(new NumberThread()); //适用于Runnable
//service.submit(Callable callable); //适用于Callable
//3.关闭连接池
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName() + ":" +i);
}
}
}
}
Thread类中的方法
start() | 启动当前线程;调用当前线程的run() |
---|---|
run() | 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 |
currentThread() | 静态方法,返回执行当前代码的线程 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
yield() | 释放当前CPU的执行权 |
join() | 在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完后,线程A才结束阻塞状态 |
sleep(long millitime) | 让当前线程“睡眠”指定的毫秒,在指定millitime毫秒时间内,当前线程是阻塞状态 |
isAlive() | 判断当前线程是否存活 |
线程优先级
线程的优先等级
Max_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 (默认)
如何获取和设置当前线程的优先级
- getPriority():获取线程的优先级
- setPriority():设置线程的优先级
【说明】:高优先级的线程要抢占低优先级线程的CPU执行权。但是只是从概率上讲高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程被执行完以后,低优先级的线程才执行。
线程的生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它己具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法去定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程同步
方式一 synchronized锁
//方式二:实现Runnable方法
class window implements Runnable{
//实现Runnable方法不用加static静态
private int ticket = 100;
//Object obj = new Object(); //多个线程必须要共用同一把锁
@Override
public void run() {
while (true){
show();
}
}
//解决线程安全重票问题
public synchronized void show(){ //同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
ticket--;
}
}
}
方式二:使用同步代码块解决继承Thread类
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器
class window extends Thread{
private static int ticket = 100;
//private static Object obj = new Object();
@Override
public void run() {
while (true){
show();
}
}
private static synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
ticket--;
}
}
}
方式三:Lock锁
class window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定方法lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
ticket--;
}else{
break;
}
}finally {
// 3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
总结
- 非静态的同步方法,同步监视器是this
- 静态的同步方法,同步监视器是:当前类本身
线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
释放锁的操作
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到了break、return终止了该代码块、该方法的继续执行
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不释放锁的操作
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
线程通信
线程通信方法 | 说明 |
---|---|
wait() | 一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器 |
notify() | 一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程 |
notifyAll() | 一旦执行此方法,就会唤醒所有被wait的线程 |
class Number implements Runnable{
private int number = 0;
@Override
public void run() {
while (true){
synchronized (this){
notify();
if (number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
六、Java常用类
1.字符串相关的类
String是一个final类,代表不可变的字符序列
String实现了Serializable接口:表示字符串是支持序列化的
实现了Comparable接口:表示String可以比较大小
常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量
只要其中一个是变量,结果就在堆中。
如果拼接的结果调用intern()方法,返回值还在常量池中
String 与char[ ]之间的转换
@Test
public void test1(){
String str1 = "abc123";
//String --> char[]:调用String的toCharArray()方法
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
//char[] --> String:调用String的构造器
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);
}
String 与byte[ ]之间的转换
@Test
public void test2(){
//编码:String --> byte[]:调用String的getBytes()方法
//解码:byte[] --> String:调用String的构造器
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();
System.out.println(Arrays.toString(bytes));
String str2 = new String(bytes);
System.out.println(str2);
}
StringBuffer和StringBuilder
String、StringBuffer、StringBuilder三者的异同?
String:不可变的字符序列
StringBuffer:可变的字符序列:线程安全,但效率低
StringBuilder:可变的字符序列:jdk5.0新增的,线程不安全,但效率高
StrngBuffer扩容问题
如果要添加的数据底层盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
StringBuffer类的常用方法
总结 | 方法 | 描述 |
---|---|---|
增 | StringBuffer append() | 提供了很多的append()方法,用于进行字符串拼接 |
删 | StringBuffer delete(int start,int end) | 删除指定位置的内容 |
改 | StringBuffer replace(int start,int end,String str) | 把[start,end)位置替换为str |
public void setCharAt(int n,char ch) | ||
查 | public char charAt(int n) | |
插入 | StringBuffer insert(int offset,xxx) | 在指定位置插入xxx |
反转 | StringBuffer reverse() | 把当前字符序列逆转 |
长度 | StringBuffer int length() | 返回StringBuffer类的长度 |
遍历 | for + charAt() | |
StringBuffer toString() |
2.JDK8之前的日期时间API
1.java.lang.System类
System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
此方法适用于计算时间差
2.java.util.Date类
//将java.util.Date对象转换为java.sql.Date对象
Date date2 = new Date();
java.sql.Date date3 = new java.sql.Date(date2.getTime());
System.out.println(date3);
3.Java.util.SimpleDateFormat类
格式化:日期 --> 字符串
解析:字符串 --> 日期
@Test
public void test1() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
//格式化: 日期 --> 字符串
Date date = new Date();
System.out.println(date);
String format = sdf.format(date);
System.out.println(format);
//解析: 字符串 --> 日期
Date date1 = sdf.parse(format);
System.out.println(date1);
}
4. java.util.Calendar日历类
获取Calendar实例的方法
使用Calendar.getInstance()方法
调用它的子类GregorianCalendar构造器
常用方法:
public void set(int field,int value)
public int get(int field)
public add(int field,int amount)
public final Date getTime()
public final void setTime(Date date)
【注】1.获取月份是:一月是0,二月是1,以此类推,12月是11
2.获取星期时:周日是1,周二是2,…,周六是7
3.JDK8中新日期时间API
java.time 包含值对象的基础包
LocalDate、LocalTime和LocalDateTime类是其中较重要的几个类,他们的实例是不变的对象。
LocalDate代表IOS格式()的日期,可以存储生日、纪念日等日期
LocalTime类表示一个时间,而不是日期
LocalDateTime是用来表示日期和时间的,最常用的类之一
方法 | 描述 |
---|---|
now()/now(ZoneId zone) | 静态方法,根据当前时间创建对象/指定时区对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth()/getDayOfYear() | 获得月份天数(1-31)/获得年份天数(1-366) |
getDayOfWeek() | 获得星期几 |
getMonth() | 获得月份 |
getMonthValue()/getYear() | 获得月份(1-12)/获得年份 |
getHour()/getMinute()/getSecond() | 获得当前对象队形的小时、分钟、秒 |
withDayOfMonth()/withDayOfYear()/withMonth()/withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays()/plusWeeks()/plusMonths()/plusYears()/plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths()/minusWeeks()/minusDays()/minusYears()/minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
Instant时间戳
方法 | 描述 |
---|---|
now() | 获取本初子午线对应的标准时间 |
ofEpochMilli(long epochMilli) | 返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象 |
atOffset(ZoneOffset offset) | 添加时间的偏移量 |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的时间戳 |
java.time.format.DateTimeFormatter
方法 | 描述 |
---|---|
ofPattern(String pattern) | 返回一个指定字符串格式的DateTimeFormatter |
format(TemporalAccessor t) | 格式化一个日期、时间,返回字符串 |
parse(CharSequence text) | 将指定格式的字符序列解析为一个日期、时间 |
4.java比较器
Java的对象如果需要比较对象的大小,那么需要使用Comparable或Comparator
Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小
Comparator接口属于临时性的比较
java实现对象排序的方式有两种
- 自然排序:java.lang.Comparable
/**
* Comparable接口:自然排序
* 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了两个对象从小到大的排序比较
* 重写compareTo(obj)的规则:
* 1.如果当前this对象大于形参对象obj,则返回正整数
* 2.如果当前this对象小于形参对象obj,则返回负整数
* 3.如果当前this对象等于形参对象obj,则返回0
*/
@Test
public void test1(){
Goods[] arr = new Goods[5];
arr[0] = new Goods("lenovoMouse",34);
arr[1] = new Goods("DellMouse",43);
arr[2] = new Goods("xiaomiMouse",12);
arr[3] = new Goods("huaweiMouse",65);
arr[4] = new Goods("MicrosoftMouse",43);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
- 定制排序:java.util.Comparator
/**
* Comparator接口的使用:定制排序
* 背景:当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
* 或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序
* 重写compare(Object o1,Object o2)方法,比较o1和o2的大小
* 如果方法返回正整数,表示o1 > o2
* 如果返回负整数,表示 o1 < o2
* 如果返回0,表示相等
*/
@Test
public void test2(){
String[] arr = new String[]{"AA","CC","KK","MM","GG","JJ","DD"};
Arrays.sort(arr, new Comparator<String>() {
//字符串从大到小排序
@Override
public int compare(String o1, String o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 =(String)o1;
String s2 =(String)o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据类型不一致");
}
});
System.out.println(Arrays.toString(arr));
}
5.System类
常用方法
方法 | 说明 |
---|---|
native long currentTimeMillis() | 返回当前的计算机时间 |
void exit(int status) | 退出程序 |
void gc() | 请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况 |
String getProperty(String key) | 获得系统中属性值为key的属性对应的值 |
getProperty属性中key的属性名
属性名 | 属性说明 |
---|---|
java.version | java运行时环境版本 |
java.home | Java安装目录 |
os.name | 操作系统的名称 |
os.version | 操作系统的版本 |
user.name | 用户的账户名称 |
user.home | 用户的主目录 |
user.dir | 用户的当前工作目录 |
6.Math类
属性 | 描述 |
---|---|
abs | 绝对值 |
acos,asin,atan,cos,sin,tan | 三角函数 |
sqrt | 平方根 |
pow(double a,double b) | a的b次幂 |
log | 自然对数 |
exp | e为底指数 |
max(double a,double b) | 求最大值 |
min(double a,double b) | 求最小值 |
random() | 返回0.0到1.0的随机数 |
long round(double a) | double型数据a转换为long型(四舍五入) |
toDegrees(double angrad) | 弧度 --> 角度 |
toRadians(double angdeg) | 角度 --> 弧度 |
7.BigInteger类和BigDecimal类
BigInteger可以表示不可变的任意精度的整数。
常用方法
方法 | 说明 |
---|---|
public BigInteger abs() | 返回BigInteger绝对值的BigInteger |
BigInteger add(Biginteger val) | 返回其值为(this+val)的BigInteger |
BigInteger subtract(Biginteger val) | 返回其值为(this-val)的BigInteger |
BigInteger multiply(Biginteger val) | 返回其值为(this*val)的BigInteger |
BigInteger divide(Biginteger val) | 返回其值为(this/val)的BigInteger |
BigInteger remainder(Biginteger val) | 返回其值为(this%val)的BigInteger |
BigInteger[] divideAndRemainder(Biginteger val) | |
BigInteger pow(int exponent) | 返回其值为(this^exponent)的BigInteger |
一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类
构造器
public BigDecimal(double val)
public BigDecimal(String val)
常用方法
public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode)
七、枚举类与注解
枚举类
当类的对象只有有限个、确定的时,推荐使用枚举类
当需要定义一组常量时,强烈建议使用枚举类
如何自定义枚举类
//自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
private final String SeasonName;
private final String SeasonDesc;
//2.私有化类的构造器,并给对象属性赋值
public Season(String seasonName, String seasonDesc) {
SeasonName = seasonName;
SeasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4.获取对象的属性以及重写toString方法
public String getSeasonName() {
return SeasonName;
}
public String getSeasonDesc() {
return SeasonDesc;
}
@Override
public String toString() {
return "Season{" +
"SeasonName='" + SeasonName + '\'' +
", SeasonDesc='" + SeasonDesc + '\'' +
'}';
}
}
如何使用关键字enum定义枚举类
如果使用enum关键字定义枚举类,那么定义的枚举类继承于java.lang.Enum类
//使用enum关键字定义枚举类
enum Season{
//1.提供当前枚举类的对象,多个对象之间用“,”隔开,末尾对象“;”结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String SeasonName;
private final String SeasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season(String seasonName, String seasonDesc) {
SeasonName = seasonName;
SeasonDesc = seasonDesc;
}
//4.获取对象的属性以及重写toString方法
public String getSeasonName() {
return SeasonName;
}
public String getSeasonDesc() {
return SeasonDesc;
}
}
Enum类的常用方法
方法 | 说明 |
---|---|
values()方法 | 返回枚举类型的对象数组 |
valueOf(String str) | 返回枚举类中对象名时objName的对象 |
toString() | 返回当前枚举类对象常量的名称 |
实现接口的枚举类
//枚举类实现接口
interface Info{
void show();
}
enum Season1 implements Info{
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//2.声明Season对象的属性:private final修饰
private final String SeasonName;
private final String SeasonDesc;
//3.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName, String seasonDesc) {
SeasonName = seasonName;
SeasonDesc = seasonDesc;
}
}
注解Annotation
生成文档相关注解
属性 | 描述 |
---|---|
@author | 标明开发该类的作者,多个作者之间用逗号分开 |
@version | 标明该类模块的版本 |
@see | 相关主题 |
@since | 从哪个版本开始增加的 |
@param | 对方法中某参数的说明 格式要求:@param 形参名 形参类型 形参说明 |
@return | 对方法返回值的说明 格式要求:@return 返回值类型 返回值说明 |
@exception | 对方法可能抛出的异常进行说明 格式要求:@exception 异常类型 异常说明 |
JDK内置的3个注解
属性 | 描述 |
---|---|
@Override | 限定重写父类的方法 |
@Deprecated | 用于表示所修饰的元素(类或方法等)已过时。 |
@SuppressWarnings | 抑制编译器警告 |
Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。
元注解
属性 | 说明 |
---|---|
Retent | 指明Annotation的生命周期:SOURCE/CLASS(默认)/RUNTIME |
Target | 用于指定被修饰的Annotation能用于修饰哪些程序元素 |
Documented | 表示所修饰的注解在被javadoc解析时,保留下来 |
Inherited | 被它修饰的Annotation将具有代表性 |
八、Java集合
java集合分为Collection和Map两种体系
Collection接口
单列数据,定义了存取一组对象的方法的集合
方法 | 说明 |
---|---|
add(Object obj) | 将元素e添加到集合coll中 |
addAll(Collection coll) | 将集合coll集合中的元素添加到当前的集合中 |
int size() | 获取有效元素个数 |
void clear() | 清空集合 |
boolean isEmpty() | 判断当前集合是否为空 |
boolean contains(Object obj) | 判断当前集合中石佛包含obj |
boolean containsAll(Collection coll) | 判断形参coll的所有元素是否都存在与当前集合中 |
boolean remove(Object obj) | 通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素 |
boolean removeAll(Collection coll) | 从当前集合中移除coll中所有元素 |
boolean retainAll(Collection coll) | 获取当前集合和coll集合的交集,并返回给当前集合 |
boolean equals(Object obj) | 判断当前集合和形参集合是否都相同 |
Object[] toArray() | 转成对象数组 |
hashCode() | 返回当前对象的哈希值 |
iterator() | 返回Iterator接口的实例,用于遍历结合元素 |
迭代器iterator
集合对象每次调用Iterator()方法都得到一个全新的迭代器对象。
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new String("Tom"));
coll.add(456);
//iterator():返回Iterator接口的实例,用于遍历结合元素
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while (iterator.hasNext()){ //next:①指针下移 ②将下移后集合位置上的元素返回
System.out.println(iterator.next());
}
}
foreach增强循环
java5.0提供了foreach循环迭代访问Collection和数组
遍历集合的底层调用Itertor完成操作
@Test
public void test6(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new String("Tom"));
coll.add(456);
//for(集合元素的类型 局部变量 : 集合对象)
for (Object obj : coll){
System.out.println(obj);
}
}
//forEach增强:Lambda表达式
@Test
public void test7(){
Collection coll = new ArrayList();
coll.add("AA");
coll.add("BB");
coll.add(123);
coll.add(new String("Tom"));
coll.add(456);
coll.forEach(System.out::println);
}
List容器
List容器元素有序、可重复的集合
ArrayList
1.jdk7 情况下
ArrayList list = new ArrayList(); //底层创建了长度是10的Object[] 数组elementData
list.add(11); //如果此次添加导致底层elementData数组容量不够,则扩容
默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)2.jdk8 中ArrayList的变化:
ArrayList list= new ArrayList(); //底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); //第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementData[0],后续的添加和扩容操作与jdk7 无异3.小结
jdk7 中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8 中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
常用方法 | 说明 |
---|---|
void add(int index,Object e) | 在index位置上插入元素e |
boolean addAll(int index,Collection e) | 从index位置开始将e中的所有元素添加进来 |
Object get(int index) | 获取指定index位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastIndexOf(Object obj) | 返回obj在集合中最后一次出现的位置 |
Object remove(int index) | 移除指定index位置的元素,并返回此元素 |
Object set(int index,Object e) | 设置指定Index位置的元素为e |
size() | 返回List集合中已经存在的元素个数 |
List subList(int fromIndex,int toIndex) | 返回从fromIndex到toIndex位置的子集合 |
LinkedList
内部实现是用双向链表
Vector
Vector底层按照2倍扩容
Set容器
Set容器元素无序、不可重复的集合
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
向Set中添加数据,其所在的类一定要重写hashCode()和equals()
重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作equals()方法比较的Field,都应该用来计算hashCode值
添加元素的过程:以HashSet为例
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置,判断数组在此位置上是否已有元素:
如果此位置上没有其他元素,则元素a添加成功。 ----> 情形1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不同,则元素a添加成功。 ----> 情形2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,元素a添加成功 ----> 情形3总结:对于添加成功的情况2和情况3而言,元素a与已经存在指定索引位置上数据以链表的方式存储。(七上八下)
jdk 7:元素a放到数组中,只想原来的元素
jdk 8:原来的元素在数组中,指向元素a
HashSet
HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类
HashSet按Hash算法来存储集合中的元素,因此具有良好的存取、查找和删除性能
HashSet特点
- 不能保证元素的排列顺序
- HashSet不是线程安全的
- 集合元素可以是null
HashSet集合判断两个元素相等的标准:两个对象通过HashCode()方法比较相等且两个对象的equals()方法返回值也相等
对于存放在Set容器中的对象,对应的类一定要重写equals()方法和HashCode(Object obj)方法,以实现对象相等原则。即“相等的对象必须具有相等的散列码”
LinkedHashSet
TreeSet
TreeSet时SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet底层使用红黑树
结构存储数据
TreeSet有两种排序方法,即自然排序
(默认)和定制排序
。
//使用自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
Employee e5 = new Employee("liangchaowei",21,new MyDate(1978,12,4));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
public class Employee implements Comparable{
private String name;
private int age;
private MyDate birthday;
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
//按姓名从大到小排序,年龄从小到大排序
@Override
public int compareTo(Object o) {
if (o instanceof Employee){
Employee employee = (Employee) o;
int compare = this.name.compareTo(employee.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的数据类型不一致");
}
}
}
//定制排序:按生日日期的先后排序
@Test
public void test2(){
TreeSet set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Employee && o2 instanceof Employee){
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
MyDate b1 = e1.getBirthday();
MyDate b2 = e2.getBirthday();
int minusYear = b1.getYear() - b2.getYear();
if (minusYear != 0 ){
return minusYear;
}
int minusMonth = b1.getMonth() - b2.getMonth();
if (minusMonth != 0){
return minusMonth;
}
return b1.getDay() - b2.getDay();
}
throw new RuntimeException("传入的数据类型不一致!");
}
});
Employee e1 = new Employee("liudehua",55,new MyDate(1965,5,4));
Employee e2 = new Employee("zhangxueyou",43,new MyDate(1987,5,4));
Employee e3 = new Employee("guofucheng",44,new MyDate(1987,5,9));
Employee e4 = new Employee("liming",51,new MyDate(1954,8,12));
Employee e5 = new Employee("liangchaowei",21,new MyDate(1978,12,4));
set.add(e1);
set.add(e2);
set.add(e3);
set.add(e4);
set.add(e5);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
Map接口
双列数据,保存具有映射关系“key-value"的集合
Map: 双列数据,保存具有映射关系“key-value"的集合
|---->HashMap:作为Map的主要实现类;线程不安全,效率高;能存储null的key和value
|---->LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历(因为在原有的HashMap底层结构上,添加了一对指针,指向前一个和后一个元素)。对于频繁的遍历操作,此类执行效率高于HashMap
|---->TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。底层使用红黑树结构
|---->Hashtable:作为古老的实现类;线程安全,效率低;不能存储null的key和value
|---->Properties:常用来处理配置文件。key和value都是String类型
Map结构的理解
Map中的key:无序的、不可重复的,使用Set存储所有的Key
Map中的value:无序的、可重复的,使用Collection存储所有的Value
一个键值对:key-value构成了一个Entry对象
Map中的Entry:无序的、不可重复的,使用Set存储所有的Entry
常用方法 | 说明 |
---|---|
Object put(Object key,Object value) | 将指定key-value添加到(或修改)当前map对象中 |
void putAll(Map m) | 将m中的所有key-value对存放到当前map中 |
Object remove(Object key) | 移除指定key的key-value对,并返回value |
void clear() | 清空当前map中的所有数据 |
Object get(Object key) | 获取指定key对应的value |
boolean containsKey(Object key) | 是否包含指定的key |
boolean containsValue(Object value) | 是否包含指定的value |
int size() | 返回map中key-value对的个数 |
boolean isEmpty() | 判断当前map是否为空 |
boolean equals(Object obj) | 判断当前Map和参数对象obj是否相等 |
Set keySet() | 返回所有key构成的Set集合 |
Collection values() | 返回所有value构成的Collection集合 |
Set entrySet() | 返回所有key-value对构成的Set集合 |
@Test
public void test(){
Map map = new HashMap();
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);
//遍历所有的key集合:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//遍历所有的value集合:values()
Collection values = map.values();
for (Object obj : values){
System.out.println(obj);
}
//使用entrySet()方法遍历所有的key-value
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
HashMap
HashMap的底层实现原理
HashMap的底层实现在jdk7及之前:数组+链表
jdk8:数组+链表+红黑树以jdk7为例:
HashMap map = new HashMap();
在实例化以后,底层创建了长度为16的一维数组Entry[] table.
map.put(key1,value1);
首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值竞购某种算法计算以后,得到在Entry数组中的存放位置
如果此位置上的数据为空,此时的key1-value1添加成功。 ----> 情形1
如果此位置上的数据不为空(意味着此位置上存在一个或多个数据[以链表形式存在]),比较key1和已经存在的一个或多个数据的hash值:
如果key1的hash值与已经存在的数据的hash值都不同,此时key1-value1添加成功。 ----> 情形2
如果key1的与已经存在的某一个数据(key2-value2)的hash值相同,继续比较:调用key1所在类的equals(key2)方法:
equals()返回true,使用value1替换value2
equals()返回false,此时key1-value1添加成功 ----> 情形3补充:关于情况2和情况3,此时key1-value1和原来的数据以链表的方式存储
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,默认扩容为原来的2倍,并将原来的数据复制过来
jdk8相较于jdk7在底层实现方面的不同
1.new HashMap():底层没有创建一个长度为16的Node[]数组
2.首次调用put()方法时,底层创建长度为16的数组
3.jdk8:数组+链表+红黑树:当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8且当前数组的长度 > 64时,此时索引位置上的所有数据改为使用红黑树存储
HashMap源码中的重要常量
常量名 | 描述 |
---|---|
DEFAULT_INITIAL_CAPACITY | HashMap的默认容量:16 |
MAXIMUM_CAPACITY | HashMap的最大支持容量:2^30 |
DEFAULT_LOAD_FACTOR | HashMap的默认加载因子:0.75 |
TREEIFY_THRESHOLD | Bucket中链表长度大于该默认值,转化为红黑树 :8 |
UNTREEIFY_THRESHOLD | Bucket中红黑树存储的Node小于该默认值,转化为链表 |
MIN_TREEIFY_CAPACITY | 桶中的Node被树化时最小的hash表容量:64(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍) |
table | 存储元素的数组:2^n |
entrySet | 存储具体元素的集 |
size | HashMap中存储的键值对的数量 |
modCount | HashMap扩容和结构改变的次数 |
threshold | 扩容的临界值:容量*填充因子 默认12 |
loadFactor | 填充因子 |
LinkedHashMap
TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
按照Key进行排序:自然排序和定制排序
HashTable
Properties
Properties里的key和value都是字符串类型
Collections工具类
Collections常用方法
Collections:操作Collection和Map的工具类
方法名 | 说明 |
---|---|
reverse(List) | 反转List中元素的顺序 |
shuffle(List) | 对List集合元素进行随机排序 |
sort(List) | 根据元素的自然顺序对指定List集合元素按升序排序 |
sort(List,Comparator) | 根据指定的Comparator产生的顺序对List集合元素进行排序 |
swap(List,int,int) | 将指定list集合中的i处元素和j处元素进行交换 |
Object max(Collection) | 根据元素的自然排序,返回给定集合中的最大元素 |
Object max(Collection,Comparator) | 根据Comparator指定的顺序,返回给定集合中的最大元素 |
Object min(Collection) | 根据元素的自然排序,返回给定集合中的最小元素 |
Object min(Collection,Comparator) | 根据Comparator指定的顺序,返回给定集合中的最小元素 |
int frequency(Collection,Object) | 返回指定集合中指定元素的出现次数 |
void copy(List dest,List src) | 将src中的内容复制到dest中 |
boolean replaceAll(List list,Object oldVal,Object newVal) | 使用新值替换List |
@Test
public void test(){
List list = new ArrayList();
list.add(231);
list.add(42);
list.add(765);
list.add(-97);
list.add(0);
List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());
//copy方法把一个list数组里的元素复制到另一个List数组中
Collections.copy(dest,list);
System.out.println(dest);
}
Collections同步控制方法
Collections类中提供了多个synchronizedXxx()的方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
- synchronizedCollection
- synchronizedList(List list)
- synchronizedMap(Map map)
- synchronizedSet
- synchronizedSortedMap
- synchronizedSortedSet
//返回的list1是线程安全的
List list1 = Collections.synchronizedList(list);
九、泛型
泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在
在jdk1.5之前只能把元素类型设计为Object,jdk1.5之后使用泛型来解决
。把元素的类型设计成一个参数,这个类型参数叫做泛型。eg:List
泛型
:泛型就是允许定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)
在集合中使用泛型
1.集合接口或集合类在jdk5.0时都修改为带泛型的结构
2.在实例化集合类时,可以指明具体的泛型类型
3.指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
4.注意:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
5.如果实例化时,没有指明泛型的类型,默认类型为java.lang.Object型
自定义泛型结构
如果定义了泛型类,实例化没有指明类的泛型,则认为泛型类型为Object类型
静态方法中不能使用类的泛型
//自定义泛型类
class Order<T> {
String orderName;
int orderId;
T orderT;
//泛型的构造器不加<type>
public Order(){}
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
}
public class GenericTest {
@Test
public void test(){
//实例化时泛型可加<Type>
Order<Integer> order = new Order<>();
order.setOrderT(123);
}
}
泛型方法
public <E>List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e: arr){
list.add(e);
}
return list;
}
class DAO<T> { //表的共性操作的DAO
private Map<String,T> map = new HashMap<>();
//保存T类型的对象到Map成员变量中
public void save(String id,T entity){
map.put(id,entity);
}
//从map中获取id对应的对象
public T get(String id){
return map.get(id);
}
//替换map中key为id的内容,改为entity对象
public void update(String id,T entity){
if (map.containsKey(id)){
map.replace(id,entity);
}
}
//返回map中存放的所有T对象
public List<T> list(){
ArrayList<T> list = new ArrayList<>();
Collection<T> values = map.values();
for (T t: values) {
list.add(t);
}
return list;
}
//删除指定Id对象
public void delete(String id){
map.remove(id);
}
}
十、IO流
File类
File类的一个对象,代表一个文件或一个文件目录(文件夹)
File类声明在java.io包下
File类常用构造器
构造器 | 说明 |
---|---|
public File(String pathname) | 以pathname为路径创建File对象,可以时绝对路径或相对路径。如果是相对路径,则默认的当前路径在系统属性user.dir中存储 |
public File(String parent,String child) | 以Parent为父路径,child为子路径创建File对象 |
public File(File parent,String child) | 根据一个父File对象和子文件路径创建File对象 |
路径分隔符
windows和DOS系统默认使用“\”来表示路径分隔符
UNIX和URL使用“/”来表示路径分隔符为了解决多平台使用分隔符问题,File类提供了一个常量:
public static final String separator 根据操作系统,动态的提供分隔符
eg: File file1 = new File("d:\\java\\code\\info.txt"
);
File file2 = new File("d: + File.separator + java+ File.separator +code+ File.separator +info.txt"
);
File类获取功能
获取功能 | 说明 |
---|---|
public String getAbsolutePath() | 获取绝对路径 |
public String getPath() | 获取路径 |
public String getName() | 获取名称 |
public String getParent() | 获取上层文件目录路径。若无,返回Null |
public String length() | 获取文件长度(字节数)。不能获取目录的长度 |
public String lastModified() | 获取最后一次的修改时间,毫秒值 |
public String[] list() | 获取指定目录下的所有文件或者文件目录的名称数组 |
publc File[] listFiles() | 获取指定目录下的所有文件或者文件目录的File数组 |
File类重命名功能
重命名功能 | 说明 |
---|---|
public boolean renameTo(File dest) | 把文件重命名为指定的文件路径 |
@Test
public void test1(){
File file1 = new File("hello.txt");
File file2 = new File("E:\\Coding\\Java\\src\\hi.txt");
//要想保证返回的是true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在
boolean renameTo = file1.renameTo(file2);
System.out.println(renameTo);
}
File类判断功能
判断功能 | 说明 |
---|---|
public boolean isDirectory() | 判断是否是文件目录 |
public boolean isFile() | 判断是否是文件 |
public boolean exists() | 判断是否存在 |
public boolean canRead() | 判断是否可读 |
public boolean canWrite() | 判断是否可写 |
public boolean isHidden() | 判断是否隐蔽 |
File类创建功能
创建功能 | 说明 |
---|---|
public boolean createNewFile() | 创建文件。若文件存在,则不创建,返回false |
public boolean mkdir() | 创建文件目录。如果此文件目录存在,则不创建;如果此文件目录的上层目录不存在,也不创建 |
public boolean mkdirs() | 创建文件目录。如果上层文件目录不存在,一并创建 |
File类删除功能
删除功能 | 说明 |
---|---|
public boolean delete() | 删除文件或者文件夹 |
IO流
Java程序中,对于数据的输入/输出操作以“流(stream)”的方式进行
流的分类
-
按操作数据单位不同分为:字节流(8 bit)和字符流(16 bit)
-
按数据流的流向不同分为:输入流和输出流
-
按流的角色不同分为:节点流和处理流
抽象基类 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer IO流体系
分类 字节输入流 字节输出流 字符输入流 字符输出流 抽象基类 InputStream OutputStream Reader Writer 访问文件 FileInputStream FileOutputStream FileReader FileWriter 访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter 访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter 访问字符串 StringReader StringWriter 缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter 转换流 InputStreamReader OutputStreamWriter 对象流 ObjectInputStream ObjectOutputStream FilterInputStream FilterOutputStream FilterReader FilterWriter 打印流 PrintStream PrintWriter 推回输入流 PushbackInputStream PushbackReader 特殊流 DataInputStream DataOutputStream
文件流
从内存中写出数据到硬盘的文件里
说明:
1.输出操作,对应的File可以不存在,并不会报异常
2.File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件进行覆盖,而是在原有文件的基础上追加内容
//把hello.txt文件里的内容覆盖到hello1.txt文件中
@Test
public void test4(){
FileReader fr = null;
FileWriter fw = null;
//异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
try {
//1.实例化File类的对象,指明要操作的文件
File srcFile = new File("E:\\Coding\\Java\\hello.txt");
File destFile = new File("E:\\Coding\\Java\\hello1.txt");
//2.提供具体的流
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
//3.数据的读入: 用于读取多个字符的文件
//read(char[] cbuf):返回每次读入cbuf数组中字符的个数,如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1){
//每次写出Len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.流的关闭
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//非文本文件的复制
@Test
public void test5() {
String srcPath = "E:\\Coding\\Java\\src\\宇航员.jpg";
String destPath = "E:\\Coding\\Java\\src\\宇航员1.jpg";
CopyFile(srcPath,destPath);
}
public void CopyFile(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File srcFile = new File(srcPath);
File destFile = new File(destPath);
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓冲流
缓冲流的作用:提供流的读取、写入速度
提高读写速度的原因:内部提供了一个缓冲区
//文本文件的复制
@Test
public void test1(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("E:\\Coding\\Java\\hello.txt")));
bw = new BufferedWriter(new FileWriter(new File("E:\\Coding\\Java\\hello1.txt")));
char[] cbuf = new char[1024];
int len;
while ((len = br.read(cbuf))!= -1){
bw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//非文本文件的复制
@Test
public void test2() {
String srcPath = "E:\\Coding\\Java\\src\\宇航员.jpg";
String destPath = "E:\\Coding\\Java\\src\\宇航员1.jpg";
CopyFileWithBuffered(srcPath,destPath);
}
public void CopyFileWithBuffered(String srcPath,String destPath){
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//造节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//复制的过程
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//资源关闭
//要求:先关闭外层的流,再关闭内层的流
//补充:关闭外层流的同时,内层流也会自动的进行关闭。
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
转换流
转换流的作用:提供字节流与字符流之间的转换
转换流 | 说明 |
---|---|
InputStreamReader | 将一个字节的输入流转换为字符的输入流 |
OutputStreamWriter | 将一个字符的输出流转换为字节的输出流 |
标准输入、输出流
System.in
和System.out
分别代表了系统标准的输入和输出设备
打印流
PrintStream
和PrintWriter
提供了一系列重载的print()和println()方法,用于多种数据类型的输出,实现了将基本数据类型的数据格式转化为字符串输出。
数据流
为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流DataInputStream
和DataOutputStream
DataInputStream方法 | DataOutputStream方法 |
---|---|
boolean readBoolean() | boolean writeBoolean() |
char readChar() | char writeChar() |
double readDouble() | double writeDouble() |
long readLong() | long writeLong() |
String readUTF() | String writeUTF() |
byte readByte() | byte writeByte() |
float readFloat() | float writeFloat() |
short readShort() | short writeShort() |
int readInt() | int writeInt() |
void readFully(byte[] b) | void writeFully(byte[] b) |
对象流
ObjectInputStream
和ObjectOutputStream
用于存储和读取基本数据类型数据或对象的处理流。
序列化
:用ObjectOutputStream类保存
基本类型数据或对象的机制
反序列化
:用ObjectInputStream类读取
基本类型数据或对象的机制
ObjectInputStream和ObjectOutputStream不能序列化static
和transient
修饰的成员变量
/**
* 序列化过程:
* 将内存中的java对象保存到磁盘中或通过网络传输出去,使用ObjectOutputStream实现
*/
@Test
public void test1(){
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("Object.bat"));
oos.writeObject(new String("java虐我千百遍,我拿java待初恋"));
oos.flush(); //刷新操作
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 反序列化
* 将磁盘文件中的对象还原为内存中的一个java对象使用ObjectInputStream来实现
*/
@Test
public void test2(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("Object.bat"));
Object obj = ois.readObject();
String str = (String) obj;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
需要以下几点方可实现序列化操作
- 需要实现Serializable接口
- 当前类提供一个全局常量:serialVersionUID
- 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性也必须时可序列化的。(默认基本数据类型可序列化)
随机存取文件流 RandomAcdessFile
RandomAccessFile声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput两个接口,也就意味着整个类既可以读也可以写。
RandomAccessFile类对象可以自由移动记录指针:
long getFilePointer()
:获取文件记录指针的当前位置
void seek(long pos)
:将文件记录指针定位到pos位置
RandomAccessFile的使用
1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput、DataOutput两个接口
2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建,如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
RandomAccessFile构造器
public RandomAccessFile(File file,String mode)
public RandomAccessFile(String name,String mode)
mode参数 | 说明 |
---|---|
r | 以只读方式打开 |
rw | 打开以便读取和写入 |
rwd | 打开以便读取和写入;同步文件内容的更新 |
rws | 打开以便读取和写入;同步文件内容和元数据的更新 |
十一、网络编程
网络编程的目的:直接或间接地通过网络协议与其他计算机实现数据交换,进行通讯。
在java中使用InetAddress类代表IP
如何实例化InetAddress的两个方法:
实例化InetAddress方法 | 说明 |
---|---|
getByName(String host) | |
getLocalHost() | 获取本机的IP地址 |
getHostName() | 获取域名 |
getHostAddress() | 获取主机IP地址 |
socket套接字
:端口号和IP地址的组合得出一个网络套接字socket
协议
TCP网络编程
/**
* 例题3:客户端发送文件给服务端,服务端将文件保存在本地。并返回“发送成功”给客户端,并关闭相应的连接。
*/
@Test
public void client3(){
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 8899);
os = socket.getOutputStream();
fis = new FileInputStream(new File("E:\\Coding\\Java\\src\\宇航员.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1){
os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接受来自于服务器端的数据,并显示到控制台上
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buffer1 = new byte[20];
int len1;
while ((len1 = is.read(buffer1))!=-1){
baos.write(buffer1,0,len1);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (baos!=null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis!=null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os!=null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket!=null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server3(){
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接受来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
fos = new FileOutputStream(new File("宇航员2.jpg"));
//4.读取输入流中的数据
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//5.服务器端给予客户端反馈
os = socket.getOutputStream();
os.write("你好,文件已收到,非常感谢".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//6.关闭流文件
if (fos!=null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket!=null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss!=null) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os!=null){
try {
os.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
URL
URL
:统一资源定位符,它表示Internet上某一资源的地址。
URL由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
URL类构造器
URL构造器 | 示例 |
---|---|
public URL(String spec) | URL url = new URL(“http://www.baidu.com”); |
public URL(URL context,String spec) | URL downloadUrl = new URL(url,"download.html); |
public URL(String protocol,String host,String file) | new URL(“http”,“www.baidu.com”,"download.html); |
public URL(String protocol,String host,int port,String file) | URL gamelan = new URL(“http”,“www.baidu.com”,80,"download.html); |
十二、反射
反射
:是被视为动态语言的关键,反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个CIass类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为:反射。
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
//反射前
@Test
public void test1(){
Person1 p1 = new Person1();
p1.name="Angelo";
System.out.println(p1.toString());
p1.show();
}
//反射后
@Test
public void test2() throws Exception{
//1.通过反射,创建Person类的对象
Class cla = Person1.class;
Constructor cons = cla.getConstructor(int.class, String.class);
Object obj = cons.newInstance(1001, "Tom");
Person1 p = (Person1)obj;
//2.通过反射,调用对象指定的属性、方法
Field name = cla.getDeclaredField("name");
name.set(p,"zhang"); //更改的是公共的属性或方法
System.out.println(p.toString());
//调用方法
Method show = cla.getDeclaredMethod("show");
//调用invoke()方法:参数1:方法的调用者 参数2:给方法形参赋值的实参
show.invoke(p);
//通过反射可以调用Person类的私有结构。
//调用私有构造器
Constructor cons1 = cla.getDeclaredConstructor(String.class);
//保证当前属性是可访问的
cons1.setAccessible(true);
Person1 p1 = (Person1) cons1.newInstance("Jerry");
System.out.println(p1);
}
@Test
public void test3() throws ClassNotFoundException {
//获取Class的实例
//调取Class的静态方法:forName(String classpath)
Class cla = Class.forName("ParcticeJavaCode.ReflectionTest");
System.out.println(cla);
}
读取配置文件方法
@Test
public void test4() throws Exception {
Properties pros = new Properties();
//此时的文件默认在当前的module下
//读取配置文件方式一:
//FileInputStream fis = new FileInputStream("jdbc.properties");
//pros.load(fis);
//此时的文件默认在当前的module的src下
//读取配置文件方式二:使用ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String pwd = pros.getProperty("password");
System.out.println("user= " + user + ",password=" + pwd);
}
在javaBean中要求提供一个Public的空参构造器。原因:
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类,默认调用super()时,保证父类有此构造器
获取运行时类属性的结构
@Test
public void test2(){
Class cla = Person.class;
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = cla.getFields();
for (Field f : fields){
System.out.println(f);
}
//getDeclaredFields():获取当前运行时类中声明的所有属性
Field[] DeclaredFields = cla.getDeclaredFields();
for (Field f : DeclaredFields){
System.out.println(f);
}
}
getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
getDeclaredMethods():获取当前运行时类中声明的所有方法
获取运行时类的方法
class User{
public void show(){
System.out.println("我是一个中国人");
}
}
@Test
public void test3() throws Exception{
Class cla = Class.forName("ReflectionNewInstance.User");
//1.创建运行时类的对象
User user = (User) cla.newInstance();
//2.获取指定的某个方法
Method show = cla.getDeclaredMethod("show");
//3.保证当前的方法是可访问的
show.setAccessible(true);
//4.调用invoke()方法
show.invoke(user);
}
十三、Java8新特性
五、23种设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、变成风格、以及解决问题的思考方式
创建型模式
1.工厂方法模式
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
2.抽象工厂模式
3.单例模式
类的单例模式是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
public class SingletonTest {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
// 提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
//懒汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance = null;
// 提供公共的静态方法,返回类的对象
public static Bank getInstance(){
if (instance == null){
synchronized(Bank.class){
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
饿汉式和懒汉式的区别
饿汉式:好处:线程安全
坏处:对象加载时间过长
懒汉式:好处:延迟对象的创建
单例模式的优点:
曲于单例模式只生成一个实例,减少了系统性能开销,当一个对象的
产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可
以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方
式来解决。
单例模式应用场景
网站的计数器,一般也是单例模式实现,否则难以同步。
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。