Java初级知识汇总

环境的准备:
第一步:下载并安装JDK8
第二步:配置环境变量:配置哪个JDK,哪个就生效
1)JAVA_HOME 配置的是JDK的根目录C:\Program Files\Java\jdk1.8.0_241
2)Path: 不需要新建,配置的是JDK的bin目录
3)CLASSPATH ,需要新建,配置的是JDK的lib目录
验证:只要win+R输入命令:java -version 出现版本信息就说明配置正确
1 Java的标识符
由字母 数字 下划线 美元符号 组成
不能以数字开头
严格区分大小写
见名知意,并且不要使用拼音或者中英文夹杂的方式
不能使用关键字
2 关键字
被Java提前指定好的全小写单词,一共有50个
2个保留字:const 与 goto ,目前还没有明确意义的关键字
还有3个不可以用作标识符的单词:true false null
3 注释
用来解释代码,方便看看代码的人去理解代码的话

注释并不会影响程序的执行,会被编译器忽略掉
单行注释: // 可以注释一行的内容
多行注释:/ * * / 可以注释多行的内容
文档注释:/** */ 也可以注释多行内容,还可以添加一些额外的信息,比如:作者/时间/版本…
4 数据类型
4.1 基本类型

八大基本类型
byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。

double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

4.2 引用类型
我们学习的是String字符串类型,这个类型的数据需要使用双引号包裹

5 案例中总结的经验:
静态数据字符串想要与动态的变量名进行拼接,需要使用+号
char字符型既可以保存单个字符,需要用‘ ’包裹,也可以保存数字,只不过数字会去查ASCII码表
从控制台接受用户输入的数据:int a = new Scanner(System.in).nextInt();
变量进行值交换,首先需要一个第三方变量协助交换,代码的格式:斜相对,首尾相连
6 变量定义
定义的时候并且赋值:变量的类型 变量名 = 变量值; 比如:int a = 19;
先定义,后面再赋值:int a; a=99;
注意:=是赋值符号,等号右边的值交给等号左边的变量来保存
7 类型转换
boolean类型不参与类型转换
小转大,直接转 – 隐式转换
大转小,强制转 – 显式转换,格式:byte b = (byte) a;
浮变整,小数没 – 浮点型转整形是直接舍弃所有的小数部分的,不会四舍五入
类型能否转换,取决于类型的取值范围,而不是字节数,字节数只能做大概的参考
8 5条字面值规则
整数默认为int类型
小数默认为double类型
byte short char 三种比int小的类型,可以使用范围内的值直接赋值
字面值后缀:L F D
字面值前缀:0b-2 0-8 0x-16
9 5条运算规则
运算结果的数据类型与最大类型保持一致
3种比int小的类型,运算时会自动提升成int再运算
整数运算溢出的问题,一旦溢出,数据就错误了
浮点数运算不精确
浮点数的特殊值 Infinity NaN

1 运算符
普通的四则运算 + - * / 是不会改变变量本身的值的
如果想要改变变量本身的值,需要把表达式的结果重新赋值给变量
取余%(求模 mod) : 取余数,如果整除,余数为0
自增自减运算符
自增++ :表示变量本身的值+1
自减- - : 表示变量本身的值-1
前缀式:符号在前,先改变变量本身的值,再使用,比如打印,计算…
后缀式:符号在后,先使用,再改变变量本身的值
比较运算符
!= == > < >= <= 比较的结果都是布尔类型的
逻辑运算符
&&:双与/短路与:逻辑与单与相同,全真才真,只不过增加了短路的效果
||:双或/短路或:逻辑与单或相同,全假才假,只不过增加了短路的效果
三目/三元运算符
格式:1 ?2 :3;1是表达式,若1真,取2,若1假取3
赋值运算符
= :是普通的赋值运算符,等号右边的值交给等号左边的变量来保存
复合赋值运算符:+= -= *= /=,主要是可以简写&类型的自动转换
sum += 30; 等效于 sum = sum+30;
2 流程控制
2.1 顺序结构
顺序结构从头到尾所有代码依次都会执行到
可以解决输入 输出 计算等问题,但是不可以先做判断,再选择性的执行代码

2.2 分支结构
1 单分支结构
适合只有一个判断条件时使用,符合条件,执行大括号里的代码,不符合条件,大括号里的代码跳过

2 多分支结构
适合有两种情况时使用,符合条件,执行代码1,其他情况执行代码2

3 嵌套分支结构
适合有多个条件时使用,else-if的个数没有限制,else可加可不加

if(判断条件1) {
    如果符合判断条件1,执行此处代码1,如果不符合,继续向下判断条件2
}else if(判断条件2) {
    如果符合判断条件2,执行此处代码2,如果不符合,继续向下判断条件3
}else if(判断条件3) {
    如果符合判断条件3,执行此处代码3,如果不符合,继续向下判断
}else {
    如果以上条件都不满足,执行此处代码
}
4 选择结构
switch(a){
       case 1 : 操作1;break;【可选】
       case 2 : 操作2;break;【可选】
       case 3 : 操作3;break;【可选】
       case 4 : 操作4;break;【可选】
       default : 保底选项;【可选】
}

执行顺序:先拿着变量a的值,依次与每个case后的值做比较,如果相等,就执行当前case后的操作,若case后没有break,就绪继续执行下一个case后的操作,如果一直没有遇到break,就会发生穿透的现象,包括default

注意事项:

变量a支持的类型byte short char int String
变量a的数据类型与case后value的数据类型必须一致
如果没有添加break,并且又有case被匹配到,会发生穿透的现象,包括deafult
case的个数 是否加break 是否加default 完全根据自己的业务来决定
如果添加了default保底选项,又没有任何一个case被匹配到,就会执行default后的语句
一般我们习惯在每个case后加break【这个只是建议,具体还是根据业务来写】
5 循环结构
当你想多次重复干某件事的时候,可以使用循环结构
注意事项:开始条件只会在第一轮执行一次,剩下两个条件才会执行多次

普通for循环

for(开始条件 ; 循环条件 ; 更改条件){
     循环体
}
注意1:写法小窍门:从哪开始 到哪结束 循环变量如何变化
注意2:for循环能够执行多少次,取决于循环变量可以取到几个值

1 for循环
格式:
public static void main(String[] args) {
        for(开始条件; 循环条件 ; 更改条件) {
            符合循环条件后执行的循环体
        }
}
循环的开始条件只会在第一轮开始的时候执行一次,后续均不再执行
循环结构适合我们在程序中需要反复执行某一件事时使用
循环能够执行几次,取决于循环变量能够取到几个值,而不是循环变量的取值范围

public static void main(String[] args) {
        //比如此处循变量i的取值范围是8~8888,但是循环只执行了4次
        //因为i只取到了4个值,分别是8,88,888,8888
        for (int i = 8; i <= 8888; i=i*10+8) {
            System.out.println(i);
        }
    }
我们可以通过break直接结束当前循环的剩余所有轮
我们可以通过continue结束本轮循环continue后面的语句,直接开始下一轮循环
我们可以使用return关键字结束整个方法,注意break continue return 后都不可以直接写代码,都是不可到达的代码
2 嵌套for循环
外层循环控制的是执行的轮数,内层循环控制的是这一轮中执行的次数
外层循环控制的是图形的行数,内层循环控制的是这一行的列数
案例中的经验:
如果把内层循环循环变量的最大值设置为一个固定值,打印出来的是矩形

//*****
//*****
//*****
        for(int i = 1; i<=3 ;i++) {//外层循环,控制的轮数,执行3轮
            for(int j = 1;j<=5;j++) {//内层循环,控制的是每轮的次数,每轮执行5次
                System.out.print("*");
            }
            System.out.println();//本打印语句用来换行
        }

如果把内层循环的循环变量j,设置成随着外层循环循环变量i的变化而变化,打印出来的是直角三角形

System.out.println("*********打印左直角三角形********");
for(int i = 1;i<=6;i++) {//外层循环控制的是行数
    /**矩形每行中*的个数是固定不变的
     * 而左直角三角形中每行星星个数的最大值是随着行数的变化而变化的,行i星i*/
    for(int j = 1; j<=i;j++) {//内层循环控制的是这一行的列数
        System.out.print("*");
    }
    System.out.println();//打印空白行用来换行,注意一定要内层循环结束
}    

3 while循环
先判断,再执行,注意while循环设置死循环后必须设置出口~

while(判断条件){ 
    循环体
}
4. do-while循环
先执行,再判断,本循环最少会执行一次

do{
        循环体
}while(判断条件);
5 几种循环的区别
for:知道循环次数
while/do while:当循环次数不确定时
while:先判断,不符合规则,不执行代码
do while:代码最少被执行一次,再去判断,符合规则,再次执行代码
循环之间都可以互相替代,但是一般最好选择合适的循环结构来完成代码哦~

6 成员变量与局部变量
成员变量:
1)位置:类里方法外
2)注意事项:成员变量有自己的对应类型的默认值,不需要我们手动初始化/赋值
3)生效范围:在整个类中都生效,类消失,成员变量才会随之释放
局部变量:
1)位置:在方法里 / 局部代码块里
2)注意事项:使用时必须赋值/初始化
3)生效范围:在方法里/局部代码块中,对应的代码执行完毕,局部变量也随之释放


7 方法
方法定义的格式: 修饰符 返回值类型 方法名(参数列表){方法体}
一个方法会不会执行,取决于有没有调用,调用的格式:方法名+参数列表
方法定义的位置没有关系,执行顺序取决于main()怎么调用
一个方法,可以不设置参数,也可以设置多个参数,如果有参数,使用方法时,必须传对应类型的参数
如果方法的返回值类型是void,不允许有返回值
如果方法要返回一个值,必须设置返回值类型,并且return对应类型的结果
在调用有返回值的方法时,可以接这个方法的返回值,接到了后续可以多次使用这个返回值
如果不接,那就是只是调用该方法的功能,并不使用这个返回值

public class TestMethod2 {
    public static void main(String[] args) {
        //我们通过方法名+参数列表来确定调用哪个方法
        run();
        eat(5);
        String r = play("二郎神");
        System.out.println("我是r:"+r);
    }
    //格式:修饰符 返回值类型 方法名(参数列表){方法体}
    //需求1:定义一个方法run,没有参数,没有返回值,打印:小狗DOG跑的老快了~
    public static void run() {
        System.out.println("小狗DOG跑的老快了~");
    }
    //需求2:定义一个方法eat,参数为int n,没有返回值,打印:小狗今晚要吃n个肉包子~
    private static void eat(int n) {
        System.out.println("小狗今晚要吃"+n+"个肉包子~");
    }
    //需求3:定义一个方法play,参数String host,返回值是String "飞盘"
    //打印:小狗Dog与主人host玩的很开心~
    public static String play(String host) {
        System.out.println("小狗Dog与主人"+host+"玩的很开心~");
        return "飞盘";
    }
}

运行结果
小狗DOG跑的老快了~
小狗今晚要吃5个肉包子~
小狗Dog与主人二郎神玩的很开心~
我是r:飞盘

数组
1.1 静态创建
int[] a = {1,2,3,4,5};
int[] a = new int[]{1,2,3,4,5};

1.2 动态创建
int[] a = new int[5];

1. 3 数组的创建过程
在内存中开辟连续的空间,用来存放数据
给数组完成初始化过程,给每个元素赋予默认值
数组完成初始化会分配一个唯一的地址值
把唯一的地址值交给引用类型的变量a去保存
如果想要操作数组中的元素,可以根据变量保存的地址找到数组,然后根据下标来操作数组的具体元素
数组名保存的是数组的地址值,不是数组中每一个具体的元素,数组名是一个引用类型的变量


2. 数组的特性
数组的长度通过**数组名.length;**来获取
数组一旦创建,长度无法改变
数组的下标从0开始,最大下标为数组长度-1
如果访问到了不存在的下标,会数组下标越界异常
3. 数组的工具类Arrays
toString(数组名),除了char类型以外,其他类型的数组想要查看数组的具体元素,都得使用这个方法,如果不用,打印是数组的地址值
sort(数组名),给数组进行排序
copyOf(要复制的数组,新数组的长度)
如果新的长度大于原数组的长度–数组的扩容
如果新的长度小于原数组的长度–数组的缩容
如果新的长度等于原数组的长度–普通的复制
注意:不管是什么长度,都不是修改原数组,而是创建新数组


4. 数组的遍历
如果只是想查看数组中有哪些元素,直接使用System.out.println(Arrays.toString(数组名));就可以
如果想要拿到数组中的一个个的具体元素,或者是对数组中的元素做进一步的操作,就需要对数组进行遍历
遍历:把数组中的所有元素,从头到尾逐个“过一遍”
通过循环遍历数组,所以循环中的循环变量代表的是数组的下标

