java最全基础③进阶

final

1、final关键字
1.1、final修饰的类无法继承。 String就是final修饰 无法继承(成员变量才有初始值 局部变量没有初始值) new 构造方法的时候默认赋值
1.2、final修饰的方法无法覆盖。
1.3、final修饰的变量只能赋一次值
1.4、final修饰的引用一旦指向某个对象,则不能再重新指向其它对象,但该引用,该对象不会被垃圾回收器回收除非方法结束
指向的对象内部的数据是可以修改的。
1.5、final修饰的实例变量必须手动初始化,不能采用系统默认值。
1.6、final修饰的实例变量一般和static联合使用,称为常量。(常量和静态变量都存放在方法区 都在类加载时初始化)
public static final double PI = 3.1415926;

抽象类

1、什么是抽象类?
类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类。
类本身是不存在的,所以抽象类无法创建对象《无法实例化》。

2、抽象类属于什么类型?
抽象类也属于引用数据类型。

3、抽象类怎么定义?《能把基础语法先学会》
语法:
[修饰符列表] abstract class 类名{
类体;
}

4、抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。

5、final和abstract不能联合使用,这两个关键字是对立的。

6、抽象类的子类可以是抽象类。也可以是非抽象类。

7、抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。

8、抽象类关联到一个概念:抽象方法。什么是抽象方法呢?
抽象方法表示没有实现的方法,没有方法体的方法。例如:
public abstract void doSome();
抽象方法特点是:
特点1:没有方法体,以分号结尾。
特点2:前面修饰符列表中有abstract关键字。

9、抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。

public class AbstractTest01{ 
	public static void main(String[] args){
		// 错误: Account是抽象的; 无法实例化
		//Account act = new Account();
	}
}

// 银行账户类
//错误: 非法的修饰符组合: abstract和final
/*
final abstract class Account{

}
*/

abstract class Account{
	/*
	public Account(){
	
	}
	public Account(String s){
	
	}
	*/
	// 非抽象方法
	public void doOther(){
	
	}

	// 抽象方法
	public abstract void withdraw();
}

// 子类继承抽象类,子类可以实例化对象
/*
class CreditAccount extends Account{
	public CreditAccount(){
		super();
	}
}
*/

// 抽象类的子类可以是抽象类吗?可以
/*
abstract class CreditAccount extends Account{

}
*/

抽象类:

1、抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。

2、重要结论:重要结论五颗星*****(必须记住)
一个非抽象的类继承抽象类,必须将抽象类中的抽象方法实现了。
这是java语法上强行规定的,必须的,不然编译器就报错了。

这里的覆盖或者说重写,也可以叫做实现。(对抽象的实现。)

public class AbstractTest02{
	public static void main(String[] args){
		// 能不能使用多态?
		// 父类型引用指向子类型对象。
		Animal a = new Bird();  // 向上转型。(自动类型转换)

		// 这就是面向抽象编程。
		// 以后你都是调用的a.XXXX
		// a的类型是Animal,Animal是抽象的
		// 面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力。
		// 这种编程思想符合OCP原则。
		/*
			分析以下:
				编译的时候这个move()方法是谁的?
				运行的时候这个move()方法又是谁的?
		*/
		a.move();

		// 多态(当对多态不是很理解的时候,以后写代码能用多态就用多态。慢慢就理解了。)
		Animal x = new Cat();
		x.move();
	}
}

// 动物类(抽象类)
abstract class Animal{

	// 抽象方法
	public abstract void move();
}

// 子类(非抽象的)
// 错误: Bird不是抽象的, 并且未覆盖Animal中的抽象方法move()
/*
class Bird extends Animal{
}
*/

class Bird extends Animal{
	// 需要将从父类中继承过来的抽象方法进行覆盖/重写,或者也可以叫做“实现”。
	// 把抽象的方法实现了。
	public void move(){
		System.out.println("鸟儿在飞翔!");
	}
}

class Cat extends Animal{
	public void move(){
		System.out.println("猫在走猫步!");
	}
}


	面试题(判断题):java语言中凡是没有方法体的方法都是抽象方法。
		不对,错误的。
		Object类中就有很多方法都没有方法体,都是以“;”结尾的,但他们
		都不是抽象方法,例如:
			public native int hashCode();
			这个方法底层调用了C++写的动态链接库程序。
			前面修饰符列表中没有:abstract。有一个native。表示调用JVM本地程序。

interface

接口:
1、接口也是一种“引用数据类型”。编译之后也是一个class字节码文件。
2、接口是完全抽象的。(抽象类是半抽象。)或者也可以说接口是特殊的抽象类。
3、接口怎么定义,语法是什么?
[修饰符列表] interface 接口名{}
4、接口支持多继承,一个接口可以继承多个接口。
5、接口中只包含两部分内容,一部分是:常量。一部分是:抽象方法。接口中没有其它内容了。只有以上两部分。
6、接口中所有的元素都是public修饰的。(都是公开的。)
7、接口中的抽象方法定义时:public abstract修饰符可以省略。
8、接口中的方法都是抽象方法,所以接口中的方法不能有方法体。
9、接口中的常量的public static final可以省略。

public class Test01{
	public static void main(String[] args){

		// 访问接口的常量。
		System.out.println(MyMath.PI);

		// 常量能重新赋值吗?
		//错误: 无法为最终变量PI分配值
		//MyMath.PI = 3.1415928;

		//错误: 无法为最终变量k分配值
		//MyMath.k = 111;
	}
}

// 定义接口
interface A{

}

// 接口支持继承
interface B extends A{

}

// 一个接口可以继承多个接口(支持多继承)
interface C extends A, B{
}

// 我的数学接口
interface MyMath{

	// 常量
	//public static final double PI = 3.1415926;

	// public static final可以省略吗?
	double PI = 3.1415926;

	// k是不是常量????是。
	// 接口中随便写一个变量就是常量。
	// 常量:值不能发生改变的变量。
	int k = 100;

	// 抽象方法
	//public abstract int sum(int a, int b);

	// 接口当中既然都是抽象方法,那么在编写代码的时候,public abstract可以省略吗?
	int sum(int a, int b);

	// 接口中的方法可以有方法体吗?
	// 错误: 接口抽象方法不能带有主体
	/*
	void doSome(){
	
	}
	*/

	// 相减的抽象方法
	int sub(int a, int b);

}

接口的基础语法:
1、类和类之间叫做继承,类和接口之间叫做实现。
别多想:你仍然可以将"实现"看做“继承”。
继承使用extends关键字完成。
实现使用implements关键字完成。

​ 2、五颗星(*****):当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(覆盖、重写)。

public class Test02{
	public static void main(String[] args){
		//错误: MyMath是抽象的; 无法实例化
		//new MyMath();

		// 能使用多态吗?可以。
		//Animal a = new Cat();

		// 父类型的引用指向子类型的对象
		MyMath mm = new MyMathImpl();
		// 调用接口里面的方法(面向接口编程。)
		int result1 = mm.sum(10, 20);
		System.out.println(result1);

		int result2 = mm.sub(20, 10);
		System.out.println(result2);
	}
}

// 特殊的抽象类,完全抽象的,叫做接口。
interface MyMath{
	double PI = 3.1415926;
	int sum(int a, int b);
	int sub(int a, int b);
}

// 这样没问题
/*
abstract class MyMathImpl implements MyMath {
}
*/

// 编写一个类(这个类是一个“非抽象”的类)
// 这个类的名字是随意的。
//错误: MyMathImpl不是抽象的, 并且未覆盖MyMath中的抽象方法sub(int,int)
/*
class MyMathImpl implements MyMath {
}
*/

//修正
class MyMathImpl implements MyMath {

	//错误:正在尝试分配更低的访问权限; 以前为public
	/*
	int sum(int a, int b){
		return a + b;
	}
	*/

	// 重写/覆盖/实现 接口中的方法(通常叫做实现。)
	public int sum(int a, int b){
		return a + b;
	}

	public int sub(int a, int b){
		return a - b;
	}
}

重点(五颗星*****):一个类可以同时实现多个接口。

之前有一个结论:
无论向上转型还是向下转型,两种类型之间必须要有继承关系,
没有继承关系编译器会报错。(这句话不适用在接口方面。)
最终实际上和之前还是一样,需要加:instanceof运算符进行判断。
向下转型养成好习惯。转型之前先if+instanceof进行判断。

D -> A

D -> B

D -> C

// 多态该怎么用呢?
		// 都是父类型引用指向子类型对象
		A a = new D();
		//a.m2(); // 编译报错。A接口中没有m2()方法。
		B b = new D();
		C c = new D();


// 这个编译没问题,运行也没问题。
		// 调用其他接口中的方法,你需要转型(接口转型。)
		B b2 = (B)a;
		b2.m2();

		// 直接向下转型为D可以吗?可以
		D d = (D)a;
		d.m2();

继承和实现都存在的话,代码应该怎么写?
extends 关键字在前。
implements 关键字在后。

class Cat extends Animal implements Flyable{
	public void fly(){
		System.out.println("飞猫起飞,翱翔太空的一只猫,很神奇,我想做一只猫!!");
	}
}


		// 创建对象(表面看Animal类没起作用!)
		Flyable f = new Cat(); //多态。
		f.fly();

		// 同一个接口
		Flyable f2 = new Pig();
		// 调用同一个fly()方法,最后的执行效果不同。
		f2.fly();

		Flyable f3 = new Fish();
		f3.fly();

经典案例

调用者面向接口调用。(顾客)
实现者面向接口编写实现。(厨师)

public interface FoodMenu{

	// 西红柿炒蛋
	void shiZiChaoJiDan();

	// 鱼香肉丝
	void yuXiangRouSi();

}



public class AmericCooker implements FoodMenu{

	// 西红柿炒蛋
	public void shiZiChaoJiDan(){
		System.out.println("西餐师傅做的西红柿炒鸡蛋!");
	}

	// 鱼香肉丝
	public void yuXiangRouSi(){
		System.out.println("西餐师傅做的鱼香肉丝!");
	}
}


public class ChinaCooker implements FoodMenu{

	// 西红柿炒蛋
	public void shiZiChaoJiDan(){
		System.out.println("中餐师傅做的西红柿炒鸡蛋,东北口味!");
	}

	// 鱼香肉丝
	public void yuXiangRouSi(){
		System.out.println("中餐师傅做的鱼香肉丝,东北口味!");
	}
}
public class Customer{
	// 顾客手里有一个菜单
	// Customer has a FoodMenu!(这句话什么意思:顾客有一个菜单)
	// 记住:以后凡是能够使用 has a 来描述的,统一以属性的方式存在。
	// 实例变量,属性
	// 面向抽象编程,面向接口编程。降低程序的耦合度,提高程序的扩展力。
	private FoodMenu foodMenu; 
	
	// 如果以下这样写,就表示写死了(焊接了。没有可插拔了。)
	// 中餐厨师
	//ChinaCooker cc;

	// 西餐厨师
	//AmericCooker ac

	// 构造方法 
	}
	public Customer(FoodMenu foodMenu){
		this.foodMenu = foodMenu;
	}

	// setter and getter
	public void setFoodMenu(FoodMenu foodMenu){
		this.foodMenu = foodMenu;
	}
	public FoodMenu getFoodMenu(){
		return foodMenu;
	}

	// 提供一个点菜的方法
	public void order(){
		// 先拿到菜单才能点菜
		// 调用get方法拿菜单。
		//FoodMenu fm = this.getFoodMenu();
		// 也可以不调用get方法,因为在本类中私有的属性是可以访问
		foodMenu.shiZiChaoJiDan();
		foodMenu.yuXiangRouSi();
	}
}
public class Test{
	public static void main(String[] args){

		// 创建厨师对象
		//FoodMenu cooker1 = new ChinaCooker();
		FoodMenu cooker1 = new AmericCooker();

		// 创建顾客对象
		Customer customer = new Customer(cooker1);

		// 顾客点菜
		customer.order();
	}
}

类型和类型之间的关系:

is a(继承)、has a(关联)、like a(实现)

is a:
Cat is a Animal(猫是一个动物)
凡是能够满足is a的表示“继承关系”
A extends B

has a:
I has a Pen(我有一支笔)
凡是能够满足has a关系的表示“关联关系”
关联关系通常以“属性”的形式存在。
A{
B b;
}

like a:
Cooker like a FoodMenu(厨师像一个菜单一样)
凡是能够满足like a关系的表示“实现关系”
实现关系通常是:类实现接口。
A implements B

抽象和接口的区别

抽象接口
抽象类是半抽象的。接口是完全抽象的。
抽象类中有构造方法。接口中没有构造方法。
类和类之间只能单继承。接口和接口之间支持多继承。
一个抽象类只能继承一个类(单继承)。一个类可以同时实现多个接口。
接口中只允许出现常量和抽象方法。

访问权限

访问控制修饰符本类同包子类任意位置
public可以可以可以可以
protected可以可以可以不行
默认可以可以不行不行
private可以不行不行不行

1.3、访问控制权限修饰符可以修饰什么?
属性(4个都能用)
方法(4个都能用)
类(public和默认能用,其它不行。)
接口(public和默认能用,其它不行。)

Object

toString

1、源代码长什么样?
public String toString() {
return this.getClass().getName() + “@” + Integer.toHexString(hashCode());
}

System.out.println(引用); 这里会自动调用“引用”的toString()方法。

//MyTime类重写toString()方法之前
		//System.out.println(s1); // MyTime@28a418fc

//MyTime类重写toString()方法之后
		System.out.println(s1); // 1970年1月1日

//System.out.println(t1.toString()); //1970年1月1日

		// 注意:输出引用的时候,会自动调用该引用的toString()方法。
		System.out.println(t1);

equals

基本数据类型比较实用:,“==”判断的是两个java对象的内存地址

// 判断两个基本数据类型的数据是否相等直接使用“==”就行。
		int a = 100;
		int b = 100;
		// 这个“==”是判断a中保存的100和b中保存的100是否相等。
		System.out.println(a == b); //true(相等)

