java基础

-----基础部分-----

0.参考网址

1)笔记参考

https://blog.csdn.net/PorkBird/article/details/113666542?spm=1001.2014.3001.5502

2)视频参考

https://www.bilibili.com/video/BV1ny4y1Y7CW?p=247&spm_id_from=pageDriver

1.Java语言概述

1)优点

  • 舍弃了c语言中容易引起 错误的指针
  • 增加了垃圾自动回收装置
  • 面向对象

2)JDK&JRE

  • JDK:开发工具包(Java类库)
  • JRE:运行环境(编译)
JDk = JRE + 开发工具集(例如Javac编译工具)
JRE = JVM + Java SE标准类库

3)环境变量path

  • windows系统执行命令时要搜寻的路径(意思是配置环境后,可以在任何路径下执行Java程序)

4)总结

  • Java程序 编写-编译-运行的过程
编写:Java代码保存在以".java"结尾的源文件中
编译:使用javac.exe命令编译源文件(命令:javac xx.java)
运行:使用java.exe命令解释运行字节码文件(命令(不带后最):java xx)
  • Java源文件可以声明多个class
其中,最多只能有一个类可以声名public
并且,声明public的类名必须与Java文件名一致
  • 程序入口main函数,格式固定

2.基本语法

变量

1)关键字和保留字

  • 关键字
定义:被java赋予专门含义的字符串
特点:都是小写字母
  • 保留字
现有Java版本没有使用,后续可能会使用
例如:goto,const

2)标识符

  • 规则
1.大小写字母,0-9,_,$组成
2.数字开头
3.不能用关键字和保留字
4.严格区分大小写
5.不能有空格
  • 命名规范(驼峰命名法

3)变量

  • 变量类型,变量名,变量值
  • 必须先声明,在复制,后使用

4)变量类型

  • 基本数据类型-8种
1.整形:byte(1字节) short(2字节) int(4字节) long(8字节)
2.浮点型:float(4字节) double(8字节)
3.布尔型:boolean
4.字符型:char(2字节)		//'\n'(换行) '\t'(制表符)都是字符型
  • 引用数据类型
1.类(包含String)
2.数组
3.接口
  • 又分为成员变量&局部变量
  • ASCII码–>unicode–>utf-8

5)数据类型转换(不包含boolean)

  • 自动类型提升
byte -> char,short -> int -> long -> float -> double(从小到大自动转换)
  • 强制类型转换(逆运算)
1. 需要使用强转符:()
2. 可能会导致精度损失

注意

1.整型运算结果,默认int

2.浮点型运算结果,默认double

6)String(引用类型)

  • 使用
1.做运算,’+‘ 拼接
2.可以与8种基础数据类型转换
  • 强制转换
String a = "22";
int num = Integer.parseInt(a);

7)进制转换

  • 表示
二进制:0b或者0B开头
十进制:
八进制:0开头
十六进制:0x或者0X开头

注意:计算机都是以补码形式存储

  • 转换方法
Integer.toHexString(int i)	//十转八
Integer.toString(100, 16);	//转十六进制

运算符

1)算数运算符

+ - * / % ++ --

注意:

  1. a++(先自增,后运算)
  2. ++a(先运算,后自增
  3. 不会改变本身数据类型

2)赋值运算符

= += -= *= /= %=

注意:

  1. 不会改变本身数据类型

3)比较运算符

== != < > <= >= instanceof(是否是类对象)

4)逻辑运算符

&(逻辑且)  |(逻辑或)  !(非)
&&(短路且) ||(短路或) ^ (异或)

&和&&区别

&左右两边都会执行
&&当左边位flase,表达式一定为Flase,且后面的表达式不执行

5)位运算符

<<左移  >>右移  >>>无符号右移  &  |  ^  ~取反

注意

<<:(扩大)高位丢弃,地位补0
>>:(缩小)符号位0时(高位补0,地位丢弃)
		符号位1时(高位补1,地位丢弃)

6)三元运算符

条件表达式?表达式1:表达式2
# 表达式为true,执行表达式1;表达式为flase,执行表达式2

流程控制

1)顺序结构

2)分支结构

  • if-else
  • switch-case
switch(表达式){	//表达式只能是(byte short char int 枚举类 String类)6种
	case 常量:	//case后面只能是常量
	语句1;
	//break;
	...
	default:
	语句n;
	//break;
}
说明:
1.依次向下执行,匹配成功,执行语句,
执行结束后,继续向下执行,知道遇到break或者执行结束
2.case下面语句多个相同,可以合并

3)循环结构

  • for循环
int i = 1;
for (System.out.println('a'); i < 3; System.out.println('b'), i++) {
	System.out.print('c');
}
//输出:acbcb
  • while循环

  • do-while循环

至少会执行一次循环

4)break和continue

不同点:
	break:跳出当前循环(跳出最近的一次循环)
	continue:跳出当次循环
相同点:
    后面不能有执行语句
  • break指定跳出一层循环(设置标签)
// 指定跳出那层循环
sign:for (int i = 0; i <3; i++) {
    for (int j = 0; j < 4; j++) {
        System.out.print(i);
        System.out.print(j);
        if (i==2 && j==3){
            break sign;
        }
    }
}

3.数组

1)一维数组初始化

//静态初始化
int[] a1 = new int[]{1, 2, 3, 4};
int[] a11 = {1, 2, 3, 4};
//动态初始化
String[] a2 = new String[4];
a2[0] = "第一";

2)数组默认初始化值

整型(byte,short,int,long):0
浮点型(float,double):0.0
字符型(char):0或者'\u0000'			//对应ASCAII为0的字符
布尔型(boolean):false				 //0->false; 1->true
字符串(String):null

3)内存结构(重点)

  • 内存分区
栈(stack):局部变量
堆(head):对象,数组
方法区:(即时编译器编译后的代码)
	1.常量池:
	2.静态域:
	3.类信息
  • 解析1
int[] arr = new int[]{1, 2, 3};
1.arr存放在栈中
2.new int[]{1,2,3}存放在堆中    
3.堆中数组首地址值(32位,例如:0xba484.栈中arr指向地址(0xba485.堆中初始值{0,0,0}替换位{1,2,3}    
  • 解析2
String[] str = new String[3];
1.str存放在栈中
2.new int[3]存放在堆中    
3.堆中数组首地址值(32位,例如:0xba48)
4.栈中str指向地址(0xba48)
5.堆中初始值{0,0,0} 
  • 解析3
String[2] = "刘德华";
1.由str中地址找到堆中数组的首地址
2.根据要复制数组元素的下标2,将默认值null替换位"刘德华"    
  • 解析4
str = new String[4];
1.堆中会新开辟一段空间,存放new String[4]			//注意,不是修改原数组
2.栈中str不会新生成,但是他指向堆中的首地址值改变,指向new String[4]
3.自动垃圾回收机制,会在空闲时间回收原来不使用的堆空间

4)二维数组

  • 内存结构(本质还是一维数组)
1.栈中的地址,去寻找堆中一维数组的地址
2.堆中一维数组再根据其地址,指向另一个堆中一维数组的首地址
  • 初始化
//静态初始化
int[][] a1 = new int[][]{{1, 2, 3},{1, 4}};
int[][] a12 = {{1, 2, 3,},{1, 3, 4}};
//动态初始化
String[][] a2 = new String[4][1];
a2[0][2] = "第一";

String[][] a3 = new String[4][];
a3[4] = new String[2];

5)数据结构

  • 分类
1.按逻辑关系:
	1)集合,一对一,一对多,多对多
2.按存储结构:
	1)线性表:顺序表,链表,栈,队列
	2)树结构:二叉树
	3)图形结构
  • 复制
array1 = array2
//array2的地址给array1(类似快捷方式)		--只有一个堆空间
  • 查找(线性查找,二分法查找)
boolean isHave = true;
int result = 4;
int[] ints = {1, 2, 3, 4, 5, 6, 7, 8};
//顺序查找
for (int i = 0; i < ints.length; i++) {
    if (ints[i] == result){
        System.out.println("位置:" + i);
        isHave = false;
        break;
    }
}
if (isHave){
    System.out.println("没找打!!");
}
//二分查找
int start = 0;
int end = ints.length-1;
for (int j = 0; j < ints.length; j++) {
    int mid = (end + start)/2;
    if (result == ints[mid]){
        System.out.println("位置:" + mid);
        break;
    }else if (result < ints[mid]){
        end = mid - 1;
    }else {
        start = mid + 1;
    }
}
  • 排序
1.选择排序:
	直接选择排序,堆排序
2.交换排序:
	冒泡排序,快排
3.插入排序:
	直接插入排序,折半插入排序,shell排序(希尔排序)
4.归并排序
5.桶排序
6.基数排序
//冒泡排序
int[] sort = {34, 53, 56, 34, 567, 86, 789};
for (int i = 0; i < sort.length-1; i++) {
    for (int j = i+1; j < sort.length-1; j++) {
        if (sort[j-1] > sort[j]){
            int temp = sort[j];
            sort[j] = sort[j-1];
            sort[j-1] = temp;
        }
    }
}

6)Arrays工具类

1.boolean equals(int[] a, int[] b)		//比较两个数组是否相等
2.String toString(int[] a)				//输出数组信息
3.void fill(int[] a, int pos)			//全部填充
4.void sort(int[] a)    				//排序
5.int binarySearch(int[] a, int valuse) //二分查找(负数没找到)

7)数组中常见异常

1.ArrayIndexOutOfBoundsException	//数组下标越界
2.NullPointerException				//空指针异常

3.面向对象1(类及类的成员)

1)面向过程&面向对象

1.面向过程:强调的是功能行为,以函数为最小单位,考虑咋么做
2.面向对象;强调了具备功能的对象,以对象/类为最小单位,考虑的是谁来做

2)类&对象

1.类;对一类事物的描述,是抽象的,概念上的定义
2.对象;是实际存在的该事物的每个个体,因而也称实例(instance)
    匿名对象:new Object().name();		//不设置变量名,只能用一次

3)属性&方法

1.属性:成员变量 = filed = 域,字段
2.方法:成员方法 = 函数(c语言) = method

4)内存解析

  • 一个类创建多个对象
1.每个对象都有自己的一套独立属性
2.修改其中一个对象的属性,不影响另一个对象的属性
3.复制对象,两个对象指向同一个地址值,要改都改变。		//注意
  • 内存解析(与数组类似3-3)

5)属性(成员变量)&局部变量

  • 相同点
1.格式相同
2.先声明,后使用
3.都有对应的作用域
  • 不同点
1.声明的位置不同;
	属性:直接定义在一对{}中
	局部变量:声明在方法内,方法形参,代码块,构造器形参,构造器内部的变量
2.权限修饰符不同:privatepublicdefault,portect(封装)
	属性:在声明属性时,可以指定权限,使用修饰符
	局部变量:不可以使用权限修饰符
3.默认初始化值
	属性:整型(byteshortintlong):0
		浮点型(floatdouble):0.0
		字符型(char):0或者'\u0000'			//对应ASCAII为0的字符
		布尔型(boolean):false				 //0->false; 1->true
		字符串(String):null
	局部变量:必须有初始化值,形参在调用时赋值
4.内存中加载的位置
    属性:堆空间中(非static)
	局部变量:栈空间中

6)方法

  • 方法声明
权限修饰符 返回值类型 方法名(形参列表){
	方法体
}
  • 注意事项
1.可以调用当前类的属性和方法(注意:递归调用)
2.方法中不能定义方法
  • 重载&重写
1.重载:同一个类中,允许有多个同名方法,但他们的参数个数或者参数类型不同
	跟方法的修饰符,返回值类型,形参变量名,方法体都无关
2.重写
  • 可变个数的形参
// 格式:数据类型 ... 变量名(可以传入参数个数0个,1个,2个...)
// 注意:数组形式相同的参数,无法重载
		可变形参必须声明在所有形参的末尾
        最多只能声明一个
public static void changeParam(String... args){
    System.out.println("String... args");
}

7)值传递(重点)

  • 变量的传递
1.基本数据类型:赋值后变量保存的是数据值
2.引用数据类型:赋值后变量保存的是对象的地址值
public class ValueGet {
    public static void main(String[] args) {
    //变量赋值
        //1.基本类型变量
        int m = 5;
        int n = m;
        System.out.println(m + "\t" + n);		//输出5	5
        m = 10;
        System.out.println(m + "\t" + n);		//输出10	5
        //2.引用数据类型
        Value value1 = new Value();
        Value value2 = value1;
        System.out.println( value1.num + "\t" + value2.num);	//输出5	5
        value1.num = 10;
        System.out.println( value1.num + "\t" + value2.num);	//输出10	10
    }
}
class Value{
    int num = 5;
}
  • 方法形参的传递

    形参:方法定义时,小括号里面的参数

    实参:方法调用时,要传给形参的数据

1.基本数据类型:实参赋值给形参的是数据值		//注意:他不改变实参的值
2.引用数据类型:赋值后变量保存的是对象的地址值	//他改变引用类型实参的值
public class ParamGet {
    public static void main(String[] args) {
    //形参赋值
        int m = 5;
        int n = 10;
        System.out.println(m + "\t" + n);   //输出:5     10
        //基本数据类型
        exchange(m, n);
        System.out.println(m + "\t" + n);   //输出:5     10
        //引用数据类型
        Exchange exchange = new Exchange();
        System.out.println(exchange.m + "\t" + exchange.n); //输出:5     10
        exchange(exchange);
        System.out.println(exchange.m + "\t" + exchange.n); //输出:10     5
    }
    public static void exchange(int m, int n){
        int temp = m;
        m = n;
        n = temp;
    }
    public static void exchange(Exchange exchange){
        int temp = exchange.m;
        exchange.m = exchange.n;
        exchange.n = temp;
    }
}
class Exchange{
    int m = 5;
    int n = 10;
}

8)递归调用

public class Recursion {
    public static void main(String[] args) {
        //求前一百数总和
        int num = 100;
        System.out.println(sum(num));
    }
    public static int sum(int num){
        if (num == 1){
            return 1;
        }else {
            return num + sum(num-1);
        }
    }
}