public class ReviewDemo {
    public static void main(String[] args) {
        int[] a = new int[10];
        for(int i = 0;i<a.length;i++) {
            //System.out.println(i);//打印的是数组的下标0-9
            a[i] = i+11;//a[i]代表的是数组中每一个具体的元素
        }
        System.out.println(Arrays.toString(a));
        //[11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
    }
}

5. 方法的重载
我们根据方法名+参数列表确定具体调用哪个方法
方法的重载:在同一个类中,存在方法名相同,但参数列表不同的方法
如果在同类中,同名方法的参数个数不同,一定构成重载
如果在同类中,同名方法的参数个数相同,需要查看对应位置上参数的类型,而不是参数名,与参数名无关

比如:
(int a,String b)与(int b,String a)–不构成重载
(int a,String b)与(String a,int b)–构成重载

6. 冒泡排序
冒泡排序详细笔记
排序思路:要比较多轮,每轮两个相邻的元素做比较,位置不对就互换
代码思路:
通过嵌套for循环来完成

外层循环:控制的是轮数,如果有n个数,最多n-1轮
内层循环:控制的是每轮中比较的次数,每一轮的次数会递减,之前轮确定的最大值不应该参与后面轮的比较
注意:外层循环的循环变量代表的是轮数1 2 3 4…
内层循环的循环变量代表的是数组的下标[0,a.length-i)

7. 简易计算器实现
package cn.tedu.review;

import java.util.Scanner;

/*本类用于实现简易计算器*/
public class TestCalc {
    public static void main(String[] args) {
        f0();//调用一个实现计算器的方法
    }

    private static void f0() {
        while(true){//死循环,让用户可以一直算
            //1.提示并接收用户输入的两个数
            System.out.println("请您输入要计算的第1个数:");
            int a = new Scanner(System.in).nextInt();
            System.out.println("请您输入要计算的第2个数:");
            int b = new Scanner(System.in).nextInt();

            //2.提示并接收用户输入的运算符
            System.out.println("请您输入要计算的运算符,支持:+ - * / :");
            String s = new Scanner(System.in).nextLine();
            //3.根据运算符的不同,调用不同的计算方法进行计算并输出结果
            switch(s){
                case "+" : f1(a,b);break;
                case "-" : f2(a,b);break;
                case "*" : f3(a,b);break;
                case "/" : f4(a,b);break;
                default: System.out.println("您输入的运算符不正确!");
            }
            //4.询问用户是否想继续运算
            System.out.println("如果想退出,请输入1,继续请输入2");
            int flag = new Scanner(System.in).nextInt();
            if(flag == 1){break;}
        }
    }

    private static void f4(int a, int b) {//将拿到的两个数a和b做除法运算并打印结果
        System.out.println(a+"和"+b+"做除法的结果为:"+(a/b));
    }
    private static void f3(int a, int b) {//将拿到的两个数a和b做乘法运算并打印结果
        System.out.println(a+"和"+b+"做乘法的结果为:"+(a*b));
    }
    private static void f2(int a, int b) {//将拿到的两个数a和b做减法运算并打印结果
        System.out.println(a+"和"+b+"做减法的结果为:"+(a-b));
    }
    private static void f1(int a, int b) {//将拿到的两个数a和b做加法运算并打印结果
        System.out.println(a+"和"+b+"做加法的结果为:"+(a+b));
    }
}
1. 面向过程:
这种编程思想强调的是过程,凡事亲力亲为

2. 面向对象:
这种思想强调的是结果,Java就是一门面向对象的语言
不在意完成的过程,在意的是有对象可以帮我干活
比如:我们想吃饭,不在意是哪个厨师做的,也不在意是哪个骑手送的,只要有厨师做,有骑手派送就好了
我们可以把OOP的思维理解成一种行为习惯 思维方式
比如衣服,没有办法给出一个明确的定义,但是,只要我们看到任何一件衣服,我们就自动把它归类到衣服这个分类中,因为你在过去的生活经验中已经见过太多的衣服,积累经验

3.类
类指的是类型Type,是指一类事物,使用Java中的class关键字来描述
类是抽象的,要提取这一类事物的特征与功能
可以把类理解成设计图纸,模板
注意:类在现实世界中不是真实存在,它只是一种对象的数据类型

4.对象
对象就是根据类创建出来的一个个独立且具体的实例
一个类可以创建出多个对象,我们通过对象唯一的地址值区分不同的对象
对象具有各种特征,并且每个对象的每个特征都可以有自己特定的值
对象具有各种行为,每个对象可以执行的操作

5.对象创建过程分析
Phone p = new Phone();创建对象时,内存发生了什么?

在栈内存中开辟一块空间,Phone类型的引用类型变量p,把p压入栈底,此时p只有一个默认值null
在堆内存中开辟一块空间用于存放Phone类型的对象
要给这个对象进行初始化,比如:String brand = null;
此对象已经准备好,所以会生成一个唯一的地址值,并将这个地址值交给栈内存中的变量p来保存
如果后续想要对对象做操作,比如:p.price=88.8;先会找到栈中p变量保存的地址值,根据这个地址找到堆中的对象再做进一步的操作

3.面向对象的特征–封装
3.1为啥要用封装?
封装可以提高程序的安全性
封装可以让资源按照我们预先规定的方式来操作
3.2属性的封装
用private修饰属性,一旦属性被private修饰,就只能在本类中使用,外界无法访问
所以为了让外界能够按照我们提供的方式来调用,需要根据属性生成公共的getXxx()与setXxx()方法

3.2 方法的封装
方法的封装也是使用private来修饰方法
如果想要调用私有方法的功能,就需要在本类的公共方法里调用这个私有方法

1.构造函数:
格式:与本类类名同名,且没有返回值类型
作用:创建对象
执行时机:每次创建对象时都会执行构造方法
分类:
1)无参构造:默认存在,如果添加了其他构造,默认的构造函数会被覆盖,所以要记得手动提供哦~
2)含参构造:对于参数没有任何要求,有参数就行
3)全参构造:全参构造的参数必须与本类属性一致
全参构造不仅可以创建对象,还可以给对象的所有属性赋值

2.方法的重载
在同一个类中,存在多个方法名相同,但参数列表不同的方法
如果在同类中,多个同名方法的参数个数不同,一定构成重载
如果在同类中,多个同名方法的参数个数相同:需要查看对应位置上的参数的类型,而不是参数名,与参数名无关
如:(int a ,String b)与(int b,String a)–不构成重载
(int a,String b)与(String a,int b)–构成–构成重载


3.构造代码块:
位置:类里方法外
执行时机:每次创建对象时执行,并且优先于构造方法执行
作用:用于提取所有构造方法的共性功能
4.局部代码块:
位置:方法里
执行时机:调用本局部代码块所处的方法时执行
作用:用于控制变量的作用范围
执行顺序:
构造代码块->构造方法->普通方法->局部代码块

5.2 super的用法:
1.当父类的成员变量与子类的变量同名时,使用super指定父类的成员变量
2.使用super在子类构造方法的第一行调用父类构造方法的功能
super();–调用的是父类的无参构造
super(参数);–调用的是父类对应参数的构造方法

6. 继承
继承的关键字extends 格式: 子类 extends 父类
继承相当于子类把父类的功能复制了一份
Java只支持单继承:一个子类只能有一个父类,一个父类可以有多个子类
继承具有传递性:爷爷的功能会传给爸爸,爸爸的功能会传给孙子
子类只可以使用父类的非私有资源,私有资源由于private限制,不可用
子类可以拥有自己的特有功能
继承是is a 强耦合的关系,依赖性非常强,比如我们看到”熊孩子”,就知道他有一个”熊父母”

7 继承中的构造方法
子类在创建对象时,默认会先调用父类的构造方法
原因是子类构造函数中的第1行默认存在super();–表示调用父类的无参构造
当父类没有无参构造时,可以通过super(参数);调用父类的其他含参构造
注意:子类必须调用父类的一个构造函数,不论是无参还是含参,选一个即可
构造方法不可以被继承!因为语法的原因,要求构造方法的名字必须是本类类名
不能在子类中出现一个父类名字的构造方法

1.继承中变量的使用
父类成员变量与子类成员变量同名时,使用super.变量名指定父类的成员变量
2.继承中构造方法的使用
创建子类对象时,会先调用父类的无参构造,因为子类的构造函数中默认存在一个super();
如果父类没有无参构造的话,我们就需要手动指定子类去调用父类的含参构造super(参数);
构造方法不可以被继承,原因是:构造方法名必须是本类的类名,不可能在子类中存在一个父类名字的构造方法

3.方法的重写
重写:子类对父类的方法不满意时,可以重写父类的方法
注意:重写是在不改变父类方法的前提下,实现功能的修改,重写后子类对象调用的就是重写后的功能
原则:两同 两小 一大
两同:方法名+参数列表相同
一大:子类方法的修饰符权限 >= 父类方法的修饰符权限
两小:子类方法的返回值类型,有两种选择:
1)子类方法的返回值类型与父类方法的返回值类型相同
2)子类方法的返回值类型是父类方法的返回值类型的子类
比如:父:void 子:void
父:int 子:int 但是:byte short long都不行!没有继承关系
父:Animal 子:Animal/Cat都可以,但不可以是Car
注意:我们还可以给方法上加@Override注解,标记这是一个重写的方法

package cn.tedu.review;
/*本类用于回顾继承*/
public class TestExtends {
    public static void main(String[] args) {
        //7.创建子类对象进行测试
        ComputerStudent s1 = new ComputerStudent(1,"张三",'男',98);
        MathStudent s2 = new MathStudent(2,"李四",'女',89);
        ChineseStudent s3 = new ChineseStudent(3,"王五",'女',100);
        //8.利用创建好的对象进行方法测试
        s1.proClass();
        s1.hobby();
        s2.proClass();
        s2.hobby();
        s3.proClass();
        s3.hobby();
        //9.创建父类对象进行方法测试
        Student s0 = new Student(0,"海绵宝宝",'男',3);
        s0.thinkingClass();
        s0.proClass();
    }
}
//1.定义学生类
class Student{
    //2.定义学生的属性
    int sno;//学号
    String name;//姓名
    char gender;//性别
    double score;//分数

    //6.1添加父类的全参构造
    public Student(int sno, String name, char gender, double score) {
        this.sno = sno;
        this.name = name;
        this.gender = gender;
        this.score = score;
    }
    //3.1定义父类的功能
    public void thinkingClass(){
        System.out.println("正在上思修课");
    }
    //3.2定义父类的功能
    public void proClass(){
        System.out.println("正在上专业课");
    }
}
//2.定义学生类的子类,区分不同的专业
class ComputerStudent extends Student{//计算机专业学生
    //6.2生成子类的全参构造,并调用父类的全参构造
    public ComputerStudent(int sno, String name, char gender, double score) {
        super(sno, name, gender, score);
    }
    //4.1重写父类的功能
    @Override
    public void proClass(){
        System.out.println("正在上编程课");
    }
    //5.1定义子类的特有功能
    public void hobby(){
        System.out.println("喜欢敲代码~");
    }
}
class MathStudent extends Student{//数学专业学生
    //6.3生成子类的全参构造,并调用父类的全参构造
    public MathStudent(int sno, String name, char gender, double score) {
        super(sno, name, gender, score);
    }
    //4.2重写父类的功能
    @Override
    public void proClass(){
        System.out.println("正在上数学理论课");
    }
    //5.2定义子类的特有功能
    public void hobby(){
        System.out.println("喜欢学模糊数学");
    }
}
class ChineseStudent extends Student{//中文专业学生
    //6.4生成子类的全参构造,并调用父类的全参构造
    public ChineseStudent(int sno, String name, char gender, double score) {
        super(sno, name, gender, score);
    }
    //4.3重写父类的功能
    @Override
    public void proClass(){
        System.out.println("正在上写作课");
    }
    //5.3定义子类的特有功能
    public void hobby(){
        System.out.println("喜欢写小说");
    }
}

4.静态:
1.static可以修饰成员变量和方法
2.被static修饰的资源称为静态资源
3.静态资源随着类的加载而加载,最先加载,优先于对象进行加载
4.静态资源可以通过类名直接调用,也被称作类资源
5.静态被全局所有对象共享,值只有一份
6.静态资源只能调用静态资源
7.静态区域内不允许使用this与super关键字,因为this代表本类对象,super代表父类对象,有静态时还没有对象呢~