对象和对象比较:调用equals方法

	MyTime t1 = new MyTime(2008, 8, 8); //MyTime t1 = 0x1234;
		// 创建了一个新的日期对象,但表示的日期也是:2008年8月8日。
		MyTime t2 = new MyTime(2008, 8, 8); //MyTime t2 = 0x3698;
		
		//测试以下,比较两个对象是否相等,能不能使用“==”???
		// 这里的“==”判断的是:t1中保存的对象内存地址和t2中保存的对象内存地址是否相等。
		System.out.println(t1 == t2); // false
// 重写Object equals方法之后(比较的是内容。)
		boolean flag = t1.equals(t2);
		System.out.println(flag); //true

String类

1、String类已经重写了equals方法,比较两个字符串不能使用==,必须使用equals。
equals是通用的。

		String s1 = "hello";      ==true
		String s2 = "abc";


		String s3 = new String("Test1");
		String s4 = new String("Test1");  ==false


2、String类已经重写了toString方法。

大结论:
java中什么类型的数据可以使用“== ”判断
java中基本数据类型比较是否相等,使用 ==

​ java中什么类型的数据需要使用equals判断
​ java中所有的引用数据类型统一使用equals方法来判断是否相等。

finalize

1、在Object类中的源代码:
protected void finalize() throws Throwable { }

​ GC:负责调用finalize()方法。

2、finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的。

3、这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法。
不像equals toString,equals和toString()方法是需要你写代码调用的。
finalize()只需要重写,重写完将来自动会有程序来调用。

4、finalize()方法的执行时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用
finalize()方法。

内部类

class Test01{
		内部类的分类:
			静态内部类:类似于静态变量
			实例内部类:类似于实例变量
			局部内部类:类似于局部变量
	// 静态变量
	static String country;
	// 该类在类的内部,所以称为内部类
	// 由于前面有static,所以称为“静态内部类”
	static class Inner1{
	}
	
	// 实例变量
	int age;
	// 该类在类的内部,所以称为内部类
	// 没有static叫做实例内部类。
	class Inner2{
	}

	// 方法
	public void doSome(){
		// 局部变量
		int i = 100;
		// 该类在类的内部,所以称为内部类
		// 局部内部类。
		class Inner3{
		}
	}

	public void doOther(){
		// doSome()方法中的局部内部类Inner3,在doOther()中不能用。
	}

	// main方法,入口
	public static void main(String[] args){
		// 调用MyMath中的mySum方法。
		MyMath mm = new MyMath();
		/*
		Compute c = new ComputeImpl();
		mm.mySum(c, 100, 200);
		*/
		
		//合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)
		//mm.mySum(new ComputeImpl(), 100, 200);
	
		// 使用匿名内部类,表示这个ComputeImpl这个类没名字了。
		// 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。
		// 后面的{} 代表了对接口的实现。
		// 不建议使用匿名内部类,为什么?
		// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
		mm.mySum(new Compute(){
			public int sum(int a, int b){
				return a + b;
			}
		}, 200, 300);



	}

}

静态内部类

	public static void main(String[] args){
		// 调用MyMath中的mySum方法。
		MyMath mm = new MyMath();
		/*
		Compute c = new ComputeImpl();
		mm.mySum(c, 100, 200);
		*/
		
		//合并(这样写代码,表示这个类名是有的。类名是:ComputeImpl)
		//mm.mySum(new ComputeImpl(), 100, 200);
	
		// 使用匿名内部类,表示这个ComputeImpl这个类没名字了。
		// 这里表面看上去好像是接口可以直接new了,实际上并不是接口可以new了。
		// 后面的{} 代表了对接口的实现。
		// 不建议使用匿名内部类,为什么?
		// 因为一个类没有名字,没有办法重复使用。另外代码太乱,可读性太差。
		mm.mySum(new Compute(){
			public int sum(int a, int b){
				return a + b;
			}
		}, 200, 300);



	}

}

// 负责计算的接口
interface Compute{ 
	
	// 抽象方法
	int sum(int a, int b);
}

// 你自动会在这里编写一个Compute接口的实现类
/*
class ComputeImpl implements Compute{

	// 对方法的实现
	public int sum(int a, int b){
		return a + b;
	}
}
*/

// 数学类
class MyMath{
	// 数学求和方法
	public void mySum(Compute c, int x, int y){
		int retValue = c.sum(x, y);
		System.out.println(x + "+" + y + "=" + retValue);
	}	
}

数组

  • 数组是一种引用数据类型。数组的父类是Object。

  • 数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据。

  • 数组因为是引用类型,所以数组对象是堆内存当中。(数组是存储在堆当中的)

  • 数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象。

  • 数组一旦创建,在java中规定,长度不可变。(数组长度不可变)

  • 所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数。

优点:检索效率高。
缺点:随机增删效率较低,数组无法存储大数据量。
注意:数组最后一个元素的增删效率不受影响。


一维数组的静态初始化和动态初始化
静态初始化:
int[] arr = {1,2,3,4};

​ int arr1[] =new int[]{1,2,3};

​ Object[] objs = {new Object(), new Object(), new Object()};
​ 动态初始化:
​ int[] arr = new int[4]; // 4个长度,每个元素默认值0
​ Object[] objs = new Object[4]; // 4个长度,每个元素默认值null


一维数组的遍历
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}

    // 如果直接传递一个静态数组的话,语法必须这样写。
    printArray(new int[]{1,2,3});
   // 动态初始化一维数组
    int[] a2 = new int[4];
    printArray(a2);

main方法上面的“String[] args”有什么用?
分析以下:谁负责调用main方法(JVM)
JVM调用main方法的时候,会自动传一个String数组过来。

// JVM默认传递过来的这个数组对象的长度?默认0
        // 通过测试得出:args不是null。
        System.out.println("JVM给传递过来的String数组参数,它这个数组的长度是?" + args.length);

Animal[] ans = new Animal[2];
ans[0] = new Animal();   //可以直接放子类
ans[1] = new Cat();
class Animal{
    public void move(){
        System.out.println("Animal move...");
    }
}

class Cat extends Animal {
    public void move(){
        System.out.println("猫在走猫步!");
    }
    // 特有方法
    public void catchMouse(){
        System.out.println("猫抓老鼠!");
    }
}


class Bird extends Animal {
    public void move(){
        System.out.println("Bird Fly!!!");
    }
    // 特有的方法
    public void sing(){
        System.out.println("鸟儿在歌唱!!!");
    }
}


        Cat c = new Cat();
        Bird b = new Bird();
        Animal[] anis = {c, b};

        //Animal[] anis = {new Cat(), new Bird()}; // 该数组中存储了两个对象的内存地址。
        for (int i = 0; i < anis.length; i++){
            // 这个取出来的可能是Cat,也可能是Bird,不过肯定是一个Animal
            // 如果调用的方法是父类中存在的方法不需要向下转型。直接使用父类型引用调用即可。
            //anis[i]
            //Animal an = anis[i];
            //an.move();

            //Animal中没有sing()方法。
            //anis[i].sing();

            // 调用子对象特有方法的话,需要向下转型!!!
            if(anis[i] instanceof Cat){
                Cat cat = (Cat)anis[i];
                cat.catchMouse();
            }else if(anis[i] instanceof Bird){
                Bird bird = (Bird)anis[i];
                bird.sing();
            }
        }

    }

二维数组

int[][] a = {
                {100, 200, 300},
                {30, 20, 40, 50, 60},
                {6, 7, 9, 1},
                {0}
        };
        System.out.println(a.length); // 4
        System.out.println(a[0].length); // 3
        System.out.println(a[1].length); // 5
        System.out.println(a[2].length); // 4
        System.out.println(a[3].length); // 1



        // 取出第2个一维数组当中第3个元素
        System.out.println("第二个一维数组中第三个元素:" + a[1][2]);
// 二维数组
        String[][] array = {
                {"java", "oracle", "c++", "python", "c#"},
                {"张三", "李四", "王五"},
                {"lucy", "jack", "rose"}
        };


        // 合并代码
        for(int i = 0; i < array.length; i++){ // 外层循环3次。(负责纵向。)
            for(int j = 0; j < array[i].length; j++){
                System.out.print(array[i][j] + " ");
            }
            System.out.println();
        }

String

1、String表示字符串类型,属于引用数据类型,不属于基本数据类型。

2、在java中随便使用双引号括起来的都是String对象。例如:“abc”,“def”,“hello world!”,这是3个String对象。

3、java中规定,双引号括起来的字符串,是不可变的,也就是说"abc"自出生到最终死亡,不可变,不能变成"abcd",也不能变

4、在JDK当中双引号括起来的字符串,例如:“abc” "def"都是直接存储在“方法区”的“字符串常量池”当中的。
为什么SUN公司把字符串存储在一个“字符串常量池”当中呢。因为字符串在实际的开发中使用太频繁。为了执行效率,
所以把字符串放到了方法区的字符串常量池当中。

5.String 是 final的byte[] 数组

// 这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。
        String s1 = "abcdef";
        String s2 = "abcdef" + "xy";

 // 分析:这是使用new的方式创建的字符串对象。这个代码中的"xy"是从哪里来的?
        // 凡是双引号括起来的都在字符串常量池中有一份。
        // new对象的时候一定在堆内存当中开辟空间。
        String s3 = new String("xy");

 // i变量中保存的是100这个值。
        int i = 100;
        // s变量中保存的是字符串对象的内存地址。
        // s引用中保存的不是"abc",是0x1111
        // 而0x1111是"abc"字符串对象在“字符串常量池”当中的内存地址。
        String s = "abc";


User user = new User(110, “张三”);


String s1 = "hello";
        // "hello"是存储在方法区的字符串常量池当中
        // 所以这个"hello"不会新建。(因为这个对象已经存在了!)
        String s2 = "hello";
        // 分析结果是true还是false?
        // == 双等号比较的是不是变量中保存的内存地址?是的。
        System.out.println(s1 == s2); // true

        String x = new String("xyz");
        String y = new String("xyz");
        // 分析结果是true还是false?
        // == 双等号比较的是不是变量中保存的内存地址?是的。
        System.out.println(x == y); //false


几个对象

  public static void main(String[] args) {
        /*
        一共3个对象:
            方法区字符串常量池中有1个:"hello"
            堆内存当中有两个String对象。
            一共3个。
         */
        String s1 = new String("hello");
        String s2 = new String("hello");
    }

String构造方法

  • 第一个:String s = new String("");
  • 第二个:String s = “”; 最常用
  • 第三个:String s = new String(char数组);
  • 第四个:String s = new String(char数组,起始下标,长度);
  • 第五个:String s = new String(byte数组);
  • 第六个:String s = new String(byte数组,起始下标,长度);
        String s1 =  "hello world!";
        // 但是输出一个字符串,说明String类已经重写了toString()方法。
        System.out.println(s1);//hello world!

        byte[] bytes = {97, 98, 99}; // 97是a,98是b,99是c
        String s2 = new String(bytes);
        System.out.println(s2.toString()); //abc

        String s3 = new String(bytes, 1, 2);
        System.out.println(s3); // bc


        // 将char数组全部转换成字符串
        char[] chars = {'我','是','中','国','人'};
        String s4 = new String(chars);
        System.out.println(s4);
        // 将char数组的一部分转换成字符串
        String s5 = new String(chars, 2, 3);
        System.out.println(s5);

        String s6 = new String("helloworld!");
        System.out.println(s6); //helloworld!

常用方法

.char charAt(int index)

        char c = "中国人".charAt(1); // "中国人"是一个字符串String对象。只要是对象就能“点.”
        System.out.println(c); // 国

.int compareTo(String anotherString)

		// 字符串之间比较大小不能直接使用 > < ,需要使用compareTo方法。
        int result = "abc".compareTo("abc");
        System.out.println(result); //0(等于0) 前后一致  10 - 10 = 0


        int result2 = "abcd".compareTo("abce");
        System.out.println(result2); //-1(小于0) 前小后大 8 - 9 = -1


        int result3 = "abce".compareTo("abcd");
        System.out.println(result3); // 1(大于0) 前大后小 9 - 8 = 1

        // 拿着字符串第一个字母和后面字符串的第一个字母比较。能分胜负就不再比较了。
        System.out.println("xyz".compareTo("yxz")); // -1

.boolean contains(CharSequence s)

        // 判断前面的字符串中是否包含后面的子字符串。
        System.out.println("HelloWorld.java".contains(".java")); // true
        System.out.println("http://www.baidu.com".contains("https://")); // false

. boolean endsWith(String suffix) 、boolean startsWith(String prefix)

        // 判断当前字符串是否以某个子字符串结尾。
        System.out.println("test.txt".endsWith(".java")); // false
        System.out.println("test.txt".endsWith(".txt")); // true
        System.out.println("fdsajklfhdkjlsahfjkdsahjklfdss".endsWith("ss")); // true

.boolean equals(Object anObject)

        // 比较两个字符串必须使用equals方法,不能使用“==”
        // equals方法有没有调用compareTo方法? 老版本可以看一下。JDK13中并没有调用compareTo()方法。
        // equals只能看出相等不相等。
        // compareTo方法可以看出是否相等,并且同时还可以看出谁大谁小。
        System.out.println("abc".equals("abc")); // true

.boolean equalsIgnoreCase(String anotherString)

        // 判断两个字符串是否相等,并且同时忽略大小写。
        System.out.println("ABc".equalsIgnoreCase("abC")); // true

.byte[] getBytes()

        // 将字符串对象转换成字节数组
        byte[] bytes = "abcdef".getBytes();
        for(int i = 0; i < bytes.length; i++){
            System.out.println(bytes[i]);
        }

.int indexOf(String str)

 // 判断某个子字符串在当前字符串中第一次出现处的索引(下标)。
        System.out.println("oraclejavac++.netc#phppythonjavaoraclec++".indexOf("java")); // 6

.boolean isEmpty()

        // 判断某个字符串是否为“空字符串”。底层源代码调用的应该是字符串的length()方法。
        //String s = "";
        String s = "a";
        System.out.println(s.isEmpty());