4.面向对象2(三大特征)

1)封装

  • 高内聚&低耦合
1.高内聚:类的内部数据操作细节自己完成,不允许外部干涉
2.高耦合:仅对外暴露少量的方法用于使用
  • 使用
属性私有化(private),创建set和get方法
  • 四种权限修饰符
内部类 同一个包 不同包的子类 同一个工程
private yes
缺省 yes yes
protect yes yes yes
public yes yes yes yes
四种权限可以用来修饰:
	1.类											//(private,缺省)
	2.类的内部结构:属性,方法,构造器,内部类		   //(private,缺省,protect,public)
  • 构造器
1.作用:创建对象,初始化对象信息
2.使用:系统默认提供一个默认的无参构造器
	当自定义创建构造器,系统将不提供默认的构造器;	
  • this
this用来修饰:(this可以理解为当前对象)
	属性:this.name
	方法:this.get()
	构造器:this(name)	
package day04_object02;

public class thisUse {
    public static void main(String[] args) {
        User user = new User("小张", 4);
        /*
        * 输出:   无参的构造器
        *         参数name
        *         参数name,age
        *		  小张	4
        */
    }
}
class User{
    String name;
    int age;
    
    public User(){
        System.out.println("无参的构造器");
    }
    public User(String name){
        this();
        this.name = name;
        System.out.println("参数name");
    }
    public User(String name, int age){
        this(name);
        this.age = age;
        System.out.println("参数name,age");
        System.out.println(name + "\t" +age);

    }
}
  • package,import关键字
1.package:同一个包下,不能定义同名的接口,类
2.import:如果使用不同包下同名的类,则必须有一个类需要以全类名的方式显示。new java.util.List();
			import java.util.*	调用util下所有的包,但不能调用包的子类

2)继承

  • 定义
1.格式:class A extends B{}
	A:子类,派生类,subclass
	B:父类,超类,基类,superclass
2.体现:一旦A类继承B类之后,子类A就获取父类B中的所有属性,方法
	特别的,当父类中声明private的属性或方法时,仍然认为子类可以获取父类中的私有结构
	只是因为封装性的影响,使得子类不能直接调用父类的私有结构
3.子类继承父类之后,还可以声明子类独有的属性或方法,实现功能的扩展
  • 继承性的规定(记忆)
1.一个类可以被多个子类继承
2.java中的类是单继承的,一个子类只能有一个父类
3.子类继承父类,就可以获得父类以及间接父类(父类的父类)的所有属性及方法
  • Object类

    所有的类都直接或者间接继承Object类
    
  • 方法的重载写

1.重写:子类继承父类以后,对父类的同名,同参数方法,进行覆盖
2.规定: 1)子类与父类重写的方法名,方法形参列表相同
		2)子类重写方法的修饰权限不小于父类被重写的方法
			子类不能重写父类修饰符为private的方法
    	3)返回值类型:
			父类方法返回值类型void,则子类一定为void
            父类方法返回值A类,则子类一定为A类或者A类的子类(例如,intdouble不能被重写)
    	4)子类重写的方法抛出异常不大于父类被重写的方法抛出的异常类型
    	5)要非static修饰都一样,要么都是static修饰(此时不是方法的重写,不能被覆盖)
    
  • super
可以修饰:			//(当有同名的属性,方法,构造器需要加super,其他默认可以 不写)
	属性:super.id
	方法:super.getMethod()
	构造器:super(id)		//没有指定this或者super,会默认调用父类的无参构造方法
    					  //类中多个构造器,至少有一个使用super(形参)去调用父类的构造器
  • 子类对象实例化过程
1.从结果上来看:
		子类继承父类以后,就获取了父类中声明的属性或方法。
  		创建子类的对象中,在堆空间中,就会加载所有父类中声明的属性。
2.从过程上看:
  		当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类构造器, 
  		直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有
  		父类中的结构,子类对象可以考虑进行调用。
 //明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。

3)多态

  • 理解
 * 1.理解多态性:可以理解为一个事物的多种态性。
 * 2.何为多态性:
 * 	 对象的多态性:父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
 * 3.多态的使用:虚拟方法调用
 * 	有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法
 * 	简称:编译时,看左边;运行时,看右边。若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
 *  	多态情况下,
 *  		“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
 *  		“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
 *  4.多态性的使用前提:
 *  	① 类的继承关系
 *  	② 方法的重写
 *  5.对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
public class Animal {
    void eat(){
        System.out.println("动物会吃");
    }
    public static void main(String[] args) {
        Animal animal = new Animal();
        test(new Dog());    //狗吃肉
        test(new Cat());    //猫吃老鼠
    }
    public static void test(Animal animal){
        animal.eat();
    }
}
//狗类
class Dog extends Animal{
    void eat(){
        System.out.println("狗吃肉");
    }
}
//猫类
class Cat extends Animal{
    void eat(){
        System.out.println("猫吃老鼠");
    }
}
  • instanceof关键字
1.	较高级的数据类型----------->较低级的数据类型
				 (强制类型转换)	
	较低级的数据类型----------->较高级的数据类型
				 (自动类型提升)
    
2.	父类------------------------------->子类
			 (向下转型)instanceof判断
	子类------------------------------->父类
				  (向上转型)多态
Animal animal1 = new Dog();
if (animal1 instanceof Cat){		//false
	System.out.println("Cat");		
}
if (animal1 instanceof Dog){		//true
	System.out.println("Dog");
}
if (animal1 instanceof Animal){		//true
	System.out.println("animal");
}
  • == & equals
1.==:运算符
	基本数据类型:比较的是数据值(不一定要类型一致)
	引用数据类型:比较两个对象的地址值(即两个引用是否指向同一个地址)
2.equals:方法()只适用于引用数据类型
	Object类中的equals 与 “==”的作用是相同的
	像StringDateFile,包装类都重写了Object类的equals()方法,比较的是引用数据类型的值
//注:	要比较实体类是否相同,需要重写equals()方法
  • toString()
1.当我们去调用一个对象的引用,实际上是调用它的toString()方法
2.Object中toString()的定义:
	 public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
3.StringDateFile,包装类都重写了toString()方法
  • 包装类Wrapper
基本数据类型 byte short int long float double boolean char
包装类 Byte Short Integer Long Float Double Boolean Character
1.基本数据类型,包装类,String相互转化:
	1)基本数据类型转包装类
		Integer integer = new Integer("123");
	2)包装类转基本数据类型
		int i = integer.intValue();
	3)基本数据类型,包装类&String类型的转化
        String s = String.valueOf(integer); 	//其他转String
		int num = Integer.parseInt(s);			//String转其他
2.自动装箱,自动拆箱(JDK1.5

5.面向对象3(关键字)

1)static

1.可以用来修饰:
	属性(成员变量):
			1)创建多个类的多个对象,多个对象共享同一个静态变量。当其中一个静态变量改变,会导致其他对象调用该静态变量的值随					之改变。
			2)静态变量随着类加载而加载,可以通过”类.静态变量“使用
			3)静态变量的加载要早于对象的创建
			4)由于类只加载一次,所以静态变量在内存中也只会存在一份,放在内存方法区的静态域中
	方法:
			1)静态方法随着类加载而加载,可以通过”类.方法()“使用
			2)静态方法中,只能调用静态方法或属性
			3)在静态的方法中,不能使用this,super关键字
	代码块:
	内部类:
2.那些需要定义静态?
	属性;
			1)被多个类共享,不随对象的不同而改变
			2)类中的常量通常声明为static
	方法:
			1)操作静态变量的方法一般是static的
			2)工具类一般声明static的,例如:Arrays,Math等

2)设计模式(单例)

  • 定义
1.单例模式,在一个系统中,某个类只能存在一个实例对象。
	首先,将类的构造器私有化private,使得外部类无法创建改类的对象
	只能通过调用该类的某个静态方法,返回类内部创建的对象,
	静态方法只能访问该类的静态成员变量,所以指向内部产生的该类对象的变量也必须为静态的
2.区分饿汉式和懒汉式。
 	 1)饿汉式:坏处:对象加载时间过长。
 	 	       好处:饿汉式是线程安全的。
     2)懒汉式:好处:延迟对象的创建。
 		       坏处:目前的写法,会线程不安全。---》到多线程内容时,再修改
1.优点:	
	减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产
	生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。(例如:RunTime)
  • 代码
public class DesignModel {
    public static void main(String[] args) {
        Bank bank1 = Bank.getBank();
        Bank bank2 = Bank.getBank();
        System.out.println(bank1 == bank2);	//true

        Order order1 = Order.getOrder();
        Order order2 = Order.getOrder();
        System.out.println(order1 == order2);	//true
    }
}
//(1)单例的饿汉式
class Bank{
    private Bank() {
    }
    private static Bank bank = new Bank();
    public static Bank getBank(){
        return bank;
    }
}
//单例的懒汉式实现
class Order{
    private Order(){
    }
    private static Order order = null;
    public static Order getOrder(){
        if (order != null){
            return new Order();
        }
        return order;
    }
}

3)main方法

main()方法的使用说明
	 * 1.main()方法作为程序的入口;
	 * 2.main()方法也是一个普通的静态方法
	 * 3.main()方法也可以作为我们与控制台交互的方式。(之前,使用 Scanner)

4)代码块(初始化块)

  • 作用
1.初始化对象,类的
	只能用static修饰
  • 静态&非静态代码块
4.静态代码块
 * 	》内部可以有输出语句
 *  》随着类的加载而执行,而且只执行一次
 *  》作用:初始化类的信息
 *  》如果一个类中,定义了多个静态代码块,则按照声明的先后顺序执行
 *  》静态代码块的执行,优先于非静态代码块的执行
 *  》静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
 * 
5.非静态代码块
 *  >内部可以有输出语句
 *  >随着对象的创建而执行
 *  >每创建一个对象,就执行一次非静态代码块。
 *  >作用:可以在创建对象时,对对象的属性等进行初始化。
 *  >如果一个类中,定义了多个非静态代码块,则按照声明的先后顺序执行
 *  >非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
  • 例子
public class Son extends Father{
    static {
        System.out.println("44444444444");
    }
    {
        System.out.println("55555555555");
    }
    public Son() {
        System.out.println("66666666666");
    }
    
    public static void main(String[] args) { // 由父及子 静态先行
        System.out.println("77777777777");
        System.out.println("************************");
        new Son();
        System.out.println("************************");

        new Son();
        System.out.println("************************");
        new Father();
    }
}
//总结:由父类到子类,静态先行
class Father {
    static {
        System.out.println("11111111111");
    }
    {
        System.out.println("22222222222");
    }
    public Father() {
        System.out.println("33333333333");
    }
}
/**输出
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
**/
  • 顺序
* 对属性可以赋值的位置:
 *  ①默认初始化
 *  ②显式初始化 / ⑤在代码块中赋值
 *  ③构造器中初始化
 *  ④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值。

5)final关键字

修饰范围

1.类:
	1)此类不能被继承,例:String类,System类,StringBuffer类等等
2.方法:
	1)方法不能被重写,例:Object类中的getClass()方法
3.变量:(此时的"变量"(成员变量或局部变量)就是一个常量。名称大写,且只能被赋值一次)
	属性:(变量名大写)
		1)默认赋值(显式初始化、代码块中初始化、构造器中初始化)
	局部变量:
		1)尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。
	    2)一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。

注:static final 用来修饰:全局常

6)抽象类与抽象方法

  • 修饰范围
1.类
	1)此类不能实例化
	2)抽象类一定有构造器,子类需要调用父类的构造器
	3)开发中,都会提供抽象类的子类,让子类对象实例化,实现相关的操作
2.对象
	1)抽象方法只有方法的声明,没有方法体
	2)包含抽象方法得类一定是抽象类。反之,抽象类中可以没有抽象方法
	3)若子类重写了父类所有的抽象方法,此子类可以实例化;	
		若子类没有重写了父类所有的抽象方法,此子类是抽象类,需要用abstract修饰
		
注意:
	1.abstract 不能用来修饰变量、代码块、构造器; 
	2.abstract 不能用来修饰私有方法、静态方法、final 的方法、final 的类。
  • 抽象类的匿名子类对象
1.method(new Student());	//匿名对象
2.//创建了一个匿名子类的对象:p
    Person p = new Person(){
        @Override
        public void eat() {
            System.out.println("吃东西");
        }
        @Override
        public void breath() {
            System.out.println("呼吸空气");
        }
	};
  • 模板方法设计模式(Template Method)
	当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
	换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
public class TemplateMethod {
    public static void main(String[] args) {
        SubTemplate subTemplate = new SubTemplate();
        subTemplate.sendTime();
    }
}
abstract class Template{
    void sendTime(){
        long strat = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
    }
    abstract void code();
}
class SubTemplate extends Template{
    @Override
    void code() {
        for(int i = 2;i <= 1000;i++){
            boolean isFlag = true;
            for(int j = 2;j <= Math.sqrt(i);j++){
                if(i % j == 0){
                    isFlag = false;
                    break;
                }
            }
            if(isFlag){
                System.out.println(i);
            }
        }
    }
}

7)接口interface

  • 特点
 * 用 interface 来定义。
 * 接口中的所有成员变量都默认是由 public static final 修饰的。
 * 接口中的所有抽象方法都默认是由 public abstract 修饰的。
 * 接口中没有构造器。
 * 接口采用多继承机制。
  • 接口的使用
 * 1.接口使用 interface 来定义。
 * 2.在 Java 中:接口和类是并列的两个结构
 * 3.如何去定义两个接口:定义接口中的成员
 * 	》3.1 JDK7 及以前:只能定义全局常量和抽象方法
 * 		》全局常量:public static final 的,但是书写中,可以省略不写。
 * 		》抽象方法:public abstract 的
 * 
 *  》3.2 JDK8:除了全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)。
 * 
 * 4.接口中不能定义构造器!意味着接口不可以实例化。
 * 
 * 5.Java 开发中,接口通过让类去实现(implements)的方式来使用。
 *   如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化
 *   如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
 * 
 * 6.Java 类可以实现多个接口 ---》弥补了 Java 单继承性的局限性
 *  	格式:class AA extends BB implementd CC,DD,EE
 *  
 * 7.接口与接口之间是继承,而且可以多继承
 *  
 **********************************
 * 8.接口的具体使用,体现多态性
 * 	   接口的主要用途就是被实现类实现。(面向接口编程)
 * 9.接口,实际可以看作是一种规范
  • 代码