5.静态代码块static{}
格式:static{ }
位置:类里方法外
执行时机:随着类的加载而加载,优先于对象进行加载【只加载一次】
作用:用于加载那些需要第一时间就加载,并且只加载一次的资源,常用来初始化
顺序:静态代码块 构造代码块 构造方法 普通方法【如果普通方法里有局部代码块,执行局部代码块】
TIPS: 如果有多个静态资源,加载顺序取决于先后位置
TIPS: 静态不可以与this和super共用
6.final 关键字
修饰类:最终类,不可以被继承
修饰方法:这个方法的最终实现,不可以被重写
修饰常量:值不可以被更改,并且常量定义时必须赋值
注意:常量的定义需要使用全大写,单词之间使用下划线分隔

7.面向对象之多态
1. 前提:继承+重写
2. 口诀1:父类引用指向子类对象
解释:父类类型的引用类型变量保存的是子类类型的对象的地址值

3. 口诀2:编译看左边,运行看右边
解释:编译时要看父类是否定义了这个资源,运行时使用的是子类的功能

4. 资源使用情况
成员变量使用的是父类的
成员方法使用的是父类的方法定义,子类的方法体
如果多态对象调用的是子类没有重写过的方法,方法定义与方法体使用的都是父类的,所以这个不符合多态的前提,直接使用纯纯的父类对象调用即可
静态资源属于类资源,随着类的加载而加载,只会加载一次,优先于对象进行加载,可以通过类名直接调用,被全局所有对象共享,所以静态不存在重写的现象,在哪个类定义,就属于哪个类的资源
我们现在学习的多态,把自己看作是父类类型,参考“花木兰替父从军”

1 异常
1. 异常的继承结构
异常层次结构中的根是Throwable
Error:目前我们编码解决不了的问题
Exception:异常
编译异常:未运行代码就报错了,强制要求处理
运行时异常:运行代码才报错,可以通过编译,不强制要求处理

Throwable:异常的顶级父类
    --Error:错误,程序无法处理
    --Exception:异常,我们可以通过编码修复
        -- 编译异常:还没运行已经报错了
        -- 运行时异常:可以通过编译,一运行就报错
        
2.异常的解决方案
1. 捕获处理try-catch–自己解决
异常捕获处理的格式:
 try{
   可能会抛出异常的代码
 }catch(异常的类型 异常的名字){
    万一捕获到了异常,进行处理的解决方案
 }

 
try-catch结构可以嵌套,如果有多种异常类型需要特殊处理的话
使用多态的思想,不论是什么子异常,统一看作父类型Exception
做出更加通用的解决方案,甚至可以只写这一个
2.向上抛出throws–交给别人解决
异常抛出的格式:
在方法的小括号与大括号之间,写:throws 异常类型
如果有多个异常,使用逗号分隔即可

private static void method3() throws ArithmeticException,InputMismatchException,Exception{ }

private static void method3() throws Exception{ }

如果一个方法抛出了异常,那么谁来调用这个方法,谁就需要处理这个异常,这里的处理也有两种方案:捕获解决 或者 继续向上抛出
不能直接把异常抛给main(),因为调用main()是JVM,没人解决了,该报错还报错,所以我们一般会在main()调用之前将异常解决掉


3. 抽象
1.抽象类
被abstract修饰的方法是抽象方法,抽象方法没有方法体
一旦一个类中有抽象方法,这个类必须被声明成抽象类
如果一个子类继承了一个抽象父类,有两种解决方案:
1)抽象子类:不实现/实现一部分抽象父类中的抽象方法
2)普通子类:实现抽象父类中全部的抽象方法
抽象类不能实例化
抽象类有构造函数的,但是不是为了自己使用,为了子类super()调用
抽象类可以定义成员变量/成员常量
抽象类中可以定义全普/全抽/半普半抽
如果一个类不想被外界实例化,可以把这个类声明成抽象类
abstract关键字不可以与private static final共用

2 抽象方法
被关键字abstract 修饰的方法就是抽象方法
抽象方法没有方法体{ },直接以分号结束

4. 练习案例1:
package cn.tedu.oop2;
/*本类用于面向抽象编程的推导*/
public class DesignTeacher {
}
//3.抽取共性,向上形成父类
abstract class Teacher{
    String name;
    int id;
    public abstract void ready();
    public abstract void teach();
}

//1.创建培优CGB老师类
//业务:主打互联网架构与微服务
class CGBTeaher extends Teacher{
    @Override
    public void ready(){
        System.out.println("正在备课:互联网架构与微服务");
    }
    @Override
    public void teach(){
        System.out.println("正在讲课:互联网架构与微服务");
    }
}
//2.创建高手加薪班老师类
//主打:基础加强 框架加强 高新技术
class ACTTeacher extends Teacher{
    @Override
    public void ready(){
        System.out.println("正在备课:基础加强 框架加强 高新技术");
    }
    @Override
    public void teach(){
        System.out.println("正在讲课:基础加强 框架加强 高新技术");
    }
}

//创建抽象父类的子实现类SCDTeacher
//这是一个抽象子类,只实现抽象父类中的一个抽象方法
abstract class SCDTeacher extends Teacher{
    @Override
    public void ready() {
        System.out.println("正在研发中...");
    }
}

5.1 练习案例2-1:
定义父类小鸟类Bird :
腿legNumbers的数量为2
下蛋eggNumbers数量可以自定义
拥有飞行fly()功能 下蛋layEggs()功能
package cn.tedu.review;
//1.定义父类小鸟类
public abstract class Bird {
    //2.定义小鸟类中的成员常量,腿的数量为2
    final int LEG_NUMBERS = 2;
    //3.定义小鸟类中的成员变量,下蛋的数量
    int eggNumbers;
    //4.定义小鸟类的普通方法
    public void fly(){
        System.out.println("小鸟飞呀飞~");
    }
    //5.定义小鸟类的抽象方法
    public abstract void layEggs();
    
    
定义子级鸽子类Pigeon:
它既会飞,也会下蛋
package cn.tedu.review;
//6.定义小鸟类的子类鸽子类
public class Pigeon extends Bird{
    //7.实现抽象父类中未实现的方法
    @Override
    public void layEggs() {
        System.out.println("鸽子的下蛋数量为:2个");
    }
}

定义子级燕子类Swallow:
它既会飞,也会下蛋,还有自己的特有功能筑巢makeNest()
package cn.tedu.review;
//8.创建小鸟类的第2个子类燕子类
public class Swallow extends Bird{
    //9.定义燕子类自己的成员变量(与父类的成员变量同名)
    int eggNumbers = 6;
    //10.实现抽象父类中未实现的抽象方法
    @Override
    public void layEggs() {
        System.out.println("燕子的下蛋数量:"+eggNumbers);
    }
    //11.定义子类独有的功能
    public void makeNest(){
        System.out.println("燕子筑巢~");
    }
}

创建测试类进行测试:
package cn.tedu.review;
//12.创建测试类
public class TestAnimal {
    public static void main(String[] args) {
        //13.创建鸽子类对象并进行测试
        Pigeon p = new Pigeon();
        System.out.println(p.LEG_NUMBERS);//2
        System.out.println(p.eggNumbers);//0
        p.layEggs();//鸽子的下蛋数量为:2个
        p.fly();//小鸟飞呀飞~

        //14.创建燕子类对象并进行测试
        Swallow s = new Swallow();
        System.out.println(s.LEG_NUMBERS);//2
        System.out.println(s.eggNumbers);//6
        s.fly();//继承过来的方法
        s.layEggs();//实现了的抽象方法
        s.makeNest();//自己的特有方法
    }
}


5.2 练习案例2-2:
定义父类昆虫类Insect:
腿的数量为6,产卵的数量可以自定义,拥有飞行的功能还有产卵spawn的功能
package cn.tedu.review;
//1.创建抽象父类昆虫类
public abstract class Insect {
    //2.定义成员常量:腿的数量为6
    final int LEG_NUMBERS = 6;
    //3.定义成员变量
    int eggNumbers;//产卵的数量
    //4.定义一个普通方法
    public void fly(){
        System.out.println("我是昆虫的飞行功能~");
    }
    //5.定义抽象方法--产卵功能
    public abstract void spawn();
}

定义子级蚂蚁类Ant:
它既会飞,也会产卵

package cn.tedu.review;
//6.创建昆虫类的子类蚂蚁类
public class Ant extends Insect{
    //7.添加抽象父类中未实现的抽象方法
    @Override
    public void spawn() {
        System.out.println("蚂蚁产卵的数量为:300");
    }
}

定义子级蜜蜂类Bee:
它既会飞,也会产卵,还有自己的特有功能制作蜂蜜makeHoney

package cn.tedu.review;
//8.创建昆虫类的子类蜜蜂类
public class Bee extends Insect{
    //9.定义子类自己的同名成员变量
    int eggNumbers = 200;
    //10.实现抽象父类中未实现的抽象方法
    @Override
    public void spawn() {
        System.out.println("蜜蜂的产卵数量为:"+eggNumbers);
    }
    //11.定义子类自己的独有功能
    public void makeHoney(){
        System.out.println("蜜蜂正在产蜂蜜~");
    }
}

创建测试类进行测试:

package cn.tedu.review;
//12.创建测试类
public class TestAnimal {
    public static void main(String[] args) {
        //12.创建蚂蚁类的对象
        Ant a = new Ant();
        System.out.println(a.LEG_NUMBERS);//6
        System.out.println(a.eggNumbers);//0
        a.fly();//使用继承过来的方法
        a.spawn();//使用子类实现的方法

        //13.创建蜜蜂类的对象
        Bee b = new Bee();
        System.out.println(b.LEG_NUMBERS);//6
        System.out.println(b.eggNumbers);//200
        b.fly();//使用继承过来的方法
        b.spawn();//使用子类实现的方法
        b.makeHoney();//使用子类的特有功能
    }
}

1.接口的特点:
我们使用interface关键字定义接口
我们使用implements关键字建立接口实现类与接口的实现关系
接口是父级,接口实现类是子级
接口实现类如果实现部分/不实现接口中的抽象方法,那么实现类是一个抽象类
接口实现类如果实现了接口所有的抽象方法,那么这个实现类是一个普通类
抽象类与接口都不可以实例化/创建对象
接口没有构造函数,实现类使用的super()是父类的无参构造
如果没有明确指定父类,super()代表的才是Object的无参构造
接口中都是静态常量,没有成员变量,因为会默认拼接public static final
接口中都是抽象方法,默认会拼接public abstract
故此:静态常量与抽象方法默认拼接的部分,可以省略不写
接口不是类!!!
接口是用来制定规则的【有哪些功能?方法有参数吗?有返回值吗?】
方法具体的实现交给接口的实现类去完成

2.接口与类的复杂关系
1. 类与类的关系
继承关系,只支持单继承
比如A是父类,B是子类,B具备A的所有功能
如果B对A的功能不满意,可以重写(两同 两小 一大)

2. 类和接口的关系
实现关系,可以单实现,也可以多实现
比如:多实现:class A implements B,C{}
A是实现类,B和C是接口,A需要实现BC接口的所有抽象方法,否则A就是一个抽象类

3. 接口与接口的关系
继承关系,可以单继承,也可以多继承
interface A extends B,C{}
A B C 都是接口,A是子接口,具有B C父接口的所有功能(抽象方法)
class X implements A{}
X实现类需要实现A接口,以及A接口继承自B C接口的所有抽象方法,否则就是抽象类

class O extends A implements B,C{}
其中O是实现类,也是A的子类,同时拥有A的所有功能,并且需要实现BC的所有功能

4.接口与抽象类的区别
1.接口是一种用interface定义的类型
抽象类是一种用class定义的类型
2.接口中的方法都是抽象方法
抽象类中的方法不做限制
3.接口中的都是静态常量
抽象类中可以写普通的成员变量
4.接口中没有构造方法,不可以实例化
抽象类中有构造方法,但是也不可以实例化
5.接口是先天设计的结果,抽象是后天重构的结果
6.接口可以多继承,抽象类只能单继承

3.内部类总结
1.内部类创建对象的格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象
2.根据内部类的位置不同,分为:
成员内部类(类里方法外)
局部内部类(方法里)
3.内部类可以直接使用外部类的资源,但是外部类使用内部类资源时
需要先创建内部类的对象,通过内部类的对象来调用
4.如果内部类被private修饰,无法直接在外界创建内部类对象
我们可以创建外部类的对象,间接访问内部类的资源
5.如果内部类被static修饰,可以把这个类称为静态内部类
静态内部类不需要先创建外部类对象,而是先通过外部类的类名找到内部类,再创建内部类对象
6.如果静态内部类中有静态资源,可以不创建一个对象,就通过
外部类名.内部类名.静态资源名的链式加载的方式,使用这个资源
7.直接创建外部类对象,调用局部内部类所处的方法时,并不会触发局部内部类的功能
需要在局部内部类所处的方法中,先创建局部内部类对象,并调用其功能,功能才会被触发
8.匿名内部类没有名字,通常与匿名对象结合在一起使用
9.匿名对象只能使用一次,一次只能调用一个功能
匿名内部类其实就是充当了实现类的角色,去实现未实现的方法,只是没有名字而已
如果想要多次使用实现后的功能,还要创建之前的普通对象


1 内部类概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
2 特点
1) 内部类可以直接访问外部类中的成员,包括私有成员
2) 外部类要访问内部类的成员,必须要建立内部类的对象
3) 在成员位置的内部类是成员内部类
4) 在局部位置的内部类是局部内部类
3 练习 : 内部类入门案例
创建包: cn.tedu.innerclass
创建类: TestInner1.java