. int length()

判断数组长度是length属性,判断字符串长度是length()方法。

        // 面试题:判断数组长度和判断字符串长度不一样
        // 判断数组长度是length属性,判断字符串长度是length()方法。
        System.out.println("abc".length()); // 3
		System.out.println("".length()); // 0	

.int lastIndexOf(String str)

    // 判断某个子字符串在当前字符串中最后一次出现的索引(下标)
    System.out.println("oraclejavac++javac#phpjavapython".lastIndexOf("java")); //22

. String replace(CharSequence target, CharSequence replacement)

        // 替换。
        // String的父接口就是:CharSequence
        String newString = "http://www.baidu.com".replace("http://", "https://");
        System.out.println(newString); //https://www.baidu.com
        // 把以下字符串中的“=”替换成“:”
        String newString2 = "name=zhangsan&password=123&age=20".replace("=", ":");
        System.out.println(newString2); //name:zhangsan&password:123&age:20

.String[] split(String regex)

    String[] ymd = "1980-10-11".split("-"); //"1980-10-11"以"-"分隔符进行拆分。
    for(int i = 0; i < ymd.length; i++){
        System.out.println(ymd[i]);
    }
//1980
//10
//11
    String param = "name=zhangsan&password=123&age=20";
    String[] params = param.split("&");
    for(int i = 0; i <params.length; i++){
        System.out.println(params[i]);
        // 可以继续向下拆分,可以通过“=”拆分。
    }

String substring(int beginIndex) 参数是起始下标。

        // 截取字符串
        System.out.println("http://www.baidu.com".substring(7)); //www.baidu.com

String substring(int beginIndex, int endIndex)

        // beginIndex起始位置(包括)
        // endIndex结束位置(不包括)
        System.out.println("http://www.baidu.com".substring(7, 10)); //www

char[] toCharArray()

        // 将字符串转换成char数组
        char[] chars = "我是中国人".toCharArray();
        for(int i = 0; i < chars.length; i++){
            System.out.println(chars[i]);
        }
// 我
// 是
// 中
// 国
// 人

String toLowerCase()

// 转换为小写。
        System.out.println("ABCDefKXyz".toLowerCase());
  // 19(掌握)、String toUpperCase();
        System.out.println("ABCDefKXyz".toUpperCase());

String trim();

  // 去除字符串前后空白
        System.out.println("           hello      world             ".trim());

String中只有一个方法是静态的,不需要new对象

valueOf

        // 这个方法叫做valueOf
        // 作用:将“非字符串”转换成“字符串”
        //String s1 = String.valueOf(true);
        //String s1 = String.valueOf(100);
        //String s1 = String.valueOf(3.14);




        // 这个静态的valueOf()方法,参数是一个对象的时候,会自动调用该对象的toString()方法吗?
        String s1 = String.valueOf(new Customer());
        //System.out.println(s1); 
// 没有重写toString()方法之前是对象内存地址 com.bjpowernode.javase.string.Customer@10f87f48
        System.out.println(s1); //我是一个VIP客户!!!!

StringBuffer

如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:

  •  java.lang.StringBuffer
    
  •  java.lang.StringBuilder
    

如何优化StringBuffer的性能?

  •  在创建StringBuffer的时候尽可能给定一个初始化容量。(原来的空间释放)
    
  •  最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
    
  •  关键点:给一个合适的初始化容量。可以提高程序的执行效率。
    
        // 创建一个初始化容量为16个byte[] 数组。(字符串缓冲区对象)
        StringBuffer stringBuffer = new StringBuffer();

        // 拼接字符串,以后拼接字符串统一调用 append()方法。
        // append是追加的意思。
		// abd3.14true100
        stringBuffer.append("a");
        stringBuffer.append("b");
        stringBuffer.append("d");
        stringBuffer.append(3.14);
        stringBuffer.append(true);
        // append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。
        stringBuffer.append(100L);

        System.out.println(stringBuffer.toString());



// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
        StringBuffer sb = new StringBuffer(100);
        sb.append("hello");
        sb.append("world");
        sb.append("hello");
        sb.append("kitty");

        System.out.println(sb);

StringBuilder

tringBuffer和StringBuilder的区别?
StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
StringBuilder中的方法都没有:synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。

StringBuffer是线程安全的。

StringBuilder是非线程安全的。


面试题:String为什么是不可变的?
我看过源代码,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,
因为数组一旦创建长度不可变。并且被final修饰的引用一旦指向某个对象之后,不
可再指向其它对象,所以String是不可变的!
“abc” 无法变成 “abcd”

StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组,
这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化
容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法
System.arraycopy()…是这样扩容的。所以StringBuilder/StringBuffer
适合于使用字符串的频繁拼接操作。

包装类

8种基本数据类型对应的包装类型名是什么?
基本数据类型 包装类型
-------------------------------------
​ byte java.lang.Byte(父类Number)
​ short java.lang.Short(父类Number)
​ int java.lang.Integer(父类Number)
​ long java.lang.Long(父类Number)
​ float java.lang.Float(父类Number)
​ double java.lang.Double(父类Number)
​ boolean java.lang.Boolean(父类Object)
​ char java.lang.Character(父类Object)

八种包装类中其中6个都是数字对应的包装类,他们的父类都是Number,可以先研究一下Number中公共的方法:
Number是一个抽象类,无法实例化对象。
Number类中有这样的方法:
byte byteValue() 以 byte 形式返回指定的数值。
abstract double doubleValue()以 double 形式返回指定的数值。
abstract float floatValue()以 float 形式返回指定的数值。
abstract int intValue()以 int 形式返回指定的数值。
abstract long longValue()以 long 形式返回指定的数值。
short shortValue()以 short 形式返回指定的数值。
这些方法其实所有的数字包装类的子类都有,这些方法是负责拆箱的。

// 123这个基本数据类型,进行构造方法的包装达到了:基本数据类型向引用数据类型的转换。
        // 基本数据类型 -(转换为)->引用数据类型(装箱)
        Integer i = new Integer(123);

        // 将引用数据类型--(转换为)-> 基本数据类型
        float f = i.floatValue();
        System.out.println(f); //123.0

        // 将引用数据类型--(转换为)-> 基本数据类型(拆箱)
        int retValue = i.intValue();
        System.out.println(retValue); //123



        // Java9之后不建议使用这个构造方法了。出现横线表示已过时。
        // 将数字100转换成Integer包装类型(int --> Integer)
        Integer x = new Integer(100);
        System.out.println(x);

        // 将String类型的数字,转换成Integer包装类型。(String --> Integer)
        Integer y = new Integer("123");
        System.out.println(y);

        // double -->Double
        Double d = new Double(1.23);
        System.out.println(d);

        // String --> Double
        Double e = new Double("3.14");
        System.out.println(e);
    }



        // 通过访问包装类的常量,来获取最大值和最小值
        System.out.println("int的最大值:" + Integer.MAX_VALUE);
		// 900是基本数据类型
        // x是包装类型
        // 基本数据类型 --(自动转换)--> 包装类型:自动装箱
        Integer x = 900;
        System.out.println(x);

        // x是包装类型
        // y是基本数据类型
        // 包装类型 --(自动转换)--> 基本数据类型:自动拆箱
        int y = x;
        System.out.println(y);

        // z是一个引用,z是一个变量,z还是保存了一个对象的内存地址。
        Integer z = 1000; // 等同于:Integer z = new Integer(1000);
        // 分析为什么这个没有报错呢?
        // +两边要求是基本数据类型的数字,z是包装类,不属于基本数据类型,这里会进行自动拆箱。将z转换成基本数据类型
        // 在java5之前你这样写肯定编译器报错。
        System.out.println(z + 1);

        Integer a = 1000; // Integer a = new Integer(1000); a是个引用,保存内存地址指向对象。
        Integer b = 1000; // Integer b = new Integer(1000); b是个引用,保存内存地址指向对象。
        // == 比较的是对象的内存地址,a和b两个引用中保存的对象内存地址不同。
        // == 这个运算符不会触发自动拆箱机制。(只有+ - * /等运算的时候才会。)
        System.out.println(a == b); //false

		Integer a = 128;
        Integer b = 128;
        System.out.println(a == b); //false

        /*
        java中为了提高程序的执行效率,将[-128到127]之间所有的包装对象提前创建好,
        放到了一个方法区的“整数型常量池”当中了,目的是只要用这个区间的数据不需要
        再new了,直接从整数型常量池当 中取出来。

        原理:x变量中保存的对象的内存地址和y变量中保存的对象的内存地址是一样的。
         */
        Integer x = 127;
        Integer y = 127;
        // == 永远判断的都是两个对象的内存地址是否相同。
        System.out.println(x == y); //true
 // 重点方法
        // static int parseInt(String s)
        // 静态方法,传参String,返回int
        //网页上文本框中输入的100实际上是"100"字符串。后台数据库中要求存储100数字,此时java程序需要将"100"转换成100数字。
        int retValue = Integer.parseInt("123"); // String -转换-> int
        //int retValue = Integer.parseInt("中文"); // NumberFormatException
        System.out.println(retValue + 100);

xxx.valueOf 转变为xxx的包装类

 // valueOf方法作为了解
        //static Integer valueOf(int i)
        // 静态的:int-->Integer
        Integer i1 = Integer.valueOf(100);
        System.out.println(i1);

        // static Integer valueOf(String s)
        // 静态的:String-->Integer
        Integer i2 = Integer.valueOf("100");
        System.out.println(i2);

        // String --> int
        int i1 = Integer.parseInt("100"); // i1是100数字
        System.out.println(i1 + 1); // 101

        // int --> String
        String s2 = i1 + ""; // "100"字符串
        System.out.println(s2 + 1); // "1001"

        // int --> Integer
        // 自动装箱
        Integer x = 1000;

        // Integer --> int
        // 自动拆箱
        int y = x;

        // String --> Integer
        Integer k = Integer.valueOf("123");

        // Integer --> String
        String e = String.valueOf(k);

Date

        // java.util.Date类的toString()方法已经被重写了。
		Date nowTime = new Date();  //Fri Jun 19 20:33:09 GMT+08:00 2020
		
		/*
        yyyy 年(年是4位)
        MM 月(月是2位)
        dd 日
        HH 时
        mm 分
        ss 秒
        SSS 毫秒(毫秒3位,最高999。1000毫秒代表1秒)
        注意:在日期格式中,除了y M d H m s S这些字符不能随便写之外,剩下的符号格式自己随意组织。
         */

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

		String nowTimeStr = sdf.format(nowTime);


		// 假设现在有一个日期字符串String,怎么转换成Date类型?
        // String --> Date
        String time = "2008-08-08 08:08:08 888";
//SimpleDateFormat sdf2 = new SimpleDateFormat("格式不能随便写,要和日期字符串格式相同");
        // 注意:字符串的日期格式和SimpleDateFormat对象指定的日期格式要一致。不然会出现异常:java.text.ParseException
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date dateTime = sdf2.parse(time);
        System.out.println(dateTime); //Fri Aug 08 08:08:08 CST 2008

简单总结一下System类的相关属性和方法:

  • System.out 【out是System类的静态变量。】
  • System.out.println() 【println()方法不是System类的,是PrintStream类的方法。】
  • System.gc() 建议启动垃圾回收器
  • System.currentTimeMillis() 获取自1970年1月1日到系统当前时间的总毫秒数。
  • System.exit(0) 退出JVM。
        // 获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数。
        long nowTimeMillis = System.currentTimeMillis();
        System.out.println(nowTimeMillis); //1583377912981

        // 获取昨天的此时的时间。
        Date time2 = new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24);
        String strTime2 = sdf.format(time2);
        System.out.println(strTime2); //2020-03-04 11:44:14 829

数字格式化

        /*
        数字格式有哪些?
            # 代表任意数字
            , 代表千分位
            . 代表小数点
            0 代表不够时补0

            ###,###.##
                表示:加入千分位,保留2个小数。
         */
        DecimalFormat df = new DecimalFormat("###,###.##");
        String s = df.format(1234.561232);
        System.out.println(s); // "1,234.56"
        DecimalFormat df2 = new DecimalFormat("###,###.0000"); //保留4个小数位,不够补上0
        String s2 = df2.format(1234.56);
        System.out.println(s2); // "1,234.5600"

BigDecimal

BigDecimal 属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型)

        // 这个100不是普通的100,是精度极高的100
        BigDecimal v1 = new BigDecimal(100);
        // 精度极高的200
        BigDecimal v2 = new BigDecimal(200);
        // 求和
        // v1 + v2; // 这样不行,v1和v2都是引用,不能直接使用+求和。
        BigDecimal v3 = v1.add(v2); // 调用方法求和。
        System.out.println(v3); //300

随机数

        // 创建随机数对象
        Random random = new Random();

        // 随机产生一个int类型取值范围内的数字。
        int num1 = random.nextInt();

        System.out.println(num1);

        // 产生[0~100]之间的随机数。不能产生101。
        // nextInt翻译为:下一个int类型的数据是101,表示只能取到100.
        int num2 = random.nextInt(101); //不包括101
        System.out.println(num2);

枚举

总结:
1、枚举是一种引用数据类型
2、枚举类型怎么定义,语法是?
enum 枚举类型名{
枚举值1,枚举值2
}
3、结果只有两种情况的,建议使用布尔类型。
结果超过两种并且还是可以一枚一枚列举出来的,建议使用枚举类型。
例如:颜色、四季、星期等都可以使用枚举类型。

// 枚举:一枚一枚可以列举出来的,才建议使用枚举类型。
// 枚举编译之后也是生成class文件。
// 枚举也是一种引用数据类型。
// 枚举中的每一个值可以看做是常量。
enum Result{
    // SUCCESS 是枚举Result类型中的一个值
    // FAIL 是枚举Result类型中的一个值
    // 枚举中的每一个值,可以看做是“常量”
    SUCCESS, FAIL
}

异常

​ Object

​ |

​ Throwable

​ / \

Error(不可处理,直接退出JVM) Exception(可处理的)

