Java学习!

第一天

Hello World

1
2
3
4
5
6
7
8
9
10
// 第一行的第三个单词必和文件名一样
// public class 后面代表定义一个类的名称 类是Java当中所有源码的基本组织单位。
public class Main {
//第二行代表main方法
//执行程序的起点
public static void main(String[] args) {
//输入输出语句
System.out.println("hello world");
}
}

基本数据类型

整数型 byte short int long

浮点型 float double

字符型 char

布尔型 boolean

P.S float的数据范围比long大

自动类型转换:可以从小到大转(数据范围)

强制类型转换:可以大到小,也可以小到大;(一般不推荐使用,可能会发生数据精度缺失)

byte /shot/char运算时首先提升为int型;

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
byte num1 = 40;
byte num2 = 50;
byte num3 = num1 + num2; //编译出错!
//byte + byte -->int + int-->int
//同理:
//short + byte --> int + int-->int
//解决方法:使用强制类型转换;
//但必须注意保证逻辑上的数据范围!
}
}

boolean型不能发生强制类型转换!

int + double —> double + double —>double

第二天

四则运算

+号

对于字符串来说“+”号代表着字符串连接操作!任何数据类型和字符串进行连接时,结果都会变成字符串;

自增 自减

前置:变量先自增(减),后使用;

后置:变量先使用,后自增(减);

方法

定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
一个格式:
public static void 方法名 (){
方法体;
}
不能嵌套定义;
*/
public class Demo{
public static void main(String[] args){
Prin();
}

public static void Prin(){
System.out.println("方法");
}
}
/*
完整格式:
修饰符 返回值类型 方法名(参数类型 参数名,.....){
方法体;
return 返回值;
}
*/
public class Demo1{
public static void main(String[] args){
System.out.println(Add(10,16));
}

public static int Add(int a,int b){
return a+b;
}
}

第三天

方法重载

多个方法的名称一样,但是参数列表不同;

与下列因素有关:

1.参数个数不同;

2.参数类型不同;

3.参数的多类型顺序不同;

与下列因素无关:

1.参数名称无关;

2.返回值类型无关;

数组

初始化数组:

动态(指定长度): 数据类型[] 数组名称 = new 数据类型[数组长度];

元素自动初始化为0(整型)/0.0(浮点型)/‘\u0000’(字符型)/false(布尔型)/null(引用类型 );

静态(指定内容): 数据类型[] 数组名称 = new 数据类型[] { 元素1, 元素2,};

静态省略格式: 数据类型[] 数组名称 = { 元素1, 元素2,};.

面向对象

“静态创建对象指的是在栈上创建对象,比如A a;它会在对象的作用域结束后自动销毁. 动态创建对象指的是在堆上创建对象,然后栈上的指针指向创建的对象.比如A *pa = new A();它需要程序员手动delete掉.如果不进行delete操作,只能等到程序结束后,由OS来回收掉.

1
2
3
4
5
6
7
8
9
10
public class Student {
// 成员变量 定义在方法外,类里面
int age;
String name;
// 成员方法 成员方法不要写static关键字
public void eat() {} //吃
public void drink() {} //喝
public void play() {} //玩
public void sleep() {} //睡
}

通常情况下,一个类不能直接使用,需要根据类创建一个对象,才能使用;

1.导包

1
2
3
import 包名称.类名称
对于和当前类属于同一个包的情况,可以省略;
只有java.lang包下的内容不用导包,其他的包都需要import语句

2.创建

1
2
类名称 对象名 = new 类名称();
Student stu = new Student();

3.使用

使用成员变量: 对象名.成员变量

使用成员方法: 对象名.成员方法

第四天

Scanner类

可以实现键盘输入数据到程序中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//导包
import java.util.Scanner;

public class Sca {
public static void main(String[] args) {
//创建
//备注:System.in代表从键盘进行输入
Scanner sc = new Scanner(System.in);
//使用
int num = sc.nextInt(); //获取数字
String str = sc.next(); //获取字符串
}

}

匿名对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Person {
String name;
public void Showname(){
System.out.println(name);
}
}

public class Main {
public static void main(String[] args) {
new Person().name = "邱";
//Scanner
int num = new Scanner(System.in).nextInt();

//匿名对象传参
Niin(new Scanner(System.in));

//返回匿名对象
Scanner sc = niout();
int num1 = sc.nextInt();
System.out.println(num1);
}

public static void Niin(Scanner sc){
int num = sc.nextInt();
System.out.println(num);
}

public static Scanner Niout(){
/*Scanner sc = new Scanner(System.in);
return sc;*/
return new Scanner(System.in);
}
}

使用建议:如果确定有一个对象只需要使用唯一一次,就可以使用匿名对象;

注意事项:匿名对象只能使用唯一的一次,下次使用不得不再创建一个对象;

Random类

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Random

public class Ra {
public static void main(String[] args){
Random r = new Random();
//获取一个随机的int型数字(范围为int的数据范围)
int num = r.nextInt();
//获取一个随机的int型数字(范围为参数,左闭右开)
int num = r.nextInt(3); //[0,3)
int num1 = r.nextInt(3); //[1,4)
}
}

ArrayList类

ArrayList类集合不同于数组,数组长度不可变,ArrayList长度可变;

< E > 泛型 代表着装在集合中的所有元素都是同一类型;注意 只能是引用类型,不能是基本类型;

对于ArrayList集合来说,直接打印得到的不是地址值,而是内容;若内容为空,则得到空的中括号;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class demoArray {
public static void main(String[] args) {
//ArrayList
//创建了一个ArrayList集合,集合名称为list,里面装的是String类型数据
//备注:从JDK1.7+开始,右侧尖括号内部可空,但尖括号仍需要存在!
ArrayList<String> list = new ArrayList<>();

/*
常用方法
public boolean add(E e); 向集合当中添加元素,参数的类型和泛型一致。返回值代表是否成功;

public E get(int index); 从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素;

public E remove(int index); 从集合当中删除元素,参数是索引编号,返回值就是被删除的元素;

public int size(); 获取集合的尺寸长度,返回值为集合中包含的元素个数;
*/

//添加元素 add
boolean success = list.add("qiu");
System.out.println(list);
list.add("wu");
list.add("lu");
System.out.println(list); //[qiu, wu, lu]

//获取元素 get
String name = list.get(1);
System.out.println(name); //wu

//删除元素 remove
String whoremove = list.remove(1);
System.out.println(whoremove); //wu
System.out.println(list); //[qiu, lu]

//集合长度 size
int size = list.size();
System.out.println("集合的长度是" + size);

//遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}


}
}

如果希望向集合ArrayList当中存储基本类型数据,必须使用基本类型对于的包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
基本类型 包装类(引用类型,包装类都位于Java.lang包下)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
*/
public class demoArray {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
System.out.println(list); //[100, 200]
int num = list.get(1);
System.out.println("第一号元素是" + num); //第一号元素是200
}
}

第五天

String类

String类代表字符串;字符串是常量;

字符串的效果相当于是char[]字符数组,但底层原理是byte[]字节数组;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//三种构造方法
public String()//创建一个空白字符串,不含有任何内容

String str1 = new String();

public String(char[] Array); //根据字符数组的内容,来创建对应的字符串

char[] array = {'a','b','c'};
String str2 = new String(array);

public String(byte[] array); //根据字节数组的内容,来创建对应的字符串

byte[] array1 = {97,98,99};
String str3 = new String(array1);

//一种直接创建
String str4 = "abc"; //字符串常量池(在堆中):程序当中直接写上的双引号字符串,就在字符串常量池中;
String str5 = "abc";

//注意:
str4 == str2 //返回值为false
str5 == str4 //返回值为true 地址值一样因为字符串为常量具有可共享的属性
/*
对于基本类型来说;==是进行数值比较
对于引用类型来说;==是进行地址值的比较
*/


//常用方法

/*字符串内容比较
public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串且内容相同才会返回true
任何对象都可以用object接收

public boolean equalsIgnoreCase(String str); 忽略大小写进行比较
*/
str4.equals(str2); -->true
"abc".equals(str2); -->true //推荐 “abc”.equals(str); 若str为null时,上一种方法会报错空指针异常

/*字符串获取方法
public int length(); //获取字符串长度
public String concat(String str); //拼接字符串
public char charAt(int index); //获取指定索引位置的字符
public int indexOf(String str); //查找参数字符串在本字符串出现的首个索引位置,无则返回-1
*/

/*字符串截取方法
public String substring(int index); //截取从参数位置到最后的字符串
public String substring(int begin,int end); //截取[begin,end)范围的字符串
*/