package cn.tedu.innerclass;
/*本类用作测试内部类的入门案例*/
public class TestInner1 {
    public static void main(String[] args) {
        //3.创建内部类对象,使用内部类的资源
        /*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
        Outer.Inner oi = new Outer().new Inner();
        oi.delete();
        System.out.println(oi.sum);
        //4.调用外部类的方法--这样是创建了一个外部类的匿名对象,只使用一次
        new Outer().find();
    }
}

//1.创建外部类 Outer
class Outer{
    //1.1创建外部类的成员变量
    String name;
    private int age;
    //1.2创建外部类的成员方法
    public void find(){
        System.out.println("Outer...find()");
        //6.测试外部类如何使用内部类的资源
        //System.out.println(sum);--不能直接使用内部类的属性
        //delete();--不能直接调用内部类的方法
        /*外部类如果想要使用内部类的资源,必须先创建内部类对象
        * 通过内部类对象来调用内部类的资源*/
        Inner in = new Inner();
        System.out.println(in.sum);
        in.delete();
    }
    //2.创建内部类Inner--类的特殊成员
    /*根据内部类位置的不同,分为:成员内部类(类里方法外)、局部内部类(方法里)*/
    class Inner{
        //2.1定义内部类的成员变量
        int sum = 10;
        //2.2定义内部类的成员方法
        public void delete(){
            System.out.println("Inner...delete()");
            //5.测试内部类是否可以使用外部类的资源
            /*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
            System.out.println(name);
            System.out.println(age);
            /*注意:此处测试完毕需要注释掉,否则来回调用
            * 会抛出异常StackOverFlowException栈溢出异常*/
            //find();
        }
    }
}

4 成员内部类
4.1 练习 : 被private修饰
创建包: cn.tedu.innerclass
创建类: TestInner2.java

package cn.tedu.innerclass;
/**本类用来测试成员内部类被private修饰*/
public class TestInner2 {
    public static void main(String[] args) {
        /**怎么使用内部类Inner2的资源?*/
        //4.创建内部类Inner2对象进行访问
        //Outer2.Inner2 oi = new Outer2().new Inner2();
        //oi.eat();
        
        /**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
        //7.创建外部类对象,间接访问私有内部类资源
        new Outer2().getInner2Eat();
    }
}
//1.创建外部类Outer2
class Outer2{
    //6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
    public void getInner2Eat() {
        Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
        in.eat();
    }
    //2.1创建成员内部类Inner2
    /**成员内部类的位置:类里方法外*/
    //5.成员内部类,被private修饰私有化,无法被外界访问
    private class Inner2{
        //3.创建内部类的普通成员方法
        public void eat() {
            System.out.println("我是Inner2的eat()");
        }
    }
}

总结:
成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源

4.2 练习 : 被static修饰
创建包: cn.tedu.innerclass
创建类: TestInner3.java

package cn.tedu.innerclass;
/**本类用来测试成员内部类被static修饰*/
public class TestInner3 {
    public static void main(String[] args) {
        /**如何访问内部类的show()?*/
        //4.创建内部类对象访问show()
        //方式一:按照之前的方式,创建内部类对象调用show()
        //Outer3.Inner3 oi = new Outer3().new Inner3();
        //oi.show();
        //方式二:创建匿名内部类对象访问show()
        //new Outer3().new Inner3().show();
        
        /**现象:当内部类被static修饰以后,new Outer3()报错*/
        //6.用static修饰内部类以后,上面的创建语句报错,注释掉
        //通过外部类的类名创建内部类对象
        Outer3.Inner3 oi = new Outer3.Inner3();
        oi.show();
        
        //7.匿名的内部类对象调用show()
        new Outer3.Inner3().show();
        
        //9.访问静态内部类中的静态资源--链式加载
        Outer3.Inner3.show2();
    }
}

//1.创建外部类Outer3
class Outer3{
    //2.创建成员内部类Inner3
    //5.内部类被static修饰—并不常用!浪费内存!
    static class Inner3{
        //3.定义成员内部类中普通的成员方法
        public void show() {
            System.out.println("我是Inner3类的show()");
        }
        //8.定义成员内部类的静态成员方法
        static public void show2() {
            System.out.println("我是Inner3的show2()");
        }
    }
}

总结:
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问

5 局部内部类
创建包: cn.tedu.innerclass
创建类: TestInner4.java

package cn.tedu.innerclass;
/**本类用来测试局部内部类*/
public class TestInner4 {
    public static void main(String[] args) {
        /**如何使用内部类的资源呢?
         * 注意:直接调用外部类的show()是无法触发内部类功能的
         * 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
         * */
        //5.创建外部类对象调用show()
        //7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
        new Outer4().show();
    }
}
//1.创建外部类Outer4
class Outer4{
    //2.创建外部类的成员方法
    public void show() {
        //3.创建局部内部类Inner4—不太常用!!!
        /**位置:局部内部类的位置在方法里*/
        class Inner4{
            //4.创建局部内部类的普通属性与方法
            String name;
            int age;
            public void eat() {
                System.out.println("我是Inner4的eat()");
            }
        }
        /**如何使用局部内部类的资源?*/
        //6.在show()里创建内部类对象
        Inner4 in = new Inner4();
        in.eat();
        System.out.println(in.name);
        System.out.println(in.age);
    }
}

6 匿名内部类
创建包: cn.tedu.innerclass
创建类: TestInner5.java

package cn.tedu.innerclass;
/*本类用于测试匿名内部类
* 匿名内部类没有名字,通常与匿名对象结合在一起使用*/
public class TestInner5 {
    public static void main(String[] args) {
        //传统方式:创建接口的实现类+实现类实现接口中的抽象方法+创建实现类对象+通过对象调用方法
        //3.创建接口一对应的匿名对象与匿名内部类,并调用实现了的方法save()
        new Inter1(){
            @Override
            public void save() {
                System.out.println("save()...");
            }
            @Override
            public void get() { }
        }.save();

        //5.创建抽象类对应的匿名对象与匿名内部类
        new Inter2(){
            @Override
            public void drink() {
                System.out.println("一人饮酒醉");
            }
        }.drink();
        //7.调用普通类的功能怎么调用?创建匿名对象直接调用
        new Inter3().powerUp();
        new Inter3().powerUp();//new了2次,所以是两个匿名对象
        /*如果想要多次使用实现后的功能,还是要创建普通的对象
        * 匿名对象只能使用一次,一次只能调用一个功能
        * 匿名内部类其实就充当了实现类的角色,去实现未实现的抽象方法,只是没有名字而已*/
        Inter3 in = new Inter3();
        in.study();
        in.study();
        in.study();
        in.study();
        in.study();
        in.study();

    }
}

//1.创建接口
interface Inter1{
    //2.定义接口中的抽象方法
    void save();
    void get();
}
//4.创建抽象类
abstract class Inter2{
    public void play(){
        System.out.println("Inter2...play()");
    }
    abstract public void drink();
}
//6.创建普通类
class Inter3{
    public void study(){
        System.out.println("什么都阻挡不了我想学习赚钱的决心");
    }
    public void powerUp(){
        System.out.println("我们会越来越强的!");
    }
}

总结:
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用

学习API阶段的前提:
API是一些别人制定或者写好的应用程序接口/功能
学习的重点:学习这些功能如何更好的使用,怎么使用,使用后有什么效果
比如:怎么创建某个类的对象–看构造方法
怎么使用某个功能–需不需要传参数,传什么样的参数
比如:这个方法有什么样的结果/执行效果:结果看返回值类型,效果要在IDEA去尝试
所以我们可以把API手册当成一个“字典”,哪里不会查哪里
1. 顶级父类 java.lang.Object
Object是Java中所有类的超类,Java中的类都直接或者间接的继承自Object
如果一个类没有明确的指定父类,那么这个类默认继承Object
java.lang包是Java的核心包,无需导包,会自动导入
1.1 hashCode()
hashCode() : 返回对象对应的哈希码值
Object实现方式:根据对象的地址值生成对应的哈希码值

1.2 toString()
toString():返回对象对应的字符串
Object实现方式:返回对象的地址值(包名.类名@十六进制的哈希码值)

1.3 equals()
equals():比较当前对象与参数对象是否相等
Object实现方式:==比较两个对象的地址值,地址值不同就返回false

注意:需要牢记Object中的默认实现方式,后续遇到的类,只要与Object中默认的实现方式不同,说明发生了重写,具体重写的效果每个类都是不同的,遇到哪个,积累哪个

1.4 自定义类Student
添加了重写的方法后,重写的效果

hashCode(): 根据传入的当前对象的属性值生成
toString(): 打印当前对象的类型+属性+属性值
equals() : 比较两个对象的类型+属性+属性值
1.5 拓展
查找类的4种方式:

查API手册 点这里:API手册使用指导
连点两下Shift打开IDEA的搜索,注意勾选"include non-Project items",再搜Object
按住Ctrl点hashCode()
在拓展库External Libraries找到jdk1.8->rt.jar->java.lang.Object

2 String
String 类

底层的结构是字符数组char[ ]
String的常用方法
创建方式一:
char[] values = {‘a’,‘b’,‘c’};
String s1 = new String(value);
注意:每new一次,创建一个String对象,存在堆中

创建方式二:
String s = “abc”;
注意:存在堆中的常量池中,有高效的效果,如果是第二次创建,不会新建
注意:== 比较的如果是引用类型,那么是地址值
注意:String重写了Object中的toString(),所以可以直接打印字符串的具体内容
String重写了Object中的equals(),所以比较的也是两个字符串的具体内容
String重写了Object中的hashCode(),根据字符串的具体内容生成

String API总结
int hashCode() 返回此字符串的哈希码。
boolean equals(Object anObject) 将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() 返回此对象本身(它已经是一个字符串!)。

int length() 返回此字符串的长度。
String toUpperCase() 所有字符都转换为大写。
String toLowerCase() 所有字符都转换为小写
boolean startsWith(String prefix) 测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) 测试此字符串是否以指定的字符串结束。

char charAt(int index) 返回指定索引/下标处的 char 值/字符
int indexOf(String str) 返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(String str) 返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) 将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) 根据给定元素来分隔此字符串。

String trim() 返回去除首尾空格的字符串
byte[] getBytes() 把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) 返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) 返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) 把int转成String

1. 包装类
Java的数据类型只有两大类:8大基本类型与引用类型
包装类是引用类型中的一种,包装类与基本类型一一对应,也有8种
基本类型只能存自己类型的值,没有其他额外的功能
包装类型是对基本类型进行了包装,提供了丰富的功能,包装类型是基本类型的拓展
引用类型
java为每种基本类型都提供了对应的封装类型,分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。引用类型是一种对象类型,它的值是指向内存空间的引用,就是地址。

包装类型Integer的创建方式:
1)Integer i1 = new Integer(5); 没有高效的效果,new一次,创建一个包装类对象
2)Integer i2 = Integer.valueOf(5); 有高效的效果,数据在-128~127的范围内,才有高效的效果
3)Integer i3 = 5; 自动装箱:编译器会把int类型5装箱,变成Integer,底层调用的方法:valueOf(5)
包装类型Double的创建方式:
1)Double d1 = new Double(3.4); 没有高效的效果,new一次,创建一个包装类对象
2)Double d2 = Double.valueOf(3.4);这个也没有高效的效果,只有Integer有
Integer的常用方法:i1.parseInt(“80”);将字符串80转成int类型的80
Double的常用方法:d1.parseDouble(“80”);将字符串80转成double类型的80
自动装箱与自动拆箱:
1)Integer i3 = 5; 自动装箱【基本类型 到 包装类型】:
编译器会把int类型的5装箱,变成Integer类型,底层调用的方法:valueOf(5)
2)int n = i3;自动拆箱【包装类型 到 基本类型】:
编译器会把包装类型的i3中的数据取出来,把这个值赋值给基本类型int
底层调用的方法:i3.intVlaue();