​ / \

​ 编译时异常 运行时异常

1.2、编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生就是new异常对象。

1.3、编译时异常和运行时异常的区别?

​ 编译时异常一般发生的概率比较高。

​ 运行时异常一般发生的概率比较低。

1.7、Java语言中对异常的处理包括两种方式:

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。

第二种方式:使用try…catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。

思考:
异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要
对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。

1.8、注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续
向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。


/*
以下代码报错的原因是什么?
    因为doSome()方法声明位置上使用了:throws ClassNotFoundException
    而ClassNotFoundException是编译时异常。必须编写代码时处理,没有处理
    编译器报错。
 */
public class ExceptionTest04 {
    public static void main(String[] args) {
        // main方法中调用doSome()方法
        // 因为doSome()方法声明位置上有:throws ClassNotFoundException
        // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
        // 如果不处理,编译器就报错。
        //编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
        //doSome();
    }

    /**
     * doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
     * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
     * 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
     * @throws ClassNotFoundException
     */
    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }

}

注意:
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。
另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行。
try…catch捕捉异常之后,后续代码可以执行。

public class ExceptionTest06 {
    /*
    
    main begin
	m1 begin
	m2 begin
	文件不存在,可能路径错误,也可能该文件被删除了!
	java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)
	main over

    */
  
    public static void main(String[] args) {


        System.out.println("main begin");
        try {
            // try尝试
            m1();
            // 以上代码出现异常,直接进入catch语句块中执行。
            System.out.println("hello world!");
        } catch (FileNotFoundException e){ // catch后面的好像一个方法的形参。
            // 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址。
            // catch是捕捉异常之后走的分支。
            // 在catch分支中干什么?处理异常。
            System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!");
            System.out.println(e); //java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)
        }

        // try..catch把异常抓住之后,这里的代码会继续执行。
        System.out.println("main over");
    }

    private static void m1() throws FileNotFoundException {
        System.out.println("m1 begin");
        m2();
        // 以上代码出异常,这里是无法执行的。
        System.out.println("m1 over");
    }

    
    private static void m2() throws FileNotFoundException {
        System.out.println("m2 begin");
        // 编译器报错原因是:m3()方法声明位置上有:throws FileNotFoundException
        // 我们在这里调用m3()没有对异常进行预处理,所以编译报错。
        // m3();

        m3();
        // 以上如果出现异常,这里是无法执行的!
        System.out.println("m2 over");
    }

    private static void m3() throws FileNotFoundException {
        /*
        编译报错的原因是什么?
            第一:这里调用了一个构造方法:FileInputStream(String name)
            第二:这个构造方法的声明位置上有:throws FileNotFoundException
            第三:通过类的继承结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,
            最终得知,FileNotFoundException是编译时异常。

            错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就报错。
         */
        //new FileInputStream("D:\\course\\01-开课\\学习方法.txt");

        // 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
        // 一个方法体当中的代码出现异常之后,如果上报的话,此方法结束。
        new FileInputStream("D:\\course\\01-课\\学习方法.txt");

        System.out.println("如果以上代码出异常,这里会执行吗??????????????????不会!!!");
    }
}

深入try…catch
1、catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。
2、catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
3、catch写多个的时候,从上到下,必须遵守从小到大。

 /*
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
            //读文件
            fis.read();
        } catch(IOException e){
            System.out.println("读文件报错了!");
        } catch(FileNotFoundException e) {
            System.out.println("文件不存在!");
        }
         */

        // JDK8的新特性!
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
            // 进行数学运算
            System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
        } catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
            System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
        }

异常对象有两个非常重要的方法:

​ 获取异常简单的描述信息:

​ String msg = exception.getMessage();

​ 打印异常追踪的堆栈信息:

​ exception.printStackTrace();

		// 这里只是为了测试getMessage()方法和printStackTrace()方法。
        // 这里只是new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象。
        NullPointerException e = new NullPointerException("空指针异常fdsafdsafdsafds");

        // 获取异常简单描述信息:这个信息实际上就是构造方法上面String参数。
        String msg = e.getMessage(); //空指针异常fdsafdsafdsafds
        System.out.println(msg);

        // 打印异常堆栈信息
        // java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
        e.printStackTrace();

关于try…catch中的finally子句:
1、在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。

​ 2、finally语句通常使用在哪些情况下呢?

​ 通常在finally语句块中完成资源的释放/关闭。

​ 因为finally中的代码比较有保障。

​ 即使try语句块中的代码出现异常,finally中代码也会正常执行。

/*
        try和finally,没有catch可以吗?可以。
            try不能单独使用。
            try finally可以联合使用。
        以下代码的执行顺序:
            先执行try...
            再执行finally...
            最后执行 return (return语句只要执行方法必然结束。)
         */
        try {
            System.out.println("try...");
            return;
        } finally {
            // finally中的语句会执行。能执行到。
            System.out.println("finally...");
        }

        // 这里不能写语句,因为这个代码是无法执行到的。
        //System.out.println("Hello World!");
    public static void main(String[] args) {
        int result = m();
        System.out.println(result); //100
    }

    /*
    java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
        java中有一条这样的规则:
            方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
        java中还有一条语法规则:
            return语句一旦执行,整个方法必须结束(亘古不变的语法!)
     */
    public static int m(){
        int i = 100;
        try {
            // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
            // return语句还必须保证是最后执行的。一旦执行,整个方法结束。
            return i;
        } finally {
            i++;
        }
    }

/*
反编译之后的效果
public static int m(){
    int i = 100;
    int j = i;
    i++;
    return j;
}
 */

final finally finalize有什么区别?
final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值。

​ finally 关键字

​ 和try一起联合使用。

​ finally语句块中的代码是必须执行的。

​ finalize 标识符
​ 是一个Object类中的方法名。
​ 这个方法是由垃圾回收器GC负责调用的。

自定义异常

/*
1、SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,
这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。那么异常类我们
程序员可以自己定义吗?
    可以。

2、Java中怎么自定义异常呢?
    两步:
        第一步:编写一个类继承Exception或者RuntimeException.
        第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

    死记硬背。
 */
public class MyException extends Exception{ // 编译时异常
    public MyException(){

    }
    public MyException(String s){
        super(s);
    }
}

总结异常中的关键字:
异常捕捉:
try
catch
finally

throws 在方法声明位置上使用,表示上报异常信息给调用者。
throw 手动抛出异常!

集合

1.2、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,
集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100); //自动装箱Integer
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”。

1.6、在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;

​ 一类是以键值对儿的方式存储元素
​ 以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

Collection

2、Collection中的常用方法
boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】

// 创建一个集合对象
        //Collection c = new Collection(); // 接口是抽象的,无法实例化。
        // 多态
        Collection c = new ArrayList();
        // 测试Collection接口中的常用方法
        c.add(1200); // 自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
        c.add(3.14); // 自动装箱
        c.add(new Object());
        c.add(new Student());
        c.add(true); // 自动装箱

        // 获取集合中元素的个数
        System.out.println("集合中元素个数是:" + c.size()); // 5

        // 清空集合
        c.clear();
        System.out.println("集合中元素个数是:" + c.size()); // 0

        // 再向集合中添加元素
        c.add("hello"); // "hello"对象的内存地址放到了集合当中。
        c.add("world");
        c.add("浩克");
        c.add("绿巨人");
        c.add(1);

        // 判断集合中是否包含"绿巨人"
        boolean flag = c.contains("绿巨人");
        System.out.println(flag); // true
        boolean flag2 = c.contains("绿巨人2");
        System.out.println(flag2); // false
        System.out.println(c.contains(1)); // true

        System.out.println("集合中元素个数是:" + c.size()); // 5

        // 删除集合中某个元素
        c.remove(1);
        System.out.println("集合中元素个数是:" + c.size()); // 4

        // 判断集合是否为空(集合中是否存在元素)
        System.out.println(c.isEmpty()); // false
        // 清空
        c.clear();
        System.out.println(c.isEmpty()); // true(true表示集合中没有元素了!)

        c.add("abc");
        c.add("def");
        c.add(100);
        c.add("helloworld!");
        c.add(new Student());

        // 转换成数组(了解,使用不多。)
        Object[] objs = c.toArray();
        for(int i = 0; i < objs.length; i++){
            // 遍历数组
            Object o = objs[i];
            System.out.println(o);
        } 

iterator

// 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
        // 在Map集合中不能用。在所有的Collection以及子类中使用。
Iterator it = c.iterator();
        // 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
        /*
            以下两个方法是迭代器对象Iterator中的方法:
                boolean hasNext()如果仍有元素可以迭代,则返回 true。
                Object next() 返回迭代的下一个元素。
         */
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }

contains

深入Collection集合的contains方法:
boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true, 如果不包含返回false。

contains方法是用来判断集合中是否包含某个元素的方法,
那么它在底层是怎么判断集合中是否包含某个元素的呢?
调用了equals方法进行比对。
equals方法返回true,就表示包含这个元素。

// 创建集合对象
        Collection c = new ArrayList();

        // 向集合中存储元素
        String s1 = new String("abc"); // s1 = 0x1111
        c.add(s1); // 放进去了一个"abc"

        String s2 = new String("def"); // s2 = 0x2222
        c.add(s2);

        // 集合中元素的个数
        System.out.println("元素的个数是:" + c.size()); // 2

        // 新建的对象String
        String x = new String("abc"); // x = 0x5555
        // c集合中是否包含x?结果猜测一下是true还是false?
        System.out.println(c.contains(x)); //判断集合中是否存在"abc" true

结论:存放在一个集合中的类型,一定要重写equals方法。

remove

        // 创建集合对象
        Collection cc = new ArrayList();
        // 创建字符串对象
        String s1 = new String("hello");
        // 加进去。
        cc.add(s1);

        // 创建了一个新的字符串对象
        String s2 = new String("hello");
        // 删除s2
        cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
        // 集合中元素个数是?
        System.out.println(cc.size()); // 0

remove

关于集合元素的remove
重点:当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现
异常:java.util.ConcurrentModificationException

重点:在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:
c.remove(o); 迭代过程中不能这样。
会出现:java.util.ConcurrentModificationException

重点:在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素, it.remove
不要使用集合自带的remove方法删除元素。

List

1、List集合存储元素特点:有序可重复
有序:List集合中的元素有下标。
从0开始,以1递增。
可重复:存储一个1,还可以再存储1.
2、List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:
以下只列出List接口特有的常用的方法:
void add(int index, Object element)
Object set(int index, Object element)
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)

 // 创建List类型的集合。
        //List myList = new LinkedList();
        //List myList = new Vector();
        List myList = new ArrayList();

        // 添加元素
        myList.add("A"); // 默认都是向集合末尾添加元素。
        myList.add("B");
        myList.add("C");
        myList.add("C");
        myList.add("D");

        //在列表的指定位置插入指定元素(第一个参数是下标)
        // 这个方法使用不多,因为对于ArrayList集合来说效率比较低。
        myList.add(1, "KING");

        // 迭代
        Iterator it = myList.iterator();
        while(it.hasNext()){
            Object elt = it.next();
            System.out.println(elt);
        }

        // 根据下标获取元素
        Object firstObj = myList.get(0);
        System.out.println(firstObj);

        // 因为有下标,所以List集合有自己比较特殊的遍历方式
        // 通过下标遍历。【List集合特有的方式,Set没有。】
        for(int i = 0; i < myList.size(); i++){
            Object obj = myList.get(i);
            System.out.println(obj);
        }

        // 获取指定对象第一次出现处的索引。
        System.out.println(myList.indexOf("C")); // 3

        // 获取指定对象最后一次出现处的索引。
        System.out.println(myList.lastIndexOf("C")); // 4

        // 删除指定下标位置的元素
        // 删除下标为0的元素
        myList.remove(0);
        System.out.println(myList.size()); // 5

        System.out.println("====================================");

        // 修改指定位置的元素
        myList.set(2, "Soft");

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

ArrayList

ArrayList集合:
1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
2、集合底层是一个Object[]数组。
3、构造方法:
new ArrayList();
new ArrayList(20);
4、ArrayList集合的扩容:
增长到原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合
的时候预估计元素的个数,给定一个初始化容量。
5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7、向数组末尾添加元素,效率很高,不受影响。
8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。

9、ArrayList集合是非线程安全的。(不是线程安全的集合。)

        // 指定初始化容量
        // 数组的长度是20
        List list2 = new ArrayList(20);
        // 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
        System.out.println(list2.size()); // 0

        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        list1.add(5);
        list1.add(6);
        list1.add(7);
        list1.add(8);
        list1.add(9);
        list1.add(10);

        System.out.println(list1.size());

        // 再加一个元素
        list1.add(11);
        System.out.println(list1.size()); // 11个元素。



LinkedList

链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
使用LinkedList。

链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
较低。

ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。

   // LinkedList集合底层也是有下标的。
        // 注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
        // LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
        List list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");

        for(int i = 0; i <list.size(); i++){
            Object obj = list.get(i);
            System.out.println(obj);
        }

        // LinkedList集合有初始化容量吗?没有。
        // 最初这个链表中没有任何元素。first和last引用都是null。
        // 不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
        // 因为我们要面向接口编程,调用的方法都是接口中的方法。
        //List list2 = new ArrayList(); // 这样写表示底层你用了数组。
        List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。

        // 以下这些方法你面向的都是接口编程。
        list2.add("123");
        list2.add("456");
        list2.add("789");

        for(int i = 0; i < list2.size(); i++){
            System.out.println(list2.get(i));
        }

Vector

Vector:
1、底层也是一个数组。
2、初始化容量:10
3、怎么扩容的?
扩容之后是原容量的2倍。
10–> 20 --> 40 --> 80

4、ArrayList集合扩容特点:
ArrayList集合扩容是原容量1.5倍。

5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,
是线程安全的。效率比较低,使用较少了。

6、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;