public class USBTest {
    public static void main(String[] args) {
        Computer computer = new Computer();
        //1.创建了接口的非匿名实现类的非匿名对象
        Flash flash = new Flash();
        computer.transferData(flash);
        //2. 创建了接口的非匿名实现类的匿名对象
        computer.transferData(new Printer());
        //3. 创建了接口的匿名实现类的非匿名对象
        Use phone = new Use(){
            @Override
            public void start() {
                System.out.println("手机开始工作");
            }
            @Override
            public void stop() {
                System.out.println("手机结束工作");
            }
        };
        computer.transferData(phone);
        //4. 创建了接口的匿名实现类的匿名对象
        computer.transferData(new Use() {
            @Override
            public void start() {
                System.out.println("mp3 开始工作");
            }
            @Override
            public void stop() {
                System.out.println("mp3 结束工作");
            }
        });
    }
}
class Computer{

    public void transferData(Use usb){//USB usb = new Flash();
        usb.start();
        System.out.println("具体传输数据的细节");
        usb.stop();
    }
}
interface Use{
    void start();
    void stop();
}
class Flash implements Use{
    @Override
    public void start() {
        System.out.println("U 盘开始工作");
    }
    @Override
    public void stop() {
        System.out.println("U 盘结束工作");
    }
}
class Printer implements Use{
    @Override
    public void start() {
        System.out.println("打印机开启工作");
    }
    @Override
    public void stop() {
        System.out.println("打印机结束工作");
    }
}
  • 抽象类与接口对比
区别点 抽象类 接口
1 定义 包含抽象方法的类 主要是抽象方法和全局常量的集合
2 组成 构造方法、抽象方法、普通方法、常量、变量 常量、抽象方法、(jdk8.0:默认方法、静态方法)
3 使用 子类继承抽象类(extends) 子类实现接口(implements)
4 关系 抽象类可以实现多个接口 接口不能继承抽象类,但允许继承多个接口
5 常见设计模式 模板方法 简单工厂、工厂方法、代理模式
6 对象 都通过对象的多态性产生实例化对象
7 局限 抽象类有单继承的局限 接口没有此局限
8 实际 作为一个模板 是作为一个标准或是表示一种能力
9 选择 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限

8)代理模式(Proxy)

  • 代理模式(静态)
public class NetWorkTest {
    public static void main(String[] args) {
        Server server = new Server();
        ProxyServer proxyServer = new ProxyServer(server);
        proxyServer.browse();
    }
}
interface NetWork{
    public void browse();
}
//被代理类
class Server implements NetWork{
    @Override
    public void browse() {
        System.out.println("真正的服务器");
    }
}
//代理类
class ProxyServer implements NetWork{
    private NetWork netWork;
    public ProxyServer(NetWork netWork){
        this.netWork = netWork;
    }
    public void check(){
        System.out.println("检查网络!");
    }
    @Override
    public void browse() {
        check();
        netWork.browse();
    }
}
  • 动态代理(动态生成代理类)
JDK 自带的动态代理,需要反射等知识

9)工厂模式

10)JDK8,接口特性

  • JDK8:除了全局常量和抽象方法之外,还可以定义静态方法、默认方法
//		知识点 1:接口中定义的静态方法,只能通过接口来调用。
		CompareA.method1();
//		知识点 2:通过实现类的对象,可以调用接口中的默认方法。
//		如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
		s.method2();
//		知识点 3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//		那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则

//		知识点 4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//		那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
//		这就需要我们必须在实现类中重写此方法
		s.method3();
//		知识点 5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法

11)内部类

 * 1.Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类.
 * 
 * 2.内部类的分类:成员内部类	VS	局部内部类(方法内、代码块内、构造器内)
 * 		
 * 3.成员内部类
 * 	》作为外部类的成员,
 * 		- 调用外部类的结构
 * 		- 可以被static修饰
 * 		- 可以被4种不同的权限修饰
 * 
 *  》作为一个类,
 *  	- 类内可以定义属性、方法、构造器等
 *  	- 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
 *  	- 可以abstract修饰
 * 
 * 4.关注如下的3个问题
 *   》 如何实例化成员内部类的对象
 *   》 如何在成员内部类中区分调用外部类的结构
 *   》 开发中局部内部类的使用  见《InnerClassTest1.java》
public class InnerClassTest {
    public static void main(String[] args) {
        //创建Dog实例(静态的成员内部类)
        Person.Dog dog = new Person.Dog();
        dog.show();

        //创建Bird实例(非静态的成员内部类)
//		Person.Bird bird = new Person.Bird();
        Person p = new Person();
        Person.Bird bird = p.new Bird();
        bird.sing();

        System.out.println();

        bird.display("喜鹊");
    }
}
class Person{
    String name = "李雷";
    int age;

    public void eat(){
        System.out.println("人,吃饭");
    }
    //静态成员内部类
    static class Dog{
        String name;
        int age;

        public void show(){
            System.out.println("卡拉是条狗");
//			eat();
        }
    }
    //非静态成员内部类
    class Bird{
        String name = "杜鹃";
        public Bird(){

        }
        public void sing(){
            System.out.println("我是一只猫头鹰");
            Person.this.eat();//调用外部类的非静态属性
            eat();
            System.out.println(age);
        }
        public void display(String name){
            System.out.println(name);	//方法的形参
            System.out.println(this.name);	//内部类的属性
            System.out.println(Person.this.name);	//外部类的属性
        }
    }
    public void method(){
        //局部内部类
        class AA{
        }
    }
    {
        //局部内部类
        class BB{

        }
    }
    public Person(){
        //局部内部类
        class CC{
        }
    }
}

6.异常处理

1)异常体系

  • Error

    Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。

    比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。

public class ErrorTest {
	public static void main(String[] args) {
		//1.栈溢出:java.lang.StackOverflowError
		main(args);
		//2.堆溢出:java.lang.OutOfMemoryError
		Integer[] arr = new Integer[1024*1024*1024];
	}
}
  • Exception:(编译时异常&运行时异常

    其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:

1.空指针访问
2.试图读取不存在的文件
3.网络连接中断
4.数组角标越界

2)常见异常

 * java.lang.Throwable
 * 		|----java.lang.Error:一般不编写针对性的代码进行处理
 * 		|----java.lang.Exception:可以进行异常处理
 * 			|----编译时异常(checked)
 * 				|----IOEXception
 * 					|----FileNotFoundException
 * 				|----ClassNotFoundException
 * 			|----运行时异常(unchecked)
 * 				|----NullPointerException
 * 				|----ArrayIndexOutOfBoundsException
 * 				|----ClassCaseException
 * 				|----NumberFormatException
 * 				|----InputMismatchException
 * 				|----ArithmaticException

3)异常处理机制

  • 方式一:try-catch-finally
1.抛:
	在程序运行过程中,一旦出现异常,就会在生成异常代码处生成一个对应的异常类的对象,并抛出,一旦抛出,其后的代码就不执行了
2.抓:
	可以理解为异常的处理方式:① try-catch-finallythrows
	
 * 说明:
 * 1.finally是可选的。
 * 2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
 * 3.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码。
 * 4.catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。
 *   catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则,报错
 * 5.常用的异常对象处理的方式: ① String  getMessage()printStackTrace()
 * 6.try结构中声明的变量,再出了try结构以后,就不能再被调用,
 * 7.try-catch-finally结构可以嵌套  
 * 
 * 体会1:使用try-catch-finally处理编译时异常,是得程序在编译时就不再报错,但是运行时仍可能报错。相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
 * 体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
  • finally使用
1.finally是可选的。
2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。
3.像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。
  • 方式二:throws + 异常类型
 * 1. "throws + 异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
 *    一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常
 *    类型时,就会被抛出。异常代码后续的代码,就不再执行!
 *    关于异常对象的产生:① 系统自动生成的异常对象
 * 					② 手动生成一个异常对象,并抛出(throw)
 *     
 * 2. 体会:try-catch-finally:真正的将异常给处理掉了。
 *    throws的方式只是将异常抛给了方法的调用者。  并没有真正将异常处理掉。
  • 异常使用
注意:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

开发中如何选择使用try-catch-finally 还是使用throws*   3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
 *   3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
  • 自定义异常特别
 * 如何自定义异常类?
 * 1.继承于现有的异常结构:RuntimeExceptionException
 * 2.提供全局常量:serialVersionUID
 * 3.提供重载的构造器
public class ExceptionTest extends RuntimeException{
    static final long serialVersionUID = -70348347193246939L;
    public ExceptionTest(){
    }
    public ExceptionTest(String var){
        super(var);
    }
}
class Student{
    private int score;
    public void setScore(int score){
        if (score > 0){
            this.score = score;
        }else {
            throw new ExceptionTest("成绩不能为负数!");
        }
    }
    public static void main(String[] args) {
        Student student = new Student();
        student.setScore(-1);
    }
}

-----高级部分-----

1.多线程

1)概念:程序,进程,线程

  • 程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

  • 进程(process):程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存 在和消亡的过程。——生命周期

    如:运行中的QQ,运行中的MP3播放器程序是静态的

    进程是动态的进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

  • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径

    若一个进程同一时间并行执行多个线程,就是支持多线程的

    线程是调度和执行的单位,每个线程拥有独立的运行栈程序计数器(pc),线程切换的开销小

    一个进程中的多个线程共享相同的内存单元/内存地址空间—》它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

简单来说

1.每个线程都有一份虚拟机栈,程序计数器
2.一个进程对应一个方法区,堆。而一个进程包含了多个线程
  • 单核CPU和多核CPU的理解
1.单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
2.如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
3.一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
  • 并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
  • 多线程程序的优点
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程:
	1.程序需要同时执行两个或多个任务。
	2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
	3.需要一些后台运行的程序时。

2)线程的创建和使用

  • 创建多线程的方式一:继承Thread类
过程:
 * 1.创建一个继承于Thread类的子类
 * 2.重写Threadrun()方法 ---> 将此线程的方法声明在run()* 3.创建Thread类的子对象
 * 4.通过此对象调用start()
     	①启动当前线程 
     	②调用当前线程的run()     
问题:
     问题1:我们不能通过直接调用run()的方式启动线程
     问题2:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException    
public class ThreadCreate {
    public static void main(String[] args) {
        SubThread subThread = new SubThread();
        subThread.start();
    }
}
class SubThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (i%2==0){
                System.out.println(i);
            }
        }
    }
}
//创建Thread类的匿名子类的方式
new Thread(){
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}.start();
  • Thread类的有关方法
 * 1.start():启动当前线程,执行当前线程的run()
 * 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
 * 3.currentThread(): 静态方法,返回当前代码执行的线程
 * 4.getName():获取当前线程的名字
 * 5.setName():设置当前线程的名字	//thread.setName("线程一");
 * 6.yield():释放当前CPU的执行权
 * 7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
 * 8.stop():已过时。当执行此方法时,强制结束当前线程。
 * 9.sleep(long millitime):让当前线程“睡眠”指定时间的millitime毫秒)。在指定的millitime毫秒时间内,当前线程是阻塞状态的。
 * 10.isAlive():返回boolean,判断线程是否还活着
  • 线程优先级
 * - 线程的优先级等级
 *   - MAX_PRIORITY:10
 *   - MIN _PRIORITY:1
 *   - NORM_PRIORITY:5 --->默认优先级
 * - 涉及的方法
 *   - getPriority() :返回线程优先值
 *   - setPriority(intnewPriority) :改变线程的优先级
 *
 *   说明:高优先级的线程要抢占低优先级线程cpu的执行权。
 *       但是只是从概率上讲,高优先级的线程高概率的情况下被执行。
 *       并不意味着只有当高优先级的线程执行完以后,低优先级的线程才会被执行。
  • 创建多线程的方式二:实现Runnable接口
 * 1.创建一个实现了Runnable接口得类
 * 2.实现类去实现Runnable中的抽象方法:run()
 * 3.创建实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Thread类的对象调用start()
 		①启动线程 
 		②调用当前线程的run() --> 调用了Runnable类型的target的run()
public class RennableCreate {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
class RunnableImpl implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2!=0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
  • 比较Thread&Runnable
开发中:优先选择:实现Runnable接口的方式
 *  原因: 1. 实现的方式没有类的单继承性的局限性
 *        2. 实现的方式更适合来处理多个线程有共享数据的情况。
 *  
 *  联系:public class Thread implements Runnable
 *  相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
  • 补充:一种是守护线程,一种是用户线程
1.它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
2.守护线程是用来服务用户线程的,通过在start()方法前调用**thread.setDaemon(true)**可以把一个用户线程变成一个守护线程。
3.Java垃圾回收就是一个典型的守护线程。
4.若JVM中都是守护线程,当前JVM将退出。
5.形象理解:兔死狗烹,鸟尽弓藏

3)线程的生命周期

1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3.运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

​ <-------------------- (阻塞) <-------------------

1sleep(),join()结束 | | 1sleep(long time),join(),

2获取同步锁 | | 2等待同步锁

**3notify()/notifyAll() **| | 3wait()

4resume() | <----------------------------------------------- | 4suspend()

(新建)----------------->(就绪) 失去cpu执行权 (运行)----------------------------->(死亡)

调用start() --------------------------------------------------> (执行完run(),调用stop(),

获取cpu执行权或yield() 出现error/Exception未处理)