/*字符串转换方法
public char[] toCharArray(); //当前字符串拆分成字符数组
public byte[] getBytes(); //当前字符串拆分成字节数组
public String replace(CharSequence oldString,CharSequence newString);
将所有出现的老字符串替换成新的字符串,返回替换之后的新字符串;
CharSequence 可以接收字符串
*/

/*字符串截取方法
public String[] split(String regex); //按照参数规则切割字符串
*/

第六天

static静态

一旦使用static关键字,那么这样的内容不再属于对象自己,而是属于类的,所以凡是本类的对象,都共享同一份;

一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,属于类;

如果没有static关键字,那么必须先创建对象,然后通过对象才能使用成员方法;

对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用;

P.s 静态不能直接访问非静态;原因:在内存当中先有的静态内容,后有的非静态内容;

​ 静态方法当中不使用this关键字;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class Student {

private int id;
private String name;
private int age;
static String Room;
private static int idCounter = 0; //每当new一个新对象的时候计数器++

public Student() {
this.id = ++idCounter;
}

public Student(String name, int age) {
this.name = name;
this.age = age;
this.id = ++idCounter;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public static void Staticmethod(){
System.out.println("静态方法");
}

public void method(){
System.out.println("对象方法");
}
}

public class Main{
public static void main(String[] args){
//首先创建对象
Student one = new Student();
//然后才能调用无static关键字的方法
one.method();
//静态方法:两者都正确,但推荐使用第二种方法;
one.Staticmethod();
Student.Staticmethod();
}
}
/*
静态代码块:
public class 类名称{
static {
//静态代码块内容
}
}
特点:当第一次用到本类时,静态代码块执行唯一的一次;
静态内容总是优先于非静态,所以静态代码块比构造方法先执行;
典型用途:
用来一次性地对静态成员变量进行赋值;
*/

public class Person{
static{
System.out.println("静态代码块");
}
}

Arrays类

java.util.Arrays是一个与数组相关的工具类,里面提供了大量静态方法,用来实现数组常见操作;

1
2
3
4
public static String toString(数组):将参数数组变成字符串(按照默认格式:[元素1,元素2,元素3]);

public static void sort(数组): 将参数数组升序排序(字符按字典序);
如果是自定义的类型,那么这个自定义的类需要有Comparable或Comparator接口支持;

Math类

java.util.Math是一个与数学相关的工具类,里面提供了大量静态方法,用来实现数学常见操作;

1
2
3
4
5
public static double abs(double num):获取绝对值
public static double ceil(double num):向上取整
public static double floot(double num):向下取整
public static long round(double num):四舍五入
Math.PI Π的近似值

第七天

面向对象

三大特征:封装性,继承性,多态性;

继承是多态的前提;如果没有继承,就没有多态;

继承主要解决的问题就是:共性抽取

共性存放于父类,也叫基类,超类;

特殊的存放于子类,也叫派生类;

继承关系中的特点:

1.子类可以拥有父类的“内容”;

2.子类还可以拥有自己的专属内容;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
在继承的关系中,“子类就是一个父类”;也就是说,子类可以被当成父类看待;
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a;

定义父类格式:
public class 父类名称{
//.....
}

定义子类格式:
public class 子类名称 extends 父类名称{
//.....
}

在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
1.直接通过子类对象访问成员变量
(new的时候)等号左边是谁就优先用谁,没有则向上找;
2.间接通过成员方法访问成员变量
方法是哪类的就优先使用哪类的成员变量,无则向上找;

局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁就优先用谁;无则向上找;

方法的重写(Override)
概念:在继承关系当中,方法名称一样,参数列表也一样;
方法的覆盖重写特点:创建的是子类对象,则优先使用子类方法;
注意:
1.必须保证父子类之间的方法名称相同,参数列表也相同;
@Override:写在方法前面用来检测是不是有效的覆盖重写;
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写;
2.子类方法的返回值必须小于等于父类方法的返回值范围;
object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类;
3.子类方法的权限必须大于等于父类方法的权限修饰符;
public > protected > (default) > private
备:(default)不是关键字default,而是什么都不写,留空;

设计原则:
对于已经投入使用的类,尽量不要进行修改;
推荐定义一个新的类,来重复利用其中的共性内容,并添加改动的新内容;

继承关系中,父子类构造方法的访问特点:
1.子类构造方法当中有个隐含的“super()”调用(先构造父类后构造子类);每当new上一个子类,系统便会new上一个父类;
2.可以通过super关键字来子类构造调用父类重载构造方法;
3.super的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构造方法中调用多次super构造;
4.子类必须调用父类构造方法,不写系统自动补充super();写了则用指定的super调用方法;

super关键字:
1.在子类的成员方法中,访问父类的成员变量;
2.在子类的成员方法中,访问父类的成员方法;
3.在子类的构造方法中,访问父类的构造方法;

this关键字:
1.在本类的成员方法中,访问本类的成员变量;
2.在本类的成员方法中,访问本类的另一个成员方法;
3.在本类的构造方法中,访问本类的另一个构造方法;

java语言是单继承的;
一个类的直接父亲只能有唯一一个;
java可以多级继承;
class A{};
class B extends A{};
class c extends B{};
一个子类只有一个直接父类,但一个父类可以有多个子类;
*/

第八天

抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
如果父类当中的方法不确定任何进行{}方法体的实现,那么这就应该是一个抽象方法;
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束;
抽象类:抽象方法所在的类,必须是抽象类;在class之前加上abstract关键字;

使用方法:
1.不能直接创建new抽象类对象;
2.必须要一个子类来继承抽象父类;
3.子类必须覆盖重写父类当中所有的抽象方法;
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号;
4.创建子类对象进行使用;

抽象方法格式:
public abstract 返回值类型 方法名称(参数列表);


注意:一个抽象类不一定包含抽象方法;没有抽象方法的抽象类也不能直接new对象;子类也可以是抽象类;
*/

接口

接口就是一种公共的标准规范;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*
接口就是多个类的规范;
接口是一种引用类型,最重要的内容就是其中的:抽象方法;
定义格式:
public interface 接口名称{
//接口内容
}

Java7 :
接口中可以有:
1.常量
2.抽象方法
3.默认方法(Java8以上)
4.静态方法(Java8以上)
5.私有方法(Java9以上)

接口当中的抽象方法,修饰符必须是两个固定的关键字: public abstract(可以选择性省略)

接口使用步骤:
1.接口不能直接使用,必须有一个“实现类”来“实现”该接口;
格式:
public class 实现类名称 implements 接口名称 {
//.....
}
2.接口的实现类必须覆盖重写(实现)接口中的所有的抽象方法;
去掉abstract关键字,加上方法体大括号;
3.创建实现类的对象,进行使用;

注意:
如果实现类并没有覆盖重写接口中的所有的抽象方法,那么那个实现类自己就必须是抽象类;

Java8开始支持默认方法; 备注接口当中的默认方法,可以解决接口升级问题;
格式:
public default 返回值类型 方法名称(参数列表){ //public 可省略
//方法体
}
注意:
1.接口的默认方法,可以通过接口实现类对象,直接调用;
2.接口的默认方法,可以被接口实现类进行覆盖重写;

Java 8开始允许定义静态方法;
格式:
public static 返回值类型 方法名称(参数列表){
//方法体
}
注意:不能通过接口实现类的对象来调用接口当中的静态方法;
正确用法:直接通过接口名称直接调用静态方法;

Java 9接口中允许定义私有方法;
1.普通私有方法,解决多个默认方法之间重复代码问题;
格式:
private 返回值类型 方法名称(参数列表){
//方法体
}
2.静态私有方法,解决多个静态方法之间重复代码问题;
格式:
private static 返回值类型 方法名称(参数列表){
//方法体
}

接口中可以定义“成员变量” 但是必须使用public static final 这三个关键字进行修饰
从效果上看,这其实是接口的【常量】;
格式:
public static final 数据类型 常量名称 = 数据值;
一旦使用final关键字进行修饰,说明不可变;
public static final 可以省略,但任然存在;
接口当中的常量,必须进行赋值,不能不赋值;
接口当中的常量的名称,使用完全大写的字母,用下划线进行分隔(推荐命名规则)

1.接口不能有静态代码块或者构造方法;
2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口;
格式:
public class 实现类名称 implements 接口1,接口2{
//覆盖重写所有的抽象方法
}
3.如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次就好;
4.如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类必须是一个抽象类;
5.如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先使用父类当中的方法;
7.类与接口之间是多实现的,一个类可以实现多个接口;
8.接口与接口之间是多继承的;
8.1.多个父接口当中的抽象方法如果重复,没关系;
8.2.多个父接口当中的默认方法不能重复,需要在子接口中进行默认方法覆盖重写(default关键字不可省略);
*/

第九天

多态

一个对象拥有多种形态,这也就是对象的多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
代码当中体现多态性,父类引用指向子类对象;
格式:
父类名称 对象名 = new 子类名称();
或者
接口名称 对象名 = new 实现类名称();

成员方法:
口诀:编译看左边,运行看右边;
成员变量:
口诀:编译看左边,运行看左边;

对象的向上转型,其实就是多态的写法;
格式:父类名称 对象名 = new 子类名称;
含义:右侧创建了一个子类对象,把他当成父类来看待使用;
注意事项:向上转型一定是安全的;从小范围转向大范围;

对象一旦向上转型为父类,那么就无法调用子类原本特有的内容;
解决方案:
对象的向下转型:其实是一个还原的动作:
格式:
子类名称 对象名 = (子类名称)父类对象;
含义:将父类对象,还原成为本来的子类对象;

如何才能知道一个父类引用对象,本来是什么子类?
格式:
对象 instanceof 类名称;
这将会得到一个Boolean值,也就是判断前面的对象是否能够成为后面类型的实例;

*/

第十天

final关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
final关键字代表最终,不可改变的
常见用法:
1.可以修饰一个类
2.可以用来修饰一个方法
3.修饰一个局部变量
4.修饰一个成员变量

当final用来修饰一个类的时候
格式:
public final class 类名称{
//......
}
含义:当前这个类不能有任何子类;且其中的所有成员方法都无法进行覆盖重写;

当final用来修饰一个方法的时候,这个方法就是最终方法,不能被覆盖重写;
格式:
修饰符 final 返回值类型 方法名称(参数列表){
//方法体
}
对于类,方法来说,abstract和final不能同时使用;

一旦用final修饰局部变量,就一次赋值,终身不变;
对于基本类型来说,变量当中的数据不可变;
对于引用类型来说,变量当中的地址值不变;

final修饰成员变量
1.由于成员变量具有默认值,一旦使用final修饰后必须手动赋值;
2.对于final的成员变量,要么直接赋值,要么通过构造方法赋值;
3.必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值;

*/

权限修饰符

1
2
3
4
5
6
7
8
9
10
/*
Java 中有四种权限修饰符:
public > protected > (default) > private
同一个类 yes yes yes yes
同一个包 yes yes yes no
不同包子类 yes yes no no
不同包非子类 yes no no no

注意:(default)并不是关键字default而是根本不写;
*/

第十一天

Object类

java.lang.Object类是Java语言中的根类,即所有类的父类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
String toString(); 返回该对象的字符串表示;(可以获取对象的地址值)
但打印对象的地址值是没有意义的,需要覆盖重写toString方法;
直接打印对象名,其实就是调用对象的toString方法;

boolean equals(Object obj); 指示其他某个对象是否与此对象"相等";
Object类equals方法的源码:
public boolean equals(Object obj){
return (this == obj);
}
方法体:
== 比较运算符:
基本数据类型 比较值;
引用数据类型 比较两个对象的地址值;
所以我们应该覆盖重写equals方法,比较两个对象的属性值;
问题:
隐含着一个多态;
多态弊端:无法使用子类的特有内容;
解决:使用向下转型;把Object类型强制转换为当前类型;
*/

Objects类

1
2
3
4
5
6
7
/*
jdk7:
public static boolean equals(Object a, Object b){
retrun (a == b)||(a != null && a.equals(b));
}
对象为空时,不能调用Object的equals方法,这容易抛出空指针异常,而Objects类中的equals方法就优化了这个问题;
*/

第十一天

内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
如果一个事物的内部包含着另一个事物,那么就是一个类的内部包含另一个类;
分类:
1.成员内部类
2.局部内部类(包括匿名内部类)

成员内部类:
格式:
修饰符 class 外部类名称{
修饰符 class 内部类名称{
//...
}
//...
}
注意:内用外,随意访问;外用内,一定需要借助内部类对象;
———————-----------------—————————————————————
使用成员内部类方法:
1.间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类方法;
2.直接方式:外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
如果出现了重名现象: 外部类.this.外部类成员变量名;

局部内部类:定义于一个方法的内部;
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不可使用了;
定义格式:
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//...
}
}
}
如果希望访问所在方法的局部变量,那么这个局部变量必须是有效final的;
原因:
1.new出来的对象在堆内存中;
2.局部变量跟着方法在栈内存中;
3.方法运行完之后会立即出栈,局部变量消失;
4.但是new出来的对象会在堆中持续存在,直到垃圾回收消失;