​ java.util.Collection 是集合接口。
​ java.util.Collections 是集合工具类。

        // 创建一个Vector集合
        List vector = new Vector();
        //Vector vector = new Vector();

        // 添加元素
        // 默认容量10个。
        vector.add(1);
        vector.add(2);
        vector.add(3);
        vector.add(4);
        vector.add(5);
        vector.add(6);
        vector.add(7);
        vector.add(8);
        vector.add(9);
        vector.add(10);

        // 满了之后扩容(扩容之后的容量是20.)
        vector.add(11);

        Iterator it = vector.iterator();
        while(it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }

        // 这个可能以后要使用!!!!
        List myList = new ArrayList(); // 非线程安全的。

        // 变成线程安全的
        Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!

        // myList集合就是线程安全的了。
        myList.add("111");
        myList.add("222");
        myList.add("333");
    }

泛型

1、JDK5.0之后推出的新特性:泛型
2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)
3、使用了泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!

4、泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。

// 使用JDK5之后的泛型机制
        // 使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据。
        // 用泛型来指定集合中存储的数据类型。
        List<Animal> myList = new ArrayList<Animal>();

        // 指定List集合中只能存储Animal,那么存储String就编译报错了。
        // 这样用了泛型之后,集合中元素的数据类型更加统一了。
        //myList.add("abc");

        Cat c = new Cat();
        Bird b = new Bird();

        myList.add(c);
        myList.add(b);

        // 获取迭代器
        // 这个表示迭代器迭代的是Animal类型。
        Iterator<Animal> it = myList.iterator();
        while(it.hasNext()){
            // 使用泛型之后,每一次迭代返回的数据都是Animal类型。
            //Animal a = it.next();
            // 这里不需要进行强制类型转换了。直接调用。
            //a.move();

            // 调用子类型特有的方法还是需要向下转换的!
            Animal a = it.next();
            if(a instanceof Cat) {
                Cat x = (Cat)a;
                x.catchMouse();
            }
            if(a instanceof Bird) {
                Bird y = (Bird)a;
                y.fly();
            }
        }

自定义泛型可以吗?可以
自定义泛型的时候,<> 尖括号中的是一个标识符,随便写。
java源代码中经常出现的是:

E是Element单词首字母。
T是Type单词首字母。

foreach

// 增强for(foreach)
        // 以下是语法
        /*for(元素类型 变量名 : 数组或集合){
            System.out.println(变量名);
        }*/

        System.out.println("======================================");
        // foreach有一个缺点:没有下标。在需要使用下标的循环中,不建议使用增强for循环。
        for(int data : arr) {
            // data就是数组中的元素(数组中的每一个元素。)
            System.out.println(data);
        }

HashSet

HashSet集合:
无序不可重复。

        // 演示一下HashSet集合特点
        Set<String> strs = new HashSet<>();

        // 添加元素
        strs.add("hello3");
        strs.add("hello4");
        strs.add("hello1");
        strs.add("hello2");
        strs.add("hello3");
        strs.add("hello3");
        strs.add("hello3");
        strs.add("hello3");

        // 遍历
        /*
        hello1
        hello4
        hello2
        hello3
        1、存储时顺序和取出的顺序不同。
        2、不可重复。
        3、放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
         */
        for(String s : strs){
            System.out.println(s);
        }

TreeSet

TreeSet集合存储元素特点:
无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。

1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。

自定义类型的排序

public class TreeSetTest04 {
    public static void main(String[] args) {
        Customer c1 = new Customer(32);
        Customer c2 = new Customer(20);
        Customer c3 = new Customer(30);
        Customer c4 = new Customer(25);

        // 创建TreeSet集合
        TreeSet<Customer> customers = new TreeSet<>();
        // 添加元素
        customers.add(c1);
        customers.add(c2);
        customers.add(c3);
        customers.add(c4);

        // 遍历
        for (Customer c : customers){
            System.out.println(c);
        }
    }
}

// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
// 并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{

    int age;
    public Customer(int age){
        this.age = age;
    }

    // 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
    // k.compareTo(t.key)
    // 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
    // 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
    @Override
    public int compareTo(Customer c) { // c1.compareTo(c2);
        // this是c1
        // c是c2
        // c1和c2比较的时候,就是this和c比较。
        /*int age1 = this.age;
        int age2 = c.age;
        if(age1 == age2){
            return 0;
        } else if(age1 > age2) {
            return 1;
        } else {
            return -1;
        }*/
        //return this.age - c.age; // =0 >0 <0
        return c.age - this.age;
    }

    public String toString(){
        return "Customer[age="+age+"]";
    }
}
    /*
    compareTo方法的返回值很重要:
        返回0表示相同,value会覆盖。
        返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
        返回<0,会继续在左子树上找。
     */
    @Override
    public int compareTo(Vip v) {
        // 写排序规则,按照什么进行比较。
        if(this.age == v.age){
            // 年龄相同时按照名字排序。
            // 姓名是String类型,可以直接比。调用compareTo来完成比较。
            return this.name.compareTo(v.name);  //字符串比较
        } else {
            // 年龄不一样
            return this.age - v.age;
        }
    }

/*
TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
最终的结论:
    放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
        第一种:放在集合中的元素实现java.lang.Comparable接口。
        第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
    当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
    如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。

    Comparator接口的设计符合OCP原则。
 */
public class TreeSetTest06 {
    public static void main(String[] args) {
        // 创建TreeSet集合的时候,需要使用这个比较器。
        // TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。

        // 给构造方法传递一个比较器。
        //TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

        // 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });

        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for(WuGui wuGui : wuGuis){
            System.out.println(wuGui);
        }
    }
}

// 乌龟
class WuGui{

    int age;

    public WuGui(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "小乌龟[" +
                "age=" + age +
                ']';
    }
}

// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {

    @Override
    public int compare(WuGui o1, WuGui o2) {
        // 指定比较规则
        // 按照年龄排序
        return o1.age - o2.age;
    }
}
 */

  1. 类实现Comparable接口 add元素然后直接输出就行了
  2. 匿名内部类参数构造然后实现Comparator接口,实现方法
public class Person implements Comparable<Person>{
    private  int age;

    public Person(){

    }

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }

    @Override
    public int compareTo(Person o) {
        return this.age-o.age;
    }
}

        Set<Person> s =new TreeSet<>();
        s.add(new Person(12));
        s.add(new Person(15));
        s.add(new Person(13));
        s.add(new Person(14));

        for(Person a :s){
            System.out.println(a);
        }

        Set<Person> p = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        p.add(new Person(10));
        p.add(new Person(150));
        p.add(new Person(131));
        p.add(new Person(104));
        for(Person a :p){
            System.out.println(a);
        }

Collections

public class CollectionsTest {
    public static void main(String[] args) {

        // ArrayList集合不是线程安全的。
        List<String> list = new ArrayList<>();

        // 变成线程安全的
        Collections.synchronizedList(list);

        // 排序
        list.add("abf");
        list.add("abx");
        list.add("abc");
        list.add("abe");

        Collections.sort(list);
        for(String s : list){
            System.out.println(s);
        }

        List<WuGui2> wuGuis = new ArrayList<>();
        wuGuis.add(new WuGui2(1000));
        wuGuis.add(new WuGui2(8000));
        wuGuis.add(new WuGui2(500));
        // 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
        Collections.sort(wuGuis);
        for(WuGui2 wg : wuGuis){
            System.out.println(wg);
        }

        // 对Set集合怎么排序呢?
        Set<String> set = new HashSet<>();
        set.add("king");
        set.add("kingsoft");
        set.add("king2");
        set.add("king1");
        // 将Set集合转换成List集合
        List<String> myList = new ArrayList<>(set);
        Collections.sort(myList);
        for(String s : myList) {
            System.out.println(s);
        }

        // 这种方式也可以排序。
        //Collections.sort(list集合, 比较器对象);
    }
}

class WuGui2 implements Comparable<WuGui2>{
    int age;
    public WuGui2(int age){
        this.age = age;
    }

    @Override
    public int compareTo(WuGui2 o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "WuGui2{" +
                "age=" + age +
                '}';
    }
}

Map

1、Map和Collection没有继承关系。
2、Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品。

3、Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection values() 获取Map集合中所有的value,返回一个Collection

​ Set keySet() 获取Map集合所有的key(所有的键是一个set集合)

Set<Map.Entry<K,V>> entrySet()
将Map集合转换成Set集合
假设现在有一个Map集合,如下所示:
map1集合对象

​ key value

​ 1 zhangsan
​ 2 lisi
​ 3 wangwu
​ 4 zhaoliu

​ Set set = map1.entrySet();

​ set集合对象

​ 1=zhangsan

【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】

​ 2=lisi

【Map.Entry和String一样,都是一种类型的名字,只不过: Map.Entry是静态内部类,是Map中的静态内部类】

​ 3=wangwu

​ 4=zhaoliu —> 这个东西是个什么?Map.Entry

// 创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        // 向Map集合中添加键值对
        map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        // 通过key获取value
        String value = map.get(2);
        System.out.println(value);
        // 获取键值对的数量
        System.out.println("键值对的数量:" + map.size());
        // 通过key删除key-value
        map.remove(2);
        System.out.println("键值对的数量:" + map.size());
        // 判断是否包含某个key
        // contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
        System.out.println(map.containsKey(new Integer(4))); // true
        // 判断是否包含某个value
        System.out.println(map.containsValue(new String("wangwu"))); // true

        // 获取所有的value
        Collection<String> values = map.values();
        // foreach
        for(String s : values){
            System.out.println(s);
        }

        // 清空map集合
        map.clear();
        System.out.println("键值对的数量:" + map.size());
        // 判断是否为空
        System.out.println(map.isEmpty()); // true

遍历

        // 第一种方式:获取所有的key,通过遍历key,来遍历value
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");
        // 遍历Map集合
        // 获取所有的key,所有的key是一个Set集合
        Set<Integer> keys = map.keySet();
        // 遍历key,通过key获取value
        // 迭代器可以
        /*Iterator<Integer> it = keys.iterator();
        while(it.hasNext()){
            // 取出其中一个key
            Integer key = it.next();
            // 通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }*/
        // foreach也可以
        for(Integer key : keys){
            System.out.println(key + "=" + map.get(key));
        }


-----------------------------------------------------------------------------------
        // 第二种方式:Set<Map.Entry<K,V>> entrySet()
        // 以上这个方法是把Map集合直接全部转换成Set集合。
        // Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        // 遍历Set集合,每一次取出一个Node
        // 迭代器
        /*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
        while(it2.hasNext()){
            Map.Entry<Integer,String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
        }*/

        // foreach
        // 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
        // 这种方式比较适合于大数据量。
        for(Map.Entry<Integer,String> node : set){
            System.out.println(node.getKey() + "--->" + node.getValue());
        }

HashMap