4)线程的同步

  • 同步代码块
 *  例子:创建三个窗口卖票,总票数为100.使用实现Runnable接口的方式
 *  1.卖票过程中出现重票、错票 ---》出现了线程的安全问题
 *  2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
 *  3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他
 *            线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
 *  4.在java中,我们通过同步机制,来解决线程的安全问题。 
方式一:同步代码块
 *  synchronized(同步监视器){
 *      //需要被同步的代码
 *  }
 *  说明:1.操作共享数据的代码,即为需要被同步的代码 --->不能包含代码多了,也不能包含代码少了。
 *       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
 *       3.同步监视器,俗称:锁。任何一个类的对象,都可以来充当锁。
 *          要求:多个线程必须要共用同一把锁。
 *
 *       补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
方式二:同步方法
 *      如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
 *
 *  5.同步的方式,解决了线程的安全问题。---好处
 *    操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---局限性
  • 同步方法
 * 关于同步方法的总结:
 *  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
 *  2. 非静态的同步方法,同步监视器是:this
 *     静态的同步方法,同步监视器是:当前类本身

4)线程安全的单例模式之懒汉式

public class SingleSecurity {
    public static void main(String[] args) {
        Single single1 = Single.getSingle();
        Single single2 = Single.getSingle();
        System.out.println(single1==single2);		//true
    }
}
class Single{
    private Single(){
        System.out.println("aa");
    }
    private static Single single = null;

    public static Single getSingle() {
        if(single == null) {
            synchronized (Single.class) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }
}

5)死锁

 * 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
 *       都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
 * 2.说明:
 *      》出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 *      》我们使用同步时,要避免出现死锁。
public class StringBufferTest {    public static void main(String[] args) {        StringBuffer s1 = new StringBuffer();        StringBuffer s2 = new StringBuffer();        new Thread(){            @Override            public void run() {                synchronized (s1){                    s1.append("a");                    s2.append("1");                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (s2){                        s1.append("b");                        s2.append("2");                        System.out.println(s1);                        System.out.println(s2);                    }                }            }        }.start();        new Thread(new Runnable() {            @Override            public void run() {                synchronized (s2){                    s1.append("c");                    s2.append("3");                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    synchronized (s1){                        s1.append("d");                        s2.append("4");                        System.out.println(s1);                        System.out.println(s2);                    }                }            }        }).start();    }}
  • Lock锁方式解决线程安全问题
 * 解决线程安全问题的方式三:lock锁---》JDK5.0新增
 *
 * 注意:如果同步代码有异常,要将unlock()写入finally语句块
 *
 * 1. 面试题:synchronizedLock的异同?
 *    相同:二者都可以解决线程安全问题
 *    不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
 *         Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()*
 * 2.优先使用顺序:
 *      1)Lock =>2)同步代码块(已经进入了方法体,分配了相应资源)->3)同步方法(在方法体之外)
public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(0);
        Customer customer1 = new Customer("甲",account);
        Customer customer2 = new Customer("乙",account);
        customer1.start();
        customer2.start();
    }
}
class Account{
    public static int banlance;
    private ReentrantLock lock = new ReentrantLock();
    public Account(int money){
        banlance = money;
    }
    public void save(){
        lock.lock();
        try {
            if (banlance >= 0){
                banlance += 1000;
                System.out.println(Thread.currentThread().getName()+"存款,余额:"+banlance);
            }
        }finally {
            lock.unlock();
        }
    }
}
class Customer extends Thread{
    private  Account account;
    public Customer(String name, Account account){
        super(name);
        this.account = account;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.save();
        }
    }
}

6)线程的通信

 * 涉及到的三个方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
 * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
 *
 * 说明:
 *      1.wait()notify()notifyAll()三个方法必须使用在同步代码块或同步方法中。
 *      2.wait()notify()notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
 *         否则,会出现IllegalMonitorStateException异常
 *      3.wait()notify()notifyAll()三个方法是定义在java.lang.Object类中。
public class Printor {
    public static void main(String[] args) {
        PrintTest print1 = new PrintTest("打印机1");
        PrintTest print2 = new PrintTest("打印机2");
        print1.start();
        print2.start();
    }
}
class PrintTest extends Thread{
    private static int num = 1;
    static final Object obj =new Object();
    public PrintTest(String name){
        super(name);
    }
    @Override
    public void run() {
        synchronized (obj){
            obj.notify();
            while (num <= 100){
                System.out.println(Thread.currentThread().getName()+ ":" + num);
                num++;
            }
            try {
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • sleep()和wait()的异同
 * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
 * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
 *          2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
 *          3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

7)JDK5.0新增线程创建方式

  • 方式三:实现Callable接口
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
 *      1.call()可以有返回值的。
 *      2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
 *      3.Callable是支持泛型的
 *      4.需要借助FutureTask类,比如获取返回结果
Future接口:
1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
2.FutrueTask是Futrue接口的唯一的实现类
3.FutureTask同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        Thread1 thread1 = new Thread1();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask<Integer>  futureTask= new FutureTask<>(thread1);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();
        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//1.创建一个实现Callable的实现类
class Thread1 implements Callable<Integer>{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
  • 方式四:使用线程池
1、背景:
	经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
2、思路:
	提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
3、好处:
	提高响应速度(减少了创建新线程的时间)
	降低资源消耗(重复利用线程池中线程,不需要每次都创建)
	便于线程管理
		corePoolSize:核心池的大小
		maximumPoolSize:最大线程数
		keepAliveTime:线程没有任务时最多保持多长时间后会终止
1.线程池相关API
	JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors
2.ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
	void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
	Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
	void shutdown():关闭连接池
3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
	Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
	Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
	Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
	Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
public class ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1. 提供指定线程数量的线程池
        ThreadPoolExecutor service = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
        //设置线程池的属性
//        service.setCorePoolSize(15);    //核心池的大小
//        service.setKeepAliveTime();     //线程没有任务时最多保持多长时间后会终止
//        service.setMaximumPoolSize(15); //最大线程数
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new Thread3());
        Future<Integer> submit = service.submit(new Thread4());
        Integer integer = submit.get();
        System.out.println(integer);
        //3.关闭连接池
        service.shutdown();

    }
}
class Thread3 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class Thread4 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 0;i <= 10;i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            sum += i;
        }
        return sum;
    }
}

2.常见类

1)字符串相关的类

  • 概述
String:字符串,使用一对“”引起来表示。
* 1.String声明为final的,不可被继承
* 2.String实现了Serializable接口:表示字符串是支持序列化的。
*         实现了Comparable接口:表示String可以比较大小
* 3.String内部定义了final char[] value用于存储字符串数据
* 4.String:代表不可变的字符序列。简称:不可变性
*      体现:1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
*           2.当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
*           3.当调用Stringreplace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
* 5.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
* 6.字符串常量池中是不会存储相同内容的字符串的。
  • 字符串创建
String str = "hello";

//本质上this.value = new char[0];
String  s1 = new String(); 

//this.value = original.value;
String  s2 = new String(String original); 

//this.value = Arrays.copyOf(value, value.length);
String  s3 = new String(char[] a);

String  s4 = new String(char[] a,int startIndex,int count);
* String的实例化方式
     * 方式一:通过字面量定义的方式
     * 方式二:通过new + 构造器的方式
     *
     * 面试题:String s = new String("abc");方式创建对象,在内存中创建了几个对象?
     *      两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"
  • String常用方法
 * int length():返回字符串的长度:return value.length
 * char charAt(int index):返回某索引处的字符return value[index]
 * boolean isEmpty():判断是否是空字符串:return value.length==0
 * String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写
 * String toUpperCase():使用默认语言环境,将String中的所有字符转换为大写
 * String trim():返回字符串的副本,忽略前导空白和尾部空白
 * boolean equals(Object obj):比较字符串的内容是否相同
 * boolean equals IgnoreCase(String anotherString):与equals方法类似,忽略大小写
 * String concat(String str):将指定字符串连接到此字符串的结尾。等价于用“+* int compareTo(String anotherString):比较两个字符串的大小
 * String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
 * String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
 * boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
 * boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
 * boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
 *
 * boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
 * int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
 * int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
 * int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
 * int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
 *
 * 注:indexOf和lastIndexOf方法如果未找到都是返回-1
 * 替换:
 * String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
 * String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
 * String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
 * String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
 *
 * 匹配:
 * boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
 *
 * 切片:
 * String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
 * String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
*    String与基本数据类型、包装类之间的转换
*    String --> 基本数据类型、包装类:调用包装类的静态方法:parseXxx(str)
*    基本数据类型、包装类 --> String:调用String重载的valueOf(xxx)
* Stringchar[]之间的转换
*
* String --> char[]:调用StringtoCharArray()
* char[] --> String:调用String的构造器
 * Stringbyte[]之间的转换
 *
 * 编码:String --> byte[]:调用StringgetBytes()
 * 解码:byte[] --> String:调用String的构造器
 *
 * 编码:字符串 -->字节  (看得懂 --->看不懂的二进制数据)
 * 解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
 *
 * 说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
  • StringBuffer和StringBuilder的介绍
 * StringStringBufferStringBuilder三者的异同?
 *
 * String:不可变的字符序列;底层使用char[]存储
 * StringBuffer:可变的字符序列;线程安全的,效率低;底层使用char[]存储
 * StringBuilder:可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储
StringBuffer的源码分析
 
	* 源码分析:
     * String str = new String();//char[] value = new char[0];
     * String str1 = new String("abc");//char[] value = new char[]{'a','b','c'};
     *
     * StringBuffer sb1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度是16的数组。
     * System.out.println(sb1.length());//
     * sb1.append('a');//value[0] = 'a';
     * sb1.append('b');//value[1] = 'b';
     *
     * StringBuffer sb2 = new StringBuffer("abc");//char[] value = new char["abc".length() + 16];
     *
     * //问题1.System.out.println(sb2.length());//3
     * //问题2.扩容问题:如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
     *        默认情况下,扩容为原来容量的2+ 2,同时将原有数组中的元素复制到新的数组中。
     *
     * 意义:开发中建议大家使用:StringBuffer(int capacity)StringBuilder(int capacity)
StringBuffer的常用方法:

    * StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
    * StringBuffer delete(int start,int end):删除指定位置的内容
    * StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
    * StringBuffer insert(int offset, xxx):在指定位置插入xxx
    * StringBuffer reverse() :把当前字符序列逆转
    * public int indexOf(String str)
    * public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
    * public int length()
    * public char charAt(int n )
    * public void setCharAt(int n ,char ch)
    *
    * 总结:
    *     增:append(xxx)
    *     删:delete(int start,int end)
    *     改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
    *     查:charAt(int n )
    *     插:insert(int offset, xxx)
    *     长度:length();
    *     遍历:for() + charAt() / toString()

2)JDK 8之前的日期时间API

  • Java中两个Date类的使用
   * java.util.Date---> 表示特定的瞬间,精确到毫秒
   *            |---java.sql.Date*
   * 1.两个构造器的使用
   *     >构造器一:Date():创建一个对应当前时间的Date对象
   *     >构造器二:创建指定毫秒数的Date对象
   * 2.两个方法的使用
   *     >toString():显示当前的年、月、日、时、分、秒
   *     >getTime():获取当前Date对象对应的毫秒数。(时间戳)
   *
   * 3. java.sql.Date对应着数据库中的日期类型的变量
   *     >如何实例化
   *     >如何将java.util.Date对象转换为java.sql.Date对象
  • SimpleDateFormat的使用
两个操作
     * 1.1格式化:日期---》字符串
     * 1.2解析:格式化的逆过程,字符串---》日期
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化
String format1 = sdf1.format(new Date());
System.out.println(format1);    
//解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
//否则,抛异常
Date date2 = sdf1.parse("2021-12-03 08:43:00");
System.out.println(date2);  /
  • Calendar日历类
Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。
获取Calendar实例的方法
	使用Calendar.getInstance()方法
	调用它的子类GregorianCalendar的构造器。
一个Calendar的实例是系统时间的抽象表示,通过get(intfield)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
	public void set(intfield,intvalue)
	public void add(intfield,intamount)
	public final Date getTime()
	public final void setTime(Date date)
注意:
	获取月份时:一月是0,二月是1,以此类推,12月是11
	获取星期时:周日是1,周二是2,。。。。周六是7

3)JDK8中日期时间API的介绍

  • LocalDate、LocalTime、LocalDateTime的使用
1.LocalDate、LocalTimeLocalDateTime类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
2.LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
3.LocalTime表示一个时间,而不是日期。
4.LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历
  • Instant类的使用
public class JDK8DateTimeTest {

    /**
     * Instant的使用
     */
    @Test
    public void test2(){
        //now():获取本初子午线对应的标准时间
        Instant instant = Instant.now();
        System.out.println(instant);    //2020-05-10T09:55:55.561Z

        //添加时间的偏移量
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));//东八区
        System.out.println(offsetDateTime); //2020-05-10T18:00:00.641+08:00

        //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
        long milli = instant.toEpochMilli();
        System.out.println(milli);  //1589104867591

        //ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
        Instant instant1 = Instant.ofEpochMilli(1550475314878L);
        System.out.println(instant1);   //2019-02-18T07:35:14.878Z
    }
}
  • DateTimeFormatter的使用
//重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2020-05-10 06:26:40

//解析
TemporalAccessor accessor = formatter3.parse("2020-05-10 06:26:40");
System.out.println(accessor);

4)Java比较器

  • Java实现对象排序的方式有两种:

    - 自然排序:`java.lang.Comparable`
    - 定制排序:`java.util.Comparator`
    
  • Comparable接口