匿名内部类:
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次
那么这种情况下就可以省略该类的定义,而改为使用【匿名内部类】
格式:
接口名称 对象名 = new 接口名称(){
//覆盖重写所有的抽象方法;
};
P.s:匿名内部类是省略了【实现类/子类名称】,但匿名对象省略了【对象名】;
匿名内部类和匿名对象不是同一回事;

权限修饰符:
定义一个类的时候:
1.外部类:public / (default)
2.成员内部类: public / protected / (default) / private
3.局部内部类:什么都不能写; 不同于(default)
*/

第十二天 日期时间类

Date类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
java.util.Date:表示日期和时间的类;
类Date表示特定的瞬间,精确到毫秒;
时间原点(0毫秒):1970.1.1 00:00:00(英国格林威治)

构造方法:
无参:
Date(); 获取当前的系统的日期和时间;
带参:
Date(long date); 传递毫秒值,把毫秒转换为Date日期;

成员方法:
long getTime() 返回自时间原点以来此Date对象表示的毫秒值;
*/

DateFormat类(抽象类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
日期/时间格式化子类的抽象类;
作用:
1.格式化(日期->文本)
2.解析(文本->日期)

成员方法:
String format(Date date) 按照指定的模式,把Date日期格式化为符合模式的字符串;
Date parse(String source) 把符合模式的字符串,解析为Date日期;


SimpleDateFormat类继承DateFormat类
构造方法:
SimpleDateFormat(String pattern) 用给定的模式和默认语言环境日期格式构造SimpleDateFormat。
参数:
String patten :传递指定的模式;
模式:区分大小写
y - 年 写对应的模式,会把模式替换成对应的日期和时间;
M - 月
d - 日
H - 时
m - 分
s - 秒
*/

//练习题
//计算出一个人已经出生了多少天?
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Demo01 {
public static void main(String[] args) throws ParseException {
System.out.println("请输入出生日期: 格式 YYYY-MM-dd");
String birth = new Scanner(System.in).next();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date birthDay = sdf.parse(birth);
Date today = new Date();
System.out.println("当前时间对应毫秒值:" + today.getTime());
System.out.println("出生日期对应毫秒值:" + birthDay.getTime());
long time = today.getTime()-birthDay.getTime();
System.out.println("时间差为:" + time);
long day = time/1000/60/60/24;
System.out.println("相差天数为:" + day);
long year = day/365;
System.out.println("已经" + year + "岁啦!");
}
}

第十三天

枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
常量:
public static final int SPRING = 1;
缺点:类型不安全,可以当做整数参与计算,并且输出的意义不明确!

枚举类
一种特殊的类,可以清晰的枚举出每一项数据,可以避免错误的运算!

规范:
1. 枚举类是特殊的类,通过enum关键字进行定义;
2. 枚举类可以定义成员变量、成员方法、构造方法,也可以实现接口;
3. 枚举类默认继承于java.lang.Enum类,并且不能继承于其他父类;
4. 非抽象的枚举类默认使用final修饰,所以枚举类不能派生出子类;
5. 枚举类的构造方法默认使用private修饰,并且只能使用private修饰;
6. 枚举类的所有实例,必须在类中第一行显示列出,它们默认是public static final的。

实现接口:
可以直接在枚举实例上进行方法重写;

在枚举类中定义抽象方法,需在实例内重写抽象方法;
一般来说enum类为final修饰,一旦定义了抽象方法后被abstra修饰;


*/

topview面试问题总结

1.面向接口编程

疑问:为什么有的地方必须使用接口而不是抽象类,而在另一些地方,又必须使用抽象类而不是接口呢?

原则:行为模型应该总是通过接口而不是抽象类定义;

根本的原因在于使用抽象类不仅意味着定义特定的行为,而且意味着定义实现的模式。也就是说,应该定义一个事物如何获得行为的模型,而不仅仅是声明事物具有某一个行为。

interface关键字用来声明一个接口,它可以产生一个完全抽象的类,并且不提供任何具体实现。interface的特性整理如下:

  1. 接口中的方法可以有参数列表和返回类型,但不能有任何方法体。

  2. 接口中可以包含字段,但是会被隐式的声明为static和final。

  3. 接口中的字段只是被存储在该接口的静态存储区域内,而不属于该接口。

  4. 接口中的方法可以被声明为public或不声明,但结果都会按照public类型处理。

  5. 当实现一个接口时,需要将被定义的方法声明为public类型的,否则为默认访问类型,Java编译器不允许这种情况。

  6. 如果没有实现接口中所有方法,那么创建的仍然是一个接口。

  7. 扩展一个接口来生成新的接口应使用关键字extends,实现一个接口使用implements。

interface在某些地方和abstract有相似的地方,但是采用哪种方式来声明类主要参照以下两点:

  1. 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。

  2. 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。

接口应有两类:

第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);