1、HashMap集合底层是哈希表/散列表的数据结构。
2、哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、HashMap集合底层的源代码:
public class HashMap{
// HashMap底层实际上就是一个数组。(一维数组)
Node<K,V>[] table;
// 静态的内部类HashMap.Node
static class Node<K,V> {
final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。)
final K key; // 存储到Map集合中的那个key
V value; // 存储到Map集合中的那个value
Node<K,V> next; // 下一个节点的内存地址。
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
4、最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理,是必须掌握的。
5、HashMap集合的key部分特点:
无序,不可重复。
为什么无序? 因为不一定挂到哪个单向链表上。
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。
如果key重复了,value会覆盖。

​ 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
​ 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

6、哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
纯单向链表。这种情况我们成为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,
是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。
也是散列分布不均匀。
散列分布均匀需要你重写hashCode()方法时有一定的技巧。
7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
8、HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。

重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,
这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。

public static void main(String[] args) {
        // 测试HashMap集合key部分的元素特点
        // Integer是key,它的hashCode和equals都重写了。
        Map<Integer,String> map = new HashMap<>();
        map.put(1111, "zhangsan");
        map.put(6666, "lisi");
        map.put(7777, "wangwu");
        map.put(2222, "zhaoliu");
        map.put(2222, "king"); //key重复的时候value会自动覆盖。

        System.out.println(map.size()); // 4

        // 遍历Map集合
        Set<Map.Entry<Integer,String>> set = map.entrySet();
        for(Map.Entry<Integer,String> entry : set){
            // 验证结果:HashMap集合key部分元素:无序不可重复。
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }

1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。

2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
equals方法返回true表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以hashCode()方法的返回值也应该相同。

3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。

4、终极结论:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

5、对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

HashMap集合key部分允许null吗?
允许
但是要注意:HashMap集合的key null值只能有一个。
有可能面试的时候遇到这样的问题。

HashTable

Hashtable的key可以为null吗?
Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。

Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理
导致效率较低,使用较少了。

Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1

Properties

目前只需要掌握Properties属性类对象的相关方法即可。
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。

        // 创建一个Properties对象
        Properties pro = new Properties();

        // 需要掌握Properties的两个方法,一个存,一个取。
        pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
        pro.setProperty("driver","com.mysql.jdbc.Driver");
        pro.setProperty("username", "root");
        pro.setProperty("password", "123");

        // 通过key获取value
        String url = pro.getProperty("url");
        String driver = pro.getProperty("driver");
        String username = pro.getProperty("username");
        String password = pro.getProperty("password");

        System.out.println(url);
        System.out.println(driver);
        System.out.println(username);
        System.out.println(password);

IO

一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫做输入(Input)。或者叫做读(Read)。
从内存中出来,叫做输出(Output)。或者叫做写(Write)。

	另一种方式是按照读取数据方式不同进行分类:
		有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。
		这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等....
			假设文件file1.txt,采用字节流的话是这样读的:
				a中国bc张三fe
				第一次读:一个字节,正好读到'a'
				第二次读:一个字节,正好读到'中'字符的一半。
				第三次读:一个字节,正好读到'中'字符的另外一半。

		有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取
		普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯
		文本文件,连word文件都无法读取。
			假设文件file1.txt,采用字符流的话是这样读的:
				a中国bc张三fe
				第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
				第二次读:'中'字符('中'字符在windows系统中占用2个字节。)

​ 综上所述:流的分类

输入流、输出流

字节流、字符流

​ java.io.InputStream 字节输入流
​ java.io.OutputStream 字节输出流

​ java.io.Reader 字符输入流
​ java.io.Writer 字符输出流

​ 四大家族的首领都是抽象类。(abstract class)

所有的都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。

所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()
刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据
强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。

java.io包下需要掌握的流有16个:
	
	文件专属:
		java.io.FileInputStream(掌握)
		java.io.FileOutputStream(掌握)
		java.io.FileReader
		java.io.FileWriter

	转换流:(将字节流转换成字符流)
		java.io.InputStreamReader
		java.io.OutputStreamWriter

	缓冲流专属:
		java.io.BufferedReader
		java.io.BufferedWriter
		java.io.BufferedInputStream
		java.io.BufferedOutputStream

	数据流专属:
		java.io.DataInputStream
		java.io.DataOutputStream

	标准输出流:
		java.io.PrintWriter
		java.io.PrintStream(掌握)

	对象专属流:
		java.io.ObjectInputStream(掌握)
		java.io.ObjectOutputStream(掌握)

FileInputStream

/*
java.io.FileInputStream:
    1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
    2、字节的方式,完成输入的操作,完成读的操作(硬盘---> 内存)
 */
public class FileInputStreamTest01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            // 创建文件字节输入流对象
            // 文件路径:D:\course\JavaProjects\02-JavaSE\temp (IDEA会自动把\编程\\,因为java中\表示转义)
            // 以下都是采用了:绝对路径的方式。
            //FileInputStream fis = new FileInputStream("D:\\course\\JavaProjects\\02-JavaSE\\temp");
            // 写成这个/也是可以的。
            fis = new FileInputStream("D:/course/JavaProjects/02-JavaSE/temp");

            // 开始读
            int readData = fis.read(); // 这个方法的返回值是:读取到的“字节”本身。
            System.out.println(readData); //97

            readData = fis.read();
            System.out.println(readData); //98

            readData = fis.read();
            System.out.println(readData); //99

            readData = fis.read();
            System.out.println(readData); //100

            readData = fis.read();
            System.out.println(readData); //101

            readData = fis.read();
            System.out.println(readData); //102

            // 已经读到文件的末尾了,再读的时候读取不到任何数据,返回-1.
            readData = fis.read();
            System.out.println(readData);

            readData = fis.read();
            System.out.println(readData);

            readData = fis.read();
            System.out.println(readData);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally语句块当中确保流一定关闭。
            if (fis != null) { // 避免空指针异常!
                // 关闭流的前提是:流不是空。流是null的时候没必要关闭。
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

升级

分析这个程序的缺点:
一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费
在交互上面了。能不能一次读取多个字节呢?可以。

  int readData = 0;
            while((readData = fis.read()) != -1){
                System.out.println(readData);
            }

最终版,需要掌握。

 public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("chapter23/src/tempfile3");
            // 准备一个byte数组
            byte[] bytes = new byte[4];
            /*while(true){
                int readCount = fis.read(bytes);
                if(readCount == -1){
                    break;
                }
                // 把byte数组转换成字符串,读到多少个转换多少个。
                System.out.print(new String(bytes, 0, readCount));
            }*/

            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1) {
                System.out.print(new String(bytes, 0, readCount));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

FileInputStream类的其它常用方法:
int available():返回流当中剩余的没有读到的字节数量
long skip(long n):跳过几个字节不读。

            fis = new FileInputStream("tempfile");
            System.out.println("总字节数量:" + fis.available());
            // 读1个字节
            //int readByte = fis.read();
            // 还剩下可以读的字节数量是:5
            //System.out.println("剩下多少个字节没有读:" + fis.available());
            // 这个方法有什么用?
            //byte[] bytes = new byte[fis.available()]; 
		   // 这种方式不太适合太大的文件,因为byte[]数组不能太大。
            // 不需要循环了。
            // 直接读一次就行了。
            //int readCount = fis.read(bytes); // 6
            //System.out.println(new String(bytes)); // abcdef

            // skip跳过几个字节不读取,这个方法也可能以后会用!
            fis.skip(3);
            System.out.println(fis.read()); //100

FileOutputStream

FileOutputStream fos = null;
        try {
            // myfile文件不存在的时候会自动新建!
            // 这种方式谨慎使用,这种方式会先将原文件清空,然后重新写入。
            //fos = new FileOutputStream("myfile");
            //fos = new FileOutputStream("chapter23/src/tempfile3");

            // 以追加的方式在文件末尾写入。不会清空原文件内容。
            fos = new FileOutputStream("chapter23/src/tempfile3", true);
            // 开始写。
            byte[] bytes = {97, 98, 99, 100};
            // 将byte数组全部写出!
            fos.write(bytes); // abcd
            // 将byte数组的一部分写出!
            fos.write(bytes, 0, 2); // 再写出ab

            // 字符串
            String s = "我是一个中国人,我骄傲!!!";
            // 将字符串转换成byte数组。
            byte[] bs = s.getBytes();
            // 写
            fos.write(bs);

            // 写完之后,最后一定要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

copy

使用FileInputStream + FileOutputStream完成文件的拷贝。
拷贝的过程应该是一边读,一边写。
使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。

 FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            // 创建一个输入流对象
            fis = new FileInputStream("D:\\course\\02-JavaSE\\video\\chapter01\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi");
            // 创建一个输出流对象
            fos = new FileOutputStream("C:\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi");

            // 最核心的:一边读,一边写
            byte[] bytes = new byte[1024 * 1024]; // 1MB(一次最多拷贝1MB。)
            int readCount = 0;
            while((readCount = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, readCount);
            }

            // 刷新,输出流最后要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 分开try,不要一起try。
            // 一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭。
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

使用FileReader FileWriter进行拷贝的话,只能拷贝“普通文本”文件。

        FileReader in = null;
        FileWriter out = null;
        try {
            // 读
            in = new FileReader("chapter23/src/com/bjpowernode/java/io/Copy02.java");
            // 写
            out = new FileWriter("Copy02.java");

            // 一边读一边写:
            char[] chars = new char[1024 * 512]; // 1MB
            int readCount = 0;
            while((readCount = in.read(chars)) != -1){
                out.write(chars, 0, readCount);
            }

            // 刷新
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

FileReader

FileReader reader = null;
        try {
            // 创建文件字符输入流
            reader = new FileReader("tempfile");

            //准备一个char数组
            char[] chars = new char[4];
            // 往char数组中读
            reader.read(chars); // 按照字符的方式读取:第一次e,第二次f,第三次 风....
            for(char c : chars) {
                System.out.println(c);
            }

            /*// 开始读
            char[] chars = new char[4]; // 一次读取4个字符
            int readCount = 0;
            while((readCount = reader.read(chars)) != -1) {
                System.out.print(new String(chars,0,readCount));
            }*/
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

FileWriter

   FileWriter out = null;
        try {
            // 创建文件字符输出流对象
            //out = new FileWriter("file");
            out = new FileWriter("file", true);

            // 开始写。
            char[] chars = {'我','是','中','国','人'};
            out.write(chars);
            out.write(chars, 2, 3);

            out.write("我是一名java软件工程师!");
            // 写出一个换行符。
            out.write("\n");
            out.write("hello world!");

            // 刷新
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

BufferedReader

带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。

 FileReader reader = new FileReader("Copy02.java");
        // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
        // 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
        // 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
        BufferedReader br = new BufferedReader(reader);

        // 读一行
        /*String firstLine = br.readLine();
        System.out.println(firstLine);

        String secondLine = br.readLine();
        System.out.println(secondLine);

        String line3 = br.readLine();
        System.out.println(line3);*/

        // br.readLine()方法读取一个文本行,但不带换行符。
        String s = null;
        while((s = br.readLine()) != null){
            System.out.print(s);
        }

        // 关闭流
        // 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
        br.close();

InputStreamReader

        /*// 字节流
        FileInputStream in = new FileInputStream("Copy02.java");

        // 通过转换流转换(InputStreamReader将字节流转换成字符流。)
        // in是节点流。reader是包装流。
        InputStreamReader reader = new InputStreamReader(in);

        // 这个构造方法只能传一个字符流。不能传字节流。
        // reader是节点流。br是包装流。
        BufferedReader br = new BufferedReader(reader);*/

        // 合并
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("Copy02.java")));

        String line = null;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }

        // 关闭最外层
        br.close();
    }
/*
BufferedWriter:带有缓冲的字符输出流。
OutputStreamWriter:转换流
 */
public class BufferedWriterTest {
    public static void main(String[] args) throws Exception{
        // 带有缓冲区的字符输出流
        //BufferedWriter out = new BufferedWriter(new FileWriter("copy"));

        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy", true)));
        // 开始写。
        out.write("hello world!");
        out.write("\n");
        out.write("hello kitty!");
        // 刷新
        out.flush();
        // 关闭最外层
        out.close();
    }
}

DataInputStream:数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。

        DataInputStream dis = new DataInputStream(new FileInputStream("data"));
        // 开始读
        byte b = dis.readByte();
        short s = dis.readShort();
        int i = dis.readInt();
        long l = dis.readLong();
        float f = dis.readFloat();
        double d = dis.readDouble();
        boolean sex = dis.readBoolean();
        char c = dis.readChar();

        System.out.println(b);
        System.out.println(s);
        System.out.println(i + 1000);
        System.out.println(l);
        System.out.println(f);
        System.out.println(d);
        System.out.println(sex);
        System.out.println(c);

        dis.close();
    // 创建数据专属的字节输出流
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
    // 写数据
    byte b = 100;
    short s = 200;
    int i = 300;
    long l = 400L;
    float f = 3.0F;
    double d = 3.14;
    boolean sex = false;
    char c = 'a';
    // 写
    dos.writeByte(b); // 把数据以及数据的类型一并写入到文件当中。
    dos.writeShort(s);
    dos.writeInt(i);
    dos.writeLong(l);
    dos.writeFloat(f);
    dos.writeDouble(d);
    dos.writeBoolean(sex);
    dos.writeChar(c);

    // 刷新
    dos.flush();
    // 关闭最外层
    dos.close();
}

PrintStream

标准的字节输出流。默认输出到控制台。

// 联合起来写
        System.out.println("hello world!");

        // 分开写
        PrintStream ps = System.out;
        ps.println("hello zhangsan");
        ps.println("hello lisi");
        ps.println("hello wangwu");

        // 标准输出流不需要手动close()关闭。
        // 可以改变标准输出流的输出方向吗? 可以
        /*
        // 这些是之前System类使用过的方法和属性。
        System.gc();
        System.currentTimeMillis();
        PrintStream ps2 = System.out;
        System.exit(0);
        System.arraycopy(....);
         */

        // 标准输出流不再指向控制台,指向“log”文件。
        PrintStream printStream = new PrintStream(new FileOutputStream("log"));
        // 修改输出方向,将输出方向修改到"log"文件。
        System.setOut(printStream);
        // 再输出
        System.out.println("hello world");
        System.out.println("hello kitty");
        System.out.println("hello zhangsan");

File

  // 创建一个File对象
        File f1 = new File("D:\\file");

        // 判断是否存在!
        System.out.println(f1.exists());

        // 如果D:\file不存在,则以文件的形式创建出来
        /*if(!f1.exists()) {
            // 以文件形式新建
            f1.createNewFile();
        }*/

        // 如果D:\file不存在,则以目录的形式创建出来
        /*if(!f1.exists()) {
            // 以目录的形式新建。
            f1.mkdir();
        }*/

        // 可以创建多重目录吗?
        File f2 = new File("D:/a/b/c/d/e/f");
        /*if(!f2.exists()) {
            // 多重目录的形式新建。
            f2.mkdirs();
        }*/

        File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
        // 获取文件的父路径
        String parentPath = f3.getParent();
        System.out.println(parentPath); //D:\course\01-开课
        File parentFile = f3.getParentFile();
        System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());

        File f4 = new File("copy");
        System.out.println("绝对路径:" + f4.getAbsolutePath()); // C:\Users\Administrator\IdeaProjects\javase\copy


        File f1 = new File("D:\\course\\01-开课\\开学典礼.ppt");
        // 获取文件名
        System.out.println("文件名:" + f1.getName());

        // 判断是否是一个目录
        System.out.println(f1.isDirectory()); // false

        // 判断是否是一个文件
        System.out.println(f1.isFile()); // true

        // 获取文件最后一次修改时间
        long haoMiao = f1.lastModified(); // 这个毫秒是从1970年到现在的总毫秒数。
        // 将总毫秒数转换成日期?????
        Date time = new Date(haoMiao);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(time);
        System.out.println(strTime);

        // 获取文件大小
        System.out.println(f1.length()); //216064字节。


        // File[] listFiles()
        // 获取当前目录下所有的子文件。
        File f = new File("D:\\course\\01-开课");
        File[] files = f.listFiles();
        // foreach
        for(File file : files){
            //System.out.println(file.getAbsolutePath());
            System.out.println(file.getName());
        }
    }

序列反序列化

1、java.io.NotSerializableException : Student对象不支持序列化!!!!

2、参与序列化和反序列化的对象,必须实现Serializable接口。

3、注意:通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable {
}
这个接口当中什么代码都没有。
那么它起到一个什么作用呢?
起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成
一个序列化版本号。

4、序列化版本号有什么用呢?
java.io.InvalidClassException:
com.bjpowernode.java.bean.Student;
local class incompatible:
stream classdesc serialVersionUID = -684255398724514298(十年后),
local class serialVersionUID = -3463447116624555755(十年前)

java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

小鹏编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
胡浪编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,
都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)

请思考?
这种自动生成序列化版本号有什么缺陷?
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,
因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java
虚拟机会认为这是一个全新的类。(这样就不好了!)

最终结论:

凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。

这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

/*
一次序列化多个对象呢?
    可以,可以将对象放到集合当中,序列化集合。
提示:
    参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
 */        