Comparable接口的使用举例:  自然排序
* 1.像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。
* 2.像String、包装类重写compareTo()方法以后,进行了从小到大的排列
* 3. 重写compareTo(obj)的规则:
*		 如果当前对象this大于形参对象obj,则返回正整数,
*		 如果当前对象this小于形参对象obj,则返回负整数,
*		 如果当前对象this等于形参对象obj,则返回零。
* 4.对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法。在compareTo(obj)方法中指明如何排序
    @Override
    public int compareTo(Object o) {
        if (o instanceof Goods){
            Goods goods = (Goods) o;
            if (this.price > goods.price){
                return 1;
            }else if (this.price < goods.price){
                return -1;
            }else {
                return -this.name.compareTo(goods.name);
            }
        }
        throw new RuntimeException("错误!!");
    }
  • 使用Comparator实现定制排序
 * 一、说明:Java中的对象,正常情况下,只能进行比较:==  或  != 。不能使用 > 或 < 的
 *          但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。
 *          如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
 *
 * 二、Comparable接口与Comparator的使用的对比:
 *    Comparable接口的方式一旦一定,保证Comparable接口实现类的对象在任何位置都可以比较大小。
 *    Comparator接口属于临时性的比较。
Arrays.sort(arr, (g1, g2) -> {
	if (g1.getPrice() == g2.getPrice()){
		return g1.getName().compareTo(g2.getName());
	}else {
		return -Double.compare(g1.getPrice(), g2.getPrice());
	}
});

5)System类、Math类、BigInteger与BigDecimal类

  • System类
1.System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
成员方法

2.native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。

3.void exit(int status):
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。

4.void gc():
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。String

5.getProperty(String key):
该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示
public class SystemTest {
    public static void main(String[] args) {
        //java版本:1.8.0_292
        System.out.println(System.getProperty("java.version"));
        //jdk地址:C:\Users\monkey\.jdks\corretto-1.8.0_292\jre
        System.out.println(System.getProperty("java.home"));
        //电脑版本:10.0
        System.out.println(System.getProperty("os.version"));
        //电脑系统:Windows 10
        System.out.println(System.getProperty("os.name"));
        //电脑当前用户:monkey
        System.out.println(System.getProperty("user.name"));
        //用户路径:C:\Users\monkey
        System.out.println(System.getProperty("user.home"));
        //当前文件夹:C:\Users\monkey\Desktop\java\java\java
        System.out.println(System.getProperty("user.dir"));
    }
}
  • Math类
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.01.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度
  • BigInteger类
1.Integer类作为int的包装类,能存储的最大整型值为2^31 -1,Long类也是有限的,最大为2^63 -1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。
2.java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger提供所有Java 的基本整数操作符的对应物,并提供java.lang.Math 的所有相关方法。另外,BigInteger还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
3.构造器
	BigInteger(String val):根据字符串构建BigInteger对象
  • BigDecimal类
1.BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
2.构造器
	public BigDecimal(double val)
	public BigDecimal(String val)
3.常用方法
	public BigDecimal add(BigDecimal augend)
	public BigDecimal subtract(BigDecimal subtrahend)
	public BigDecimal multiply(BigDecimal multiplicand)
	public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
public class BigDecimalTest {
    public static void main(String[] args) {
        BigInteger bigInteger = new BigInteger("432847232424325");
        BigDecimal b1 = new BigDecimal("234.53");
        BigDecimal b2 = new BigDecimal("23");

        System.out.println(bigInteger); //432847232424325
//        System.out.println(b1.divide(b2)); //无线小数异常
        System.out.println(b1.divide(b2, BigDecimal.ROUND_HALF_DOWN)); //10.20
        System.out.println(b1.divide(b2, 20, BigDecimal.ROUND_HALF_DOWN));  //10.19695652173913043478
    }
}

3.枚举类&注解

1)枚举类

 * 一、枚举类的使用
 * 1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类。
 * 2.当需要定义一组常量时,强烈建议使用枚举类
 * 3.若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
 *
 * 二、如何定义枚举类
 *     方式一:JDK1.5之前需要自定义枚举类
 *     方式二:JDK 1.5 新增的enum 关键字用于定义枚举类
 * 三、Enum类的常用方法
 *      values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
 *      valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
 *      toString():返回当前枚举类对象常量的名称
 * 四、使用enum关键字定义的枚举类实现接口的情况
 *   情况一:实现接口,在enum类中实现抽象方法
 *   情况二:让枚举类的对象分别实现接口中的抽象方法
public enum EnumTest implements Test2{
    valid(1, "有效"){
        @Override
        public void show() {
            System.out.println("valid,show");
        }
    },
    invalid(2, "无效"){
        @Override
        public void show() {
            System.out.println("invalid,show");
        }
    },
    delete(3, "删除"){
        @Override
        public void show() {
            System.out.println("delete,show");
        }
    };
    private int key;
    private String value;
    EnumTest(int key, String value){
        this.key = key;
        this.value = value;
    }
    public int getKey() {
        return key;
    }
    public void setKey(int key) {
        this.key = key;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
//    @Override
//    public void show() {
//        System.out.println("show");
//    }
}
class Test01{
    public static void main(String[] args) {
        EnumTest[] values = EnumTest.values();
        for (EnumTest value : values) {
            System.out.println(value);
            value.show();
        }
        System.out.println("-------------------------");
        System.out.println(EnumTest.valueOf("valid"));
        System.out.println("--------------------------");
        System.out.println(EnumTest.valid.getValue());
        System.out.println(EnumTest.valid.getKey());
    }
}
interface Test2{
    void show();
}

2)注解

  • Annotation的使用示例
使用Annotation 时要在其前面增加@ 符号, 并把该Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
	示例一:生成文档相关的注解
		@author标明开发该类模块的作者,多个作者之间使用,分割
		@version标明该类模块的版本
		@see参考转向,也就是相关主题
        @since从哪个版本开始增加的
        @param对方法中某参数的说明,如果没有参数就不能写
        @return对方法返回值的说明,如果方法的返回值类型是void就不能写
        @exception对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写其中
        @param@return和@exception这三个标记都是只用于方法的。
        @param的格式要求:@param形参名形参类型形参说明
        @return的格式要求:@return返回值类型返回值说明
        @exception的格式要求:@exception异常类型异常说明
        @param和@exception可以并列多个
	示例二:在编译时进行格式检查(JDK内置的三个基本注解)
        @Override: 限定重写父类方法, 该注解只能用于方法
        @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
        @SuppressWarnings: 抑制编译器警告
	示例三:跟踪代码依赖性,实现替代配置文件功能
        Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署。
        spring框架中关于“事务”的管理
  • 自定义注解
注解的使用
 *
 *  3.如何自定义注解:参照@SuppressWarnings定义
 *      ① 注解声明为:@interface
 *      ② 内部定义成员,通常使用value表示
 *      ③ 可以指定成员的默认值,使用default定义
 *      ④ 如果自定义注解没有成员,表明是一个标识作用。
 *
 *      如果注解有成员,在使用注解时,需要指明成员的值。
 *      自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
 *      自定义注解通过都会指明两个元注解:Retention、Target
public @interface AnnotationTest {
   String value() default "helli" ;
   boolean isTrue();
}
@AnnotationTest(isTrue = true)
class A{
}
  • jdk中4个基本的元注解的使用
1.@Retention: 
	只能用于修饰一个Annotation定义, 用于指定该Annotation 的生命周期, @Rentention包含一个RetentionPolicy类型的成员变量, 使用@Rentention时必须为该value 成员变量指定值:
	1RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
	2RetentionPolicy.CLASS:class文件中有效(即class保留),当运行Java 程序时, JVM 不会保留注解。这是默认值
	3}RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。

2.@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
	用于修饰Annotation 定义, 用于指定被修饰的Annotation 能用于修饰哪些程序元素。@Target 也包含一个名为value 的成员变量。
        
3.@Documented: 
	用于指定被该元Annotation 修饰的Annotation 类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
	定义为Documented的注解必须设置Retention值为RUNTIME。
        
4.@Inherited: 
	被它修饰的Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的Annotation, 则其子类将自动具有该注解。
	比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解
实际应用中,使用较少
  • 可重复注解
1.可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.classMyAnnotationTargetRetention等元注解与MyAnnotations相同。
  • jdk8新特性:类型注解
JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USE。
在Java8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
	ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)。
	ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中

4.集合

1)概述

1.集合框架
 *      &---Collection接口:单列集合,用来存储一个一个的对象
 *          &---List接口:存储有序的、可重复的数据。  -->“动态”数组
 *              &---ArrayListLinkedListVector
 *          &---Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
 *              &---HashSetLinkedHashSetTreeSet
 *      &---Map接口:双列集合,用来存储一对(key - value)一对的数据   -->高中函数:y = f(x)
 *          &---HashMapLinkedHashMapTreeMapHashtableProperties
     
 2.Collection 接口
     是ListSetQueue 接口的父接口,该接口里定义的方法既可用于操作Set 集合,也可用于操作ListQueue 集合。
	JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:SetList)实现。
	在Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成Object 类型处理;从JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。

2)Collection类

1.添加
	add(Objec tobj)
	addAll(Collection coll)
2.获取有效元素的个数
	int size()
3.清空集合
	void clear()
4.是否是空集合
	boolean isEmpty()
5.是否包含某个元素
	boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
	boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6.删除
	boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
	boolean removeAll(Collection coll):取当前集合的差集
7.取两个集合的交集
	boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8.集合是否相等
	boolean equals(Object obj)
9.转成对象数组
	Object[] toArray()
10.获取集合对象的哈希值
	hashCode()
11.遍历
	iterator():返回迭代器对象,用于集合遍历
  • Iterator迭代器接口
1.Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。
2.GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
3.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
4.Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
public void test04(){
	Collection coll = new ArrayList();
	coll.add(123);
	coll.add(456);
	coll.add(new Person("Jerry",20));
	coll.add(new String("Tom"));
	coll.add(false);

	Iterator iterator = coll.iterator();
	while (iterator.hasNext()){
		System.out.println(iterator.next());
	}
}
  • 新特性foreach循环遍历集合或数组
//for(集合元素的类型 局部变量 : 集合对象),内部仍然调用了迭代器。
for(Object obj : coll){
	System.out.println(obj);
}

3)List

  • 比较
 *  List接口框架
 *
 *    |----Collection接口:单列集合,用来存储一个一个的对象
 *          |----List接口:存储有序的、可重复的数据。  -->“动态”数组,替换原有的数组
 *              |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
 *              |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
 *              |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
  • ArrayList源码分析
1.jdk7:
    ArrayList list = new ArrayList(); 	//创建对象时,底层创建了长度为10的Object[]数组,名为elementData
	list.add(123);	//elementData[0] = new Integer(123);
	...
    list.add(11);	//当添加第十一个元素时,导致elementData数组容量不足,需要扩容

	/*默认情况下,扩容为原来容量的1.5倍,同时将原数组的数据复制到新的数组中
	  结论:建议开发使用:ArrayList list = new ArrayList(int capacity)*/		
    
2.jdk8:
    ArrayList list = new ArrayList(); 	//创建对象时,底层创建Object[]数组为{},没有创建长度为10的数组
	list.add(123);	//第一次add()时,创建长度为10的数组,并将数据123添加到elementData[0]
	...
    list.add(11);	//当添加第十一个元素时,导致elementData数组容量不足,需要扩容

3.小结:
    1)jdk7中的ArrayList的对象的创建类似于单例的饿汉式,
    2)而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
  • LinkedList源码分析
jdk7之后,从双向循环链表改为双向链表:
  *       LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
  *       list.add(123);//将123封装到Node中,创建了Node对象。
  *
  *       其中,Node定义为:体现了LinkedList的双向链表的说法
  *       private static class Node<E> {
  *            E item;
  *            Node<E> next;
  *            Node<E> prev;
  *
  *            Node(Node<E> prev, E element, Node<E> next) {
  *            this.item = element;
  *            this.next = next;     //next变量记录下一个元素的位置
  *            this.prev = prev;     //prev变量记录前一个元素的位置
  *            }
  *        }
  • Vector源码分析
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
  *      在扩容方面,默认扩容为原来的数组长度的2倍。
//线程安全的
//很少使用,因为ArrayLst提供synchronized()方法
  • 常用方法
void add(intindex, Object ele):在index位置插入ele元素
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
Object get(int index):获取指定index位置的元素
int indexOf(Object obj):返回obj在集合中首次出现的位置
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
Object remove(int index):移除指定index位置的元素,并返回此元素
Object set(int index, Object ele):设置指定index位置的元素为ele
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
  • List遍历
//方式一:Iterator迭代器方式
Iterator iterator = list.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
//方式二:增强for循环
for(Object obj : list){
    System.out.println(obj);
}
//方式三:普通for循环
for(int i = 0;i < list.size();i++){
    System.out.println(list.get(i));
}

4)Set

  • 比较
 * 1.Set接口的框架:
 * |----Collection接口:单列集合,用来存储一个一个的对象
 *          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
 *             |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null*                 |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
 *                                    对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
 *             |----TreeSet:可以按照添加对象的指定属性,进行排序。
  • HashSet源码分析
1.Set:
    1)无序性:不等于随机性。底层数组不是按照数索引的顺序添加的,而是根据数据的哈希值决定的
    2)不可重复性:根据数据的hash值,以及equals()方法,去比较元素,相同的元素只能添加一次
2.添加元素的过程:
    1)首先,调用元素a的hashCode()方法,获取hash值此哈希值按照某种算法算出存放在底层数组中的存放位置,
    2)然后,去判断该位置上是否已经有元素
    		如果没有,则添加a成功		//情况1
    		如果有元素b(或者以链表形式存在),则比较a和b的hash值
    			如果值不相同,添加a成功	//情况2
    			如果值相同,则调用a所在类的equals()方法
    				equals()返回true,则a添加失败		//情况3
    				equals()返回flase,则元素添加成功	   //情况4	
          对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
          jdk 7 :元素a放到数组中,指向原来的元素。
          jdk 8 :原来的元素在数组中,指向元素a
                  总结:七上八下
            
          HashSet底层:数组+链表的结构。