第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);

Linkedlist的底层实现逻辑

ArrayList和LinkedList的区别如下:

  1. ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
  2. 对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问。而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
  3. 对于插入和删除操作,LinkedList优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。
  4. LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

第十五天

容器

数组

类型确定,长度固定,不适合进行增删操作

集合

大小不固定,类型也不固定,适合进行元素的增删操作

不支持基本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
可变参数:
在形参中可以接受多个数据 格式: 数据类型...参数名称
可变参数在方法内部其实是一个数组
每个方法只能定义一个可变参数
一般将可变参数放在参数列表的最后面;
一.单列集合 Collection(集合)(存储元素对象的地址值)
提供的Api:
list.add(); 添加成功返回true
list.clear();
list.isEmpty(); 为空返回true
list.size(); 返回集合大小
list.contains(); 含有该元素返回true
list.remove(); 成功返回true,只会删前面第一个
list.toArray(); 默认转成Object[]
list.addAll(); 将括号内的集合添加到list
遍历方式:
迭代器:list.iterator()获取迭代器(默认索引位置为0)
方法:boolean hasNext()判断当前位置是否有元素
E next() 返回当前位置元素,同时将迭代器对象移向下一个位置
foreach:可以遍历集合与数组(必须实现Iterable接口),无法修改值
Lambert:list.forEach(new Consumer<string>){重写accept方法}
P.s:
并发修改问题:
当你使用迭代器且使用集合自身remove方法删除元素的时候,会导致删完索引位置的元素后,后面的元素会前移,导致忽略一个元素,此时会抛出异常ConcurrentModificationError;推荐使用迭代器的方法remove;
foreach不能实现并发修改;有上述bug,但无法解决;
for:从末尾开始删除即可;从头开始的话,每当删除一个元素,索引后移(i--)

1.list(有序,可重复,有索引)
void add(int index,E e) 指定位置插入元素
E remove(int index) 删除索引元素并返回
E set(int index,E e) 修改索引元素,并返回修改前的元素
E get(int index) 返回索引元素

​ 1.ArrayList
底层原理:
1.第一次创建并添加第一个元素的时候,创建一个长度为10的泛型数组
2.每加一个元素,size++
3.当size要超过当前容量,进行扩容,每次扩容1.5倍;(新建数组)
​ 2.Linkedlist(点餐可以用linkedlist实现队列)
可以实现栈与队列
特有功能API:
void addFirst(E e) 在列表开头插入元素 ->push()
void addLast(E e) 在列表末尾追加元素 ->offerLast
E getFirst(E e) 返回列表第一个元素
E getLast(E e) 返回列表第最后一个元素
E removeFirst(E e) 删除并返回列表第一个元素 ->pop()
E removeLast(E e) 删除并返回列表最后一个元素


​ 2.set(无序(非随机无序),不重复,无索引)
如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法;
先判断哈希值,后判断equals;
​ 1.HashSets
底层原理:采用哈希表(一种对于增删改查数据性能较好的结构)jdk8之前底层为数组加链表,之后为数组加链表加红黑树;哈希值:根据对象的地址,按照某种规则算出来的int类型数值(可以通过Hashcode()方法获取)
哈希算法:根据元素哈希值跟数组的长度求余计算出应存入的位置;
红黑树的作用:当数组某个位置下的链表节点过多(超过8个),会影响性能,故采用红黑树进行优化;
​ 1.LinkedHashSet(有序,不重复,无索引)
底层数据结构仍为哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序;
​ 2.TreeSet(按照默认大小升序,不重复,无索引)
底层:基于红黑树实现排序,增删改查性能都较好;一定是有序的,可以指定排序规则;
自定义的类实现comparable接口,重写compareTo方法;或者用Treeset构造器的时候,实现comparable接口的比较器(优先使用);
返回值规则:第一个元素大于第二个元素返回正整数,小于返回负整数,等于则返回零;相等时会去重;
比较浮点类型时,可以调用Double.compare()方法比较,以避免出现-0.5,0.5强转精度损失,进而被认为相等的情况;
二.双列集合 Map(由键决定)
特点
无序,不重复,无索引,值不做要求
后面重复的键对应的值会覆盖前面重复键的值
键值对可以为null
元素格式:key==value(键值对元素)
如在购物车中,可以把商品对象看成键,购买数量看成值;
Api:
V put(k,v); 添加元素
V remove(E k); 根据键删除元素
void clear(); 移除所有键值对
boolean containKey(E k); 判断是否含有指定的键
boolean containValue(E v); 判断是否含有指定的值
boolean inEmpty(); 判断集合是否为空
int size(); 键值对的个数
Set<K> keySet(); 获取全部键的集合(键不可重复所以用Set接收)
Collection<V> values(); 获取全部值的集合(值可重复所以用Collection接收)
map1.putAll(map2); 将map2的元素添加到map1;
遍历方式:
1.键找值
先拿到集合的全部键(keySet()),然后使用get()方法
2.键值对
Set<Map,Entry<String,Integer>> entries = map.entrySet(); Enrty是一个接口;
先调用entrySet()方法获取所有键值对对象的集合,将每一个键值对封装成一个实例对象,再通过getKey(),getValue()获取键值对;
3.lambda表达式
default void forEach(BiConsumer<k,v> action);
map.forEach(k,v)->{System.out.println(k + "->" + v)}
实现类:
1.HashMap(无序,不重复,无索引,值不做要求)
底层实现与Hashset相同;HashSet构造方法是调用的HashMap
依赖hashCode和equals方法来保证键的唯一;
1.LinkedHashMap(有序,不重复,无索引,值不做要求)
2.HashTable
1.Properties
3.TreeMap(排序(只能对键排序),不重复(只要大小规则一样则判定为重复),无索引,值不做要求)
*/

数据结构

精心选择的数据结构可以带来更高的运行或者存储效率

抽象数据类型(ADT):一个数学模型以及定义在这模型上的一系列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*

~后进先出,先进后出(进入称压栈/进栈,离开称出栈/弹栈)


队列
~后进后出,先进先出(进入称入队,离开称出队)

数组(查询快,增删慢)
~查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同(元素在内存中是连续存储的)
~删除效率低:要将原始数据删除,同时后面每个数据前移
~添加效率极低:添加位置后的每个数据后移,再添加元素

链表
~查询速度慢:无论查询哪个数据都要通过头节点开始找(元素在内存中不是连续存储的)
~增删效率相对快:只有再增删的时候要快,因为首先要找到增删的位置
~双链表:增删首尾位置的元素极快
(单链表还需要遍历到尾节点进行增加数据,双链表之间在头指针之前插入数据即可)

二叉树
~一个节点包括父节点地址,左右节点地址,及数据域;
~只能有一个根节点,每个节点最多两个直接子节点
~节点的度:节点拥有的子树的个数,二叉树的度不大于2,叶节点度为零的节点,也称之为终端节点;
~叶节点的高度为1,叶子节点的父节点高度为2,根节点的高度最高
~层:根节点为第一层
~兄弟节点:拥有共同父节点的节点互称兄弟节点

二叉查找树
~左子树的所有节点值都小于根节点值,右子树反之
目的:提高检索数据的效率
~存入规则:
小的存左边
大的存右边
一样的不存

平衡二叉树
~二叉查找树可能会存在“瘸子问题” ->变成单链表
~在满足查找二叉树的大小规则下,让树尽可能层数小
~要求:任意节点左右子树高度差不超过1,任意节点左右子树为平衡二叉树

红黑树(平衡二叉B树)
~通过红黑规则自平衡
根节点必须是黑色
如果一个节点没有子节点,则该节点相应的指针属性为Nil,这些Nil视为叶节点,叶节点为黑色;
如果某一个节点为红色,那么它的子节点必须是黑色
对于每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
~添加节点红黑都可,但默认推荐红色(效率高);添加相同个数的元素,默认红,调整次数少;
~增删改查的性能好