2. 浮点数运算不精确的解决方案:BigDecimal
如果使用一个工具,必须先创建这个类的对象,如果想创建对象,必须先了解它提供了哪些构造方法
创建BigDecimal对象的方式:
1)BigDecimal(double val) :将double类型的数据作为参数,交给BigDecimal对象【不用,因为double本身不精确】
2)BigDecimal(String val) :将String类型的数据作为参数,交给BigDecimal对象【用这个】
注意:double->String直接拼接一个空串“”就可以
使用对象进行加减乘除操作,注意:除法除不尽时会抛出异常,所以需要指定除不尽时保留几位小数以及舍入方式

3. 正则表达式
作用: 拿着我们指定好的规则,去判断数据是否符合这个规则

指定规则:String regex = “[0-9]{17}[0-9X]”;
拿着数据与规则做比较:input.matches(regex)–>如果匹配,matches方法返回true
注意:单个斜杠表示转义字符,所以在正则中如果想要表示单个斜杠,需要写双斜杠
至于正则表达式的对照关系,详见笔记中正则速查表

正则表达式简单语法及常用正则表达式
基本符号:
^  表示匹配字符串的开始位置  (例外  用在中括号中[ ] 时,可以理解为取反,表示不匹配括号中字符串)
$  表示匹配字符串的结束位置
*  表示匹配 零次到多次
+  表示匹配 一次到多次 (至少有一次)
?  表示匹配零次或一次
.  表示匹配单个字符 
|  表示为或者,两项中取一项
(  ) 小括号表示匹配括号中全部字符
[  ] 中括号表示匹配括号中一个字符 范围描述 如[0-9 a-z A-Z]
{  } 大括号用于限定匹配次数  如 {n}表示匹配n个字符  {n,}表示至少匹配n个字符  {n,m}表示至少n,最多m
\  转义字符 如上基本符号匹配都需要转义字符   如 \*  表示匹配*号
\w 表示英文字母和数字  \W  非字母和数字
\d  表示数字   \D  非数字

常用的正则表达式(转)
匹配中文字符的正则表达式: [\u4e00-\u9fa5]
匹配双字节字符(包括汉字在内):[^\x00-\xff]
匹配空行的正则表达式:\n[\s| ]*\r
匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/ 
匹配首尾空格的正则表达式:(^\s*)|(\s*$)
匹配IP地址的正则表达式:/(\d+)\.(\d+)\.(\d+)\.(\d+)/g //
匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
匹配网址URL的正则表达式:http://(/[\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
sql语句:^(select|drop|delete|create|update|insert).*$
1、非负整数:^\d+$ 
2、正整数:^[0-9]*[1-9][0-9]*$ 
3、非正整数:^((-\d+)|(0+))$ 
4、负整数:^-[0-9]*[1-9][0-9]*$ 
5、整数:^-?\d+$ 
6、非负浮点数:^\d+(\.\d+)?$ 
7、正浮点数:^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$ 
8、非正浮点数:^((-\d+\.\d+)?)|(0+(\.0+)?))$ 
9、负浮点数:^(-((正浮点数正则式)))$ 
10、英文字符串:^[A-Za-z]+$ 
11、英文大写串:^[A-Z]+$ 
12、英文小写串:^[a-z]+$ 
13、英文字符数字串:^[A-Za-z0-9]+$ 
14、英数字加下划线串:^\w+$ 
15、E-mail地址:^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$ 
16、URL:^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$ 或:^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$
17、邮政编码:^[1-9]\d{5}$
18、中文:^[\u0391-\uFFE5]+$
19、电话号码:^((\d2,3)|(\d{3}\-))?(0\d2,3|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$
20、手机号码:^((\d2,3)|(\d{3}\-))?13\d{9}$
21、双字节字符(包括汉字在内):^\x00-\xff
22、匹配首尾空格:(^\s*)|(\s*$)(像vbscript那样的trim函数)
23、匹配HTML标记:<(.*)>.*<\/\1>|<(.*) \/> 
24、匹配空行:\n[\s| ]*\r
25、提取信息中的网络链接:(h|H)(r|R)(e|E)(f|F) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)?
26、提取信息中的邮件地址:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
27、提取信息中的图片链接:(s|S)(r|R)(c|C) *= *('|")?(\w|\\|\/|\.)+('|"| *|>)?
28、提取信息中的IP地址:(\d+)\.(\d+)\.(\d+)\.(\d+)
29、提取信息中的中国手机号码:(86)*0*13\d{9}
30、提取信息中的中国固定电话号码:(\d3,4|\d{3,4}-|\s)?\d{8}
31、提取信息中的中国电话号码(包括移动和固定电话):(\d3,4|\d{3,4}-|\s)?\d{7,14}
32、提取信息中的中国邮政编码:[1-9]{1}(\d+){5}
33、提取信息中的浮点数(即小数):(-?\d*)\.?\d+
34、提取信息中的任何数字 :(-?\d*)(\.\d+)? 
35、IP:(\d+)\.(\d+)\.(\d+)\.(\d+)
36、电话区号:/^0\d{2,3}$/
37、腾讯QQ号:^[1-9]*[1-9][0-9]*$
38、帐号(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
39、中文、英文、数字及下划线:^[\u4e00-\u9fa5_a-zA-Z0-9]+$

4. File 文件类
创建File类的对象:new File(String pathname) :这个参数是一个String类型的路径名,这个路径可能是:
1)文件的路径 “D:\ready\1.txt”
2)文件夹的路径 “D:\ready\a”
3)之前不存在的文件/文件夹的路径:这个路径暂时在windows中还未创建出来
注意:new File类的对象只是在内存中多个一个Java对象,并不会真的帮我们在外部创建一个新的文件/文件夹

创建:
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。

删除:
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。

文件夹相关:
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。


5.流的分类
1)按照方向分类:输入流 输出流
2)按照操作的单位分类:字节流 字符流
3)组合情况:字节输入流 字节输出流 字符输入流 字符输出流

6.字节输入流
1)抽象父级:InputStream–不能实例化
2)普通子级:

FileInputStream–操作文件的字节输入流
构造方法参数:File file / String pathname
7.字节输出流 OutputStream
8.字符输入流 Reader
9.字符输出流 Writer


1 流的分类
1)按照方向分类:输入流 输出流
2)按照操作的单位分类:字节流 字符流
3)组合情况:字节输入流 字节输出流 字符输入流 字符输出流

2 字节输入流
1)抽象父级:InputStream–不能实例化
2)普通子级:

FileInputStream–操作文件的字节输入流
构造方法参数:File file / String pathname
BufferedInputStream–高效字节输入流
构造方法参数:InputStream,但无法创建抽象父级对象,所以传的是FileInputStream
3 字节输出流 OutputStream
1)抽象父级:OutputStream–不能实例化
2)普通子级:

FileOutputStream–操作文件的字节输出流
构造方法参数:File file / String pathname
注意:默认存在一个参数boolean append,默认值为false,也就是覆盖输出
如果将FileOutputStream构造函数的第2个参数appned设置为true,就会实现追加输出的效果
BufferedOutputStream–高效字节输出流
构造方法参数:OutputStream,但无法创建抽象父级对象,所以传的是FileOutputStream
4 字符输入流 Reader
1)抽象父级:Reader
2)普通子级:

FileReader–操作文件的字符输入流
构造方法参数:File file /String filename
BufferedReader–高效字符输入流
构造方法参数:Reader,但无法创建抽象父级对象,所以传的是FileReader
5 字符输出流 Writer
1)抽象父级:Writer
2)普通子级:

FileWriter–操作文件的字符输出流
构造方法参数:File file /String filename
注意:默认存在一个参数boolean append,默认值为false,也就是覆盖输出
如果将FileWriter构造函数的第2个参数appned设置为true,就会实现追加输出的效果
BufferedWriter–高效字符输出流
构造方法参数:Writer,但无法创建抽象父级对象,所以传的是FileWriter


1. 序列化:
将程序中对象的各项信息,序列化输出到文件中保存
方向是Out,使用的流是ObjectOutputStream
使用的方法是out.writeObject(目标对象);
注意:如果一个类的对象想要被序列化,那么这个类必须实现Serializable接口

2. 反序列化:
将之前输出到文件中的数据,读取回程序中,并把读到的数据重新恢复成对象
方向是in,使用的流是ObjectInputStream
使用的方法是in.readObject();
注意:反序列化指定的文件路径,必须与序列化输出的文件路径一样
注意:自定义类需要重写toString()才能查看对象的属性与属性值,否则打印地址值
注意:一次序列化操作对应一次反序列化操作,或者UID必须保持一致,如果不一致,会报错

反序列化如何成功?
核心:Student类中的UID,与反序列化流中的UID保持一致
1)一次序列化对应一次反序列化[推荐]
2)一次序列化后不修改Student中的内容,然后反序列化
3)将Student中的UID写成固定值
注意:反序列化流持有的UID与Student类中的UID不一致时,反序列化会失败
比如:使用自动生成的UID,先序列化,然后修改Student,再来反序列化,这样就会失败


3 泛型
泛型,不是指一种具体的类型,而是说,这里有个类型需要设置
那么后续具体需要设置成什么类型,得看具体的业务
泛型通常与集合一起使用,用来限制集合中存入的元素类型
泛型具体设置成什么类型,那么这个集合只能存这个类型的元素
泛型是一颗"语法糖"
1)泛型可以把报错的时机提前,用于在编译期检查集合的元素类型
只要不是泛型设置的类型,就报错,通不过编译
2)泛型只在编译时生效,编译通过以后,说明符合语法规范
泛型就会被抛弃,编译生成的字节码文件中没有泛型
泛型的类型必须使用引用类型,比如:Student String Integer
泛型方法:如果想要在方法上使用泛型,必须两处同时出现
1)一个是方法的参数列表中的参数类型
2)方法返回值类型前的泛型类型,表示这是一个泛型方法

4 Collection
Collection是集合层次中的根接口
集合的继承关系

java集合的关系
在 Java2中,有一套设计优良的接口和类组成了Java集合框架Collection,使程序员操作成批的数据或对象元素极为方便。这些接口和类有很多对抽象数据类型操作的API,而这是我们常用的且在数据结构中熟知的。例如Map,Set,List等。并且Java用面向对象的设计对这些数据结构和算法进行了封装,这就极大的减化了程序员编程时的负担。程序员也可以以这个集合框架为基础,定义更高级别的数据抽象,比如栈、队列和线程安全的集合等,从而满足自己的需要。 

Java2的集合框架,抽其核心,主要有三种:List、Set和Map。如下图所示: 

需要注意的是,这里的 Collection、List、Set和Map都是接口(Interface),不是具体的类实现。 List lst = new ArrayList(); 这是我们平常经常使用的创建一个新的List的语句,在这里, List是接口,ArrayList才是具体的类。 

常用集合类的继承结构如下: 
Collection<--List<--Vector 
Collection<--List<--ArrayList 
Collection<--List<--LinkedList 
Collection<--Set<--HashSet 
Collection<--Set<--HashSet<--LinkedHashSet 
Collection<--Set<--SortedSet<--TreeSet 
Map<--SortedMap<--TreeMap 
Map<--HashMap 

Collection 和Collections
1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
 Collection   
├List   
│├LinkedList   
│├ArrayList   
│└Vector   
│ └Stack   
└Set 
2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
  
public class TestCollections {  
      
    public static void main(String args[]) {  
        //注意List是实现Collection接口的  
        List list = new ArrayList();  
        double array[] = { 112, 111, 23, 456, 231 };  
        for (int i = 0; i < array.length; i++) {  
            list.add(new Double(array[i]));  
        }  
        Collections.sort(list);  
        for (int i = 0; i < array.length; i++) {  
            System.out.println(list.get(i));  
        }  
        // 结果:23.0 111.0 112.0 231.0 456.0  
    }  
}  
List: 
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下 >标)来访问List中的元素,这类似于Java的数组。 

Vector: 
基于数组(Array)的List,其实就是封装了数组所不具备的一些功能方便我们使用,所以它难易避免数组的限制,同时性能也不可能超越数组。所以,在可能的情况下,我们要多运用数组。另外很重要的一点就是Vector是线程同步的(sychronized)的,这也是Vector和ArrayList 的一个的重要区别。 

ArrayList: 
同Vector一样是一个基于数组上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector好一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。 

LinkedList: 
LinkedList不同于前面两种List,它不是基于数组的,所以不受数组性能的限制。 
它每一个节点(Node)都包含两方面的内容: 
1.节点本身的数据(data); 
2.下一个节点的信息(nextNode)。 
所以当对LinkedList做添加,删除动作的时候就不用像基于数组的ArrayList一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了,这是LinkedList的优势。 