3.hashCode()equals():
		要求:向Set(主要指:HashSetLinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()equals()
		要求:重写的hashCode()equals()尽可能保持一致性:相等的对象必须具有相等的散列码
        	重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
  • LinkedHashSet源码分析
1.LinkedHashSet是HashSet的子类
2.LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
3.LinkedHashSet插入性能略低于HashSet,但在迭代访问Set 里的全部元素时有很好的性能。
4.LinkedHashSet不允许集合元素重复。
  • TreeSet源码分析
 * 1.TreeSet中添加的数据,要求是相同类的对象。
 * 2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator* 3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
 * 4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

5)Map

  • 概述
 * 一,分类
 *  |----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
 *         |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
 *              |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
 *                      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
 *                      对于频繁的遍历操作,此类执行效率高于HashMap*         |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
 *                      底层使用红黑树
 *         |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
 *              |----Properties:常用来处理配置文件。key和value都是String类型
 *
 *
 *      HashMap的底层:数组+链表  (jdk7及之前)
 *                    数组+链表+红黑树 (jdk 8*  二、Map结构的理解:
 *    Map中的key:无序的、不可重复的,使用Set存储所有的key  ---> key所在的类要重写equals()hashCode() (以HashMap为例)
 *    Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
 *    一个键值对:key-value构成了一个Entry对象。
 *    Map中的entry:无序的、不可重复的,使用Set存储所有的entry
  • hashMap源码解析
一,HashMap的底层实现原理(jdk7)
    1.HashMap map = new HashMap();
		在实例化之后,创建了一个长度为16的一维数组Entry[] table。
    2.执行多次put方法
    3.map.put(key1, value1);
		1)首先,调用key1所在类的hashCode()方法获取其hash值,经过计算,得到存储在Entry数组中的位置
        2)如果,该位置的数据为空,则key1-value1添加成功		//情况1
        3)如果不为空(意味着该位置有一个或者多个数据(链表形式存在)),此时,需要去比较key1和该位置的数据的哈希值
            **如果key1哈希值与其他数据都不同,则key1-value1添加成功		//情况2
            **如果与其中一个key1-value1哈希值相同,继续比较。调用其所在类的equals()方法
        		**如果返回flase,则key1-value1添加成功		//情况3
				**如果返回true:使用value1替换value2。//特殊
		补充:在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:
                    扩容为原来容量的2倍,并将原有的数据复制过来。
                    
二,HashMap的底层实现原理(jdk8)
	1.HashMap map = new HashMap();
		底层没有创建一个长度为16的数组,而是Node[],不是Entry[]
    2.首次调用put()方法时,底层创建长度为16的数组
    3.jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
          1)形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
          2)当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,
              此时此索引位置上的所数据改为使用红黑树存储。   
            
三,HashMap的扩容
  *HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,
  *     因为数组的长度是固定的。所以为了提高查询的效率,
  *     就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,
  *     最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,
  *     并放进去,这就是resize。
    
四,那么HashMap什么时候进行扩容呢?
  *HashMap中的元素个数超过数组大小(数组总大小length,
  *      不是数组中个数size)*loadFactor时,就 会 进 行 数 组 扩 容,
  *      loadFactor的默认值(DEFAULT_LOAD_FACTOR)0.75,这是一个折中的取值。
  *      也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)16*      那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,
  *      也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,
  *      然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,
  *      所以如果我们已经预知HashMap中元素的个数,
  *      那么预设元素的个数能够有效的提高HashMap的性能
              
 *      DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
 *      DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
 *      threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
 *      TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
 *      MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
  • LinkedHashMap的底层实现原理(了解)
 *  四、LinkedHashMap的底层实现原理(了解)
 *      源码中:
 *      static class Entry<K,V> extends HashMap.Node<K,V> {
 *            Entry<K,V> before, after;//能够记录添加的元素的先后顺序
 *            Entry(int hash, K key, V value, Node<K,V> next) {
 *               super(hash, key, value, next);
 *            }
 *        }
  • 常见方法
1.添加、删除、修改操作:
 *      Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
 *      void putAll(Map m):将m中的所有key-value对存放到当前map中
 *      Object remove(Object key):移除指定key的key-value对,并返回value
 *      void clear():清空当前map中的所有数据
2.元素查询的操作:
 *      Object get(Object key):获取指定key对应的value
 *      boolean containsKey(Object key):是否包含指定的key
 *      boolean containsValue(Object value):是否包含指定的value
 *      int size():返回map中key-value对的个数
 *      boolean isEmpty():判断当前map是否为空
 *      boolean equals(Object obj):判断当前map和参数对象obj是否相等
3.元视图操作的方法:
 *      Set keySet():返回所有key构成的Set集合
 *      Collection values():返回所有value构成的Collection集合
 *      Set entrySet():返回所有key-value对构成的Set集合
//遍历
//方式一
Set set1 = map.entrySet();
Iterator iterator2 = set1.iterator();
while (iterator2.hasNext()){
    Map.Entry map2 = (Map.Entry) iterator2.next();
    System.out.println(map2.getKey()+"--->"+map2.getValue());
}
//方式二
Set set2 = map.keySet();
Iterator iterator3 = set2.iterator();
while (iterator3.hasNext()){
    Object key = iterator3.next();
    System.out.println(key+"===="+map.get(key));
}
  • TreeMap
TreeSet的自然排序
TreeSet的定制排序
  • Properties处理属性文件
public static void main(String[] args) throws IOException {
    FileInputStream file = null;
    try {
        Properties properties = new Properties();
        file = new FileInputStream("jdbc.properties");	//读取文件流
        properties.load(file);		//加载文件

        String name = properties.getProperty("name");
        String age = properties.getProperty("age");
        System.out.println(name + ":" + age);
    } catch (Exception e) {
        file.close();		//关闭文件流
    }
}

6)Collections工具类

1.排序操作:(均为static方法)
	reverse(List):反转List 中元素的顺序
	shuffle(List):对List集合元素进行随机排序
	sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
	sort(ListComparator):根据指定的Comparator 产生的顺序对List 集合元素进行排序
	swap(Listintint):将指定list 集合中的i处元素和j 处元素进行交换
2.    
     * Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
     * Object max(CollectionComparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
     * Object min(Collection)
     * Object min(CollectionComparator)
     * int frequency(CollectionObject):返回指定集合中指定元素的出现次数
     * void copy(List dest,List src):将src中的内容复制到dest中
     * boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
3.
     * Collections 类中提供了多个 synchronizedXxx() 方法,
     * 该方法可使将指定集合包装成线程同步的集合,从而可以解决
     * 多线程并发访问集合时的线程安全问题 
    

5.泛型

1)泛型使用

1.jdk5.0新增的特征
 *
2.在集合中使用泛型:
 *  总结:
 *  ①集合接口或集合类在jdk5.0时都修改为带泛型的结构。
 *  ②在实例化集合类时,可以指明具体的泛型类型
 *  ③指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
 *    比如:add(E e)  --->实例化以后:add(Integer e)
 *  ④注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
 *  ⑤如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
 *
3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 GenericTest1.java
  • 泛型类,接口
注意点:
    1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
    2.泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass(){}
    3.实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
    4.泛型不同的引用不能相互赋值。尽管在编译时ArrayListArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
    5.泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
    经验:泛型要使用一路都用。要不用,一路都不要用。
    6.如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
    7.jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
    8.泛型的指定中不能使用基本数据类型,可以使用包装类替换。
    9.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
    10.异常类不能是泛型的
  • 泛型方法
[访问权限] <泛型> 返回类型  方法名([泛型标识参数名称]) 抛出的异常
如:public static <E>  List<E> copyFromArrayToList(E[] arr) throws  Exception{  }

2)泛型在继承上的体现【通配符】

  • 使用
 1.泛型在继承方面的体现
     * 虽然类A是类B的父类,但是G<A>G<B>二者不具备子父类关系,二者是并列关系。
     * 补充:类A是类B的父类,A<G>B<G> 的父类
2.通配符的使用
	 * 通配符:?
     *A是类B的父类,G<A>G<B>是没有关系的,二者共同的父类是:G<?>
  • 读取和写入要求
1.使用类型
	通配符:?
	比如:List<?>Map<?,?>
	List<?>ListList等各种泛型List的父类。
2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。
	1)唯一的例外是null,它是所有类型的成员。
	2)将任意元素加入到其中不是类型安全的:
4.另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object
  • 注意事项
1.有限制条件的通配符的使用。
	1? extends A:
          G<? extends A> 可以作为G<A>G<B>的父类,其中BA的子类
    2? super A:
          G<? super A> 可以作为G<A>G<B>的父类,其中BA的父类

6.IO流

1) File类使用

  • File类的实例化
     * 1.如何创建file类的实例
     *      File(String filePath):以filePath为路径创建File对象,可以是绝对路径或者相对路径
     *      File(String parentPath,String childPath):以parentPath为父路径,childPath为子路径创建File对象。
     *      File(File parentFile,String childPath):根据一个父File对象和子文件路径创建File对象
     * 2.
     *   相对路径:相较于某个路径下,指明的路径。
     *   绝对路径:包含盘符在内的文件或文件目录的路径
     *
     * 3.路径分隔符
     *      windows:\\
     *      unix:/
     * 4.Java程序支持跨平台运行,因此路径分隔符要慎用。
     *
     * 5.为了解决这个隐患,File类提供了一个常量:
     *   public  static final String separator。
     *   根据操作系统,动态的提供分隔符。
 
 * 1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
 * 2. File类声明在java.io包下
 * 3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
 *    并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
 * 4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
  • 常用方法
     * public String getAbsolutePath():获取绝对路径
     * public String getPath() :获取路径
     * public String getName() :获取名称
     * public String getParent():获取上层文件目录路径。若无,返回null
     * public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
     * public long lastModified() :获取最后一次的修改时间,毫秒值
     *
     * 如下的两个方法适用于文件目录:
     * public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
     * public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
注意:
     *  public boolean renameTo(File dest):把文件重命名为指定的文件路径
     *    比如:file1.renameTo(file2)为例:
     *    要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在
     * public boolean isDirectory():判断是否是文件目录
     * public boolean isFile() :判断是否是文件
     * public boolean exists() :判断是否存在
     * public boolean canRead() :判断是否可读
     * public boolean canWrite() :判断是否可写
     * public boolean isHidden() :判断是否隐藏
         
     * 创建硬盘中对应的文件或文件目录
     * public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
     * public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
     * public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
     *
     *     删除磁盘中的文件或文件目录
     * public boolean delete():删除文件或者文件夹
     *     删除注意事项:Java中的删除不走回收站。

2)IO流

  • 流的分类
1.按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
2.按数据流的流向不同分为:输入流,输出流
3.按流的角色的不同分为:节点流,处理流
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer
  • IO 流体系
分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
节点流(或文件流) FileInputStream FileOutputStream FileReader FileWriter
缓冲流(处理流的一种) BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
  • FileReader读入数据的基本操作
     *     1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
     *     2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
     *     3. 读入的文件一定要存在,否则就会报FileNotFoundException
1.read():返回读入的一个字符。如果达到文件末尾,返回-1.
2.read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
  • FileWriter从内存中写出数据到硬盘的文件里。
 说明:
     * 1.输出操作,对应的File可以不存在的。并不会报异常
     * 2.
     *   File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
     *   File对应的硬盘中的文件如果存在:
     *       如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
     *       如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
  • FileInputStream&FileOutputStream字节流
 * 结论:
 *    1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
 *    2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
     
注意:
     1.用字节流处理带有中文字符(3byte)文本,可能会有乱码
     2.用字符流处理图片,视频等非文本文件,会导致文本无法打开
     3.字节流可以复制文本文件
  • 缓冲流
1.缓冲流:
 *  BufferedInputStream
 *  BufferedOutputStream
 *  BufferedReader
 *  BufferedWriter
2.为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
3.说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
  • 转换流
处理流之二:转换流的使用
 * 1.转换流:属于字符流
 *      InputStreamReader:将一个字节的输入流转换为字符的输入流
 *      OutputStreamWriter:将一个字符的输出流转换为字节的输出流
 *
 * 2.作用:提供字节流与字符流之间的转换
 *
 * 3.解码:字节、字节数组  --->字符数组、字符串
 *   编码:字符数组、字符串 ---> 字节、字节数组
 * 4.字符集
 *  ASCII:美国标准信息交换码。
 *     用一个字节的7位可以表示。
 *  ISO8859-1:拉丁码表。欧洲码表
 *     用一个字节的8位表示。
 *  GB2312:中国的中文编码表。最多两个字节编码所有字符
 *  GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
 *  Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
 *  UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
  • 标准输入,输出流&打印流&数据流(了解)
1.标准的输入、输出流
     *   1.1
     *     System.in:标准的输入流,默认从键盘输入
     *     System.out:标准的输出流,默认从控制台输出
     *   1.2
     *     System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
     *
     *   1.3练习:
     *     从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
     *     直至当输入“e”或者“exit”时,退出程序。
     *
     *   方法一:使用Scanner实现,调用next()返回一个字符串
     *   方法二:使用System.in实现。System.in  --->  转换流 ---> BufferedReaderreadLine()

2.打印流:PrintStreamPrintWriter
     *  2.1 提供了一系列重载的print()println()

3.数据流
     *   3.1 DataInputStreamDataOutputStream
     *   3.2 作用:用于读取或写出基本数据类型的变量或字符串
     *
     *   练习:将内存中的字符串、基本数据类型的变量写出到文件中。
  • 对象流
1.对象流的使用
 * 1.ObjectInputStream 和 ObjectOutputStream
 * 2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
 * 3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
 *
 * 4.序列化机制:
 * 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种
 * 二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
 * 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
2.Person需要满足如下的要求,方可序列化
 * 1.需要实现接口:Serializable
 * 2.当前类提供一个全局常量:serialVersionUID
 * 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性
 *   也必须是可序列化的。(默认情况下,基本数据类型可序列化)
 *
 * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
  • 随机存取文件流
 * RandomAccessFile的使用
 * 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
 * 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
 * 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
 *   如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
 * 4.可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
  • NIO.2中Path、Paths、Files类的使用

7.网络编程

1)概述

一、网络编程中有两个主要的问题:
 * 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
 * 2.找到主机后如何可靠高效地进行数据传输
 *
二、网络编程中的两个要素:
 * 1.对应问题一:IP和端口号
 * 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)

2)通信要素1:IP和端口号