List<User> userList = new ArrayList<>();
        userList.add(new User(1,"zhangsan"));
        userList.add(new User(2, "lisi"));
        userList.add(new User(3, "wangwu"));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));

        // 序列化一个集合,这个集合对象中放了很多其他对象。
        oos.writeObject(userList);

        oos.flush();
        oos.close();



        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));
        //Object obj = ois.readObject();
        //System.out.println(obj instanceof List);
        List<User> userList = (List<User>)ois.readObject();
        for(User user : userList){
            System.out.println(user);
        }
        ois.close();

多线程

4.1、什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

注意:
进程A和进程B的内存独立不共享。

线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。

4.4、思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在
压栈弹栈。

4.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。

5、java语言中,实现线程有两种方式,那两种方式呢?

java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
		// 定义线程类
		public class MyThread extends Thread{
			public void run(){
			
			}
		}
		// 创建线程对象
		MyThread t = new MyThread();
		// 启动线程。
		t.start();
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
		// 定义一个可运行的类
		public class MyRunnable implements Runnable {
			public void run(){
			
			}
		}
		// 创建线程对象
		Thread t = new Thread(new MyRunnable());
		// 启动线程
		t.start();

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承
其它的类,更灵活。

    public static void main(String[] args) {
        // 这里是main方法,这里的代码属于主线程,在主栈中运行。
        // 新建一个分支线程对象
        MyThread t = new MyThread();
        // 启动线程
        //t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
        // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
        // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
        // 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
        // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
        t.start();
        // 这里的代码还是运行在主线程中。
        for(int i = 0; i < 1000; i++){
            System.out.println("主线程--->" + i);
            
 class MyThread extends Thread {
    @Override
    public void run() {
        // 编写程序,这段程序运行在分支线程中(分支栈)。
        for(int i = 0; i < 1000; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

       
public class ThreadTest03 {
    public static void main(String[] args) {
        // 创建一个可运行的对象
        //MyRunnable r = new MyRunnable();
        // 将可运行的对象封装成一个线程对象
        //Thread t = new Thread(r);
        Thread t = new Thread(new MyRunnable()); // 合并代码
        // 启动线程
        t.start();

        for(int i = 0; i < 100; i++){
            System.out.println("主线程--->" + i);
        }
    }
}

// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}


匿名内部类

public class ThreadTest04 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类方式。
        // 这是通过一个没有名字的类,new出来的对象。
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
                }
            }
        });

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

        for(int i = 0; i < 100; i++){
            System.out.println("main线程---> " + i);
        }
    }
}

6、关于线程对象的生命周期?
新建状态
就绪状态
运行状态
阻塞状态
死亡状态


1、怎么获取当前线程对象?
Thread t = Thread.currentThread();
返回值t就是当前线程。

2、获取线程对象的名字
String name = 线程对象.getName();

3、修改线程对象的名字
线程对象.setName(“线程名字”);

4、当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
Thread-3

class MyThread2 extends Thread {
    public void run(){
        for(int i = 0; i < 100; i++){
            // currentThread就是当前线程对象。当前线程是谁呢?
            // 当t1线程执行run方法,那么这个当前线程就是t1
            // 当t2线程执行run方法,那么这个当前线程就是t2
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName() + "-->" + i);

            //System.out.println(super.getName() + "-->" + i);
            //System.out.println(this.getName() + "-->" + i);
        }
    }
}

// 创建线程对象
        MyThread2 t = new MyThread2();
        // 设置线程的名字
        t.setName("t1");
        // 获取线程的名字
        String tName = t.getName();
        System.out.println(tName); //Thread-0

关于线程的sleep方法:
static void sleep(long millis)
1、静态方法:Thread.sleep(1000);
2、参数是毫秒
3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
4、Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

        // 让当前线程进入休眠,睡眠5秒
        // 当前线程是主线程!!!
        /*try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        // 5秒之后执行这里的代码
        //System.out.println("hello world!");

        for(int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);

            // 睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
public class ThreadTest07 {
    public static void main(String[] args) {
        // 创建线程对象
        Thread t = new MyThread3();
        t.setName("t");
        t.start();

        // 调用sleep方法
        try {
            // 问题:这行代码会让线程t进入休眠状态吗?
            t.sleep(1000 * 5); // 在执行的时候还是会转换成:Thread.sleep(1000 * 5);
                                     // 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
                                     // 这样代码出现在main方法中,main线程睡眠。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 5秒之后这里才会执行。
        System.out.println("hello World!");
    }
}

class MyThread3 extends Thread {
    public void run(){
        for(int i = 0; i < 10000; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是终止线程的睡眠。

public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
        t.interrupt(); // 干扰,一盆冷水过去!
        
    }
}

class MyRunnable2 implements Runnable {

    // 重点:run()当中的异常不能throws,只能try catch
    // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---> begin");
        try {
            // 睡眠1年
            Thread.sleep(1000 * 60 * 60 * 24 * 365);
        } catch (InterruptedException e) {
            // 打印异常信息
            //e.printStackTrace();
        }
        //1年之后才会执行这里
        System.out.println(Thread.currentThread().getName() + "---> end");

        // 调用doOther
        //doOther();
    }

    // 其它方法可以throws
    /*public void doOther() throws Exception{

    }*/
}

在java中怎么强行终止一个线程的执行。
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,
线程没有保存的数据将会丢失。不建议使用。

t.stop(); // 已过时(不建议使用。)

怎么合理的终止一个线程的执行。这种方式是很常用的。

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        // 模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 终止线程
        // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
        r.run = false;
    }
}

class MyRunable4 implements Runnable {

    // 打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            if(run){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                // return就结束了,你在结束之前还有什么没保存的。
                // 在这里可以保存呀。
                //save....

                //终止当前线程
                return;
            }
        }
    }
}

实例方法:
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。

实例方法:
void join()
合并线程

			class MyThread1 extends Thread {
				public void doSome(){
					MyThread2 t = new MyThread2();
					t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
				}
			}

			class MyThread2 extends Thread{
				
			}
    @Override
    public void run() {
        for(int i = 1; i <= 10000; i++) {
            //每100个让位一次。
            if(i % 100 == 0){
                Thread.yield(); // 当前线程暂停一下,让给主线程。
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }

数据安全

2.2、什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。

3、Java中有三大变量?

​ 实例变量:在堆中。

​ 静态变量:在方法区。

​ 局部变量:在栈中。

以上三大变量中:
局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。

​ 实例变量在堆中,堆只有1个。静态变量在方法区中,方法区只有1个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。

​ 局部变量+常量:不会有线程安全问题。成员变量:可能会有线程安全问题。

synchronized代码块

/*
银行账户
    使用线程同步机制,解决线程安全问题。
 */
public class Account {
    // 账号
    private String actno;
    // 余额
    private double balance; //实例变量。

    //对象
    Object obj = new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    public void withdraw(double money){

        //int i = 100;
        //i = 101;

        // 以下这几行代码必须是线程排队的,不能并发。
        // 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
        /*
        线程同步机制的语法是:
            synchronized(){
                // 线程同步代码块。
            }
            synchronized后面小括号中传的这个“数据”是相当关键的。
            这个数据必须是多线程共享的数据。才能达到多线程排队。

            ()中写什么?
                那要看你想让哪些线程同步。
                假设t1、t2、t3、t4、t5,有5个线程,
                你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
                你一定要在()中写一个t1 t2 t3共享的对象。而这个
                对象对于t4 t5来说不是共享的。

            这里的共享对象是:账户对象。
            账户对象是共享的,那么this就是账户对象吧!!!
            不一定是this,这里只要是多线程共享的那个对象就行。

            在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
            100个对象,100把锁。1个对象1把锁。

            以下代码的执行原理?
                1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
                2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
                找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
                占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
                3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
                共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
                直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
                t2占有这把锁之后,进入同步代码块执行程序。

                这样就达到了线程排队执行。
                这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
                执行的这些线程对象所共享的。
         */
        //Object obj2 = new Object();
        //synchronized (this){  //如果放的account 不同  那就不行
        //synchronized (obj) {   //实例变量只有一个
        //synchronized ("abc") { // "abc"在字符串常量池当中。全部都锁
        //synchronized (null) { // 报错:空指针。
        //synchronized (obj2) { // 这样编写就不安全了。因为obj2不是共享对象。
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        //}
    }
}
public class AccountThread extends Thread {

    // 两个线程必须共享同一个账户对象。
    private Account act;

    // 通过构造方法传递过来账户对象
    public AccountThread(Account act) {
        this.act = act;
    }

    public void run(){
        // run方法的执行表示取款操作。
        // 假设取款5000
        double money = 5000;
        // 取款
        // 多线程并发执行这个方法。
        //synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
        synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
            act.withdraw(money);
        }

        System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
    }
}

4、如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。

ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的.

5、总结:
	synchronized有三种写法:

		第一种:同步代码块
			灵活
			synchronized(线程共享对象){
				同步代码块;
			}

		第二种:在实例方法上使用synchronized
			表示共享对象一定是this
			并且同步代码块是整个方法体。
		
		第三种:在静态方法上使用synchronized
			表示找类锁。
			类锁永远只有1把。
			就算创建了100个对象,那类锁也只有一把。
		
		对象锁:1个对象1把锁,100个对象100把锁。
		类锁:100个对象,也可能只是1把类锁。

面试题

// 面试题:
oSome方法的结束吗?
//不需要,因为doOther()方法没有synchronized
public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc = new MyClass();

        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run(){
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass {
    public synchronized void doSome(){
        System.out.println("doSome begin");   
        //doSome begin
        //doOther begin
        //doOther over
        //doSome Over
        
        
        /*
        两个方法都synchronized的话就
        doSome begin
        doSome over
        doOther begin
        doOther Over
        */
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
    //不需要,因为MyClass对象是两个,两把锁。
public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();

        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;
    public MyThread(MyClass mc){
        this.mc = mc;
    }
    public void run(){
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass {
    // 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
    //需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。
    //public synchronized static void doSome(){
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    // public synchronized static void doOther(){
    public synchronized void doOther(){
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

死锁

public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        // t1和t2两个线程共享o1,o2
        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);

        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){

            }
        }
    }
}

class MyThread2 extends Thread {
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

6、聊一聊,我们以后开发中应该怎么解决线程安全问题?

	是一上来就选择线程同步吗?synchronized
		不是,synchronized会让程序的执行效率降低,用户体验不好。
		系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
		线程同步机制。
	
	第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

	第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
	实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
	对象不共享,就没有数据安全问题了。)

	第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
	就只能选择synchronized了。线程同步机制

守护线程

​ 守护线程的特点:

​ 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程。

        // 启动线程之前,将线程设置为守护线程
        t.setDaemon(true);

1.2、定时器
定时器的作用:
间隔特定的时间,执行特定的程序。

/*
使用定时器指定定时任务。
 */
public class TimerTest {
    public static void main(String[] args) throws Exception {

        // 创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true); //守护线程的方式

        // 指定定时任务
        //timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-03-14 09:34:30");
        //timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
        // 每年执行一次。
        //timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);

        //匿名内部类方式
        timer.schedule(new TimerTask(){
            @Override
            public void run() {
                // code....
            }
        } , firstTime, 1000 * 10);

    }
}

// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
        // 编写你需要执行的任务就行了。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":成功完成了一次数据备份!");
    }
}

实现多线程的第三个方式

/*
实现线程的第三种方式:
    实现Callable接口
    这种方式的优点:可以获取到线程的执行结果。
    这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
 */
public class ThreadTest15 {
    public static void main(String[] args) throws Exception {

        // 第一步:创建一个“未来任务类”对象。
        // 参数非常重要,需要给一个Callable接口实现类对象。
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; //自动装箱(300结果变成Integer)
            }
        });

        // 创建线程对象
        Thread t = new Thread(task);

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

        // 这里是main方法,这是在主线程中。
        // 在主线程中,怎么获取t线程的返回结果?
        // get()方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);

        // main方法这里的程序要想执行必须等待get()方法的结束
        // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
        // 另一个线程执行是需要时间的。
        System.out.println("hello world!");
    }
}

消费者和生产者

/*
1、使用wait方法和notify方法实现“生产者和消费者模式”

2、什么是“生产者和消费者模式”?
    生产线程负责生产,消费线程负责消费。
    生产线程和消费线程要达到均衡。
    这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

4、wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

7、模拟这样一个需求:
    仓库我们采用List集合。
    List集合中假设只能存储1个元素。
    1个元素就表示仓库满了。
    如果List集合中元素个数是0,就表示仓库空了。
    保证List集合中永远都是最多存储1个元素。

    必须做到这种效果:生产1个消费1个。
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建1个仓库对象,共享的。
        List list = new ArrayList();
        // 创建两个线程对象
        // 生产者线程
        Thread t1 = new Thread(new Producer(list));
        // 消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}

// 生产线程
class Producer implements Runnable {
    // 仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        // 一直生产(使用死循环来模拟一直生产)
        while(true){
            // 给仓库对象list加锁。
            synchronized (list){
                if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
                    try {
                        // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒消费者进行消费
                list.notifyAll();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable {
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while(true){
            synchronized (list) {
                if(list.size() == 0){
                    try {
                        // 仓库已经空了。
                        // 消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据,进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒生产者生产。
                list.notifyAll();
            }
        }
    }
}




反射

2.1、反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。
优点类似于黑客。(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class文件。)

2.3、反射机制相关的重要的类有哪些?

java.lang.Class:代表整个字节码,代表一个类型,代表整个类。

java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。

java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法

java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

		java.lang.Class:
			public class User{
				// Field
				int no;

				// Constructor
				public User(){
				
				}
				public User(int no){
					this.no = no;
				}

				// Method
				public void setNo(int no){
					this.no = no;
				}
				public int getNo(){
					return no;
				}
			}
/*
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
    三种方式
        第一种:Class c = Class.forName("完整类名带包名");
        第二种:Class c = 对象.getClass();
        第三种:Class c = 任何类型.class;

 */