List总结: 

所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ]
 

所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]
 

所有的List中可以有null元素,例如[ tom,null,1 ]
 

基于Array的List(Vector,ArrayList)适合查询,而LinkedList 适合添加,删除操作
 

Set: 
Set是一种不包含重复的元素的无序Collection。 

HashSet: 
虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在 HashMap的基础上来实现的,这个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。看看 HashSet的add(Object obj)方法的实现就可以一目了然了。
public boolean add(Object obj) {   
   return map.put(obj, PRESENT) == null;   
}   
这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的。 

LinkedHashSet: 
HashSet的一个子类,一个链表。 

TreeSet: 
SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。 

Set总结: 

Set实现的基础是Map(HashMap)
 

Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象

Map: 
Map 是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个 Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求,你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。 

Map有两种比较常用的实现:HashMap和TreeMap。 

HashMap也用到了哈希码的算法,以便快速查找一个键, 

TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。 
键和值的关联很简单,用put(Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。 

其它: 
一、几个常用类的区别 
1.ArrayList: 元素单个,效率高,多用于查询 
2.Vector: 元素单个,线程安全,多用于查询 
3.LinkedList:元素单个,多用于插入和删除 
4.HashMap: 元素成对,元素可为空 
5.HashTable: 元素成对,线程安全,元素不可为空 

二、Vector、ArrayList和LinkedList 
大多数情况下,从性能上来说ArrayList最好,但是当集合内的元素需要频繁插入、删除时LinkedList会有比较好的表现,但是它们三个性能都比不上数组,另外Vector是线程同步的。所以: 
如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List; 
如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList; 
如果在多线程条件下使用,可以考虑Vector; 
如果需要频繁地删除插入,LinkedList就有了用武之地; 
如果你什么都不知道,用ArrayList没错。 

三、Collections和Arrays 
在 Java集合类框架里有两个类叫做Collections(注意,不是Collection!)和Arrays,这是JCF里面功能强大的工具,但初学者往往会忽视。按JCF文档的说法,这两个类提供了封装器实现(Wrapper Implementations)、数据结构算法和数组相关的应用。 
想必大家不会忘记上面谈到的“折半查找”、“排序”等经典算法吧,Collections类提供了丰富的静态方法帮助我们轻松完成这些在数据结构课上烦人的工作: 
binarySearch:折半查找。 

sort:排序,这里是一种类似于快速排序的方法,效率仍然是O(n * log n),但却是一种稳定的排序方法。 

reverse:将线性表进行逆序操作,这个可是从前数据结构的经典考题哦! 

rotate:以某个元素为轴心将线性表“旋转”。 

swap:交换一个线性表中两个元素的位置。 
…… 
Collections还有一个重要功能就是“封装器”(Wrapper),它提供了一些方法可以把一个集合转换成一个特殊的集合,如下: 

unmodifiableXXX:转换成只读集合,这里XXX代表六种基本集合接口:Collection、List、Map、Set、SortedMap和SortedSet。如果你对只读集合进行插入删除操作,将会抛出UnsupportedOperationException异常。 

synchronizedXXX:转换成同步集合。 

singleton:创建一个仅有一个元素的集合,这里singleton生成的是单元素Set, 
singletonList和singletonMap分别生成单元素的List和Map。 

空集:由Collections的静态属性EMPTY_SET、EMPTY_LIST和EMPTY_MAP表示。

是集合层次的根接口,学习抽象父级的公共方法
Collection集合方法总结
单个集合的操作:

boolean add(E e) 将指定元素添加到集合中
void clear() 清空集合
boolean contains(Object o) 判断本集合是否包含指定的元素
boolean equals(Object o) 比较集合对象与参数对象o是否相等
int hashCode() 返回本集合的哈希码值。
boolean isEmpty() 判断本集合是否为空
boolean remove(Object o) 从本集合中移除指定元素o
int size() 返回本集合中元素的个数
Object[] toArray() 将本集合转为数组

集合间的操作:

boolean addAll(Collection<> c) 将c集合中的所有元素添加到本集合中
boolean containsAll(Collection<> c) 判断本集合是否包含c集合的所有元素
boolean removeAll(Collection<> c) 移除本集合中属于参数集合c的所有元素
boolean retainAll(Collection<> c) 保留本集合与参数集合c的公共元素


集合的迭代:

Iterator iterator() 返回本集合的迭代器

//5.集合的迭代/遍历
/*迭代步骤:
1.获取迭代器 集合名.iterator();
2.通过迭代器判断集合中是否有下一个元素可以迭代 迭代器.hasNext()
3.获取当前迭代到的元素
注意:迭代器的泛型取决于要迭代的集合的泛型,比如c2是Integer*/
Iterator<Integer> it = c2.iterator();
while(it.hasNext()){
    Integer num = it.next();
    System.out.println(num);
}

测试常用方法
可以查询API手册进行方法的练习
5. List接口
5.1 List接口的特点
List集合是有下标的
List集合是有顺序的
List集合可以存放重复的数据
5.2 List集合方法总结
单个集合间的操作

void add(int index, E element) 在集合的指定下标index处插入指定元素element
E get(int index) 返回本集合中指定下标index处的元素
E remove(int index) 移除本集合中指定下标index处的元素
E set(int index, E element) 用参数元素element替换集合中指定下标index处的元素
int indexOf(Object o) 判断指定元素o在本集合中第一次出现的下标,如果不存在,返回-1
int lastIndexOf(Object o) 判断指定元素o在本集合中最后一次出现的下标,如果不存在,返回-1
List subList(int fromIndex, int toIndex) 截取子集合,包含formidex处的元素,不包含toIndex处的元素


1. List接口
1.1 List接口的特点
List集合是有下标的
List集合是有顺序的
List集合可以存放重复的数据
1.2 List集合方法总结
单个集合间的操作

void add(int index, E element) 在集合的指定下标index处插入指定元素element
E get(int index) 返回本集合中指定下标index处的元素
E remove(int index) 移除本集合中指定下标index处的元素
E set(int index, E element) 用参数元素element替换集合中指定下标index处的元素
int indexOf(Object o) 判断指定元素o在本集合中第一次出现的下标,如果不存在,返回-1
int lastIndexOf(Object o) 判断指定元素o在本集合中最后一次出现的下标,如果不存在,返回-1
List subList(int fromIndex, int toIndex) 截取子集合,包含formidex处的元素,不包含toIndex处的元素

集合间的操作与集合的迭代

boolean addAll(int index, Collection<> c) 将参数集合c中的所有元素,插入到本集合中指定的下标index处
ListIterator listIterator() 返回此列表元素的迭代器,这个是List自己的,不太常用,可以逆序迭代

1.3 ArrayList的特点:
1.List接口的实现类
2.底层的数据结构是数组,内存空间是连续的
3.元素有下标,有序,允许存放重复的元素
4.通常可以根据下标进行操作
5.增删操作比较慢,查询操作比较快[数据量比较大时]

1.4 LinkedList的特点:
1.List接口的实现类
2.底层的数据结构是链表,内存空间是不连续的
3.元素有下标,有序,允许存放重复的元素
4.通常进行首尾节点的操作比较多
5.增删操作比较快,查询操作比较慢[数据量比较大时]
注意:LinkedList的查询操作也不是都慢,首尾操作还是很快的
简单方法:

void addFirst(E e) 添加首元素
void addLast(E e) 添加尾元素
E removeFirst() 删除首元素
E removeLast() 删除尾元素
E getFirst() 获取首元素
E getLast() 获取尾元素
E element() 获取首元素

功能一致但是名字不太好记的方法:

boolean offer(E e) 添加尾元素
boolean offerFirst(E e) 添加首元素
boolean offerLast(E e) 添加尾元素
E peek() 获取首元素
E peekFirst() 获取首元素
E peekLast() 获取尾元素
E poll() 返回并移除头元素
E pollFirst() 返回并移除头元素
E pollLast() 返回并移除尾元素


2. Set接口
Set接口的特点
set集合没有重复的元素
set集合的元素是无序的
set集合可以存null值,并且null最多有一个
我们自定义对象如果想去重,需要在自定义类中添加重写的equals()与hashCode()

3. Map接口
Map接口的特点
map集合的结构是:键值对、KEY与VALUE、Map.Entry<K,V>的映射关系
map中key值不允许重复,如果重复,对应的value会被覆盖
map中的映射关系是无序的
map没有自己的迭代器,所以迭代时通常需要转成set集合来迭代
Map集合方法总结
简单方法:

void clear() 清空集合
boolean equals(Object o) 判断集合对象与参数o是否相等
int hashCode() 返回本集合的哈希码值
boolean isEmpty() 判断集合是否为空
int size() 返回本集合中键值对的个数

map单个集合间的操作

boolean containsKey(Object key) 判断map中是否包含指定的key
boolean containsValue(Object value) 判断map中是否包含指定的value
V get(Object key) 根据指定的key返回对应的value,如果不存在,返回null
V remove(Object key) 删除本集合中参数key对应的键值对
V put(K key, V value) 向集合中添加映射关系(键值对)
void putAll(Map<> m) 向本集合中添加m集合的所有映射关系(键值对)

map的迭代

Collection values() 把本map中的Value值取出放入一个Collection中并返回这个Collection
Set keySet() 把本map中的Key值取出放入一个Set集合中并返回这个Set集合
Set<Map.Entry<K,V>> entrySet()
把本map中的每一对KV都看成是一个Entry,把所有的Entry取出放入一个Set集合中并返回这个Set集合

HashMap的存储过程:
HashMap的结构是数组+链表 或者 数组+红黑树 的形式
HashMap底层的Entry[ ]数组,初始容量为16,加载因子是0.75f,扩容按约为2倍扩容
当存放数据时,会根据hash(key)%n算法来计算数据的存放位置,n就是数组的长度,其实也就是集合的容量
当计算到的位置之前没有存过数据的时候,会直接存放数据
当计算的位置,有数据时,会发生hash冲突/hash碰撞
解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素
也就是说数组中的元素都是最早加入的节点
如果链表的长度>8并且数组长度>64时,链表会转为红黑树,当链表的长度<6时,会重新恢复成链表

1. 什么是进程?什么是程序?有什么区别?
程序:数据与指令的集合,程序是静态的
进程:给程序加入了时间的概念,不同的时间进程有不同的状态
进程是动态的,就代表OS中正在运行的程序
独立性,动态性,并发性

2. 什么是并行?什么是串行?什么是并发?
CPU:电脑的核心处理器,类似于“大脑”
串行:是指同一时刻一个CPU只能处理一件事,类似于单车道
并行:相对来说资源比较充足,多个CPU可以同时处理不同的多件事,类似于多车道
并发:相对来说资源比较紧缺,多个进程同时抢占公共资源,比如多个进程抢占一个CPU

3. 什么是线程?线程与进程有什么关系?
线程是OS能够进行运算调度的最小单位
一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程被称作单线程程序
注意:每个线程也有自己独立的内存空间,当然也有一部分公共的空间用于保存共享的数据

在宏观上,一个CPU看似可以同时处理多件事
在微观上,一个CPU同一时刻只能处理一件事
结论:线程的执行具有随机性,我们控制不了,是由OS底层的算法来决定的

4.线程有几种状态?它们是怎么转换的?
新建状态:new–申请PCB,进行资源的分配
就绪/可运行状态:万事俱备只欠CPU,其实是将创建好的线程对象加入到就绪队列中,等待OS选中,这个选择我们是控制不了的
执行/运行状态:就绪队列中的线程被OS选中了,正在执行
注意:只有就绪状态才能切换成执行状态
阻塞状态:线程在执行中遇到了问题:
锁阻塞、休眠阻塞、等待阻塞…问题解决后再加入到就绪队列中
终止状态:线程成功执行完毕,释放资源

线程的挂起:正在运行中的线程,由于CPU分配的时间片用完,所以需要保存当前线程运行的各项状态信息,直到CPU下次再在就绪队列中选中这个线程,恢复现场,继续执行这个线程
3.多线程实现方案总结:
多线程实现的方案一:继承Thread
1.定义自己的多线程类TicketThread,并且继承Thread
2.重写父类的run(),里面是我们自己的业务
3.创建多个自定义线程类对象
4.通过线程对象.start()将线程加入到就绪队列中
5.查看多线程抢占资源的效果

构造方法摘要
Thread() 创建一个新的线程对象,名字是系统自定义的
Thread(String name) 与上面功能一致,还可以自定义线程名
可以通过调用父类Thread的含参构造Thread(String name)
给自定义线程对象起名字,调用方式:super(name);

多线程实现的方案二:实现Runnable
1.定义自己的业务类,并且实现接口Runnable
2.在业务类中添加接口里的抽象方法run(),并实现业务
3.创建唯一的业务类对象
4.创建多个Thread类的对象,作为多个线程对象
并将刚刚的业务对象传入
5.使用多个线程类对象调用start(),将线程加入到就绪队列之中
6.查看多线程抢占资源的效果

构造方法摘要
Thread(Runnable target) 创建一个线程对象,参数为Runnable实现类的对象
Thread(Runnable target, String name) 与上面功能一致,还可以自定义线程名

虽然方案二写法较为复杂,但是方案二的优点如下:

没有继承,耦合性不强,后续仍然可以继承或者实现其他的接口 ,比较自由
可以给所有的线程对象统一业务,业务只需要发布一次,保持统一
面向接口进行编程,代码更加高级

4.多线程售票案例中问题的解决方案:
1.创建4个线程对象,售卖400张票:
解决方案:将票数设置为静态,被全局所有对象共享
可以继续预习:
2.票数出现了重卖(一张票卖给了多个人)的现象:
解决方案:使用同步代码块,确保一次只有一个线程卖票
3.票数出现了超卖(卖出了超出范围的票0 -1 -2)的现象:
解决方案:优化代码逻辑,有票的时候再卖票,没票的时候就停止,有多种方案,以测试结果为准即可。

1 同步锁
1.1 前言
经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象.

我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:

在多线程程序中 + 有共享数据 + 多条语句操作共享数据
多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务)
所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题
那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行