1.通信要素一:IP和端口号
 *
 * 1. IP:唯一的标识 Internet 上的计算机(通信实体)
 * 2.Java中使用InetAddress类代表IP
 * 3. IP分类:IPv4IPv6 ; 万维网 和 局域网
 * 4. 域名:   www.baidu.com   www.mi.com  www.sina.com  www.jd.com
 *            www.vip.com
 * 5. 本地回路地址:127.0.0.1 对应着:localhost
 *
 * 6. 如何实例化InetAddress:两个方法:getByName(String host)getLocalHost()
 *        两个常用方法:getHostName() / getHostAddress()
 *
  * 7. 端口号:正在计算机上运行的进程。
 * 要求:不同的进程有不同的端口号
 * 范围:被规定为一个 16 位的整数 0~65535*
 * 8. 端口号与IP地址的组合得出一个网络套接字:Socket

3)通信要素2:网络协议

1.网络通信协议
	计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
	问题:网络协议太复杂
	计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?

2.通信协议分层的思想
	在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

3、TCP和UDP网络通信协议的对比
	传输层协议中有两个非常重要的协议:
		传输控制协议TCP(Transmission Control Protocol)
		用户数据报协议UDP(User Datagram Protocol)。
	TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
	IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
	TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
	1)TCP协议:
		使用TCP协议前,须先建立TCP连接,形成传输数据通道
		传输前,采用“三次握手”方式,点对点通信,是可靠的
		TCP协议进行通信的两个应用进程:客户端、服务端。
		在连接中可进行大数据量的传输传输完毕,需释放已建立的连接,效率低
	2)UDP协议:
        将数据、源、目的封装成数据包,不需要建立连接
        每个数据报的大小限制在64K内
        发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
        可以广播发送
        发送数据结束时无需释放资源,开销小,速度快

4)URL编程

 * URL网络编程
 * 1.URL:统一资源定位符,对应着互联网的某一资源地址
 * 2.格式:
 *  http://127.0.0.1:8080/work/164.jpg?username=subei
 *  协议   主机名    端口号  资源地址           参数列表

8.反射&动态代理

1)概述

	概述:	Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
    
1.Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
2.Java反射机制提供的功能
	在运行时判断任意一个对象所属的类
	在运行时构造任意一个类的对象
	在运行时判断任意一个类所具有的成员变量和方法
	在运行时获取泛型信息
	在运行时调用任意一个对象的成员变量和方法
	在运行时处理注解
	生成动态代理
3.反射相关的主要API
	java.lang.Class:代表一个类
	java.lang.reflect.Method:代表类的方法
	java.lang.reflect.Field:代表类的成员变量
	java.lang.reflect.Constructor:代表类的构造器
public class ClassTest1 {
    //共有调用
    @Test
    public void test1() throws Exception {
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        Person person1 = constructor.newInstance("小张", 18);
        System.out.println(person1);

        Field name = personClass.getDeclaredField("name");
        name.set(person1, "笑话");
        System.out.println(person1);

        Method getName = personClass.getDeclaredMethod("getName");
        Object invoke = getName.invoke(person1);
        System.out.println(invoke);
    }
    //私有访问
    @Test
    public void test2() throws Exception{
        Class<Person> personClass = Person.class;
        Constructor<Person> con = personClass.getDeclaredConstructor(String.class);
        con.setAccessible(true);

        Person person = con.newInstance("李云龙");
        System.out.println(person);

        Field name = personClass.getDeclaredField("age");
        name.setAccessible(true);
        name.set(person, 12);
        System.out.println(person);

        Method show = personClass.getDeclaredMethod("show");
        show.setAccessible(true);
        show.invoke(person);
    }
}
class Person{
    public String name;
    private int age;
    private Person(String name) {
        this.name = name;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    private void show(){
        System.out.println("私有show方法");
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2)理解Class类并获取Class实例

  • Class类的理解
* 关于java.lang.Class类的理解
    * 1.类的加载过程:
    * 程序经过Javac.exe命令后,会生成一个或多个字节码文件(.class结尾)* 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
    * 加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
    * 运行时类,就作为Class的一个实例。
    *
    * 2.换句话说,Class的实例就对应着一个运行时类。
    * 3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
  • 获取Class实例的4种方式
public class ClassTest2 {
    @Test
    public void test1() throws ClassNotFoundException {
        //方式1:直接获取
        Class<Person> class1 = Person.class;
        //方式2:通过运行时类,调用getClass()
        Person person = new Person();
        Class<? extends Person> class2 = person.getClass();
        //方式3:调用Class的静态方法(常用)
        Class<?> class3 = Class.forName("day16_class.Person");
        //方式4:使用类的加载器 ClassLoader(了解)
        ClassLoader classLoader = ClassTest2.class.getClassLoader();
        Class<?> class4 = classLoader.loadClass("day16_class.Person");
    }
}
  • Class实例对应的结构的说明
1、哪些类型可以有Class对象?
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitivetype:基本数据类型
(7)void
  • 读取properties配置文件
public class ReadProperties {
    @Test
    public void test() throws Exception {
        Properties properties = new Properties();
        //方式一:时的文件默认在当前的module下
        FileInputStream fileInputStream = new FileInputStream("jdbc.properties");
        properties.load(fileInputStream);
        //方式二:配置文件默认识别为:当前module的src下
//        ClassLoader classLoader = ReadProperties.class.getClassLoader();
//        InputStream resourceAsStream = classLoader.getResourceAsStream("jdbc.properties");
//        properties.load(resourceAsStream);
        String name = properties.getProperty("name");
        String age = properties.getProperty("age");
        System.out.println(name+"-->"+age);
    }
}

3)通过反射,创建运行时类的对象

* newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
    *
    * 要想此方法正常的创建运行时类的对象,要求:
    * 1.运行时类必须提供空参的构造器
    * 2.空参的构造器的访问权限得够。通常,设置为public*
    * 在javabean中要求提供一个public的空参构造器。原因:
    * 1.便于通过反射,创建运行时类的对象
    * 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
@Test
public void test2() throws Exception {
    Class<?> class1 = Class.forName("day16_class.Person");
    Person person = (Person)class1.newInstance();

    System.out.println(person);     //Person{name='null', age=0}
}

4)获取运行时类的完整结构(了解)

  • 获取运行时类的方法结构
@Test
public void test(){
    Class clazz = Person.class;
    //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
    for(Method m : methods){
        System.out.println(m + "****");
    }
    System.out.println("++++++++++++++++++++++++++++");
    //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for(Method m : declaredMethods){
        System.out.println(m);
    }
}
  • 获取运行时类的方法结构
    /**
     * @Xxxx
     * 权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
     */
    @Test
    public void test2() {
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            //1.获取方法声明的注解
            Annotation[] annos = m.getAnnotations();
            for (Annotation a : annos) {
                System.out.println(a + "KKKK");
            }

            //2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");

            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");

            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形参列表
            Class[] pTs = m.getParameterTypes();
            if(!(pTs == null && pTs.length == 0)){
                for(int i = 0;i < pTs.length;i++){
                    if(i == pTs.length - 1){
                        System.out.print(pTs[i].getName() + " args_" + i);
                        break;
                    }
                    System.out.print(pTs[i].getName() + " args_" + i + ",");
                }
            }
            System.out.print(")");

            //6.抛出的异常
            Class[] eTs = m.getExceptionTypes();
            if(eTs.length > 0){
                System.out.print("throws ");
                for(int i = 0;i < eTs.length;i++){
                    if(i == eTs.length - 1){
                        System.out.print(eTs[i].getName());
                        break;
                    }
                    System.out.print(eTs[i].getName() + ",");
                }
            }
            System.out.println("TQA");
        }
    }
  • 获取运行时类的构造器结构
    @Test
    public void test(){
        Class clazz = Person.class;
        //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }
        System.out.println("************************");
        //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
    }
  • 获取运行时类的父类及父类的泛型
    @Test
    public void test2(){
        Class clazz = Person.class;
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

    /**
     * 获取运行时类的带泛型的父类
     */
    @Test
    public void test3(){
        Class clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    /**
     * 获取运行时类的带泛型的父类的泛型
     */
    @Test
    public void test4(){
        Class clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        //获取泛型类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
        System.out.println(((Class)actualTypeArguments[0]).getName());
    }
  • 获取运行时类的接口、所在包、注解等
  @Test
    public void test5(){
        Class clazz = Person.class;

        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }
        System.out.println("++++++++++++++++++++++");
       
        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }
    }

    /**
     * 获取运行时类所在的包
     */
    @Test
    public void test6(){
        Class clazz = Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    /**
     * 获取运行时类声明的注解
     */
    @Test
    public void test7(){
        Class clazz = Person.class;
        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

5)调用运行时类的指定结构

  • 调用运行时类中的指定属性
class Student {

    private String name;
    int age;
    public int id;

    public Student() {
    }

    private Student(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

     Student(String name) {
        this.name = name;
    }

    private String show(String nation){
        System.out.println("我来自" + nation + "星系");
        return nation;
    }


    public static String display(String interests,int age){
        return interests + age;
    }


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}
public void test1() throws Exception{
    //获取对象实例
    Class<Student> class1 = Student.class;
    //创建运行时类的对象
    Student student = class1.newInstance();
    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = class1.getDeclaredField("name");
    //2.保证当前属性是可访问的
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(student, "小张");
    System.out.println(name.get(student));
}
  • 调用运行时类中的指定方法
public void test2() throws Exception {
    //获取对象实例
    Class<Student> class1 = Student.class;
    //创建运行时类的对象
    Student student = class1.newInstance();
    //1.获取指定的某个方法
    //getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
    Method show = class1.getDeclaredMethod("show", String.class);
    //2.保证当前方法是可访问的
    show.setAccessible(true);
    //3.调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
    //invoke()的返回值即为对应类中调用的方法的返回值。
    Object o = show.invoke(student, "你好");
    System.out.println(o);
    //如何调用静态方法
    Method display = class1.getDeclaredMethod("display", String.class, int.class);
    //        Object staticInfo = display.invoke(null, "xiao", 33);
    Object staticInfo = display.invoke(Student.class, "xiao", 33);
    System.out.println(staticInfo);
}
  • 调用运行时类中的指定构造器
public void test3() throws Exception{
    //获取对象实例
    Class<Student> class1 = Student.class;
    //private Person(String name)
    //1.获取指定的构造器
    //getDeclaredConstructor():参数:指明构造器的参数列表
    Constructor<Student> constructor = class1.getDeclaredConstructor(String.class);
    //2.保证此构造器是可访问的
    constructor.setAccessible(true);
    //3.调用此构造器创建运行时类的对象
    Student name = constructor.newInstance("小张");
    System.out.println(name);
}

6)反射应用:动态代理

  • 原理
1.代理设计模式的原理:
	使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

2.之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。

3.动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

4.动态代理使用场合:

	调试
	远程方法调用
5.动态代理相比于静态代理的优点:
	抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
  • 静态代理
见面向对象5-9
  • 动态代理
* 要想实现动态代理,需要解决的问题?
 * 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
 * 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
public class DynamicProxy {
    public static void main(String[] args) {
        Object o = ProxyClass.proxyClass(new SuperMan());
        Man man = (Man) o;
        man.belief();
        System.out.println(man.eat("西瓜"));
    }
}
interface Man{
    void belief();
    String eat(String food);
}
//被代理类
class SuperMan implements Man{
    @Override
    public void belief() {
        System.out.println("我会飞!!!");
    }
    @Override
    public String eat(String food) {
        return "我喜欢的食物:" + food;
    }
}
/**
 * 要想实现动态代理,需要解决的问题?
 * 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
 * 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */
class ProxyClass {
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object proxyClass(Object obj){    //obj:被代理类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),handler);

    }
}
class MyInvocationHandler implements InvocationHandler {
    private Object obj; //需要使用被代理类的对象进行赋值
    public void bind(Object obj){
        this.obj = obj;
    }
    //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object invoke = method.invoke(obj, args);
        return invoke;
    }
}

9.Java 8新特性

1)简介

1.Java8新特性的好处
	速度更快
	代码更少(增加了新的语法:Lambda 表达式)
	强大的Stream API
	便于并行
	最大化减少空指针异常:Optional
	Nashorn引擎,允许在JVM上运行JS应用
2.并行流与串行流
	并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。

	Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel()sequential() 在并行流与顺序流之间进行切换。

2)Lambda表达式

  • 使用
* Lambda表达式的使用
 *
 * 1.举例: (o1,o2) -> Integer.compare(o1,o2);
 * 2.格式:
 *      -> :lambda操作符 或 箭头操作符
 *      ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
 *      ->右边:lambda体 (其实就是重写的抽象方法的方法体)
 *
 * 3.Lambda表达式的使用:(分为6种情况介绍)
 *
 *    总结:
 *    ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
 *    ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}return关键字
  • 例一:Runable
public void test1() {
    //1.原来
    Runnable runnable = new Runnable(){
        @Override
        public void run() {
            System.out.println("我爱天安门!");
        }
    };
    runnable.run();
    //2.lambda
    Runnable runnable1 = () -> System.out.println("我爱中国!");
    runnable1.run();
}
  • 例二:Compartor
public void test2(){
    //1.原来
    Comparator<Integer> comparator = new Comparator<Integer>(){
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    System.out.println(comparator.compare(12, 13));
    //2.lambda
    Comparator<Integer> comparator1 = (o1, o2) -> Integer.compare(o1, o2);
    System.out.println(comparator1.compare(13, 12));
    //3.方法引用
    Comparator<Integer> comparator2 = Integer::compare;
    System.out.println(comparator2.compare(13, 12));
}

3)函数式(Functional)接口

  • 介绍
1.Lambda表达式的本质:作为函数式接口的实例
2. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
这样做可以检查它是否是一个函数式接口。
函数式接口 参数类型 返回类型 用途
Consumer 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier 供给型接口 T 返回类型为T的对象,包含方法:T get()
Function函数型接口 T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法:boolean test(T t)
  • 例子