*/

集合工具类

1
2
3
4
5
Collections类
static <T> boolean addAll()
static void shuffle() 打乱list集合的顺序(采用随机数)
static void sort()
static void sort(List list,Comparator<? super T> c)

第十六天

不可变集合

集合的数据项在创建的时候提供,并且在整个生命周期都不可改变;

当集合对象被不可信的库调用时,不可变形式是安全的;

在List,Set,Map接口当中,都存在of方法,可以创建一个不可变的集合;

Lambda表达式

作用:简化匿名内部类的代码写法(只能简化函数式接口的匿名内部类的写法形式)

函数式接口:首先必须是接口,其次接口中有且仅有一个抽象方法的形式;通常会在接口上加上@FunctionalInterface注解,标记该接口必须式满足函数式接口;

1
2
3
4
省略写法:
参数类型可以不写
如果只有一个参数,参数类型可以省略,同时()也可以省略
如果表达式的方法体代码只有一行,可以省略大括号不写,同时要省略分号;若此时这行代码是return语句,必须省略return不写,同时也必须省略;分号不写

Stream流

得益于Lambda所带来的函数式编程,引入的全新概念 =》流水线

目的:用于简化集合和数组操作的API

使用步骤:

1.先得到集合或数组的Stream流;

1
2
3
4
5
6
7
//集合获取当前对象的Stream流
default Stream<E> stream();
//数组获取当前对象的Stream流
//(Arrays工具类)
public static <T> Stream<T> stream(T[] array)
//stream类静态方法
public static <T> Stream<T> of(T... values)

2.把元素放上去;

3.用stream流的简化API操作元素;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//用于对流中的数据进行过滤。
Stream<T> filter(Predicate<? super T> predicate)
//获取前几个元素
Stream<T> limit(long size)
//跳过前几个元素
Stream<T> skip(long n)
//去除流中重复的元素(依赖Hashcode,equals方法)
Stream<T> distinct()
//合并a,b两个流为一个流
static Stream<T> concat(Stream a,Stream b)
//获取元素个数
long count()
//Map加工方法
//可以给流中的每一个元素进行加工,如在集合元素前加一个字符串 T->原材料 R->加工后的
stream<T> map(Function<T,R> function){
@Override
public R apply(T t){
return "标记" + t;
}
}
//获取最大值
Opentional<T> max(comparetor c);
/*Demo
需求: 某个公司的开发部门, 分为开发一部和二部, 现在需要进行年中数据结算。
分析
1.员工信息至少包含了( 名称、性别、工资、奖金、处罚记录)
2.开发一部有4个员工、开发二部有5名员工
3.分别筛选出2个部门的最高工资的员工信息, 封装装成优秀员工对象Topperformer
4.分别统计出2个部门的平均月收入要求去掉最高和最低工资。
5.统计2个开发部门整体的平均工资,去掉最低和最高工资的平均值。
*/


/*收集Stream流
将流操作后的结果数据传回集合或数组中去;
*/
R collect(Collector collector) //开始收集Stream流。指定收集器
//Collectors工具类提供了具体的收集方式
public static <T> Collector toList()
public static <T> Collector toSet()
public static <T> Collector toMap(Function keyMapper,Function valueMapper)
//直接收集成集合 list()方法但返回的是不可变集合

异常

程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中;

异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止;

Throwable
Error Exception
RuntimeException 除RuntimeException之外的所有异常

Error:系统级别问题,JVM退出等,代码无法控制

Exception:称为异常类,它表示程序本身可以处理的问题

RuntimeException及其子类:运行时异常,编译阶段不会报错;

除RuntimeException之外的所有异常 :编译时异常,编译期必须处理的,否则程序不能通过编译;

默认处理流程

​ 1.默认会在出现异常的代码那里自动的创建一个异常对象: ArithmeticException 。
​ 2.异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM 虚拟机。
​ 3.虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
​ 4.直接从当前执行的异常点干掉当前程序。
​ 5.后续代码没有机会执行了, 因为程序已经死亡。

异常处理机制

处理方式1:throws 异常1,异常2…

用在方法上,可以将方法内部出现的异 常抛出去给本方法的调用者处理;规范做法:直接抛出Exception;

处理方式2:try…catch…

监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行;建议直接catch(Exception)异常,然后打印异常栈信息;

处理方式3 前两者结合(推荐)

方法直接将异常通过throws抛出给调用者,调用者收到异常后直接捕获处理;

自定义异常

1.自定义编译时异常
· 定义一个异帛类继承Exception.
· 重写构造器。
· 在出现异常的地方用throw new 自定义对象抛出。

作用:编译时异常是编译阶段就报错,提醒更加强烈!

throw :在方法内部直接创建一个异常对象,并从此点抛出

throws:用在方法声明上的,抛出方法内部的异常;

2.自定义运行时异常
· 定义一个异帛类继承RuntimeException.
· 重写构造器。
· 在出现异常的地方用throw new 自定义对象抛出。

作用:编译阶段不报错,提醒不强烈,运行时才可能出现!

第十七天

日志

程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。

输出语句的弊端:

· 信息只能展示在控制台;

· 不能将其记录到其他的位置(文件,数据库);

· 想要取消记录的信息需要修改代码才可以完成;

· 多线程性能较差;

日志技术的优势:

· 可以将系统执行的信息选择性的记录到指定的位置( 控制台、文件中、数据库中) 。
· 可以随时以开关的形式控制是否记录日志, 无需修改源代码。

· 多线程性能较好;

1
2
3
4
5
6
7
8
9
/*日志规范接口 - 提供给日志的实现框架设计的标准
Commons Logging
Simple Logging Facade for Java
*/

/*日志实现框架
Log4J
Logback -> 实现了slf4j接口 性能优于Log4J
*/

Logback

概述:

logback 分为三个模块,logback-core、logback-classic 和 logback-access。

· logback-core 模块为其他两个模块奠定了基础。

· logback-classic 模块可以同化为 log4j 1.x 的显着改进版本。此外,logback-classic 原生实现了SLF4J API,因此您可以轻松地在 logback 和其他日志框架(例如 log4j 1.x 或 java.util.logging (JUL))之间来回切换。

· logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能。

!!需第三方jar包

入门:

1.在项目下新建文件夹lib,导入Logback的相关jar包到该目录下,并添加到项目依赖库中去;

2.将logback的核心配置文件logback.xml直接拷贝到src目录下(必须是src下);

3.在代码中获取日志的对象

1
public static final Logger LOGGER = LoggerFactory.getLogger("类对象");

第十八天

File类

可以定位文件:进行删除,获取文本信息等操作;

但不能读写文件内容

File对象可以定位文件和文件夹

File封装的对象仅仅是一个路径名,这个路径可以存在也可以不存在;

1
2
3
4
5
6
7
8
9
10
11
12
//在操作文件时一定要使用 File.separator 表示分隔符


//如果 path 是实际存在的路径,则该 File 对象表示的是目录;如果 path 是文件名,则该 File 对象表示的是文件。
//支持相对路径(相对到工程下)与绝对路径
public File(String path);

//path 是路径名,name 是文件名。
public File(String path, String name);

//dir 是路径对象,name 是文件名。
public File(File dir, String name);
方法名称 说明
boolean canRead() 测试应用程序是否能从指定的文件中进行读取
boolean canWrite() 测试应用程序是否能写当前文件
boolean delete() 删除当前对象指定的文件(占用可删),非空文件夹不能删除;(不走回收站!)
boolean exists() 测试当前 File 是否存在
String getAbsolutePath() 返回由该对象表示的文件的绝对路径名
String getName() 返回表示当前对象的文件名或路径名(如果是路径,则返回最后一级子路径名)
String getParent() 返回当前 File 对象所对应目录(最后一级子目录)的父目录名
boolean isAbsolute() 测试当前 File 对象表示的文件是否为一个绝对路径名。该方法消除了不同平台的差异,可以直接判断 file 对象是否为绝对路径。在 UNIX/Linux/BSD 等系统上,如果路径名开头是一条斜线/,则表明该 File 对象对应一个绝对路径;在 Windows 等系统上,如果路径开头是盘符,则说明它是一个绝对路径。
boolean isDirectory() 测试当前 File 对象表示的文件是否为一个路径
boolean isFile() 测试当前 File 对象表示的文件是否为一个“普通”文件
long lastModified() 返回当前 File 对象表示的文件最后修改的时间
long length() 返回当前 File 对象表示的文件长度
String[] list() 返回当前 File 对象指定的路径文件列表
String[] list(FilenameFilter) 返回当前 File 对象指定的目录中满足指定过滤器的文件列表
boolean mkdir() 创建一个目录,它的路径名由当前 File 对象指定
boolean mkdirs() 创建一个多级目录,它的路径名由当前 File 对象指定
boolean renameTo(File) 将当前 File 对象指定的文件更名为给定参数 File 指定的路径名
boolean creatNewFile() 创建新文件,创建成功返回true,若失败或文件已经存在则返回false(几乎不用)
File[] listFiles() 返回当前目录下所有的”一级文件对象“的一个文件对象数组(常用);调用者不存在,返回null;调用者为一个空文件夹时,返回长度为0的数组;(常用)