1.2 同步与异步
那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果
也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的

接下来介绍下同步与异步的概念:
同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
坏处就是效率会降低,不过保证了安全。
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
坏处就是有安全隐患,效率要高一些。

1.3 synchronized同步关键字
1.3.1 写法
synchronized (锁对象){undefined
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}

1.3.2 前提
同步效果的使用有两个前提:

前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

1.3.3 特点
synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!
synchronized同步关键字可以用来修饰方法,称为同步方法
同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
因为同步代码块可以保证同一个时刻只有一个线程进入
但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步

1.4.1练习-改造售票案例
创建包: cn.tedu.tickets
创建类:TestRunnableV2.java

package cn.tedu.tickets;

/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestRunnableV2 {
    public static void main(String[] args) {
        //5.创建目标业务类对象
        TicketR2 target = new TicketR2();
        //6.创建线程对象
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        //7.以多线程的方式运行
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

/*1.多线程中出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*2.同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
 * 加锁之后,就有了同步(排队)的效果,但是加锁的话,需要考虑:
 * 锁的范围:不能太大,太大,干啥都得排队,也不能太小,太小,锁不住,还是会有安全隐患*/
//1.创建自定义多线程类
class TicketR2 implements Runnable {
    //3.定义成员变量,保存票数
    int tickets = 100;
    //创建锁对象
    Object o = new Object();

    //2.实现接口中未实现的方法,run()中放着的是我们的业务
    @Override
    public void run() {
        //4.通过循环结构完成业务
        while (true) {
            /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
            /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
            //synchronized (new Object()){
            //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
            synchronized (o) {//同步代码块解决的是重卖的问题
                //如果票数>0就卖票
                if (tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名以及票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.2退出死循环--没票的时候就结束
                if (tickets <= 0) break;
            }
        }
    }
}

1.4.2 练习-改造售票案例
创建包: cn.tedu.tickets
创建类:TestThreadV2.java

package cn.tedu.tickets;

/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestThreadV2 {
    public static void main(String[] args) {
        //5.创建多个线程对象并以多线程的方式运行
        TickectT2 t1 = new TickectT2();
        TickectT2 t2 = new TickectT2();
        TickectT2 t3 = new TickectT2();
        TickectT2 t4 = new TickectT2();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义多线程类
class TickectT2 extends Thread {
    //3.新增成员变量用来保存票数
    static int tickets = 100;
    //static Object o = new Object();

    //2.添加重写的run()来完成业务
    @Override
    public void run() {
        //3.创建循环结构用来卖票
        while (true) {
            //Ctrl+Alt+L调整代码缩进
            //7.添加同步代码块,解决数据安全问题
            //synchronized (new Object()) {
            /*static的Object的对象o这种写法也可以*/
            //synchronized (o) {
            /*我们每通过class关键字创建一个类,就会在工作空间中生成一个唯一对应的类名.class字节码文件
            * 这个类名.class对应的对象我们称之为这个类的字节码对象
            * 字节码对象极其重要,是反射技术的基石,字节码对象中包含了当前类所有的关键信息
            * 所以,用这样一个唯一且明确的对象作为同步代码块的锁对象,再合适不过了*/
            synchronized (TickectT2.class) {/*比较标准的写法*/
                if(tickets > 0){
                    //6.添加线程休眠,暴露问题
                    try {
                        Thread.sleep(10);//让线程休眠,增加线程状态切换的频率
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名与票数-1
                    System.out.println(getName() + "=" + tickets--);
                }
                //4.2给程序设置一个出口,没有票的时候就停止卖票
                if (tickets <= 0) break;
            }
        }
    }
}

注意:如果是继承的方式的话,锁对象最好用"类名.class",否则创建自定义线程类多个对象时,无法保证锁的唯一

1.5 之前遇到过的同步例子
StringBuffer JDK1.0
加了synchronized ,性能相对较低(要排队,同步),安全性高
StringBuilder JDK1.5
去掉了synchronized,性能更高(不排队,异步),存在安全隐患

快速查找某个类的快捷键:Ctrl+Shift+T

2 线程创建的其他方式
2.1 ExecutorService/Executors
ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

execute(Runnable任务对象) 把任务丢到线程池
Executors 辅助创建线程池的工具类

newFixedThreadPool(int nThreads) 最多n个线程的线程池
newCachedThreadPool() 足够多的线程,使任务不必等待
newSingleThreadExecutor() 只有一个线程的线程池

2.2 练习:线程的其他创建方式
创建包: cn.tedu.tickets
创建类: TestThreadPool.java

package cn.tedu.tickets;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*本类用于测试线程池*/
public class TestThreadPool {
    public static void main(String[] args) {
        //5.创建接口实现类TicketR3类的对象作为目标业务对象
        TicketR3 target = new TicketR3();
        /*Executors是用来辅助创建线程池的工具类对象
        * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
        * 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/
        //6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            /*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/
            pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/
        }
    }
}
//同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3
//1.创建自定义多线程类
class TicketR3 implements Runnable {
    //3.定义成员变量,保存票数
    int tickets = 100;
    //创建锁对象
    Object o = new Object();

    //2.实现接口中未实现的方法,run()中放着的是我们的业务
    @Override
    public void run() {
        //4.通过循环结构完成业务
        while (true) {
            /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
            /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
            //synchronized (new Object()){
            //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
            synchronized (o) {//同步代码块解决的是重卖的问题
                //如果票数>0就卖票
                if (tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名以及票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.2退出死循环--没票的时候就结束
                if (tickets <= 0) break;
            }
        }
    }
}

3 拓展:线程锁
3.1 悲观锁和乐观锁
悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
3.2 两种常见的锁
synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

3.3 尝试用读写锁改造售票案例
package cn.tedu.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 本类用于改造售票案例,使用可重入读写锁
 * ReentrantReadWriteLock
 * */
public class TestSaleTicketsV3 {
    public static void main(String[] args) {
        SaleTicketsV3 target = new SaleTicketsV3();
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
class SaleTicketsV3 implements Runnable{
    static int tickets = 100;
    //1.定义可重入读写锁对象,静态保证全局唯一
    static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
    @Override
    public void run() {
        while(true) {
            //2.在操作共享资源前上锁
            lock.writeLock().lock();
            try {
                if(tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                if(tickets <= 0) break;
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了
                lock.writeLock().unlock();
            }
        }
    }

3.4 两种方式的区别
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。
1.多线程数据安全隐患解决方案
1. 出现数据安全问题的原因:
多线程程序
多个线程拥有共享数据
多条语句操作共享数据
2. 同步与异步
异步:是多个线程抢占资源的效果,不排队,所以效率高,但是数据不安全
同步:每次只有一个线程独占资源,排队,所以效率低,但是安全,为了安全必要应该牺牲一部分资源
synchronized也被称作同步关键字

3. 解决方案:加锁synchronized
同步代码块【常用】,格式:
synchronized(唯一的锁对象){undefined
可能出现数据安全问题的所有代码
}

4.使用同步时的注意事项:
锁对象必须唯一!!!
比如:如果是实现接口的方式,只创建了一个目标业务类对象(接口实现类对象),那么也只有一个锁对象
比如2:如果是继承Thread类的方式,你可能要创建多个子类的对象,那这个时候需要给锁对象加static,保证锁对象唯一被所有对象共享
注意:类名.class字节码对象作为锁对象,不管哪种实现多线程的方式都可以用哦,不过一般在继承Thread实现方式使用较多
锁对象的类型不做限制,只要能保证唯一即可
加锁的范围需要认真考虑
不能太大,也不能太小,太大浪费效率,太小锁不住

5.多线程售票案例中问题的解决方案:
1.创建4个线程对象,售卖400张票:
解决方案:将票数设置为静态,被全局所有对象共享
2.票数出现了重卖(一张票卖给了多个人)的现象:
解决方案:使用同步代码块,确保一次只有一个线程卖票
3.票数出现了超卖(卖出了超出范围的票0 -1 -2)的现象:
解决方案:优化代码逻辑,有票的时候再卖票,没票的时候就停止,有多种方案,以测试结果为准即可。
详细代码与笔记请参照:

2 线程的创建方式3
多线程编程实现方案三:Executors 创建线程池的方式

创建线程池的工具类:
Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
创建好的线程池对象:ExecutorService
使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中
这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
注意:线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
合理利用线程池可以拥有的优势:
1. 降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
2. 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
3. 提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
防止因为创建多个线程消耗过多的内存导致服务器的崩溃
【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】

4.注解
1.JDK自带的注解(5个)
        要求大家掌握的是@Override注解,这个注解可以加在方法上,用来表示这是一个重写的方法
1
2.元注解5个
      元注解是用来定义其他注解的注解,也就是说,注解的语法与JAVA不同,是靠注解来定义的
      1. 定义注解的格式:@interface 注解名
      2. 可以根据元注解对注解进行设置:
      要求大家掌握的是
      表示被描述的注解可以使用的位置:值可以多选

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
表示被描述的注解的声明周期:注意值只能3选1
@Retention(RentionPolicy.RUNTIME/SOURCE/CLASS)

3.自定义注解
           1. 我们也可以根据自己的需求来定义个性化的注解
               使用的是@interface 注解名来定义的,主要使用的就是上面的两个元注解
           2. 除此之外,我们还可以给注解加功能,比如注解的属性:
               格式:属性类型 属性名(); 比如:int age();
               注意:定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
                         如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0;
           3.我们还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制
              注意:特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice("apple")
                        特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default "apple";
             注意:如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value="apple",age=10)
             
             
1 设计模式
概念:是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
单例设计模式:确保代码中本类的实例只有一个
实现思路:
方案一:饿汉式
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:公共方法需要设置成静态–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
对象也需要设置成静态的–这个对象需要在静态方法中被返回,而静态只能调用静态
方案二:懒汉式
==延迟加载的思想:==我们有的时候有些资源并不是需要第一时间就创建出来,所以需要延迟到需要时再创建
这样既可以提升性能,又可以节省资源
1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
2)创建了一个本类类型的引用类型变量【这个变量后续用来保存创建出来的对象的地址值】
3)提供公共的全局访问点向外界返回本类的唯一的一个对象
注意:这个公共的方法里,需要做判断
如果引用类型的变量值为null,说明:之前没有创建过本类对象–创建后再赋值给引用类型变量,并把它返回
如果引用类型的变量值不为null,说明:
之前有创建过本类对象,这个引用类型变量保存就是地址值,本次不再新建对象,直接返回
这个只是一个开始,远远没有结束,大家还可以继续拓展:装饰者 建造者 工厂
2. 反射的概念
反射是Java这门语言中比较有特点的一个特征,反射非常强大,我们可以通过反射获取目标类当中的资源,甚至是私有资源
不仅仅如此,我们甚至还可以使用资源,并且创建对象,所以反射是一个经常被使用到的技术
开发过程中,我们有的时候并不能拿到源代码,但是又需要使用资源,那这个时候反射的出现就很有必要了

3. 反射需要用到的API
3.1 获取字节码对象
Class.forName(“类的全路径”); 注意:传入的是类的全路径名,包含包名.类名,而且会抛出异常
类名.class 注意:这个写法需要自己手动接一下获取到的字节码对象,不能用快捷方式的
对象.getClass(); 注意:经常与匿名对象一起使用
3.2 常用方法
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.getConstructor(int.class,String.class)//要先获取构造方法
clazz.newInstance(666,”海绵宝宝”);//再执行含参构造创建对象

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
field.setAccessible(true);//使私有成员允许访问
field.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
field.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

4. 关于反射的学习方式
如果能够直接操作源码,就不需要使用反射
反射经常用于源码与三大框架底层之中,熟练掌握有助于编程思想的提高
反射的思路与正常使用略有区别,所以需要多练习,掌握这些写法

1 什么是反射?
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

2 为什么需要反射?
如果想创建对象,我们直接new User(); 不是很方便嘛,为什么要去通过反射创建对象呢?

那我要先问你个问题了,你为什么要去餐馆吃饭呢?
例如:我们要吃个牛排大餐,如果我们自己创建,就什么都得管理。
好处是,每一步做什么我都很清晰,坏处是什么都得自己实现,那不是累死了。牛接生你管,吃什么你管,屠宰你管,运输你管,冷藏你管,烹饪你管,上桌你管。就拿做菜来说,你能有特级厨师做的好?
那怎么办呢?有句话说的好,专业的事情交给专业的人做,饲养交给农场主,屠宰交给刽子手,烹饪交给特级厨师。那我们干嘛呢?
我们翘起二郎腿直接拿过来吃就好了。
再者,饭店把东西做好,不能扔到地上,我们去捡着吃吧,那不是都成原始人了。那怎么办呢?很简单,把做好的东西放在一个容器中吧,如把牛排放在盘子里。

我们在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。有点抽象,别着急,我们做个案例,你就立马清晰。

3 反射需要用到的API
3.1 获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
3.2 常用方法
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

4 反射的应用
4.1 创建 : 测试物料类
创建包: cn.tedu.reflection
创建类: Student.java*

package cn.tedu.review;
/*本类用于复习反射的物料类*/
public class Student {
    //1.定义成员变量
    private String name;
    public int age;

    //2.给被封装属性提供get与set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    //3.生成本类的无参构造与全参构造
    public Student(){}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //4.提供本类的普通方法
    public void play(){
        System.out.println("今天大结局,放学后我要写1W行代码玩玩~");
    }
    public void sunDay(int n){
        System.out.println("国庆一共放"+n+"天");
    }
    //5.为了查看学生对象的具体属性与属性值,重写toString()
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
        }
}
4.2 练习 : 获取类对象
创建包: cn.tedu.reflection
创建类: TestReflect.java

package cn.tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Method;
import java.util.Arrays;

/*本类用于反射的测试*/
public class TestReflect {
    //1.创建程序的入口函数main()--不用
    /*单元测试方法:是Java中最小的测试单位,使用灵活,推荐指数:5颗星
    * 语法要求:@Test + public + void + 没有参数
    * 注意:使用时需要导包:Add JUnit 4 library to the build path
    * 导包后的效果:import org.junit.Test
    * 执行方式:选中方法名前绿色的小三角,成功运行会有绿色的小对勾
    * */
    //2.通过单元测试方法,获取目标类Student对应的字节码对象
    @Test
    public void getClazz() throws ClassNotFoundException {
        //练习获取字节码对象的3种方式
        Class<?> clazz1 = Class.forName("cn.tedu.review.Student");
        Class<?> clazz2 = Student.class;
        Class<?> clazz3 = new Student().getClass();

        //打印的是Student类对应的字节码对象
        System.out.println(clazz1);//class cn.tedu.reflection.Student
        //获取当前字节码对象clazz1的名字
        System.out.println(clazz1.getName());//cn.tedu.reflection.Student
        //通过字节码对象,获取Student类的类名
        System.out.println(clazz2.getSimpleName());
        //通过字节码对象,获取Student类对应的包对象
        System.out.println(clazz3.getPackage());
        //通过字节码对象,先获取Student类对应的包对象,再获取这个包对象的名字
        System.out.println(clazz3.getPackage().getName());
    }
    
4.3 练习 : 获取成员变量
package cn.tedu.reflection;

import java.lang.reflect.Field;

import org.junit.Test;

/**本类用来测试反射*/
public class TestReflect {
    //3.通过单元测试方法练习引用类型数组的定义与遍历
    @Test
    public void getStu() {
        //1.创建Student类的3个对象
        Student s1 = new Student("张三", 3);
        Student s2 = new Student("李四", 4);
        Student s3 = new Student("王五", 5);
        //2.创建数组将刚刚的3个对象存入数组中
        Student[] s = {s1, s2, s3};
        //3.直接打印数组,查看数组中的元素
        System.out.println(Arrays.toString(s));
        //4.遍历学生数组,拿到每一个学生对象,做进一步的操作
        for (Student stu : s) {
            //System.out.println(stu);
            stu.play();//通过遍历到的对象,执行play()
            System.out.println(stu.age);//通过遍历到的对象,打印age属性
        }
    }

    //4.通过单元测试方法,获取Student类中的成员变量
    @Test
    public void getFie() throws ClassNotFoundException {
        //1.获取字节码对象
        Class<?> clazz = Class.forName("cn.tedu.review.Student");
        //2.通过字节码对象获取成员变量们
        Field[] fs = clazz.getFields();
        //3.遍历数组,获取每个成员变量的具体信息
        /*注意!目前成员变量的修饰符必须是public的才能获取到,不然,像默认修饰符也是获取不到的*/
        for(Field f : fs){
            System.out.println(f.getName());//通过本轮循环到的字段对象获取字段名
            System.out.println(f.getType());//通过本轮循环到的字段对象获取字段的类型
        }

    }
}
4.4 练习 : 通过字节码对象获取类的成员方法
package cn.tedu.reflection;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.junit.Test;

/**本类用来测试反射*/
public class TestReflect {
    //5.通过单元测试方法,获取Student类中的成员方法
    @Test
    public void getFunction() {
        //1.获取字节码对象
        Class<?> clazz = Student.class;
        //2.通过字节码对象获取目标类中的成员方法们
        Method[] ms = clazz.getMethods();
        //3.通过高效for循环遍历数组,拿到每一个方法对象
        for (Method m : ms) {
            System.out.println(m);//直接打印遍历到的方法对象
            System.out.println(m.getName());//通过方法对象获取方法名
            Class<?>[] pt = m.getParameterTypes();//通过方法对象获取方法所有参数的数组
            System.out.println(Arrays.toString(pt));//打印方法参数的数组
        }

    }
    4.5 练习 : 通过字节码对象获取类的构造方法
package cn.tedu.reflection;

import java.lang.reflect.Constructor;
import java.util.Arrays;

import org.junit.Test;

/**本类用来测试反射*/
public class TestReflect {
    //6.通过单元测试方法,获取Student类中的构造方法
    @Test
    public void getCons() {
        //1.获取字节码对象
        Class<?> clazz = new Student().getClass();
        //2.通过字节码对象获取目标类Student的构造方法们
        Constructor<?>[] cs = clazz.getConstructors();
        //3.通过高效for循环遍历数组
        for(Constructor c : cs){
            System.out.println(c.getName());//打印本轮遍历到的构造方法的名字
            Class[] pt = c.getParameterTypes();//通过本轮遍历到的构造函数对象获取构造函数的参数类型
            System.out.println(Arrays.toString(pt));//打印参数类型
        }
    }
    
    4.6 练习 : 创建对象
package cn.tedu.reflection;

import java.lang.reflect.Constructor;

import org.junit.Test;

/**本类用来测试反射*/
public class TestReflect {
//7.通过单元测试方法,创建Student目标类的对象
    @Test
    public void getObject() throws Exception {
        //1.获取字节码对象
        Class<?> clazz = Student.class;
        //2.通过反射技术创建目标类的对象,注意抛出异常
        /*反射创建对象方案1:通过触发目标类的无参构造创建对象*/
        Object o = clazz.newInstance();
        System.out.println(o);//这一步已经获取到了对象Student{name='null', age=0}

        /*反射创建对象方案2:通过触发目标类的全参构造创建对象
        * 思路:
        * 1.先获取指定的构造函数对象,注意需要指定构造函数的参数,传入的是.class字节码对象
        * 2.通过刚刚获取到的构造函数对象创建Student目标类的对象,并且给对象的属性赋值
        * */
        //3.获取目标类中指定的全参构造
        Constructor<?> c = clazz.getConstructor(String.class, int.class);
        //System.out.println(c);
        //4.通过获取到的构造函数:创建对象+给对象的属性赋值
        Object o2 = c.newInstance("赵六", 6);
        System.out.println(o2);
    }
}

4.7 熟悉API
自己创建类练习,获取类中的所有资源,熟悉反射中涉及的API
5 暴力反射
指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:

5.1 创建 : 测试物料类
创建包: cn.tedu. reflection
创建类: Person.java*

package cn.tedu.review;
/*本类用作暴力反射测试的物料类*/
public class Person {
    //1.提供私有属性
    private String name;
    private int age;

    //2.提供私有方法
    private void save(int n,String s){
        System.out.println("save()..."+n+s);
    }
    private void update(){
        System.out.println("update()...");
    }
}

5.2 练习 : 创建测试类
创建包: cn.tedu. reflection
创建类: TestReflect2.java

package tedu.reflection;

import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/*本类用于测试暴力反射*/
public class TestReflect2 {
   /*1.通过暴力反射获取与操作属性*/
    @Test
    public void getFie2() throws Exception {
        //1.获取字节码对象
        Class<?> clazz = Person.class;
        //2.获取指定的私有属性,传入的是属性名,注意抛出异常
        Field field = clazz.getDeclaredField("name");
        //3.根据刚刚获取到的属性对象,查看属性的信息
        System.out.println(field);//直接打印获取到的字段对象
        System.out.println(field.getType().getName());//java.lang.String
        System.out.println(field.getType());//class java.lang.String

        //4.设置属性的值
        //4.1 需要指定到底是给哪个对象的name属性设置值,没有对象就创建对象
        Object obj = clazz.newInstance();//触发无参构造利用反射创建对象

        //4.2暴力反射,需要设置私有可见权限!!!
        field.setAccessible(true);

        //4.3通过字段对象给刚刚创建好的对象obj设置属性值为海绵宝宝
        //field就是我们刚刚获取的name属性
        //set(m,n)--m是给哪个对象的name属性设置值,n是设置的值是什么
        field.set(obj,"海绵宝宝");
        //4.4 打印查看刚刚设置的属性值
        //field.get(m)--field代表的就是Person类的name属性,m是查看哪个对象的这个属性值
        System.out.println(field.get(obj));
    }

    //2.定义单元测试方法,利用暴力反射操作Person类中的私有属性age【巩固练习】
    @Test
    public void getFie3() throws Exception {
        //1.获取字节码对象
        Class<?> clazz = Person.class;
        //2.获取指定的私有属性对象
        Field f = clazz.getDeclaredField("age");
        //3.根据获取到的属性对象,查看相关信息,比如属性的类型
        System.out.println(f.getType().getName());
        //4.操作:设置属性的值:一共需要三个元素:给哪个对象【1】的哪个属性【2】设置一个什么值【3】
        //4.1 需要先指定给哪个对象的这个age属性设置值
        Object obj = clazz.newInstance();
        //4.2 在给属性设置值之前,需要设置权限私有可见,否则报错!
        f.setAccessible(true);
        //4.3通过刚刚获取到的age属性对象,给obj对象设置值
        f.set(obj,17);
        //4.4打印查看刚刚的属性值是否设置成功
        System.out.println(f.get(obj));
    }
    /*3.单元测试2:暴力反射获取和设置私有方法*/
    @Test
    public void getFunction() throws Exception {
        //1.获取Class字节码对象
        Class<?> clazz = Person.class;
        //2.通过暴力反射获取私有方法
        /*getDeclaredMethod(m,x,y,z...)
        * m:要获取的方法名
        * x,y,z...可变参数,是这个方法的参数类型,但注意要加“.class”
        * */
        Method method = clazz.getDeclaredMethod("save",int.class,String.class);
        //3.1没有对象就通过反射的方式创建对象
        Object obj = clazz.newInstance();
        //3.2 想要执行私有方法,也需要先设置私有可见
        method.setAccessible(true);
        /*invoke(o,x,y,z...),表示通过反射技术执行方法
        * o :要执行的是哪个对象的方法
        * x,y,z...:执行这个方法【method对象代表的之前获取到的save()】时需要传入的参数
        * */
        //3.3 通过反射技术invoke(),执行目标对象obj的目标方法method【save()】
        //save()被调用时传入的参数是100,"海绵宝宝"
        method.invoke(obj,100,"海绵宝宝");
    }
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值