public class FuncitionInterface {
    @Test
    public void test1(){
        //价格:100.0
        consumerTest(100.0, monkey -> System.out.println("价格:" + monkey));
        //[北京, 东京]
        List<String> strings = Arrays.asList("北京", "天津", "东京", "xijing");
        List<String> list = filterStr(strings, s -> s.contains("京"));
        System.out.println(list);
    }
    public void consumerTest(Double monkey, Consumer<Double> con){
        con.accept(monkey);
    }
    public List<String> filterStr(List<String> strList, Predicate<String> pre){
        List<String> list = new ArrayList<>();
        strList.forEach(s -> {
            if (pre.test(s)){
                list.add(s);
            }
        });
        return list;
    }
}

4)方法引用&构造器引用

  • 方法引用
 * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
 *
 * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
 *   方法引用,也是函数式接口的实例。
 *
 * 3. 使用格式:  类(或对象) :: 方法名
 *
 * 4. 具体分为如下的三种情况:
 *    情况1     对象 :: 非静态方法
 *    情况2:: 静态方法
 *
 *    情况3:: 非静态方法
 *
 * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
 *    形参列表和返回值类型相同!(针对于情况1和情况2
//实体类Employee
class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    public Employee() {
        System.out.println("Employee().....");
    }
    public Employee(int id) {
        this.id = id;
        System.out.println("Employee(int id).....");
    }
    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Employee employee = (Employee) o;
        if (id != employee.id)
            return false;
        if (age != employee.age)
            return false;
        if (Double.compare(employee.salary, salary) != 0)
            return false;
        return name != null ? name.equals(employee.name) : employee.name == null;
    }
    @Override
    public int hashCode() {
        int result;
        long temp;
        result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + age;
        temp = Double.doubleToLongBits(salary);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}
//情况1--->对象 :: 非静态方法
public void test1(){
    //Consumer中的void accept(T t)
    //PrintStream中的void println(T t)
    Consumer<String> con = System.out::println;
    con.accept("小明");

    //Supplier中的T get()
    //Employee中的String getName()
    Employee info = new Employee(1, "小张");
    Supplier<String> sup = info::getName;
    System.out.println(sup.get());
}
//情况2--->类 :: 静态方法
public void test2(){
    //Comparator中的int compare(T t1,T t2)
    //Integer中的int compare(T t1,T t2)
    Comparator<Integer> com = Integer::compare;
    System.out.println(com.compare(1, 2));

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    Function<Double, Long> fun = Math::round;
    System.out.println(fun.apply(13.5));
}
//情况3--->类 :: 非静态方法
public void test3(){
    // Comparator中的int comapre(T t1,T t2)
    // String中的int t1.compareTo(t2)
    Comparator<String> com = String::compareTo;
    System.out.println(com.compare("abc", "acd"));

    //BiPredicate中的boolean test(T t1, T t2);
    //String中的boolean t1.equals(t2)
    BiPredicate<String, String> bi = String::equals;
    System.out.println(bi.test("aaa", "abb"));

    // Function中的R apply(T t)
    // Employee中的String getName();
    Function<Employee, String> fun = Employee::getName;
    System.out.println(fun.apply(new Employee(2, "小花")));
}
  • 构造器引用
一、构造器引用
 *      和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
 *      抽象方法的返回值类型即为构造器所属的类的类型
 *
二、数组引用
 *     可以把数组看做是一个特殊的类,则写法与构造器引用一致。 
//一、构造器引用
public void test5(){
    //Supplier中的T get()
    //Employee的空参构造器:Employee()
    Supplier<Employee> sup = Employee::new;
    System.out.println(sup.get());

    //Function中的R apply(T t)
    Function<Integer, Employee> fun = Employee::new;
    System.out.println(fun.apply(1));

    //BiFunction中的R apply(T t,U u)
    BiFunction<Integer, String, Employee> f2 = Employee::new;
    System.out.println(f2.apply(3, "xiao"));
}
//二、数组引用
public void test6(){
    //Function中的R apply(T t)
    Function<Integer, String[]> fun = String[]::new;
    System.out.println(Arrays.toString(fun.apply(5)));
}

5)Stream API

  • 说明
* 1.Stream关注的是对数据的运算,与CPU打交道
 *   集合关注的是数据的存储,与内存打交道
 *
* 2.
 *Stream 自己不会存储元素。
 *Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream*Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
 *
* 3.Stream 执行流程
 *Stream的实例化
 * ② 一系列的中间操作(过滤、映射、...)
 * ③ 终止操作
 *
* 4.说明:
 * 4.1 一个中间操作链,对数据源的数据进行处理
 * 4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
  • 测试数据
class EmployeeData {
    public static List<Employee> getEmployees(){
        List<Employee> list = new ArrayList<>();
        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
        return list;
    }
}

①实例化

  • Stream方式一:通过集合
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Employee> stream = employees.stream();   //返回顺序流
    Stream<Employee> employeeStream = employees.parallelStream();   //返回并行流
}
  • Stream方式二:通过数组
public void test2(){
    Employee wang1 = new Employee(1, "wang");
    Employee wang2 = new Employee(1, "li");
    Employee[] employees = {wang1, wang2};

    Stream<Employee> stream = Arrays.stream(employees);
}
  • Stream方式三:通过Stream的of()
public void test3(){
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
}
  • Stream方式四:创建无限流
public void test4(){
    //遍历前10个偶数
    Stream.iterate(10, t -> t * 2).limit(10).forEach(System.out::println);
    //生成随机数
    Stream.generate(Math::random).limit(5).forEach(System.out::println);
}

②Stream的中间操作

  • 筛选与切片
方法 描述
filter(Predicate p) 接收Lambda ,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n)互补
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    //过滤工资大于7000
    employees.stream().filter(t -> t.getSalary() > 7000).forEach(System.out::println);
    System.out.println();
    //取前三个数据
    employees.stream().limit(3).forEach(System.out::println);
    System.out.println();
    //跳过前n个元素,若元素不足返回空
    employees.stream().skip(4).forEach(System.out::println);
    System.out.println();
    //去重,通过流生成的元素的hashCode()&eqalus()去重
    employees.add(new Employee(1013,"李飞",42,8500));
    employees.add(new Employee(1013,"李飞",42,8500));
    employees.stream().distinct().forEach(System.out::println);
    System.out.println();
}
  • 映射
方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
public void test2(){
    //map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    list.stream().map(String::toUpperCase).forEach(System.out::println);
    System.out.println();
    //练习1:获取员工姓名长度大于3的员工的姓名。
    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
    System.out.println();
    //练习2:flatMap()
    list.stream().flatMap(StreamTest2::toStream).forEach(System.out::println);

}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例
public static Stream<Character> toStream(String s){
    List<Character> list = new ArrayList<>();
    for (char c : s.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}
  • 排序
方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
public void test3(){
    //sorted()——自然排序
    List<Integer> integers = Arrays.asList(25, 45, 36, 12, 85, 64, 72, -95, 4);
    integers.stream().sorted().forEach(System.out::println);
    System.out.println();
    //sorted(Comparator com)——定制排序
    List<Employee> employees = EmployeeData.getEmployees();
    employees.stream().sorted(Comparator.comparing(Employee::getAge)).forEach(System.out::println);
    System.out.println();
    //排序
    employees.sort(Comparator.comparing(Employee::getSalary).reversed());
    System.out.println(employees);
}

③终止操作

  • 查找与匹配
方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    //1.练习:是否所有的员工的年龄都大于18
    boolean a = employees.stream().allMatch(e -> e.getAge() > 3);
    System.out.println(a);
    //2.练习:是否存在员工的工资大于 10000
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 9000);
    System.out.println(anyMatch);
    //3.练习:是否存在员工姓“马”
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println(noneMatch);
    //4.findFirst——返回第一个元素
    Optional<Employee> employee = employees.stream().findFirst();
    System.out.println(employee);
    //5.findAny——返回当前流中的任意元素
    Optional<Employee> employee1 = employees.parallelStream().findAny();
    System.out.println(employee1);
    //6. count——返回流中元素的总个数
    long count = employees.stream().count();
    System.out.println(count);
    //7.练习:返回最高的工资:
    Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
    Optional<Double> maxSalary = salaryStream.max(Double::compare);
    System.out.println(maxSalary);
    //8.练习:返回最低工资的员工
    Optional<Employee> min = employees.stream().min(Comparator.comparing(Employee::getSalary));
    System.out.println(min);
    System.out.println();
    //9.forEach(Consumer c)——内部迭代
    employees.stream().forEach(System.out::println);
    System.out.println();
    //10.使用集合的遍历操作
    employees.forEach(System.out::println);
}
  • 归约
方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional
public void test2(){
    //练习1:计算1-10的自然数的和
    List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    System.out.println(integers.stream().reduce(100, Integer::sum));
    //练习2:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum));
}
  • 收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
public void test4(){
    //练习1:查找工资大于6000的员工,结果返回为一个List或Set
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> collect = employees.stream().filter(s -> s.getSalary() > 6000).collect(Collectors.toList());
    System.out.println(collect);
    System.out.println();

    Set<Employee> collect1 = employees.stream().filter(s -> s.getSalary() > 6000).collect(Collectors.toSet());
    System.out.println(collect1);
}

6)optional操作

  • 方法
1.创建Optional类对象的方法:
	Optional.of(T t): 创建一个Optional 实例,t必须非空;
	Optional.empty() : 创建一个空的Optional 实例
	Optional.ofNullable(T t):t可以为null
2.判断Optional容器中是否包含对象:
	boolean isPresent() : 判断是否包含对象
	void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
3.获取Optional容器的对象:
	T get(): 如果调用对象包含值,返回该值,否则抛异常
	T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
	T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
	T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
  • 测试实体类
class Girl {
    private String name;
    public Girl() {
    }
    public Girl(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Boy {
    private Girl girl;
    public Boy() {
    }
    public Boy(Girl girl) {
        this.girl = girl;
    }
    public Girl getGirl() {
        return girl;
    }
    public void setGirl(Girl girl) {
        this.girl = girl;
    }
    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
}

  • 例子
public void test1(){
    Boy boy = new Boy();
    //1.空指针异常
    //Optional girl = Optional.of(boy.getGirl());
    //2.可以为空
    Girl girl = Optional.ofNullable(boy.getGirl()).orElse(new Girl("xiao"));
    System.out.println(girl);
    //3.是否包含对象
    System.out.println(Optional.of(girl).isPresent());
    System.out.println(Optional.of(boy).isPresent());
    //4.调用对象包含值
    System.out.println(Optional.of(girl).get());
}

补充:

I 使用内部迭代——它帮你把迭代做了) |

public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    //1.练习:是否所有的员工的年龄都大于18
    boolean a = employees.stream().allMatch(e -> e.getAge() > 3);
    System.out.println(a);
    //2.练习:是否存在员工的工资大于 10000
    boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 9000);
    System.out.println(anyMatch);
    //3.练习:是否存在员工姓“马”
    boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
    System.out.println(noneMatch);
    //4.findFirst——返回第一个元素
    Optional<Employee> employee = employees.stream().findFirst();
    System.out.println(employee);
    //5.findAny——返回当前流中的任意元素
    Optional<Employee> employee1 = employees.parallelStream().findAny();
    System.out.println(employee1);
    //6. count——返回流中元素的总个数
    long count = employees.stream().count();
    System.out.println(count);
    //7.练习:返回最高的工资:
    Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
    Optional<Double> maxSalary = salaryStream.max(Double::compare);
    System.out.println(maxSalary);
    //8.练习:返回最低工资的员工
    Optional<Employee> min = employees.stream().min(Comparator.comparing(Employee::getSalary));
    System.out.println(min);
    System.out.println();
    //9.forEach(Consumer c)——内部迭代
    employees.stream().forEach(System.out::println);
    System.out.println();
    //10.使用集合的遍历操作
    employees.forEach(System.out::println);
}
  • 归约
方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回Optional
public void test2(){
    //练习1:计算1-10的自然数的和
    List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    System.out.println(integers.stream().reduce(100, Integer::sum));
    //练习2:计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    System.out.println(employees.stream().map(Employee::getSalary).reduce(Double::sum));
}
  • 收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
public void test4(){
    //练习1:查找工资大于6000的员工,结果返回为一个List或Set
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> collect = employees.stream().filter(s -> s.getSalary() > 6000).collect(Collectors.toList());
    System.out.println(collect);
    System.out.println();

    Set<Employee> collect1 = employees.stream().filter(s -> s.getSalary() > 6000).collect(Collectors.toSet());
    System.out.println(collect1);
}

6)optional操作

  • 方法
1.创建Optional类对象的方法:
	Optional.of(T t): 创建一个Optional 实例,t必须非空;
	Optional.empty() : 创建一个空的Optional 实例
	Optional.ofNullable(T t):t可以为null
2.判断Optional容器中是否包含对象:
	boolean isPresent() : 判断是否包含对象
	void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
3.获取Optional容器的对象:
	T get(): 如果调用对象包含值,返回该值,否则抛异常
	T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
	T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
	T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
  • 测试实体类
class Girl {
    private String name;
    public Girl() {
    }
    public Girl(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
}
class Boy {
    private Girl girl;
    public Boy() {
    }
    public Boy(Girl girl) {
        this.girl = girl;
    }
    public Girl getGirl() {
        return girl;
    }
    public void setGirl(Girl girl) {
        this.girl = girl;
    }
    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
}

  • 例子
public void test1(){
    Boy boy = new Boy();
    //1.空指针异常
    //Optional girl = Optional.of(boy.getGirl());
    //2.可以为空
    Girl girl = Optional.ofNullable(boy.getGirl()).orElse(new Girl("xiao"));
    System.out.println(girl);
    //3.是否包含对象
    System.out.println(Optional.of(girl).isPresent());
    System.out.println(Optional.of(boy).isPresent());
    //4.调用对象包含值
    System.out.println(Optional.of(girl).get());
}

补充:

你可能感兴趣的:(Java笔记,java,开发语言,后端)