方法递归

方法直接调用自己,或者间接调用自己的形式称为方法递归(recursion);

递归死循环:递归方法无限调用自己,无法终止,出现栈内存溢出;

递归问题解决的思路:

~ 把一个复杂的问题层层转化为一个与原问题相似规模较小的问题来解决;

三要素: 递归公式 递归终点 递归方向

文件搜索(方法递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
目的:从某个盘中,搜索出某个文件名称并输出绝对路径
1.先定位出应该是一级文件对象
2.遍历一遍全部一级文件对象,判断是否为文件
3.如果是文件,判断是否为目标文件
4.如果是文件夹,需要继续递归进去重复上述过程
*/

public static void searchFile(File dir,String fileName){
//判断dir是否为目录
if(dir != null && dir.isDirectory){
File[] files = dir.listFiles();
if(files != null && files.length > 0){
for(File file : files){
if(file.isFile()){
if(file.getName().contains(fileName)){
//输出绝对路径;
System.out.println(file.getAbsolutePath());
//启动文件
Runtime r = Runtime.getRunTime();
r.exec(file.getAbsolutePath());
}
}else{
searchFile(file,fileName);
}
}
}
}else{
return;
}
}

字符集

ASCII字符集:包括了数字,英文,符号,使用一个字节存储一个字符,可以表示128个字符信息;

GBK字符集:Windows默认码表,兼容ASCII码表,也包括了几万个汉字,并支持繁体汉字以及部分日韩文字;两个字节表示一个中文;

Unicode码表:又称万国码,容纳世界上大多数国家的所有常见位置和符号;三个字节表示一个中文;

1
2
3
4
5
6
//编码
String name = "abc我爱你中国"; //18个字节
byte[] bytes = name.getBytes("UTF-8");//以当前代码默认字符集进行编码(UTF-8)
//解码(编码前与编码后的字符集必须一致)
String rs = new String(bytes,"UTF-8"); //默认UTF-

IO流

输入输出流,用来读写数据的;

按流中数据最小单位划分:字节流,字符流;

字符流和字节流

字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。字节流和字符流的区别:

(1)读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。

(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

(3)字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件,我们将在下面验证这一点。

结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

输入字节流InputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
FileInputStream
以内存为基准,把磁盘中的数据以字节的形式读取到内存中去
*/
//构造器
public FileInputStream(File file) //创建字节输入流管道与源文件对象接通
public FileInputStream(String pathName) //创建字节输入流管道与源文件路径接通
//常用API
public int read() //每次读取一个字节返回,如果没有字节可以返回,则返回-1;
public int read(byte[] buffer) //每次读取一个字节数组并返回读取长度,如果没有字节可以返回,则返回-1
//循环读入的时候buffer转String时采用 new String(byte[] bytes,int offset,int len)构造,避免数组未读满时,会将上次读取的字节重新转换;

/**如何使用字节输入流读取中文内容输出不乱码?
1.定义一个与文件大小一样大的字节数组,一次性读取完文件所有字节
带来的问题:如果文件过大,字节数组可能引起内存溢出
byte[] buffer = (new FileINputStream(String patnName)).readAllBytes;
*/

输出字节流OutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
FileOutputStream
以内存为基准,把内存中中的数据以字节的形式写入到磁盘中去
*/
//构造器
public FileOutputStream(File file) //创建字节输出流管道与源文件对象接通
public FileOutputStream(String pathName) //创建字节输出流管道与源文件路径接通
public FileOutputStream(String pathName,boolean append) //创建字节输出流管道与源文件路径接通 append为true时追加
//常用API
public void write(int a) //将一个字节写出去
public void write(byte[] buffer) //每次输出一个字节数组
public void write(byte[] bytes,int offset,int len)
public void flush() //刷新流,但能继续写数据
public void close() //关闭流,释放资源,但是会在关闭前刷新流,一旦关闭,则不能继续写数据;
/**如何使用字节输入流读取中文内容输出不乱码?
1.定义一个与文件大小一样大的字节数组,一次性读取完文件所有字节
带来的问题:如果文件过大,字节数组可能引起内存溢出
byte[] buffer = (new FileINputStream(String patnName)).readAllBytes;
*/
//回车用"\r\n",然后转成字节数组即可

资源释放

try-catch-finally

finally 关键字用来创建在 try 代码块后面执行的代码块。

无论是否发生异常,finally 代码块中的代码总会被执行。

在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

在关闭流的时候,要对流进行校验是否为空,为空会抛出空指针异常;

try-with-resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void printFileJava7() throws IOException {

try( //这里只能放置资源对象,用完会自动关闭,自动调用对象的close()方法关闭资源,即使出现异常也会关闭
//资源都是实现了Closeable/AutoCloseable接口的对象
FileInputStream input = new FileInputStream("file.txt")) {

int data = input.read();

while(data != -1){
System.out.print((char) data);
data = input.read();
}
}
}

输入字符流Read

1
2
3
4
5
6
7
8
9
/**
FileReader
*/
//构造器
public FileReader(File file) //创建字符输入流管道与源文件对象接通
public FileReader(String pathName) //创建字符输入流管道与源文件路径接通
//常用API
public int read() //每次读取一个字符返回,如果没有字符可以返回,则返回-1;
public int read(byte[] buffer) //每次读取一个字符数组并返回读取长度,如果没有字符可以返回,则返回-1

输出字符流Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
FileWriter
*/
//构造器
public FileWriter(File file) //创建字符输出流管道与源文件对象接通
public FileWriter(String pathName) //创建字符输出流管道与源文件路径接通
public FileWriter(String pathName,boolean append) //创建字符输出流管道与源文件路径接通 append为true时追加
//常用API
public void write(int c) //将一个字符写出去
public void write(char[] buffer) //每次输出一个字符数组,
public void write(char[] bytes,int offset,int len)
public void write(String str) //每次输出一个字符串,
public void write(String str,int offset,int len)

缓冲流

作用:缓冲流自带缓冲区,可以提高原始字节流,字符流读写数据的性能;

字节缓冲流

自带8KB的缓冲池

BufferInputStream

1
2
3
//构造器
//可以将低级的字节输入流包装成一个高级的缓冲字节输入管道,从而提高字节输入流读数据的功能
public BufferInputStream(InputStream is)

BufferOutputStream

1
2
3
//构造器
//可以将低级的字节输出流包装成一个高级的缓冲字节输出管道,从而提高字节输出流写数据的功能
public BufferOutputStream(OutputStream os)

字符缓冲流

BufferReader

1
2
3
4
5
6
7
//构造器
//可以将低级的字符输入流包装成一个高级的缓冲字符输入管道,从而提高字符输入流读数据的功能
public BufferReader(Reader r)

//新增API
//读取一行数据返回,如果读取没有完毕,无行可读返回null
public String readLine()

BufferWriter

1
2
3
4
5
6
7
//构造器
//可以将低级的字符输出流包装成一个高级的缓冲字符输出管道,从而提高字符输出流写数据的功能
public BufferWriter(Writer w)

//新增API
//换行操作
public String newLine()

转换流

InputStreamReader

1
2
3
4
5
//构造器
//可以将原始的字节输入流按照代码的默认编码转换成字符输入流。
public InputStreamReader(InputStream is) //(几乎不用)
//可以将原始的字节输入流按照指定编码转换成字符输入流。
public InputStreamReader(InputStream is,String charset) //常用

OutputStreamWriter

1
2
3
4
5
//构造器
//可以将原始的字节输出流按照代码的默认编码转换成字符输出流。
public OutputStreamWriter(OutputStream os) //(几乎不用)
//可以将原始的字节输入流按照指定编码转换成字符输出流。
public OutputStreamWriter(OutputStream os,String charset) //常用

对象序列化

作用:以内存为基准,把内存中的对象存储到磁盘中去,称为对象序列化

对象字节输出流ObjectOutputStream

1
2
3
4
5
//构造器
//可以将低级字节输出流包装成高级的对象字节输出流。
public ObjectOutputStream(OutputStream os)
//常用方法 ps:对象如果要序列化,必须实现Serializable接口
public void writeObject(Object o)

对象的反序列化

作用:以内存为基准,把磁盘中的对象数据恢复成内存中的对象,称为对象反序列化

对象字节输入流ObjectInputStream

1
2
3
4
5
//构造器
//可以将低级字节输入流包装成高级的对象字节输入流。
public ObjectInputStream(InputStream is)
//常用方法 ps:对象如果要序列化,必须实现Serializable接口
public Object readObject()

关键字transient

修饰的成员不参与序列化;

序列化版本号

1
2
//序列化的版本号要与反序列化的版本一致
private static final long serialVersionUID;

打印流

作用:打印流可以实现方便、高效的打印数据到文件中去;

PrintStream

1
2
3
4
5
6
7
//底层包装了缓冲流
//构造器
public PrintStream(OutputStream os) //打印流直接通向字节输出管道
public PrintStream(File f) //打印流直接通向文件对象
public PrintStream(String filePath) //打印流直接通向文件路径
//常用方法
public void print()

PrintWrite

1
2
3
4
5
6
7
8
//打印功能与PrintStream无区别,只不过支持字符数据输出,而PrintStream只能写字节
//构造器
public PrintWrite(OutputStream os) //打印流直接通向字节输出管道
public PrintWriteWriter w) //打印流直接通向字符输出管道
public PrintWrite(File f) //打印流直接通向文件对象
public PrintWrite(String filePath) //打印流直接通向文件路径
//常用方法
public void print()

若打印流要进行追加,只能用低级管道构造打印流;

输出语句重定向

1
System.setOut(PrintSream ps)

Properties属性集对象

是Map集合的实现类

作用:代表一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。

属性文件:后缀是.properties结尾的文件,内容为key=value,用来做系统配置信息。

1
2
3
4
5
6
7
8
9
//保存
public Object setProperty(String key, String value) //保存一对属性。 (put)
public String getProperty(String key) //使用此属性列表中指定的键搜索属性值 (get)
public Set<String> stringPropertyNames() //所有键的名称的集合 (keySet())
public void store(OutputStream out, String comments) //保存数据到属性文件中去
public void store(Writer fw, String comments) //保存数据到属性文件中去
//加载
public synchronized void load(InputStream inStream) //加载属性文件的数据到属性集对象中去
public synchronized void load(Reader fr) //加载属性文件的数据到属性集对象中去

IO框架

commons-io是apache开源基金组织提供的一组有关IO操作的类库,
可以挺提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类,

1
2
3
4
5
6
7
8
//读取文件中的数据, 返回字符串
String readFileToString(File file, String encoding)
//复制文件
void copyFile(File srcFile, File destFile)
//复制文件夹
void copyDirectoryToDirectory(File srcDir, File destDir)
//删除文件夹
void deleteDirectory(File f)

第十九天

多线程

线程是一个程序内部的一条执行路径

main方法的执行就是一条单独的路径

多线程是指软硬件上实现多条执行流程的技术

多线程的创建

方法一:继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
1.定义一个子类MyThread继承线程类Java.lang.Thread,重写run()方法
2.创建MyThread类的对象
3.调用线程对象的start()方法启动线程(启动之后还是执行run方法)
*/

class MyThread extends Thread{
@Override
public void run(){
for(int i = 0;i<5;i++){
System.out.println("子线程执行输出" + i);
}
}
}
public class ThreadDemo{
public static void main(String[] args){
//new一个线程对象
Thread t = new MyThread();

//启动线程
t.start();
for(int i = 0;i<5;i++){
System.out.println("主线程执行输出" + i);
}
}
}

//优缺点
//优点:编码简单
//缺点:线程类以及继承Thread,无法继承其他类,不利于扩展

//为什么不直接调用run方法,而是调用start启动线程?
//调用run方法会被当成单线程,而不是多线程!

//为什么不能把主线程的任务放在子线程之前?
//这样的话主线程一直都是先跑完的,相当于一个单线程的效果

方法二 实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
实现方案一:
1.定义一个线程任务类实现Runnable接口,重写run()方法
2.创建MyRunnable任务对象
3.把任务对象交给Thread处理
4.调用线程对象的start()方法启动线程
*/

class MyRunnable implements Runnable{
@Override
public void run(){
for(int i = 0;i<5;i++){
System.out.println("子线程执行输出" + i);
}
}
}
public class RunnableDemo{
public static void main(String[] args){
//new一个任务对象
Runnable target = new MyRunnable();

//使用Thread构造器,构造一个线程对象
Thread t = new Thread(target);

//启动线程
t.start();

for(int i = 0;i<5;i++){
System.out.println("主线程执行输出" + i);
}
}
}


/**
实现方案二(匿名内部类):
1.创建Runnable的匿名内部类
2.把任务对象交给Thread处理
3.调用线程对象的start()方法启动线程
*/

public class RunnableDemo{
public static void main(String[] args){
//new一个Runnable实例(匿名内部类重写run()方法)
Runnable target = new Runnable(){
@Override
public void run(){
for(int i = 0;i<5;i++){
System.out.println("子线程执行输出" + i);
}
}
};

//使用Thread构造器,构造一个线程对象
Thread t = new Thread(target);

//启动线程
t.start();

for(int i = 0;i<5;i++){
System.out.println("主线程执行输出" + i);
}
}
}

//Thread构造器
public Thread(String name) //为当前线程指定名称
public Thread(Runnable target) //封装Runnable对象为线程对象
public Thread(Runnable target,String name) //封装Runnable对象尾线程对象,并指定线程名称

//优缺点
//优点 线程任务类只是实现接口,可以继承类和实现接口,拓展性强。
//缺点 编程多一层对象包装,如果线程有执行结果是不可以直接返回的(run()方法无返回值)

方法三 Callable,FutureTask接口

方法一和方法二都存在一个问题:无返回结果!

解决方法:Callable和FutureTask来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**:
1.定义一个线程任务类实现Callable接口,重写call()方法
2.用FutureTask把Callable对象封装成线程任务对象
3.把任务对象交给Thread处理
4.调用线程对象的start()方法启动线程
5,线程执行完毕后,通过FutureTask的get()方法去获取任务执行结果。
*/

class MyCallable implements Callable<String>{
private int n;
pubulic MyCallable(int n){
this.n = n;
}
@Override
public String call() throws Exception{
int sum = 0;
for(int i = 1;i <= n; ++i){
sum += i;
}
return "子线程执行的结果是" + sum;
}
}
public class CallableDemo{
public static void main(String[] args){
//new一个Callable任务对象
Callable<String> call = new MyCallable(100);

//将Callable对象交给FutureTask对象
//FutureTask作用1:是Runnable的对象,实现了Runnable接口,可以交给Thread;
//FutureTask作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果;
FutureTask<String> f = new FutureTask<>(call);

//使用Thread构造器,构造一个线程对象
Thread t = new Thread(f);

//启动线程
t.start();

//获取返回值
try{
//如果f任务未执行完毕,这里代码会等待到任务执行完毕才提取结果;
String result = f.get();
System.out.println(result);
}catch(Exception e){
e.printStackTrace();
}


for(int i = 0;i<5;i++){
System.out.println("主线程执行输出" + i);
}
}
}

//优缺点
//优点 线程任务类只是实现接口,可以继承类和实现接口,拓展性强。
// 可以在线程执行完毕后去获取线程执行结果
//缺点 代码复杂

Thread类常用API

序号 方法描述
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。

静态方法

序号 方法描述
1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

线程安全

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题。

原因:存在多线程并发,同时访问共享资源,存在修改共享资源;

线程同步

· 为了解决线程安全问题

· 让多个线程实现先后依次访问共享资源,这样就解决了安全问题;

核心思想

1.加锁 把共享资源进行上锁,每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;

方法一:同步代码块

作用:把出现线程安全问题的核心代码给上锁

原理:每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;

1
2
3
4
5
6
7
8
9
10
11

synchronized(同步锁对象){
//操作共享资源的代码
}
//锁对象要求:对于当前同时执行的线程来说是同一个对象即可;->用字符串命名
//不能使用在方法内new出来的对象,这样对于当前同时执行的线程来说就不是同一个对象;

//锁对象用任意唯一的对象的问题:会影响到其他无关线程执行;
//规范上:建议使用共享资源作为锁对象
//对于实例方法建议使用this作为锁对象
//对于静态方法建议使用字节码(类名.class)对象作为锁对象

方法二:同步方法

作用:把出现线程安全问题的核心方法给上锁

原理:每次只能 一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来;

1
2
3
4
5
6
7
8
9
/**
修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源的代码
}
*/

//底层原理:隐式锁对象,只是锁的范围是整个方法代码。
//如果方法是实例方法:同步方法默认用this作为锁的对象,但是代码要高度面向对象。
//如果方法是静态方法:同步方法默认用类名.class作为锁的对象。

同步代码块锁的范围更小,同步方法锁的范围更大

方法三:Lock锁

· 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。

· Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

· Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。

1
2
3
4
5
6
//获取实现类对象(通常用final修饰)
public ReentrantLock()

//API
void lock() //获得锁
void unlock() //释放锁

线程通信

线程间相互发送数据;

常见形式

1.通过共享一个数据的方式实现

2.根据共享数据的情况决定自己该怎么做,已经通知其他线程怎么做

线程通信实际应用场景

1.生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。

​ 要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。

Object类等待和唤醒方法

1
2
3
4
5
void wait()							//让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify() //唤醒正在等待的单个线程
void notifyAll() //唤醒正在等待的所有线程

//注意:上述方法应该使用当前同步锁对象进行调用。

线程池

可以复用线程的技术

不使用线程池的问题

如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

线程池实现的API,参数说明

接口:ExecutorService

如何得到线程池对象

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象(推荐使用,参数可控)

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,                  //指定线程池的线程数量(核心线程)	不能小于零        
int maximumPoolSize, //指定线程池可支持的最大线程数 最大数量大于核心线程数
long keepAliveTime, //指定临时线程的最大存活时间 不能小于零
TimeUnit unit, //指定存活时间的单位 时间单位
BlockingQueue<Runnable> workQueue, //指定任务队列 不能为null
ThreadFactory threadFactory, //指定用哪个线程工厂创建线程 不能为null
RejectedExecutionHandler handler) //指定线程忙任务满的时候,新任务解决方式 不能为null