public class ReflectTest01 {
    public static void main(String[] args) {
        /*
        Class.forName()
            1、静态方法
            2、方法的参数是一个字符串。
            3、字符串需要的是一个完整类名。
            4、完整类名必须带有包名。java.lang包也不能省略。
         */
        Class c1 = null;
        Class c2 = null;
        try {
            c1 = Class.forName("java.lang.String"); // c1代表String.class文件,或者说c1代表String类型。
            c2 = Class.forName("java.util.Date"); // c2代表Date类型
            Class c3 = Class.forName("java.lang.Integer"); // c3代表Integer类型
            Class c4 = Class.forName("java.lang.System"); // c4代表System类型
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // java中任何一个对象都有一个方法:getClass()
        String s = "abc";
        Class x = s.getClass(); // x代表String.class字节码文件,x代表String类型。
        System.out.println(c1 == x); // true(==判断的是对象的内存地址。)

        Date time = new Date();
        Class y = time.getClass();
        System.out.println(c2 == y); // true (c2和y两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件。)

        // 第三种方式,java语言中任何一种类型,包括基本数据类型,它都有.class属性。
        Class z = String.class; // z代表String类型
        Class k = Date.class; // k代表Date类型
        Class f = int.class; // f代表int类型
        Class e = double.class; // e代表double类型

        System.out.println(x == z); // true

    }
}
/*
获取到Class,能干什么?
    通过Class的newInstance()方法来实例化对象。
    注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
 */
public class ReflectTest02 {
    public static void main(String[] args) {

        // 这是不使用反射机制,创建对象
        User user = new User();
        System.out.println(user);

        // 下面这段代码是以反射机制的方式创建对象。
        try {
            // 通过反射机制,获取Class,通过Class来实例化对象
            Class c = Class.forName("com.bjpowernode.java.bean.User"); // c代表User类型。

            // newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
            // 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
            Object obj = c.newInstance();

            System.out.println(obj); // com.bjpowernode.java.bean.User@10f87f48
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

验证反射机制的灵活性。
java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。
非常之灵活。(符合OCP开闭原则:对扩展开放,对修改关闭。)

public class ReflectTest03 {
    public static void main(String[] args) throws Exception{

        // 这种方式代码就写死了。只能创建一个User类型的对象
        //User user = new User();

        // 以下代码是灵活的,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象。
        // 通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("chapter25/classinfo2.properties");
        // 创建属性类对象Map
        Properties pro = new Properties(); // key value都是String
        // 加载
        pro.load(reader);
        // 关闭流
        reader.close();

        // 通过key获取value
        String className = pro.getProperty("className");
        //System.out.println(className);

        // 通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

研究一下:Class.forName()发生了什么?
记住,重点:
如果你只是希望一个类的静态代码块执行,其它代码一律不执行,
你可以使用:
Class.forName(“完整类名”);
这个方法的执行会导致类加载,类加载时,静态代码块执行。

public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            // Class.forName()这个方法的执行会导致:类加载。
            Class.forName("com.bjpowernode.java.reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

/*
研究一下文件路径的问题。
怎么获取一个文件的绝对路径。以下讲解的这种方式是通用的。但前提是:文件需要在类路径下。才能用这种方式。
 */
public class AboutPath {
    public static void main(String[] args) throws Exception{
        // 这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project的根。
        // 这个代码假设离开了IDEA,换到了其它位置,可能当前路径就不是project的根了,这时这个路径就无效了。
        //FileReader reader = new FileReader("chapter25/classinfo2.properties");

        // 接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
        // 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
        // 什么类路径下?方式在src下的都是类路径下。【记住它】
        // src是类的根路径。
        /*
        解释:
            Thread.currentThread() 当前线程对象
            getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象。
            getResource() 【获 取资源】这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
         */
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo2.properties").getPath(); // 这种方式获取文件绝对路径是通用的。

        // 采用以上的代码可以拿到一个文件的绝对路径。
        // /C:/Users/Administrator/IdeaProjects/javase/out/production/chapter25/classinfo2.properties
        System.out.println(path);

        // 获取db.properties文件的绝对路径(从类的根路径下作为起点开始)
        String path2 = Thread.currentThread().getContextClassLoader()
                .getResource("com/bjpowernode/java/bean/db.properties").getPath();
        System.out.println(path2);

    }
}

/*
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下。
 */
public class ResourceBundleTest {
    public static void main(String[] args) {

        // 资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下。文件扩展名也必须是properties
        // 并且在写路径的时候,路径后面的扩展名不能写。
        //ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");

        ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/java/bean/db");

        String className = bundle.getString("className");
        System.out.println(className);

    }
}

/*
反射Student类当中所有的Field(了解一下)
 */
public class ReflectTest05 {
    public static void main(String[] args) throws Exception{

        // 获取整个类
        Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");

        //com.bjpowernode.java.bean.Student
        String className = studentClass.getName();
        System.out.println("完整类名:" + className);

        String simpleName = studentClass.getSimpleName();
        System.out.println("简类名:" + simpleName);

        // 获取类中所有的public修饰的Field
		//public int no;
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length); // 测试数组中只有1个元素
        // 取出这个Field
        Field f = fields[0];
        // 取出这个Field它的名字
        String fieldName = f.getName();
        System.out.println(fieldName);

        // 获取所有的Field
        Field[] fs = studentClass.getDeclaredFields();
        System.out.println(fs.length); // 4

        System.out.println("==================================");
        // 遍历
        for(Field field : fs){
            // 获取属性的修饰符列表
            int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号!!!
            System.out.println(i);  // 1,2,3,4
			
            // 可以将这个“代号”数字转换成“字符串”吗?
            String modifierString = Modifier.toString(i);
            System.out.println(modifierString);
			
            // 获取属性的类型
            Class fieldType = field.getType();
            //String fName = fieldType.getName(); 
            String fName = fieldType.getSimpleName(); //string
            System.out.println(fName);
            // 获取属性的名字
            System.out.println(field.getName()); // name age sex no 
        }
    }
}

public class ReflectTest06 {
    public static void main(String[] args) throws Exception{

        // 创建这个是为了拼接字符串。
        StringBuilder s = new StringBuilder();

        //Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
        Class studentClass = Class.forName("java.lang.Thread");

        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");

        Field[] fields = studentClass.getDeclaredFields();
        for(Field field : fields){
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            s.append(field.getType().getSimpleName());
            s.append(" ");
            s.append(field.getName());
            s.append(";\n");
        }

        s.append("}");
        System.out.println(s);

    }
}
/*
必须掌握:
    怎么通过反射机制访问一个java对象的属性?
        给属性赋值set
        获取属性的值get
 */
public class ReflectTest07 {
    public static void main(String[] args) throws Exception{

        // 我们不使用反射机制,怎么去访问一个对象的属性呢?
        Student s = new Student();

        // 给属性赋值
        s.no = 1111; //三要素:给s对象的no属性赋值1111
                    //要素1:对象s
                    //要素2:no属性
                    //要素3:1111

        // 读属性值
        // 两个要素:获取s对象的no属性的值。
        System.out.println(s.no);

        // 使用反射机制,怎么去访问一个对象的属性。(set get)
        Class studentClass = Class.forName("com.bjpowernode.java.bean.Student");
        Object obj = studentClass.newInstance(); // obj就是Student对象。(底层调用无参数构造方法)

        // 获取no属性(根据属性的名称来获取Field)
        Field noFiled = studentClass.getDeclaredField("no");

        // 给obj对象(Student对象)的no属性赋值
        /*
        虽然使用了反射机制,但是三要素还是缺一不可:
            要素1:obj对象
            要素2:no属性
            要素3:2222值
        注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        noFiled.set(obj, 22222); // 给obj对象的no属性赋值2222

        // 读取属性的值
        // 两个要素:获取obj对象的no属性的值。
        System.out.println(noFiled.get(obj));

        // 可以访问私有的属性吗?
        Field nameField = studentClass.getDeclaredField("name");

        // 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
        // 这样设置完之后,在外部也是可以访问private的。
        nameField.setAccessible(true);

        // 给name属性赋值
        nameField.set(obj, "jackson");
        // 获取name属性的值
        System.out.println(nameField.get(obj));
    }
}

可变长度参数
int… args 这就是可变长度参数
语法是:类型… (注意:一定是3个点。)

​ 1、可变长度参数要求的参数个数是:0~N个。

​ 2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。

​ 3、可变长度参数可以当做一个数组来看待

/*
作为了解内容(不需要掌握):
    反射Method
 */
public class ReflectTest08 {
    public static void main(String[] args) throws Exception{

        // 获取类了
        Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");

        // 获取所有的Method(包括私有的!)
        Method[] methods = userServiceClass.getDeclaredMethods();
        //System.out.println(methods.length); // 2

        // 遍历Method
        for(Method method : methods){
            // 获取修饰符列表
            System.out.println(Modifier.toString(method.getModifiers()));
            // 获取方法的返回值类型
            System.out.println(method.getReturnType().getSimpleName());
            // 获取方法名
            System.out.println(method.getName());
            // 方法的修饰符列表(一个方法的参数可能会有多个。)
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                System.out.println(parameterType.getSimpleName());
            }
        }
    }
}
/*
了解一下,不需要掌握(反编译一个类的方法。)
 */
public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        //Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
        Class userServiceClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");

        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            //public boolean login(String name,String password){}
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            // 参数列表
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除指定下标位置上的字符
            s.deleteCharAt(s.length() - 1);
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}
/*
重点:必须掌握,通过反射机制怎么调用一个对象的方法?
    五颗星*****

    反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,
    将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,
    但是java代码不需要做任何改动。这就是反射机制的魅力。
 */
public class ReflectTest10 {
    public static void main(String[] args) throws Exception{
        // 不使用反射机制,怎么调用方法
        // 创建对象
        UserService userService = new UserService();
        // 调用方法
        /*
        要素分析:
            要素1:对象userService
            要素2:login方法名
            要素3:实参列表
            要素4:返回值
         */
        boolean loginSuccess = userService.login("admin","123");
        //System.out.println(loginSuccess);
        System.out.println(loginSuccess ? "登录成功" : "登录失败");

        // 使用反射机制来调用一个对象的方法该怎么做?
        Class userServiceClass = Class.forName("com.bjpowernode.java.service.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
        //Method loginMethod = userServiceClass.getDeclaredMethod("login", int.class);
        // 调用方法
        // 调用方法有几个要素? 也需要4要素。
        // 反射机制中最最最最最重要的一个方法,必须记住。
        /*
        四要素:
        loginMethod方法
        obj对象
        "admin","123" 实参
        retValue 返回值
         */
        Object retValue = loginMethod.invoke(obj, "admin","123123");
        System.out.println(retValue);
    }
}

/*
反编译一个类的Constructor构造方法。
 */
public class ReflectTest11 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        // 拼接构造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            // 拼接参数
            Class[] parameterTypes = constructor.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除最后下标位置上的字符
            if(parameterTypes.length > 0){
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}
/*
比上一个例子(ReflectTest11)重要一些!!!

通过反射机制调用构造方法实例化java对象。(这个不是重点)
 */
public class ReflectTest12 {
    public static void main(String[] args) throws Exception{
        // 不使用反射机制怎么创建对象
        Vip v1 = new Vip();
        Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);

        // 使用反射机制怎么创建对象呢?
        Class c = Class.forName("com.bjpowernode.java.bean.Vip");
        // 调用无参数构造方法
        Object obj = c.newInstance();
        System.out.println(obj);

        // 调用有参数的构造方法怎么办?
        // 第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
        // 第二步:调用构造方法new对象
        Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
        System.out.println(newObj);

        // 获取无参数构造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();
        System.out.println(newObj2);
    }
}
/*
重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
 */
public class ReflectTest13 {
    public static void main(String[] args) throws Exception{

        // String举例
        Class stringClass = Class.forName("java.lang.String");

        // 获取String的父类
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName());

        // 获取String类实现的所有接口(一个类可以实现多个接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for(Class in : interfaces){
            System.out.println(in.getName());
        }
    }
}

注解

3.2、注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。

3.3、怎么自定义注解呢?语法格式?

	 [修饰符列表] @interface 注解类型名{

	 } 
3.6、元注解
		什么是元注解?
			用来标注“注解类型”的“注解”,称为元注解。

		常见的元注解有哪些?
			Target
			Retention
		
		关于Target注解:
			这是一个元注解,用来标注“注解类型”的“注解”
			这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。

			@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。
			@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
				表示该注解可以出现在:
					构造方法上
					字段上
					局部变量上
					方法上
					....
					类上...
		
		关于Retention注解:
			这是一个元注解,用来标注“注解类型”的“注解”
			这个Retention注解用来标注“被标注的注解”最终保存在哪里。

			@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。
			@Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。
			@Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
    
    
    
    
    	3.7、Retention的源代码

			//元注解	
			public @interface Retention {
				//属性
				RetentionPolicy value();
			}
			
		RetentionPolicy的源代码:
			public enum RetentionPolicy {
				 SOURCE,
				 CLASS,
				 RUNTIME
			}

			//@Retention(value=RetentionPolicy.RUNTIME)
			@Retention(RetentionPolicy.RUNTIME)
			public @interface MyAnnotation{}
public @interface MyAnnotation {

    /**
     * 我们通常在注解当中可以定义属性,以下这个是MyAnnotation的name属性。
     * 看着像1个方法,但实际上我们称之为属性name。
     * @return
     */
    String name();

    /*
    颜色属性
     */
    String color();

    /*
    年龄属性
     */
    int age() default 25; //属性指定默认值

}

    //@MyAnnotation(属性名=属性值,属性名=属性值,属性名=属性值)
    //指定name属性的值就好了。
    @MyAnnotation(name = "zhangsan", color = "红色")
    public void doSome(){

    }

如果一个注解的属性的名字是value,并且只有一个属性的话,在使用的时候,该属性名可以省略。

    // 数组是大括号
    @OtherAnnotation(age = 25, email = {"zhangsan@123.com", "zhangsan@sohu.com"}, seasonArray = Season.WINTER)
    public void doSome(){

    }
//只允许该注解可以标注类、方法
@Target({ElementType.TYPE, ElementType.METHOD})
// 希望这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    /*
    value属性。
     */
    String value() default "北京大兴区";
}

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值