新任务拒绝策略

策略 详解
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象(不推荐使用)

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称 说明
public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。
public static ExecutorService newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor () 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的

Executor使用可能存在的陷阱

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

方法名称 存在问题
public static ExecutorService newFixedThreadPool(int nThreads) 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError )
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool() 创建的线程数量最大上限是Integer.MAX_VALUE, 线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError )
public static ExecutorService newScheduledThreadPool(int corePoolSize)

常见问题:

1.临时线程什么时候创建啊?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

2.什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

ExecutorService常用API

方法名称 说明
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等任务执行完毕后关闭线程池
ListshutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

定时器

定时器是一种控制任务延时调用,或周期调用技术。

作用:闹钟、定时邮件发送。

实现方式

1.Timer

1
2
3
4
5
6
7
8
9
10
11
12
//构造器
public Timer()

//API
//开启一个定时器,按照计划处理TimerTask任务
public void schedule(TimerTask task,long delay,long period)

//特点:
//Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
//问题:
//可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。

2.ScheduledExecutorService

ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。

Executors的方法 说明
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象
ScheduledExecutorService的方法 说明
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 周期调度方法

ScheduledExecutorService的优点

1、基于线程池,某个任务的执行情况不会影响其他定时任务的执行。

并发 并行

正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。

并发的理解

1.CPU同时处理线程的数量有限。

2.CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

线程的生命周期

状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//6种状态都定义在Thread类的内部枚举类中。
public class Thread{
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
...
}

第二十天

Junit单元测试:

* 测试分类:
    1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
    2. 白盒测试:需要写代码的。关注程序具体的执行流程。

* Junit使用:白盒测试
    * 步骤:
        1. 定义一个测试类(测试用例)
            * 建议:
                * 测试类名:被测试的类名Test        CalculatorTest
                * 包名:xxx.xxx.xx.test        cn.itcast.test

        2. 定义测试方法:可以独立运行
            * 建议:
                * 方法名:test测试的方法名        testAdd()  
                * 返回值:void
                * 参数列表:空参

        3. 给方法加@Test
        4. 导入junit依赖环境

    * 判定结果:
        * 红色:失败
        * 绿色:成功
        * 一般我们会使用断言操作来处理结果
            * Assert.assertEquals(期望的结果,运算的结果);

    * 补充:
        * @Before:
            * 修饰的方法会在测试方法之前被自动执行
        * @After:
            * 修饰的方法会在测试方法执行之后自动被执行

反射:框架设计的灵魂

* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
    * 好处:
        1. 可以在程序运行过程中,操作这些对象。
        2. 可以解耦,提高程序的可扩展性。


* 获取Class对象的方式:
    1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
        * 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
    2. 类名.class:通过类名的属性class获取
        * 多用于参数的传递
    3. 对象.getClass():getClass()方法在Object类中定义着。
        * 多用于对象的获取字节码的方式

    * 结论:
        同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。


* Class对象功能:
    * 获取功能:
        1. 获取成员变量们
            * Field[] getFields() :获取所有public修饰的成员变量
            * Field getField(String name)   获取指定名称的 public修饰的成员变量

            * Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
            * Field getDeclaredField(String name)  
        2. 获取构造方法们
            * Constructor<?>[] getConstructors()  
            * Constructor<T> getConstructor(类<?>... parameterTypes)  

            * Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  
            * Constructor<?>[] getDeclaredConstructors()  
        3. 获取成员方法们:
            * Method[] getMethods()  
            * Method getMethod(String name, 类<?>... parameterTypes)  

            * Method[] getDeclaredMethods()  
            * Method getDeclaredMethod(String name, 类<?>... parameterTypes)  

        4. 获取全类名    
            * String getName()  


* Field:成员变量
    * 操作:
        1. 设置值
            * void set(Object obj, Object value)  
        2. 获取值
            * get(Object obj) 

        3. 忽略访问权限修饰符的安全检查
            * setAccessible(true):暴力反射



* Constructor:构造方法
    * 创建对象:
        * T newInstance(Object... initargs)  

        * 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法


* Method:方法对象
    * 执行方法:
        * Object invoke(Object obj, Object... args)  

    * 获取方法名称:
        * String getName:获取方法名


* 案例:
    * 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
        * 实现:
            1. 配置文件
            2. 反射
        * 步骤:
            1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
            2. 在程序中加载读取配置文件
            3. 使用反射技术来加载类文件进内存
            4. 创建对象
            5. 执行方法

注解:

* 概念:说明程序的。给计算机看的
* 注释:用文字描述程序的。给程序员看的

* 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
* 概念描述:
    * JDK1.5之后的新特性
    * 说明程序的
    * 使用注解:@注解名称

* 作用分类:
    ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
    ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】


* JDK中预定义的一些注解
    * @Override    :检测被该注解标注的方法是否是继承自父类(接口)的
    * @Deprecated:该注解标注的内容,表示已过时
    * @SuppressWarnings:压制警告
        * 一般传递参数all  @SuppressWarnings("all")

* 自定义注解
    * 格式:
        元注解
        public @interface 注解名称{
            属性列表;
        }

    * 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
        * public interface MyAnno extends java.lang.annotation.Annotation {}

    * 属性:接口中的抽象方法
        * 要求:
            1. 属性的返回值类型有下列取值
                * 基本数据类型
                * String
                * 枚举
                * 注解
                * 以上类型的数组

            2. 定义了属性,在使用时需要给属性赋值
                1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
                2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
                3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

    * 元注解:用于描述注解的注解
        * @Target:描述注解能够作用的位置
            * ElementType取值:
                * TYPE:可以作用于类上
                * METHOD:可以作用于方法上
                * FIELD:可以作用于成员变量上
        * @Retention:描述注解被保留的阶段
            * @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
        * @Documented:描述注解是否被抽取到api文档中
        * @Inherited:描述注解是否被子类继承


* 在程序使用(解析)注解:获取注解中定义的属性值
    1. 获取注解定义的位置的对象  (Class,Method,Field)
    2. 获取指定的注解
        * getAnnotation(Class)
        //其实就是在内存中生成了一个该注解接口的子类实现对象

                public class ProImpl implements Pro{
                    public String className(){
                        return "cn.itcast.annotation.Demo1";
                    }
                    public String methodName(){
                        return "show";
                    }
                }
    3. 调用注解中的抽象方法获取配置的属性值


* 案例:简单的测试框架
* 小结:
    1. 以后大多数时候,我们会使用注解,而不是自定义注解
    2. 注解给谁用?
        1. 编译器
        2. 给解析程序用
    3. 注解不是程序的一部分,可以理解为注解就是一个标签

Java基础完结!!!!!!!!!!!!!!!