Advertisement

JAVASE相关知识点

阅读量:

JavaSE

文章目录

  • JavaSE
      • IDEA快捷键
      • EXPAND
      • TIPS
    • MEMO1 前置内容

    • MEMO2 DeBug、标识符、数据类型、进制相关

    • MEMO3 运算符

    • MEMO4 switch语句、循环语句

    • MEMO5 数组、内存分配

    • MEMO6 方法、方法重载

    • MEMO7 类和对象

    • MEMO8 String、StringBuilder

    • MEMO9 ArrayList

    • STEP0 分包思想

    • STEP1 static关键字及应用

    • STEP2 继承、重写、权限修饰符、抽象类及模板、final

    • STEP3 接口、多态

    • STEP4 内部类、Lambda、常用API

    • STEP5 包装类、算法、Arrays工具类、异常

    • STEP6 Date类、集合、迭代器、数据结构

    • STEP7 泛型、TreeSet、二叉树

    • STEP8 HashSet、Map集合、可变参数

    • STEP9 Stream流、File

    • STEP10 IO流及字符流、字节流

      • 一、字节流
      • 二、字符流
    • STEP11 转换流、对象操作流、Properties、Clone

      • 三、转换流、对象操作流
      • Clone
    • STEP12 多线程

    • STEP13 线程池、网络编程

      • 线程池
      • 网络编程
      • UDP协议
    • STEP14 TCP、日志、枚举

      • 日志Logback、枚举
    • STEP15 类加载器、反射

      • 反射
    • STEP16 XML、文档约束DTD&schema

      • XML
      • DTD
      • schema
      • 注解
      • 单元测试工具Junit
      • 注解
      • 单元测试工具Junit

Typora语法

复制代码
      
      居中文字
     
     shift增加注释

流程图语法

复制代码

IDEA

复制代码

IDEA快捷键

  • F2: 高亮错误或警告快速定位
  • Shift + F6: 重命名
  • Shift/Ctrl+ Enter:向下/向上插入新行(Space + Enter)
  • Alt + 1:打开/关闭 左边工程目录
  • Alt + 4:打开/关闭 控制台
  • Alt + F7:查找整个工程中使用的某一个类、方法或者变量的位置
  • Alt + Ins:生成构造器、getter、setter
  • Alt + Shift + /:代码上移/下移一行
  • Alt + /:切换活动窗口
  • Alt + /:在方法间快速移动
  • Alt + Enter:修正代码
  • Ctrl + X/Y:删除行
  • Ctrl + H:显示类结构图(类的继承层次)
  • Ctrl + P:显示形参列表
  • Ctrl + N:全局查找类
  • Ctrl + B:快速打开光标处的类或方法(跳转到定义处)
  • Ctrl + W:可以选择单词继而语句继而行继而函数
  • Ctrl + BackSpace:按单词删除
  • Ctrl + F12:查看类的大纲
  • Ctrl + Alt + V / method().var:快速声明接收方法返回值的变量
  • Ctrl + Alt + M:抽取语句生成方法
  • Ctrl + Alt + L:代码规范化(Space + T)
  • Ctrl + Alt + T:代码块提取为循环
  • Ctrl + Alt + Space:自动补全代码
  • Ctrl + Alt + /,返回至上次浏览的位置
  • Ctrl + Shift + M:移动到光标所在行最近的大括号的始末端
  • Ctrl + Shift + /:多行注释(Space + G)
  • Ctrl + Shift + +/-:全部展开、折叠
  • num.sout:快速输出变量值
  • num.fori / arr.fori:快速生成for循环

EXPAND

数据交换原理:一个整数与另一个整数进行两次异或运算后还是原来的数据

a = a ^ b;
b = a ^ b; //b = a ^ b ^ b = a
a = a ^ b; //a = a ^ a ^ b = b

开闭原则:对扩展内容开放,对修改内容关闭

  • 不直接修改原代码,而是在同一个包下新建一个Other+原类名的类
  1. native关键字表示调用本地的方法(JDK底层或操作系统)
  2. System.out.println(System.getProperty("user.dir"));: 查看当前文件路径
  3. order by convert(name using gbk) asc:按姓名首字母排序

TIPS

若方法返回的内容为随机产生的值,返回值在不赋值变量的情况下,整个方法调用语句仅能作为参数执行一次,否则会产生多个值

复制代码
 * 返回值不固定时,方法调用后先赋值

Scanner类录入字符串数据时,nextInt()方法以enter作为录入结束标记,若之前有其它数据类型的键盘录入则会直接enter其后的字符串录入

复制代码
 * 字符串数据优先录入
 * 用next()录入字符串(以空格或Tab为结束标记)
 * 实际问题场景:switch输入int型case后再在分支语句中输入字符串

当常量和变量都能调用方法时,优先用常量调用,避免空指针

复制代码
 * “abc”.equals(str)

在操作对象前,都须进行非空判断

复制代码
 * 关闭流对象

在分支语句中(包括 if 和try…catch)进行的操作,编译器都会认为可能不会被执行,所以不能当作一个确定的结果来写逻辑;而是必须在每种可能情况下都做类似的操作,才能通过编译

重写方法可以实现扩展方法作用域的效果,如重写Object中的由protected修饰的clone方法

MEMO1 前置内容

javac.exe: 编译命令
java.exe: 解释运行时用的命令
javadoc.exe: 生成文档注释时的命令

复制代码
 * javadoc文档注释:注释内容可以被jdk工具javadoc所解析,生成一套以网页文件形式体的该程序的说明文档(写在代码之前)  

/**
文档注释
@author wyw
@version v1.0
*/

JDK(java程序开发工具包) = JRE + Java开发工具(java.exe javac.exe javadoc.exe)

JRE(Java程序运行环境) = JVM + Java SE标准类库

MEMO2 DeBug、标识符、数据类型、进制相关

  1. DeBug设置一个断点和多个断点区别:
  • 一个断点:从断点开始,每条语句依次运行
  • 多个断点:上个断点所在语句运行完后,Resume Program即跳至下一个断点,中间语句静默运行
  • 循环语句中右键断点可以跳转到指定的循环次数

标识符命名规则:不能以数字开头;不能占用关键字

复制代码
 * 小驼峰式命名:  

适用于:变量名、方法名
比如:name、firstName
* 大驼峰式命名:
适用于:类名
比如:Name、FirstName

数据类型

复制代码
 * _byte、short、char在进行运算时会直接提升为int型_
 * 对于常量之间的运算,会在编译阶段优先运算出常量结果,再予以赋值,即为java常量优化机制
数据类型 关键字 内存占用 取值范围
整数 byte 1 -128~127
整数 short 2 -215 ~ 215-1 (-32768~32767)
整数 int 4 -231 ~ 231-1 (约21亿)
整数 long 8 -263 ~ 263-1 (19位数)
浮点数 float 4 1.401298e-45到3.402823e38 (38位数)
浮点数 double 8 4.9000000e-324 到1.797693e308 (308位数)
字符 char 2 0-65535
布尔 boolean 1 true,false

二进制:

  1. 二进制:以0b或0B开头
    八进制:以数字0开头
    十六进制:以0x或0X开头
  • 任意进制转十进制:各位数 * 基数的权次幂相加 0567 = 7 * 80 + 6 * 81 + 5
  • 十进制转任意进制:十进制%基数,整数部分除尽为止,各余数从右向左排列 20 % 8 = 4,2 % 8 = 2,所以20的八进制为024
  • 二进制按每三位划分即为八进制,每四位划分则为十六进制
  1. 原码、反码、补码:

    • 正数原反补码一致
    • 负数反码为原码除符号位以外各位取反,补码为反码+1
      • 计算机中以补码形式进行运算,负数补码符号位参与运算(规定10000000表示-128)
  2. 位移运算:符号位不变,其余数据向指定方向移动

    • 左位移<<:原数据 * 2n (n为移动位数)
    • 右位移>>:原数据 / 2n
    • 无符号位运算:空出的数位统一为0(负数右移变为整数)

MEMO3 运算符

后自增自减操作符总是变量先参与操作再进行自增自减

复制代码
 * int a = 10; b = a++ + a++; //a = 12, b = 21

复合赋值运算符(+=、-=…) 自带强转效果

优先级 1 2 3 4 5 6 7 8 9 10 11 12 13 14
运算符 . () {} ! ++ * / % + - << >> >>> < <= instanceof == != & ^ &&

取反逻辑运算符使用场景:

复制代码
 * 例:除了数字3、11、14其余都可通过 ! (num == 3 | num == 11 | num == 14)

&& 和 || 具有短路效果,即在已经确定运算结果的情况下,不会进行后续运算

MEMO4 switch语句、循环语句

switch语句:

复制代码
 *

switch()中可以接收的数据类型:byte、short、int、char、String、枚举 (仅取值范围较小的数据类型)

复制代码
 *

jdk14开始,switch语句中,一个case后可以编写多个值

复制代码
 *

switch中变量一旦匹配上case或default,便不再继续匹配关键字,仅会依次执行语句

复制代码
 *

无论default写在哪儿,一定是最后匹配

案例:switch语句执行顺序

复制代码
    • break关键字仅能在循环语句和switch语句中使用
    • continue关键字仅能在循环语句中使用

案例:输出一定范围内的质数

复制代码

MEMO5 数组、内存分配

  1. 定义格式:
  • int[ ] arr = new int[0];
  • int[ ] arr = {1,2,3};或 int[ ] arr = new int[ ]{1,2,3};//静态初始化中,后中括号内不得定义数组长度
  1. 注意事项:
  • 数组静态初始化时,每个数组索引对应的值先默认为0或null,随即再被静态赋值
  • 例:int[ ][ ] = new int[3][ ] ; arr[0] = null ; //因为一维数组没有初始化,所以没有地址值
  • 二维数组中的一组一维数组地址用另一个一维数组地址赋值时,即被新的数组取代, 即不受定义二维数组的数组长度限制
  • Arrays.toString():按既定格式表示数组元素
复制代码
>     //二维数组遍历
>     for (int i = 0 ; i < arr.length ; i++) {  
>       for (int j = 0 ; j < arr[i].length ; j++) {  
>               System.out.println(arr[i][j]);  }}
>  
>  
>       
>       
>       
>       
>  
>
  1. java内存分配
    *

栈:方法运行时所进入的内存

复制代码
 *

堆:new出来的东西会在堆中开辟空间并产生地址

复制代码
 *

方法区:字节码文件(.class)加载时进入,随后JVM自动调用main方法,然后进栈内存

复制代码
 *

本地方法栈

复制代码
 *

寄存器

整体流程 :字节码文件加载进方法区,同时加载所有方法进方法区,随后JVM自动调用main方法进栈;在栈内调用其他方法,执行结束后按先进后出顺序出栈,最后main方法出栈

一维、二维数组内存图

复制代码
>     ![二维数组内存图](https://ad.itadn.com/c/weblog/blog-img/images/2025-02-03/JyObrwGvtcM9gWn7NpQkui1ZHlsR.png)

4. 空指针异常:当一个引用数据类型(数组,类,接口)的变量,记录到null之后,代表和堆内存的连接就被切断了,此时若访问该地址,即出现此错误

MEMO6 方法、方法重载

  1. return在方法中全部被条件语句(if和for循环)控制时,编译器会报错,因为return可能无法被执行
  2. 方法中传递参数时,基本数据类型传递的是数值,引用数据类型传递的是地址值
  3. 方法重载必要条件: 同一个类中 ,方法名相同,参数不同
    • 参数不同:个数不同、类型不同、顺序不同

MEMO7 类和对象

  1. 类的字节码文件被调用时才会加载进方法区

  2. 构造方法:用于创建对象,可以给成员变量赋值

    • 构造方法中可以写return,但是不能有返回值
    • 构造方法不能被对象调用
    • JavaBean中要求提供一个public的空参构造
      • 便于通过反射创建运行时类的对象
      • 便于子类继承此运行时类时,能够调用super();
  3. 创建对象代码执行顺序:初始化父类完成再创建子类对象
    1.

先加载类

① 加载父类:父类的静态成员

② 加载子类本身:子类本身的静态成员

复制代码
2.

创建对象

③ 加载父类非静态和构造代码块

④ 加载父类构造器内部代码

⑤ 加载子类非静态和构造代码块

⑥ 加载子类构造器内部代码

MEMO8 String、StringBuilder

一、String

  1. String对象间使用==作比较时,比较的是字符串地址值而非内容

  2. 打印String对象并非显示地址值

    • System.out.println(stu + 100 + " "); //编译不通过
      • stu在底层会调用toSting方法,但stu实际上是地址值,地址值和常量不能直接做运算
  3. String字符串一旦被创建,该字符串对象就不可更改

复制代码
>     String s = "abc";
>     s = "def"         //s指向新对象的地址,原来的"abc"内容没有变化,直到被JVM自动清理
>     //相当于
>     Student stu = new Student("张三",24)
>     stu = new Student("李四",25)  
>  
>  
>       
>       
>       
>       
>       
>  
>
  1. 字符串常量池:
  • 在使用 " " 创建字符串对象的时候,会先检查该数据在常量池中是否存在,若存在,则对象指向该地址;使用多个对象创建相同内容字符串,都会指向常量池中同一地址
  • 在使用带参构造创建字符串常量时(new String("abc"))(该条语句创建了两个字符串对象),检查常量池中是否存在,存在则拷贝一份副本进堆内存,再将堆内存中的地址交给对象;不存在则创建,再重复上述地址赋值操作
    • JDK8以后,字符串常量池在堆内存中
  1. 字符串和字符数组可以相互转换

    • str.toCharArray()
    • str.charAt(int Index):返回指定索引处的元素
    • String str = new String(new char[ ] chs)
  2. String类常用方法

    • public String substring(int beginIndex):根据索引截取字符串
    • public String substring(int beginIndex,int endIndex):根据首尾索引截取字符串 [beginIndex,endIndex)
    • public void concat(String str) :在末尾添加字符串
    • public String replace(String target,String replacement):新值替换旧值
    • public String toUpperCase():小写转大写
    • public int compareTo(String s):按字典顺序依次比较两个字符串的对应字符,返回码表中对应字符串相减的结果
    • public String[] split(String regex):根据传入参数,作为规则切割字符串,并返回切割后的数据
      • str =192.168.0.1 ; String sArr = str.split(".")

二、StringBuilder

  1. 字符串拼接原理:StringBuilder sb = new StringBuilder();
复制代码
>

flowchat

op1=>operation: 系统自动创建sb对象
op2=>operation: 自动调用sb.append()拼接
append()可链式调用
op3=>operation: 自动调用sb.toString()转换成字符串
op1(right)->op2(right)->op3(right)

复制代码
>     StringBuilder线程不安全,StringBuffer线程安全(加锁),其余功能相同
>  
>  
>       
>       
>  
>

MEMO9 ArrayList

体系结构:LinkedList、HashSet、LinkedHashSet、TreeSet、ArrayList……

ArrayList原理:

复制代码

flowchat

op0=>operation: 底层先创建一个长度为0的数组
当执行add操作时,再创建一个长度为10的数组
(形参内可指定初始长度)
op1=>operation: 数据量超出长度时,会自动创建
一个原数组1.5倍大小的新数组
op2=>operation: 原数组内容自动
拷贝进新数组
op3=>operation: 超出数据存入新数组
op0(right)->op1(right)->op2(right)->op3(right)

复制代码
>     3. 格式:ArrayList\<E\> list = new ArrayList<\>();
>  
>     * <E\>:Integer、Character、其余基本数据类型首字母大写
>  
>     4. 常用方法:
>     1. 增:
>       * `public boolean add(E e)`:尾部添加数据
>       * `public void add(int Index,E e)`:插队添加数据
>       * `public boolean addAll(Collection<? extends E> c)`:将原集合的所有数据添加到新集合的末尾
>       * `public boolean addAll(int index,Collection<? extends E> c)`:将原集合的所有数据添加到新集合的指定位置
>     2. 删:
>       * `public E remove(int Index)`:删除指定索引位置的元素
>       * `public boolean remove(Object o)`:删除*首次*出现的元素(整数类型的集合中形参默认为索引而非元素)
>     3. 改:
>       * `public E set(int Index,E e)`:修改指定索引位置为传入元素
>     4. 查:
>       * `public E get(int Index)`:获取指定索引位置的数据
>       * ==public int size()==:返回集合中元素个数
>       * `public boolean contains(Object o)`:检查集合中是否包含参数
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>

STEP0 分包思想

类与类之间的访问:

复制代码
 * 同一个包下的类,可以互相直接访问
 * 不同包下的类,必须先导包才可以访问
 * 若类中使用不同包下的相同类名,只能默认导入一个包,另一个类需要使用全名访问

业务通用架构:

复制代码

flowchat

op0=>operation: entry包(程序入口)
op1=>operation: controller包(展示层)
与用户交互并封装用户数据
op2=>operation: service包(业务层)
业务逻辑处理
op3=>operation: dao包(数据层)
操作数据库(增删改查)
op0(right)->op1(right)->op2(right)->op3(right)

复制代码
>

STEP1 static关键字及应用

一、static

  1. 静态成员变量、静态成员方法:

    • 相同点:

      • 由static修饰,都 属于类 ,可由类直接调用
      • 和类一起加载,只加载一次,内存中只有一份
      • 可以被共享访问、修改
      • 同一个类中可以单独调用(不加类名.)
    • 不同点:

      • 静态成员变量只在堆内存中存储一份
      • 静态成员方法只在方法区中存储一份
  2. 使用场景:以执行一个通用功能为目的,或为方便访问

  3. 注意事项:

    • 静态方法只能访问静态成员,不可 直接访问 实例成员
    • 实例方法可以访问静态成员,也可访问实例成员
    • 静态方法中不可出现this关键字(没有对象)
    • static不能修饰局部变量(局部变量不可能随类一同加载)

二、工具类,代码块,单例模式

工具类:可将常用方法封装到工具类中,方法统一用static修饰,方便调用

复制代码
 * 工具类无需创建对象,所以可将构造器私有化

代码块:{ }

  • 静态代码块:由static修饰,属于类,与类一起优先加载一次,自动执行,用于初始化静态资源(如斗地主中初始化54张牌)
  • 局部代码块:定义在方法中,用于限定变量的生命周期,可以及早释放,提高内存利用率(不会优先执行)
  • 构造代码块:定义在类中方法外,每次调用构造器创建对象时,会优先执行代码块中代码,用于初始化实例资源。用于将多个构造方法中相同的代码,抽取到构造代码块中,提高复用性
  1. 单例模式:构造器私有,内部创建对象相关属性静态化
    *

饿汉单例:提前创建好对象,并声明一个静态变量存储

复制代码
 *

懒汉单例:需要对象时,才会创建一个对象(声明一个静态方法以返回创建好的对象)

复制代码
   *

懒汉单例中静态变量应私有化,以防初始化前 直接调用该静态变量出现空指针

复制代码
   *

好处:即用即加载,节约内存
坏处:线程不安全

复制代码
     *

解决线程不安全:获取对象的方法加锁 或 使用 双重检查锁

复制代码
>                 //双重检查锁

>                 class Student1 {
>                     //1 私有构造方法
>                     private Student1() {
>                     }
>                     //2 在类的内部声明该对象的变量
>                     private static Student1 s = null;
>                     //3 提供公共的方法(静态的)创建该对象 并返回返回该对象
>                     public static  Student1 getStudent() {
>                         if (s == null) {
>                             synchronized (Student1.class) {
>                                 if (s == null) {
>                                     s = new Student1();
>                                 }
>                             }
>                         }
>                         return s;
>                     }
>                 }
>  
>  
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>                                
>
复制代码
       * 相比于静态方法加锁,双重检查依据对象是否被初始化进行了分流,若已被初始化则直接返回对象, 避免了每个变量都须通过检查获取锁对象,提高了运行效率

STEP2 继承、重写、权限修饰符、抽象类及模板、final

一、继承

格式:public class 子类 extends 父类

定义:父类的 公共属性和方法 可以由子类共享

继承设计规范:子类们共性特征放在父类中定义,独有的特征定义在各自子类中

构造器:

复制代码
 * 子类不能继承父类的构造器
 * 子类构造器第一行语句默认为super();(可省略),以调用父类无参构造器初始化其数据空间;
 * 子类调用父类有参构造:子类构造器编写super(参数…) (参数为子类构造器形参)

构造器中使用this调用本类其他构造参数:

初始化部分参数的有参构造可以通过this()调用本类的其他有参构造以提高代码复用性,传入参数以外的参数可以写死(new关键字才会创建对象,对象个数与调用几个构造器无关)

复制代码

public A(int num){
this(num , “其他固定参数”); }
public A(int num,String str){
this.num = num;
this str = str; }

复制代码
 *

通过this调用其他构造器时,这些构造器也会通过super默认调用父类构造器


 *

由于this()和super()都只能放在构造器的第一行,所以两者不能共存于同一个构造器中

特点:

复制代码
 * 单继承:一个类只能继承一个直接父类(只有一个亲爹)
   * 父子类中特征冲突则执行就近原则,使用*super.*可以访问父类特征
   * 若支持多继承,则可能出现两个父类中有重复特征而造成调用冲突

 * 支持多层继承(子父爷)
 * java中所有的类都是Object类的子类

补充说明:

复制代码
 * 子类实际上也继承了父类的私有特征,但无法直接调用,可以暴力访问 //TODO
 * 父类的公共静态特征可以被子类名调用,且实际上可以在子类中直接无类名调用父类的静态方法

二、重写

复制代码
>     @override //重写校验注解:不构成重写时报红
>     public void method(){
>       super.method();
>       新增语句……; }
>  
>  
>       
>       
>       
>       
>  
>

注意事项:

复制代码
* 重写方法的返回值、名称、形参列表必须与被重写方法保持一致*(声明不变,重新实现)*
* 子类的返回值类型必须是父类返回值类型或其子类
* 私有、静态方法不能被重写
*  _子类重写方法的修饰权限不小于父类相应方法_
复制代码
>

flowchat

op0=>operation: public
不同包的无关类
可访问
op1=>operation: protected
同一个包内和不同包的子类
可访问
op2=>operation: default
同一个包中子类无关类
可访问
op3=>operation: private
同一个类中
可访问
op0(right)->op1(right)->op2(right)->op3(right)

复制代码
>     ***三、抽象类***
>  
>     1. 定义:若一个类中存在抽象方法,那么该类就*必须*声明为抽象类
>        * 抽象方法:将共性行为(方法)抽取到父类之后,发现该方法的实现逻辑无法在父类中给出具体定义,该方法就可以定义为抽象方法
>     2. 注意事项:
>        * 抽象类不能创建对象(以防调用到抽象方法)
>        * 抽象类中有构造方法(给子类创建对象时调用)
>        * 抽象类的子类必须重写抽象类中的所有抽象方法(子类也可声明为抽象类,但意义不大)
>        * 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
>  
>     ***四、final***
>  
>     1. 可修饰对象:
>        * ① 方法:表明该方法不能被重写
>        * ② 类:表明该类不能被继承
>        * ③ 变量:表明该变量是常量,*不能再次被赋值*
>          * 基本数据类型变量:值不能更改
>          * 引用数据类型:地址值不能更改,但可以修改对象属性
>        *  修饰成员变量时有三个赋值时机:1.声明时赋值 2.构造方法中赋值 3.*代码块中赋值*
>     2. 模板设计模式:抽象类整体可以看做一个模板,模板中不能定义的方法抽取成抽象方法,在子类中重写
>        * 可以用final修饰模板,以防被重写,破坏原结构功能
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>

STEP3 接口、多态

一、接口

  1. 定义:内部是抽象方法的特殊抽象类

    • 格式:public interface 接口名 {}
  2. JDK8后新特性:
    1. 允许在接口中定义非抽象方法,但须使用关键字default修饰。用于解决接口扩展后,实现类也必须进行非必要的重写问题

    • 格式: public default void method() { }
复制代码
2. 允许在接口中定义静态方法
   * _静态方法只能通过接口名调用,不能通过实现类类名或对象名调用_
   * static和default不能共存。~~静态方法不能被重写~~

3. 可以使用private修饰仅在接口类中使用的方法,须去掉default(JDK9)
  1. 接口实现:接口和类之间是实现关系,通过implements关键字表示

    • 接口可以单实现,也可以 多实现 :public class 实现类名 implements 接口名1,接口名2 {}
    • 接口和接口之间可以 多继承接口只能继承接口 (实现和继承的主要区别)
    • 接口的实现类必须重写接口中的所有抽象方法,或者声明为抽象类(不推荐)
  2. 接口类组成:

    • 成员变量:默认被 public static final 修饰(可省略)

      • 由于接口的多实现特性,若变量不声明为静态,则会在多个实现类中出现变量名冲突(java不允许多继承的原因之一);且静态常量可以只在内存中存在一份,节省内存。同时接口作为标准规范,不允许实现类随意修改数据,所以须用final修饰
    • 构造器:无。由于内部有抽象方法,所以不能实例化,也就没有构造器

    • 成员方法:默认被 public abstract 修饰(可省略)

  3. 接口注意事项:

    • 默认方法不是抽象方法,但可以被重写,重写时须去掉default
    • 若实现类实现了多个接口,且多个接口中存在相同的default方法声明,实现类必须重写该方法
    • 一个类的父类和接口类中出现相同的方法声明且代码逻辑不同,优先使用父类的方法(亲爹 > 干爹)
    • 接口不会默认继承Object类
  4. 接口使用思路:

    • 若类中都是抽象方法,则可将该类改进为一个接口
    • 涉及到接口大面积更新,而不想修改每一个实现类,就可以将更新的方法定义为带有方法体的default方法
    • 希望default方法调用更加简洁,可以考虑设计为静态方法(须去掉default)
    • default方法中出现了重复代码,可以考虑抽取出一个私有方法以仅供内部调用(须去掉default)

二、多态

  1. 前提:

    • 需要继承 \ 实现关系
    • 需要方法重写
    • 需要父类对象指向子类对象 Animal a = new Cat();
  2. 成员访问特点:

    • 构造方法:同继承一样,子类通过super访问父类构造方法
    • 成员变量:编译看左边(父类),执行看左边
    • 成员方法:编译看左边,执行看右边(子类)
      • 因为成员方法有重写,成员变量没有
  3. 优缺点:

    • 优点:提高了程序扩展性(定义方法时,使用父类型为参数,该方法就可以接收父类的任意子类对象)
    • 缺点:不能使用子类的特有功能
  4. 转型:

    • 向上转型:父类引用指向子类对象 Fu f = new Zi();
    • 向下转型:父类引用转为子类对象 Zi z = (Zi)f;
      • 类进行强转时,编译器会检查是否为继承关系,非继承关系则报错;
      • 接口进行强转时,由于接口是多实现,编译器会认为任何类都可以实现任何接口,所以会编译通过,但是运行时会报错
  5. instanceof:待强转变量名 instanceof 目标类型

    • 判断关键字左边的变量,是否是右边的类型,返回boolean
  6. 总结:
    1. 对象的类型永远不变
    2. 只能调用引用所属类型中的方法
    3. 运行时,会优先找子类重写后的方法,否则用父类的方法

    • 静态方法由于没有重写,所以谁调用就用谁的

STEP4 内部类、Lambda、常用API

一、内部类

创建内部类对象格式:Outer.Inner oi = new Outer().new Inner();

类型:

复制代码
1.

成员内部类:也属于类成员,随成员在实例化时一同初始化

复制代码
  1.

常用用法:

复制代码
     *

防止类名冲突:同一包下的其中一个相同类名可以声明为内部类

复制代码
     *

间接实现多继承

复制代码
       * `class A extends Super{`  

class B extends Super{ }
}

复制代码
  2.

注意事项:内部类中外部类的this格式为Outer.this

复制代码
  3.

静态成员内部类访问格式:Outer.Inner oi = new Outer.Inner(); Outer.Inner.method();

复制代码
     * _静态内部类不会随外部类加载而加载_
     *  _创建静态内部类对象时,外部构造器不会执行_
     * 静态内部类推荐不创建对象,直接使用
     *  _静态内部类中可以有常量和静态常量。因为java中常量存放在常量池中,编译时,加载常量是不需要先加载类的_


2.

局部内部类:在方法中创建的类, 只能在方法中创建对象并访问

复制代码
   * 局部内部类不仅可以访问外部类的私有成员,还可以访问所在方法的局部变量
   * 被内部类访问的局部变量会被拷贝一份到内部类中,即Inner类中存在一个成员变量,用于记录被访问的局部变量的值。若局部变量不是final的,其取值就可以被修改,而Inner对象中保存的是其原来的值,这就会出现数据不同步的问题。Java为了避免数据不同步的问题,做出了 _内部类只可以访问final的局部变量的限制_ 。在java8中,可以不使用final,如果局部变量被内部类访问,那么该局部变量相当于自动使用了final修饰。

3.

匿名内部类 :将继承\实现、方法重写、创建对象三个步骤整合在一起

复制代码
   *

格式:new 类名或接口名(){

​ 重写语句;

​ }.重写方法名();

复制代码
   *

匿名内部类编译后会产生一个单独的字节码文件,格式为 外部类$数字.class

二、Lambda表达式

格式:

复制代码
>     doMethod( new interfaceA(形参) {

>         public void method() {
>             代码块
>         }
>     });
>     //Lambda格式
>     doMethod( (形参) -> {
>         代码块
>     });
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>

使用前提:

复制代码
 * 仅适用于接口
 * 接口中有且仅有一个抽象方法

省略格式:

复制代码
 * 参数类型可以省略
 * 参数有且仅有一个时,小括号可以省略
 * 代码块中的语句仅有一条时, _可以省略大括号、分号和return_ (省略return后默认返回语句结果)

补充:Lambda表达式编译后没有单独的.class字节码文件,对应的字节码会在运行时动态生成。匿名内部类则会产生一个单独的字节码文件

三、常用API

  1. Math类:

    • public static int abs(int a):返回参数的绝对值
    • public static double ceil(double a):向上取整
    • public static double floor(double a):向下取整
    • public static int round(float a):四舍五入
    • public static int max(int a,int b):返回最大值
    • public static int min(int a,int b):返回最小值
    • public static double pow(double a,double b):返回a的b次幂
    • public static double random():返回[0,1)的double值
    • Math.sqrt()开方、Math.PI 圆周率
  2. System类:

    • public static void exit(int status):终止当前运行的虚拟机
    • public static longcurrentTimeMillis():获取当前时间距离1970年1月1日的毫秒数
    • arraycopy(原数组,起始索引,目标数组,起始索引,拷贝个数)
  3. Objects类:

    • public static String toString(Object o,nullDefault):返回对象的字符串表现形式,若为空,则返回nullDefault
    • public static Boolean isNull / nonNull(Object o):判断对象是否为空 / 不为空
  4. BigDecimal类:用于精确计算

    • public BigDecimal add/subtract/multiply/divide (BigDecimal bd2)使用形参为字符串类型的构造方法才能精确计算

      • 只能传入数字类型的字符串
    • public BigDecimal divide(BigDecimal bd2,精确几位,舍入模式):ROUND_UP(进一法)、ROUND_FLOOR(去尾法)、ROUND_HALF_UP(四舍五入)

STEP5 包装类、算法、Arrays工具类、异常

一、包装类

  1. 概述:将基本数据类型封装成对象,即可以在对象中定义更多的功能方法以操作数据

  2. 常用方法:
    1. 常量: MAX_VALUE / MIN_VALUE: int类型的最值
    2. public static Integer valueOf(int i):int --> Integer
    3. public static Integer valueOf(String s):数字形式的String --> Integer
    4. public Integer(int value)/(String s) :根据int/String创建Integer对象(已过时)
    5. public int intValue():Integer --> int
    6. public static parseInt(String s):数字形式的String --> int

    • int --> String:①加" ";②public static String valueOf(int i)
  3. 自动装箱和自动拆箱:

    • 装箱:把基本数据类型转换为对应的包装类类型
    • 拆箱:把包装类类型转换为对应的基本数据类型
      • Object o = 12;(自动拆装箱)
  4. 注意事项: 若需操作包装类类型时,须先判断是否为null(适用于所有对象)

二、算法

  1. 冒泡排序:元素排列方向必须与遍历方向相同,否则无法将最值筛选出来
  2. 递归:将一个复杂问题转换为一个较小规模的问题
    • 具体实现为:在方法中再次调用该方法。必须在最小规模处给一个return出口

三、Arrays

  1. 常用方法:
    • static binarySearch(Object o,Object target)
    • static compare(Object[] a,Object b)
    • static copyOf(Object[] o,int newLength)
    • static copyOfRange(Object[] o,int from,int to)
    • static sort(Object[] o) / sort(Object[] o,int fromIndex,int toIndex)
    • static toSting(Object[] o)

四、异常

JVM的默认处理方案:把异常的名称,异常原因及异常出现的位置等信息输出在控制台 并在异常出现处终止程序

异常类继承结构:

Throwable

public void printStackTrace()

public String getMessage()

public String toString()

Error

//严重错误

Exception

RunTimeException

//未检查异常,可处理可不处理

编译时Exception

//已检查异常,必须处理

throws 异常类名:写在方法定义处,表示调用该方法可能出现该异常;当异常发生时,将异常抛给调用者

复制代码
 * _当调用者调用该方法时,必须选择继续向上抛异常 或 自行处理异常_
 * 编译时异常必须写在方法后 进行显示声明
 * 运行时异常可以省略不写

throw new 异常类名():写在方法中,手动抛出异常,并 结束该方法运行

复制代码
 * 使用时机:①当参数传递有误时,就没有继续运行的意义,则抛出异常终止该方法运行;②告诉调用者出现了问题
 * 可使用有参构造输出自定义的错误信息

异常抛出总结:

复制代码
 * 当方法体中抛出运行时异常时,可处理可不处理
 * 当方法体中抛出编译时异常时,则必须选择在方法声明处throws向上抛 或 自行try…catch

Throwable常见方法:

复制代码
 * `getMessage()`:返回throwable的详细字符串信息
 * `toString()`:返回异常类名+详细字符串信息
 * `printStackTrace()`:把异常错误信息输出到控制台(红色字体其实打印的就是异常堆栈信息)

try…catch…:手动处理异常

复制代码
 * _try中出现异常,会直接跳转到对应的catch语句中;当catch语句全部执行完毕,则会继续执行try…catch体系之后的代码_
 * 出现catch没有捕获的问题或没有手动编写异常处理代码,则默认交给虚拟机处理
 * 有多个异常就编写多个catch;若异常之间存在继承关系,则父类一定要写在下面
   * _子类重写的方法不能抛出 父类方法抛出的异常类及其子类 以外的异常(但可以所有抛出运行时异常)(子类重写的方法也可以不抛出异常,也符合该规则)_ ,为了满足多态的要求。子类在覆盖父类方法的时候,父类的引用是可以调用该方法的,如果父类的引用调用子类的方法,那么这个多抛出来的异常,就可能处于一种无法被处理的状态(多态的用法)
   * catch中能添加的异常分支必须是try语句中可能抛出的异常。对于未检查异常而言,程序运行过程中都有可能抛出,所以都能添加;而对于已检查异常,若try中没有声明抛出,则无法添加

finally: 必须写在try…catch体系后 ,无论有无异常,finally中的代码都会执行。(System.exit()除外)

复制代码
 * 与return同时存在时,先执行finally,再执行return
 * 用于关闭数据库资源,关闭IO流资源,关闭socket资源

自定义异常类:

复制代码
 * 须继承Exception类,无参构造和有参构造可以直接通过super()调用父类构造器

STEP6 Date类、集合、迭代器、数据结构

一、Date类

构造方法:

复制代码
 * `public Date()`:创建读取当前计算机上的时间
 * `public Date(long date)`:表示从时间原点开始,过了指定毫秒的时间(在不同地区会加上时差)

常用成员方法:

复制代码
 * `public long getTime()`:获取时间对象距时间原点的毫秒值
 * `public void setTime(long time)`:设置时间,参数为距时间原点的毫秒值

SimpleDateFormat类 :可以对Date对象进行格式化和解析

复制代码
 * 2020-11-11 12:10:15 --> yyyy-MM-dd HH:mm:ss

1. 构造方法:
   * `public SimpleDateFormat()`:使用默认格式创建对象
   * `public SimpleDateFormat(String pattern)`:使用指定格式创建对象

2. 成员方法:
   * `public final String format(Date date)`:Date --> String(Date对象按SDF指定格式转为字符串)
   * `public Date parse(String source)`:String --> Date(字符串格式必须与SDF类中 有参构造中的格式保持一致)

二、集合

集合类体系结构:最底层为实现类,其余都是接口

集合

单列

双列

Collection

Map

List

//存取有序有索引

//元素可重复

Set

//存取无序无索引

//元素不可重复

public compareTo()

ArrayList

//底层为数组

LinkedList

//底层为链表

HashSet

TreeSet

HashMap

TreeMap

Collection:

常用方法:

复制代码
 * `boolean add(E e)`
 * `boolean remove(Object o)`
 * `boolean removeIf(Object o)`:根据条件进行删除
   * collection.removeIf( (String s) -> {return s.length == 3} );

 * `public clear()`:清空集合
 * `public contains(Object o)`:判断集合中是否存在指定的元素
 * `public isEmpty()`
 * `public size()`

List特有方法:

复制代码
 * `indexOf(Object o) / lastIndexOf(Object o)`:正向 / 反向查找指定元素索引位置
 * E remove(int index)

1. LinkedList:底层数据结构是链表
   * 特有方法:
     * `public void addFirst(E e) / addLast(E e)`
     * `public E getFirst() / getLast()`
     * `public E removeFirst() / removeLast()`:移除并返回此列表的第一个 / 最后一个元素

三、迭代器

  1. Iterator:集合专用遍历方式,创建后默认指向集合的0#索引

    • Iterator<E> it = list.iterator();
  2. 常用成员方法:

    • next():提取当前索引的元素并跳转至下一个索引

      • 一次循环中不要写多个next()语句,可能造成无元素异常
    • hasNext():检查当前索引是否存在元素

    • remove():删除指定元素

  3. 增强for循环:

    • 修改第三方变量的值不会影响到元素的值
    • 在遍历过程中,集合的长度不能改变

四、数据结构

  1. 数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

STEP7 泛型、TreeSet、二叉树

一、泛型

优点:把运行时可能出现的异常提前到了编译阶段,避免了强制类型转换

使用场景:在声明 类、接口、方法 时添加,在创建对象时确定泛型类型(若不指定具体类型则默认为Object)

复制代码
 * 泛型方法在调用传参时确定泛型

通配符:<?> 表示可以匹配任意数据类型

复制代码
 * 与<E>的区别: _E一旦确定即有了单一类型的限制,而 <?>可以传递任何数据类型_
 * 可以在创建对象或调用方法时使用

1. 类型通配符上限: <? extends 类型>
   * ArrayList<? extends Number> 表示它的类型是Number或者其子类

2. 类型通配符下限:<? super 类型>
   * ArrayList<? super Number> 表示其类型是Number或者其父类

一、Set集合

特点:

复制代码
 * 可去重(无重复元素,null也仅能有一个)
 * 无索引
 * 存取顺序不一致,会将元素按规则进行排序(存取无序,排列有序)

TreeSet:底层为红黑树

复制代码
1.

自然排序Comparable:按升序排列

复制代码
   * 使用 _空参构造_ 创建TreeSet集合
   * 需要自定义排序的自定义类必须实现Comparable接口
   * 重写compareTo(Object o)方法
     * 待存入元素自动调用compareTo方法,形参为集合中的元素


2.

比较器排序Comparator:让集合构造方法接收Comparator的实现类对象

复制代码
   *

使用 带参构造 ,参数为Comparator的一个匿名内部实现类

复制代码
   *

重写compare(T o1,T o2)方法,注意排序规则必须分主次

复制代码
>         public int compare(Teacher o1,Teacher o2){

>            int result = o1.getAge - o2.getAge();
>            return result = (result == 0 ? o1.getName().compareTo(o2.getName())) : result;     }
>  
>  
>                    
>                    
>
复制代码
 * 返回值规则:
   * 负数表示存入元素为较小值,存左边;正数存右边
   * 为零表示待存入元素与集合中元素重复,则不存

二、二叉树

  1. TreeSet体系:

TreeSet

二叉树

二叉查找树

平衡二叉树

红黑树

二叉树结构:

复制代码
1. 二叉树中的一个节点由四部分组成:
   * 父节点地址、左子节地址、右子节地址和值

2. 度:每一个节点的子节点数量
   * 在二叉树中,任意一个节点的度要 <= 2

3. 路径:从一个节点到另一个节点所经过的节点顺序(不可重复 )
4. 树高:二叉树的层数,根节点为第一层
5. 任意父节点的左子节点构成的树为根节点的左子树,右子节点同左。同一个父节点的子节点为兄弟节点

二叉查找树:又称二叉排序树、二叉搜索树

复制代码
 * 左子树上所有节点的值 < 根节点的值
 * 右子树上所有节点的值 > 根节点的值
 * 子节点值 = 根节点值则不存

平衡二叉树(AVL):

复制代码
 * 根节点两个子树的高度差 <= 1
 * 任意节点的左右子树都是一颗平衡二叉树
   * _若无左子节点,右子节点高度 <= 1,则也是平衡二叉树_


1.

旋转操作:当添加一个节点后,平衡二叉树被打破时触发

复制代码
   * 左旋:将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
   * 右旋为左旋镜像操作

  1. 左左:根节点左子树的左节点有节点插入,触发右旋操作
  2. 左右:根节点左子树的右节点有节点插入,先触发左子树左旋,再触发根节点右旋
  3. 右右:根节点右子树的右节点有节点插入,触发左旋操作
  4. 右左:根节点右子树的左节点有节点插入,先触发右子树右旋,再触发根节点左旋

红黑树://TODO

STEP8 HashSet、Map集合、可变参数

一、HashSet :存取顺序为非常规排序

特点:

复制代码
 * 底层数据结构是哈希表
 *  _对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致_
 * 没有带索引的方法,所以不能使用普通for循环遍历
 * 元素不重复,且 有且仅能有一个null
 * 不是线程安全的 //TODO

哈希值:是JDK根据对象的地址值或属性值算出来的十进制int类型的数值

复制代码
1.

Object类中的public native int hashCode():根据对象的 地址值 计算出哈希码值

复制代码
2.

特点:

复制代码
   *

同一个对象多次调用hashCode()方法返回的哈希值是相同的

复制代码
   *

一般情况下,不同对象的哈希值是不同的;而重写hashCode()方法,可以通过对象的属性值计算出哈希值,可实现不同对象的哈希码值相同

复制代码
>             @Override

>             public int hashCode() {
>                   int result = age;
>                   result = 31 * result + (name != null ? name.hashCode() : 0);
>                   return result;}
>             //系数31:
>             //1、选择系数的时候尽量选择大的, 因为计算出的hash地址越大,所谓的冲突就会越少
>             //2、31是5Bits  相乘造成的数据溢出概率小
>             //3、31是 i*31==(i<<5)-1 来表示 提高算法效率,很多虚拟机中都有相关的优化
>             //4、31是个素数  能够使得它和其他数相乘后得到的结果比其他方式更容易产成唯一性
>  
>  
>                          
>                          
>                          
>                          
>                          
>                          
>                          
>                          
>                          
>
复制代码
3.

原则:

复制代码
   *

等幂性:不管执行多少次获取Hash值的操作,只要对象不变,那么Hash值是固定的

复制代码
   *

对等性:若两个对象equal方法返回为true,则其hash值也应该是一样的

复制代码
   *

互异性:若两个对象equal方法返回为false,则其hash值最好也是不同的,但这个不是必须的,只是这样做会提高hash类操作的性能(冲突几率低)

复制代码
4.

常见哈希值算法:

复制代码
   * Object类:返回经处理的对象内存地址
   * String类:用一种特殊算法根据字符串内容返回哈希值
   * Integer类:返回Integer对象中所封装的数值

哈希表:

复制代码
1.

JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组

复制代码
   * 原理解析:
     1. 创建一个默认长度为16,默认加载因为0.75的数组table
     2. 根据元素的哈希值和数组的长度计算出应存入的位置 //TODO
     3. 判断当前位置是否为null,若为null直接存入;若不为null,则调用equals方法逐个比较当前位置所有元素的属性值
     4. 只要有一个属性值相同,则不存;反之,则存入数组中老元素的相同位置,老元素挂在新元素下面,形成一个链表


2.

JDK8以后,新元素挂在老元素下面(七上八下);当挂在下面的元素过多时,不利于查询;所以当链表长度超过8,则自动转换为红黑树

经典案例:

复制代码
>     public class Dem1 {

>         public static void main(String[] args) {
>             HashSet set = new HashSet();
>             Person p1 = new Person(1001, "aa");
>             Person p2 = new Person(1002, "bb");
>             System.out.println(p1);
>             p1.name = "dd";
>             System.out.println(p1);//TODO:修改属性后地址值变更
>  
>             set.add(p1);
>             set.add(p2);
>             p1.name = "cc";
>  
>             set.remove(p1); //p1没有被删除,因为remove方法是根据对象的hashCode值进行查找删除;在p1属性被修改后,p1对应的hashCode值已改变,但在Set集合中的位置没有改变,所以remove方法根据p1新的hashCode值找到的是一个没有元素的新位置,所以会删除失败。
>             System.out.println(set);
>  
>             set.add(new Person(1001, "cc"));//相同对象添加成功。因为虽然新对象和p1现在的hashCode值相同,但由于p1在集合中的位置没有改变,所以此hashCode值对应的位置是空的,所以新对象可以存入。
>             System.out.println(set);
>  
>             set.add(new Person(1001, "aa"));//添加成功。因为新对象的hashCode值所在集合中对应的位置是原来的p1,p1虽然没有更改位置,但是属性已经发生变化,所以在存入新对象时,比较完hashCode值相同后,再调用equals方法比较属性则不相同,所以会添加在老元素的下方。
>             System.out.println(set);
>         }
>     }
>  
>     class Person {
>         int id;
>         String name;
>  
>         public Person(int id, String name) {
>             this.id = id;
>             this.name = name;
>         }
>         @Override
>         public String toString() {
>             return "Person{" + "id=" + id + ", name='" + name + '\'' + '}';
>         }
>         @Override
>         public boolean equals(Object o) {
>             if (this == o) return true;
>             if (o == null || getClass() != o.getClass()) return false;
>  
>             Person person = (Person) o;
>  
>                            if (id != person.id) return false;
>             return name != null ? name.equals(person.name) : person.name == null;
>                        }
>         @Override
>         public int hashCode() {
>             int result = id;
>             result = 31 * result + (name != null ? name.hashCode() : 0);
>             return result;
>         }
>     }
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

二、Map集合 :双列集合

格式:Interface Map<K,V> K:键的数据类型 V:值的数据类型

复制代码
 *

键不能重复,值可以重复

复制代码
   *

键和值是一一对应的,每一个键只能找到自己对应的值

复制代码
   *

(键 + 值)这个整体称为“键值对”或者“键值对对象”,在 Java 中叫做"Entry 对象"

常用方法:

复制代码
 *

V put(K key,V value):若k键重复,则将对应的值替换为新值(同为修改方法)

复制代码
 *

putAll(Map<? extends K,? extends V> m):整体复制到一个新Map

复制代码
 *

V get(Object key):根据传入的键返回对应的值

复制代码
 *

V remove(Object key)

复制代码
 *

V remove(Object key,Object value):仅当指定的键映射到指定值时删除该entry

复制代码
 *

void clear():移除所有的键值对元素

复制代码
 *

int size():返回集合中键值对个数

复制代码
 *

boolean containsKey(Object key)

复制代码
 *

boolean containsValue(Object value)

复制代码
 *

boolean isEmpty()

复制代码
 *

Collection<V> values():返回所有值的集合

复制代码
 *

Set<K> keySet():返回所有key键的集合

复制代码
 *

Set<Map.Entry<K,V>> entrySet():返回所有键值对的集合

复制代码
 *

default void forEach(BiConsumer<? super K,? super V> action):参数为BiConsumer接口的实现类,可用Lambda表达式重写accept方法,可获取所有键和值,对其进行操作

遍历方式:

复制代码
 *

键遍历:keySet()获取键集合,进行遍历,再依次get(key)读取对应的值

复制代码
 *

值遍历:values()只输出集合中的值,不含键(不推荐)

复制代码
 *

键值对遍历:entrySet()获取键值对集合,进行遍历,直接输出entry集合或用entry调用getKey()getValue()获取键值

复制代码
 *

forEach(Object key,Object value)遍历:方法中遍历所有entry对象,依次用getKey()getValue()读取每一对entry中的键值,再对其进行操作

复制代码
 *
复制代码
>         map.forEach(new BiConsumer<String, String>() {

>               @Override
>               public void accept(String key, String value) {
>                       System.out.println(key + "==" + value);}}
>         /*==============================*/  
>         (k, v) -> System.out.println(k + "--" + v)
>  
>  
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>

1.HashMap :数组+链表+红黑树

  1. 底层原理:同为哈希表结构(HashSet底层用的是HashMap的key位置,value位置为一个Object对象常量)
    1.

底层创建长度为16的一维数组Entry[] table

复制代码
2.

将一对键值打包成entry对象以存入数组

复制代码
3.

仅根据 的哈希值和属性判断存入位置,删除同理

复制代码
   * 若哈希值相同,则调用equals()比较内容;若返回false,则该键值对添加成功;若返回true,则将新值替换旧值

4.

若键要存储的是自定义对象,需要重写hashCode和equals方法

2.TreeMap :数组+红黑树;自然排序或Comparator排序器排序

  1. 底层原理:同为红黑树结构(TreeSet底层用的是TreeMap的key位置)

3.LinkedHashMap保证在遍历集合的时候可以按照添加的顺序遍历 ;对于频繁的遍历操作,效率高于HashMap

三、可变参数

格式:method(int... a):可以添加0~若干个指定类型参数

原理:将多个参数添加到对应类型的一维数组中

复制代码
 * 相当于在形参列表中声明一个一维数组,因此与参数为同类型数组的方法不构成重载

注意事项:

复制代码
 * 参数列表中不允许声明多个可变长参数  
  • 普通参数和可变长参数同时存在时,可变长参数必须放在最后面
    • java 9版本以后 ,集合中可以调用of(E... e)方法一次性添加多个元素,但该集合的长度也不再可变(用于快速添加多个元素)
      * 可再调用集合的addAll() / putAll()方法将该不可变集合中所有元素复制到新集合中,即可解决长度不可变的弊端

常用方法:

复制代码
1.

Map:

复制代码
   * `static <K, V> Map<K, V> of(E... elements)`
   * `static <K, V> Map<K, V> ofEntries(Map.Entry<? extends K,? extends V>... entries)`:直接传入多个Entry对象至 _Map_ 集合中
     * HashMap<> map = _new HashMap <> (Map.ofEntries(Map.entry(“语文”, 89), Map.entry(“数学”,82), Map.entry(“英语”, 92)));_


2.

List:

复制代码
   * `static <E> List<E> of(E…elements)`
   * Collections工具类:
     * `Collections.addAll(list, E... e)`:一次性添加多个元素并复制到list中。list长度可修改


3.

Set:

复制代码
   * `static <E> Set<E> of(E…elements)`

STEP9 Stream流、File

一、Stream流 :简化代码

构成:

复制代码
1. 获取Stream流:创建一条流水线,并把数据放到流水线上准备进行操作
2. 中间方法:流水线上的操作。一次操作完毕之后,还可以继续进行其他操作
3. 终结方法:一个Stream流只能有一个终结方法,是流水线上的最后一个操作

特点:

复制代码
1. Stream流不是一种数据结构,默认不保存、不修改数据,仅操作
2.  _中间操作是惰性的_ ,即每当访问到流中的一个元素,才会在此元素上执行操作
3.  _每个流只能使用一次_ ,因此只能链式调用或使用上一个流赋值的新变量调用

Stream流的获取方法:

复制代码
1.

单列集合:使用Collection接口中的默认方法stream()生成流

复制代码
   * `default Stream<E> stream()`

2.

双列集合:不能直接获取,只能间接的生成流。可以先通过keySet()或entrySet()获取一个Set集合,再调用stream()

复制代码
3.

数组:Arrays.stream(T[ ] arr)

复制代码
4.

同种数据类型的多个数据:Stream.of(T...values)

复制代码
   *

Stream.of("aaa","bbb","ccc")

复制代码
     *

引用数据类型可以操作该类型中的所有元素;基本数据类型数组则整体作为一个元素

复制代码
>                 String[] strs = {"ab","bc","cd"};

>                 System.out.println(Stream.of(strs));//"ab","bc","cd"
>                 int[] arr = {1,3,5,7};
>                 System.out.println(Stream.of(arr))//输出数组地址值
>  
>  
>                                
>                                
>                                
>

常见中间操作方法:

复制代码
 * `Stream<T> filter(Predicate predicate)`:对于对流中的数据进行过滤
   * Predicate接口中的方法`boolean test(T t)`:对给定的参数进行判断

 * `Stream<T> limit(long maxSize)`:截取指定参数个数的数据
 * `Stream<T> skip(long n)`:跳过指定参数个数的数据
 * `static<T> Stream<T> concat(Stream a, Stream b)`:合并a、b两个流为一个流
 * `Stream<T> distinct()`:去重 (依赖hashCode()和equals()方法)
 * `Stream<T> map()`:流中元素映射到另一个流
 * `Stream<T> sorted()`:排序

常见终结操作方法:执行后中止流操作(返回值不是Stream流的都是终结方法)

复制代码
 * `void forEach(Consumer action)`:对此流的每个元素执行操作
   * 须重写Comsumer接口中的`void accept(T t)`方法

 * `long count()`:返回此流中的元素数
 * `boolean allMatch() / anyMatch() / noneMatch()`:判断流中数据是否全部 / 有一个 / 全都不符合条件

Stream流的收集操作:在Stream流中无法直接修改集合,数组等数据源中的数据

复制代码
 * `R collect(Collector collector)`
   * 工具类Collectors提供了具体的收集方式
     1. `public static <T> Collector toList() `:把元素收集到 List 集合中
     2. `public static <T> Collector toSet()`
     3. `public static Collector toMap (Function keyMapper,Function valueMapper)`
        * 也可用简写格式`.toList() / .toSet`

二、FIle类

概述:IO流可以对硬盘中的文件进行读写;File类表示要读写的文件在哪,也可以对文件进行创建,删除等操作

File类:是文件和目录路径名的抽象表示

复制代码
 * 文件和目录可以通过File封装成对象
 *  _File封装的对象仅仅是一个路径名_ 。可以是存在的,也可以是不存在的

构造方法:

复制代码
 * `File(String pathname)`:通过将给定的路径名字符串转换为抽象路径名创建对象
 * `File(String parent, String child)`:拼接父路径名字符串和子路径名字符串创建对象
 * `File(File parent, String child)`:拼接父抽象路径名和子路径名字符串创建对象

绝对路径、相对路径:

复制代码
 * 绝对路径:从盘符开始
   * `File file = new File("D:\ Code\ a.txt")`

 * 相对路径:相对当前项目下的路径
   * `File file1 = new File("a.txt");`  

File file2 = new File("模块名\ a.txt");

File类功能:

复制代码
1.

创建:

复制代码
   *

public boolean createNewFile():创建一个新的空文件

复制代码
     * 创建文件时若上级文件夹不存在则会报错

   *

public boolean mkdir():创建一个单级文件夹

复制代码
   *

public boolean mkdirs():创建一个多级文件夹(也可创建单级文件夹)

复制代码
2.

删除:

复制代码
   * `public boolean delete()`:删除由此抽象路径名表示的文件或目录
     * 注意事项
       * delete方法直接删除不走回收站
       * 只能直接删除文件和空文件夹
       * 若删除的是有内容的文件夹,需要先删除文件夹中的内容,最后才能删除文件夹
       * 需要有访问权限



3.

判断和获取方法:

复制代码
   * `public boolean isDirectory()`:测试此抽象路径名表示的File是否为目录
   * `public boolean isFile()`:测试此抽象路径名表示的是否为文件
   * `public boolean exists()`:测试此抽象路径名表示的File是否存在
   * `public String getPath() / getAbsolutePath()`
   * `public String getName()`:返回此抽象路径表示的文件或目录名
   * `public String getParent()`:返回此抽象路径的父路径
   * `public File[] listFiles()`:返回此抽象路径名表示的目录中 文件和目录的File对象数组(包括隐藏文件和文件夹)
     * 调用者为文件 或 不存在 或 需要权限,则返回null
     * 调用者为空文件夹,返回长度为0的数组

STEP10 IO流及字符流、字节流

IO流概述:内存的读写

复制代码
 *

Input是数据从硬盘进内存的过程,为读

复制代码
 *

Output是数据从内存到硬盘的过程,为写

IO流分类

复制代码
1.

按流向分

IO流

输入流

输出流

复制代码
2.

按处理数据的单位不同 分:推荐

IO流

字节流

字符流

操作所有类型的文件

只能操作纯文本文件

复制代码
   * 用Windows记事本打开且能读得懂,那就是纯文本文件(office文件不是纯文本文件)

3.

按实现功能不同分:

复制代码
   *

节点(低级)流:直接对目标设备进行操作的流(实际传输数据的流)

复制代码
   *

处理(高级)流:对节点流进行连接和封装,生成功能更加强大的流

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reader Writer
访问文件 FileInputStream FileOutputStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
转换流 InputStreamReader OutputStreamWriter InputStreamReader OutputStreamWriter
对象流 ObjectInputStream ObjectOutputStream
过滤流 FilterInputStream FilterOutputStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutputStream

一、字节流

字节流写数据步骤

复制代码
1. 创建字节输出流对象
   * `public FileOutputStream(String name, boolean append) / (File file, boolean append)`:若文件不存在,就创建;存在则根据第二个参数决定是否续写:为true则在文件末尾添加,false则清空

2. 写数据:
   * `byte[] getBytes()`:由字符串调用,将字符串转换为字符数组
   * `void write(int b)`:一次写一个字节数据
   * `void write(byte[] b)`:一次写一个字节数组数据
   * `void write(byte[] b, int off, int len)`:一次写一个字节数组的部分数据,off为b中的索引,len为写入长度
     * 写出的整数,实际写入的是整数在码表上对应的字母
     * 换行符:`Windows: \r\n` `linux: \n` `mac: \r`


3. 释放资源:`close()`每次使用完流必须要释放资源
   * 使用finally

字节流读数据步骤:一次读一个字节

复制代码
1. 创建字节输入流对象:`public FileInputStream(String name) / (File file)`
   * 若文件不存在,则直接报错

2. 读数据
   * `public int read()`:读取一个字节,没读到返回 -1
   * `public int read(byte[] b)`:从输入流读取最多b.length个字节的数据;文件中字节长度小于数组长度,则有多少读入多少
   * `read(byte[] b, int off, int len)`:off为b中的索引
     * 读出来的是文件中数据的码表值(可强转成字符)
     * _若存在多个read()方法,则会接力往后读取_


3. 释放资源

字节缓冲流 :提高读写效率

复制代码
1. 原理:将数据交换转移到内存中进行,减少了内存和磁盘间的交互,从而节约了时间

 *

BufferedOutputStream:缓冲输出流(默认缓冲区为长度8192的字节数组)

复制代码
 *

BufferedInputStream:缓冲输入流(默认缓冲区为长度8192的字节数组)

复制代码
   *

构造方法:

复制代码
   *

BufferedOutputStream(OutputStream out)

复制代码
   *

BufferedOutputStream(OutputStream out,int size):size指定缓冲区大小

复制代码
   *

BufferedInputStream(InputStream in)

复制代码
   *

BufferedOutputStream(InputStream in,int size)

复制代码
>         BufferedInputStream bis=new BufferedInputStream(new FileInputStream("C:\ a.txt"););

>          BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("a.txt"));
>          byte[] b=new byte[1024];
>          int len;
>          while ((len=bis.read(b))!=-1){
>              bos.write(b,0,len);
>          }
>          bis.close();
>          bos.close();
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>
复制代码
2.

注意事项:

复制代码
   *

字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作(其他高级处理流也是如此)

复制代码
   *

关闭缓冲流资源时会在底层关闭字节流资源

二、字符流

字节流+编码表

编码表:

复制代码
 *

按照某种规则,将字符存储到计算机中,称为 编码

复制代码
 *

按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为 解码

复制代码
   * 编码和解码的方式必须一致,否则会导致乱码
   * 计算机中储存的信息都是用二进制表示,我们在屏幕上看到的字符是二进制数据转换后的结果

常用码表:

复制代码
1.

ASCII字符表:包括数字,大小写字符和一些常见的标点符号(没有中文)

复制代码
2.

GBK:中国码表。兼容ASCII码表,包含21003个汉字,支持繁体及部分日韩文字

复制代码
   * 一个中文以两个字节的形式存储(第一个字节一定是负数)

3.

ANSI:使用平台本地默认编码表

复制代码
4.

Unicode码表:统一的万国码,容纳世界上大多数国家的常见文字和符号

复制代码
   * 因表示字符太多,Unicode表中的数字不是直接以二进制的形式存储到计算机中,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16及UTF-32的编码方式存储到计算机,其中UTF- 8最为常见(_UTF-8编码后一个中文以三个字节的形式存储_)

字符串的编码解码问题

复制代码
 * 编码:
   * `byte[] getBytes()`:使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
   * `byte[] getBytes(String charsetName)`:使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中

 * 解码:
   * `String(byte[] bytes)`:通过使用平台的默认字符集解码指定的字节数组来构造新的String
   * `String(byte[] bytes, String charsetName)`:通过指定的字符集解码指定的字节数组来构造新的String

字符流写数据

复制代码
1. 创建字符输出流对象:
   * `FIleWriter fw = new FileWriter(new File("a.txt"));`:若文件不存在,则创建,但要保证父级路径存在;若文件存在则清空

2. 写数据
   * `void write(int c)`:写一个字符
   * `void write(char[] cbuf)`:写入一个字符数组
   * `void write(char[] cbuf, int off, int len)`:写入字符数组的一部分
   * `void write(String str)`:写一个字符串
   * `void write(String str, int off, int len)`:写一个字符串的一部分
     * `flush()`:刷新流,调用后刷新Writer和OutputStream链中的所有缓冲区
     * `close()`:关闭流,在关闭前会进行一次刷新;一旦关闭,该流就不能再写出数据

   * _注意事项_ :
     * 若写出int类型的整数,实际写出的是整数在码表上对应的字母*
     * 若写出字符串数据,就是把字符串原样写出


3. 释放资源

字符流读数据

复制代码
 *

int read():一次读一个字符数据

复制代码
   * `int read(char[] cbuf)`:一次读一个字符数组数据

字符缓冲流 :BufferedWriter / BufferedReader

复制代码
 *

构造方法:

复制代码
   * `BufferedWriter(Writer out) / (Writer out, int size)`

 *

BufferedReader(Reader in) / (Reader in, int size)

复制代码
 *

特有方法:

复制代码
   *

BufferedWriter : : void newLine():换行

复制代码
   *

BufferedReader : : public String readLine():读取一整行文字,不包括任何行终止字符。若流的结尾已经到达,则为null

复制代码
   *

注意事项: 创建输出流对象不能紧跟在输入流对象的后面 ,因为输出流可能会因为文件存在而直接清空文件,导致对文件中数据进行读取时读到null,而引发空指针异常

STEP11 转换流、对象操作流、Properties、Clone

三、转换流、对象操作流

一、转换流 :字节<==>字符

概述:转换流用于进行字节流和字符流之间的转换

复制代码
 * InputStreamReader:从字节流转换成字符流
 * OutputStreamWriter:从字符流转换成字节流

构造方法:

复制代码
 * `InputStreamReader(InputStream in)`
 * `InputStreamReader(InputStream in, String charsetName)`:charsetName为编码表名
 * `OutputStreamWriter(OutputStream out)`
 * `OutputStreamWriter(OutputStream out, String charsetName)`
   * `public String getEncoding()`:返回此流使用的编码名称

JDK11新特性:可以使用字符流的新构造

复制代码
 * `FileReader(String fileName, Charset charset, boolean append)`
 * `FileWriter(String fileName, Charset charset, boolean append)`
   * Charset类中有一个静态方法`forName(String charset)`可将字符串类型的码表名称转换为Charset对象

二、对象操作流 :对象<==>字节

概述:可以将对象以 字节的形式 写到本地文件,实现加密的效果。还原则需要再次用对象操作流读到内存中

复制代码
 * `ObjectInputStream`:对象操作输入流(对象序列化流)
   * 将对象写到本地文件中,或在网络中传输对象

 * `ObjectOutputStream`:对象操作输出流(对象反序列化流)
   * 把本地文件中的对象读到内存中,或接收网络中传输的对象

常用方法

复制代码
1. ObjectInputStream : : `Object readObject()`
2. ObjectOutputStream : : `Object writeObject(Object obj)`
   * _读和写的单位均为一个对象_

Serializable接口 :是一个标记性接口,内部没有任何抽象方法。只要一个类实现了该接口,就表示这个类的 对象 可以被序列化

复制代码
1.

实现Serializable接口的目的是为类可持久化,比如在网络传输或本地存储,为系统的分布和异构部署提供先决条件。若没有序列化,现在我们所熟悉的远程调用,对象数据库都不可能存在

复制代码
2.

serialVersionUID序列号:版本控件,相当于类的标识符

复制代码
   *

序列化时,若类中没有定义序列号,则虚拟机会根据类中的信息自动计算出一个序列号,用于识别对象

复制代码
   *

JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较。
如果一致,就可以进行反序列化;而在使用系统默认给出的序列号时,修改属性会修改序列号,出现反序列化版本不一致的异常,即是InvalidCastException

复制代码
     *

为防止修改信息导致序列号前后不一致,可以手动给出序列号

private static final long serialVersionUID = 1L;

反序列化的两种方式

复制代码
 * 循环读取文件:若读到文件末尾会抛出EOFException异常,可以try…catch该异常,并break循环
 * 将对象添加到ArrayList或HashMap集合中,写入和读取的都是集合

注意事项

复制代码
 * _transient关键字_ :可以使某个成员变量的值不被序列化
   * static修饰的变量也不会被序列化: _序列化保存的是对象的状态_ ,静态变量属于类的状态,因此序列化并不保存静态变量。

 * _反序列化时读到的对象需要强转成原来的类型_

三、Properties

  1. 概述:是一个Map体系的集合类

    • 无泛型
    • 可以使用Map集合的通用方法
    • 本地文件后缀须为.properties
  2. 特有方法

    • Object setProperty(String key, String value):添加字符串类型的键值对(底层调用HashTable方法)
    • String getProperty(String key):使用此属性列表中指定的键搜索属性
    • public String getProperty(String key, String defaultValue):用指定的键在属性列表中搜索属性。如果在属性列表中未找到该键,则接着递归检查默认属性列表及其默认值。如果未找到属性,则此方法返回默认值变量。
    • Set<String> stringPropertyNames():从该属性列表中返回一个不可修改的String类型的键集,若键不是String,则忽略
  3. 与IO流结合的方法:键和值都须是字符串

    • void load(InputStream inStream):从输入字节流读取属性列表(键和元素对)
    • void load(Reader reader):从输入字符流读取属性列表
    • void store(OutputStream out, String comments):将此属性列表写入此Properties表中,以适合使用load(InputStream)方法的格式写入输出字节流。comments为注释,一般为null
    • void store(Writer writer, String comments):将此属性列表写入此Properties表中,以适合使用load(Reader)方法的格式写入输出字符流

Clone

复制代码
>     public class 序列化 {
>         public static void main(String[] args) throws Exception{
>             Student s1=new Student("yx",18,100);
>             //节点流
>             FileOutputStream fs=new FileOutputStream("Student.txt");
>             ObjectOutputStream out=new ObjectOutputStream(fs);
>             out.writeObject(s1);
>             s1.setScore(101);
>             out.writeObject(s1);
>             out.close();
>             //读取看结果
>             FileInputStream  fs2=new FileInputStream("Student.txt");
>             ObjectInputStream in=new ObjectInputStream(fs2);
>             System.out.println(in.readObject());
>             System.out.println(in.readObject());
>             in.close();
>             /*
>             Student{name='yx', age=0, score=100.0}
>             Student{name='yx', age=0, score=100.0}
>       结果相同*/
>  
>             //解决方案
>             s2.setScore(101);
>             Student s2= (Student) s1.clone();
>             out.writeObject(s2);
>         }
>     }
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>

总结:在序列化过程中,输出流在传输对象的同时,也记录了对象状态;所以在重复传输相同地址的对象时,不会重复读取内存中的对象信息,而是直接将该对象的记录再次写入

解决方案:1.重新创建流 2.创建一个新对象,内容保持一致 (步骤繁琐)

Clone:为了避免手动重复创建对象

在某对象属性进行更新后,为避免某些操作直接读取其缓存中而非内存中的该对象,所以直接通过该对象的clone方法克隆出一个地址不同的新对象来保存该对象的当前新状态

复制代码
 * 注意事项:
 * 对象须实现Cloneable接口才可以克隆
 * 对象中须重写Object的clone方法,以提升方法作用域

STEP12 多线程

一、总体概述

  1. 概述:是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

    • 并行:在同一时刻,有多个指令在多个CPU上同时执行
    • 并发:在同一时刻,有多个指令在单个CPU上交替执行
  2. 进程:指正在运行的软件
    1. 独立性:是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    2. 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    3. 并发性:任何进程都可以同其他进程一起并发执行

  3. 线程:是进程中的单个顺序控制流,是一条执行路径
    1. 单线程:一个进程如果只有一条执行路径,则称为单线程程序
    2. 多线程:一个进程如果有多条执行路径,则称为多线程程序

二、多线程实现方案

run():封装线程执行的代码,直接调用时相当于普通方法的调用,并没有开启线程

start():启动线程,表示该线程已就绪,然后由JVM调用此线程run()方法

①继承Thread类

  1. 继承Thread类
  2. 重写run()方法:单独调用run()还是在主线程里单线程执行
  3. 创建子类对象
  4. 启动线程 .start()
    • 同一个线程不能启动两次,否则会抛出IllegalThreadStateException
    • 多线程须创建多个子类对象

②实现Runnable接口

  1. 实现Runnable接口

  2. 重写run()方法

  3. 创建Thread类对象,将实现类对象作为构造方法的参数

    • 多线程须创建多个Thread对象
  4. 启动线程

    • 实际调用的就是Runnable实现类中重写的run()方法

开发中优先选择

  1. 实现的方式没有单继承的限制
  2. 实现的方式更适合处理多个线程有共享数据的情况

③利用Callable和Future接口实现

  1. 实现Callable接口

  2. 重写call()方法

    • public Object call() throws Exception
      • 相比run()方法可以有返回值
      • 方法可以抛出异常
      • 支持泛型的返回值
      • 需要借助FutureTask类
  3. 创建Future接口的唯一实现类FutureTask 的对象,将Callable的实现类对象作为构造方法参数

  4. 创建Thread类对象,再将Future实现类对象作为构造方法参数

  5. 启动线程

  6. FutureTask对象调用get()方法,即可获取线程结束后的结果

    • 不能在start()方法执行前调用,因为尚无返回值

○ 三种方案对比

  • 继承Thread类

    • 优点:实现简单,可以直接使用Thread类中的方法
    • 缺点:
      1. 不能再继承其他类,扩展性较差
      2. 由于run()方法没有抛出异常,所以重写方法中也不能抛出 已检查异常 ,只能try…catch处理
  • 实现Runnable、Callable接口

    • 优点:扩展性强,实现该接口的同时还可以继承其他的类
    • 缺点:编程相对复杂,不能直接使用Thread类中的方法

三、线程类常用方法

  • String getName():返回此线程的名称

  • void setName(String name):修改此线程名称(也可通过构造方法设置名称)

  • public static Thread currentThread():返回当前正在执行的线程对象的引用

  • public static void sleep(long time,int nanos):线程休眠指定时间,单位为毫秒;可选参数nanos表示纳秒精度(0-999999)

  • public void setPriority(int newPriority)

  • public int getPriority()

    • 线程的调度模型
    • 分时调度模型:所有线程平均分配占用CPU的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU(不绝对,仅仅是概率更大);若优先级相等,则随机选择一个线程(某线程时间片结束后仍可能抢占到下一个时间片)
      • java即是抢占式模型。优先级分为10档,10表示优先级最高,默认优先级为5,依次递减
      • 线程创建时也会继承父线程的优先级
  • public final void setDaemon(boolean on):参数为true,可以将一个用户线程变成一个守护线程

  • public final boolean isDaemon()

    • java的垃圾回收就是一个典型的守护线程,当普通线程执行完毕,守护线程短时间内就会停止
    • 若JVM中都是守护线程,当前JVM将退出

四、线程安全

多个线程同时访问一个任务时,会因多个线程抢占执行权而导致任务被错误的多次访问,从而造成异常情况

synchronized锁:执行过程中不会被其他线程打断,确保了操作的连贯性

同步代码块

复制代码
>     Object o = new Object();

>     //锁标记也可用 "类名.class",表示 类对象,不同于类的对象
>     synchronized(o){ //默认为打开状态,只有持有o对象锁标记的线程才能进入同步代码块
>     } //退出同步代码块才会释放对应的锁标记
>     //若线程在途中失去了CPU执行权,其他线程也只能等待,知道该线程退出代码块,释放锁标记
>  
>  
>              
>              
>              
>              
>
复制代码
 * 解决了多线程的安全问题,但当线程数量很多时,频繁判断同步的锁会耗费大量资源,无形中降低了运行效率
 * 修饰对象:代码块、方法、类;作用范围为各自的作用域

同步方法:public synchronized void method(){}

复制代码
 * 锁标记为this

Lock锁:ReentrantLock类实现了Lock接口

复制代码
 * `void lock()`:添加到需要锁住的代码开头
 * `void unlock()`:添加到代码末尾(可以放在finally语句中以防止中途抛出异常执行不到)
 * 使用lock,JVM将花费较少的时间调度线程,且具有很好的拓展性(提供了更多的子类)
   * 使用顺序:lock > 同步代码块 > 同步方法

五、死锁 :不同的线程分别占用着对方需要的同步资源不放弃

死锁案例

复制代码

六、生产者和消费者模式

// TODO day12 多线程笔记

STEP13 线程池、网络编程

线程池

  1. 线程状态
    1. NEW(新建):线程对象已创建但尚未启动
    2. RUNNABLE(就绪)
    3. WAITING(等待)
    4. TIMED_WAITING(计时等待)
    5. TERMINATED(结束)
  • 在某一时间节点上,一个线程只能处于一种状态
  • 线程启动后不立刻进入运行状态是因为让这个线程启动的线程(如main)正在运行状态

一、线程池

线程池和传统方式创建线程的优劣对比

复制代码

1. Executor:线程池顶级接口,提供一个execute(Runnable command)方法;一般使用其继承接口ExecutorService的两个实现类ThreadPoolExecutorScheduledThreadPoolExecutor

ExecutorService

复制代码
>      //常见方法

>     public void execute(Runnable command)  //执行任务,没有返回值, 一般用来执行Runnable
>     //创建对象,执行任务并归还对象;开发者仅需提交任务给submit即可
>     Future<?> submit(Runnable task);
>     Future<T> submit(Callable<T> task);
>     Future<T> submit(Runnable task, T result);
>     public void shutdown()             // 关闭连接池
>  
>     //Executors :工具类 线程池的工厂类,用于创建并返回不同类型的线程池
>      Executors.newCachedThreadPool(); //创建一个可根据需要创建新线程的线程池,返回值为线程控制者对象ExecutorService
>     Executors.newFixedThreadPool(n);  //创建一个可以重用固定线程数的线程池
>     Executors.newSingleThreadExecutor();      //创建一个只有一个线程的线程池
>     Executors.newScheduledThreadPool(n); //创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

线程池案例:

复制代码
>      public static void main(String[] args) throws InterruptedException {

>         //创建默认的线程池,默认是空的,最大容纳int最大值个线程
>         //ExecutorService 线程池的控制者对象
>         ExecutorService executorService = Executors.newCachedThreadPool();
>         //在执行任务之前先去线程池中看有没有空闲的线程, 如果有拿过来继续使用,没有则创建
>         executorService.submit(new Runnable() {
>             @Override
>             public void run() {
>                 for (int i = 0; i < 10; i++) {
>                     System.out.println(Thread.currentThread().getName()+"--                                   "+i);
>          }}});
>         //若此处不休眠,则两条线程同时启动,线程池会创建两个线程;若让主线程睡一会儿再开启下边的任务,此时上一条线程已执行完毕,归还后处于空闲状态,则复用该线程
>         Thread.sleep(2000);
>         executorService.submit(() -> {
>             for (int i = 0; i < 10; i++) {
>                 System.out.println(Thread.currentThread().getName()+"--                               "+i);
>         }});
>         executorService.shutdown();}
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

TreadPoolExecutor:推荐

复制代码
>      //构造方法

>     public ThreadPoolExecutor(int corePoolSize, //2
>                               int maximumPoolSize, //5
>                               long keepAliveTime, //2
>                               TimeUnit unit, //TimeUnit.SECONDS
>                               BlockingQueue<Runnable> workQueue,//new ArrayBlockingQueue<>(10)
>                               ThreadFactory threadFactory, //Executors.defaultThreadFactory
>                               RejectedExecutionHandler handler//new ThreadPoolExecutor.AbortPolicy
>     )
>     //corePoolSize -    要保留在池中的线程数, 核心线程数,>=0
>     //maximumPoolSize - 池中允许的最大线程数,>=核心线程数
>     //keepAliveTime -   空闲线程最大存活时间 多余的线程会在多长时间内被销毁,>=0
>     //keepAliveTime-    空闲线程最大存活时间的单位
>     //workQueue -       阻塞任务队列,被添加到线程池中,等待被执行的任务,不能为null
>     //threadFactory -   当执行程序创建新线程时使用的工厂 (创建线程工厂,可默认),不能为null
>     //handler -           任务的拒绝策略 当任务太多来不及处理时,如何拒绝任务,不能为null
>     1、AbortPolicy策略:该策略会丢弃任务,直接抛出异常,阻止系统正常工作;
>     2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;调用任务的run()方法绕过线程池直接执行。
>     3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
>     4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;不推荐
>     5、也可以自己扩展RejectedExecutionHandler接口,自定义策略
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

网络编程

在网络通信协议下,不同计算机中运行的程序可以进行数据传输

三要素

复制代码
1.

IP地址:设备在网络中的地址,是唯一标识

复制代码
   * 特殊IP地址:127.0.0.1 本地回环地址,可以代表本机IP地址,一般用于测试
   * 查看当前局域网所有IP地址 arp -a

2.

端口:应用程序在设备中的唯一标识,一个端口号只能被一个应用程序使用

复制代码
   *

端口范围:0-65535(超出范围则报错) (netstat -a)

复制代码
   *

分类:

公认端口:0~1023。用于一些知名的网络服务或者应用。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)
动态/私有端口:49152~65535。

复制代码
3.

协议:TCP/IP协议

复制代码
   * 传输控制协议TCP(Transmission Control Protocol)
   * 用户数据报协议UDP(User Datagram Protocol)

InetaAddress类:表示IP地址

复制代码
 * `static InetAddress getByName(String host)`:返回主机名称的IP地址对象。主机名称可以是机器名称,也可以是IP地址
 * `static InetAddress getLocalHost()`:获取本地IP
 * `String getHostName() `:获取此IP地址的主机名
 * `String getHostAddress()` :返回IP地址字符串

UDP协议

将数据、源、目的封装成数据包,不需要建立连接 (不可靠的)
每个数据报的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送
发送数据结束时无需释放资源,开销小,速度快
特点:速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据。
例如发短信

单播通信步骤

复制代码
1. 创建发送 / 接收端的DatagramSocket对象
2. 建立DatagramPacket数据包
3. 调用Socket的发送 / 接收方法
4. 接收端需getData()
5. 关闭Socket

组播

复制代码
1.

接收端需创建MulticastSocket对象接收数据

复制代码
2.

组播地址:224.0.0.0 ~ 239.255.255.255
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址,地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。

广播

复制代码
 * 发送端IP地址改为广播地址255.255.255.255

常用方法

复制代码
>     //创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。

>     public DatagramSocket(int port)
>     //创建数据报套接字,将其绑定到指定的本地地址。
>     //本地端口必须在 0 到 65535 之间(包括两者)。
>     //如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
>     public DatagramSocket(int port,InetAddress laddr)
>     //关闭此数据报套接字。
>     public void close()
>     //从此套接字发送数据报包。DatagramPacket 包含的信息指示:将
>     //要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
>     public void send(DatagramPacket p)
>     //从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
>     public void receive(DatagramPacket p)
>     //获取套接字绑定的本地地址。
>     public InetAddress getLocalAddress()
>     //返回此套接字绑定的本地主机上的端口号。
>     public int getLocalPort()
>     //返回此套接字连接的地址。如果套接字未连接,则返回 null。
>     public InetAddress getInetAddress()
>     //返回此套接字的端口。如果套接字未连接,则返回 -1
>     public int getPort()。
>  
>     DatagramPacket类的常用方法
>     //构造 DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
>     public DatagramPacket(byte[] buf,int length)
>     //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length参数必须小于等于 buf.length。
>     public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
>  
>     //构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
>         //length 参数必须小于等于 buf.length。
>         /*
>             buf - 包数据。
>             offset - 包数据偏移量。
>             length - 包数据长度。
>             address - 目的地址。
>             port - 目的端口号
>         */
>         public DatagramPacket(byte[] buf,
>                       int offset,
>                       int length,
>                       InetAddress address,
>                       int port)
>  
>     //返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
>     public InetAddress getAddress()
>     //返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
>     public int getPort()
>     //返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量offset 处开始,持续 length 长度。
>     public byte[] getData()
>     //返回将要发送或接收到的数据的长度。
>     public int getLength()
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

STEP14 TCP、日志、枚举

1.利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实上的标准。
2.网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
3.通信的两端都要有Socket,是两台机器间通信的端点。
4.网络通信其实就是Socket间的通信。
5.Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
6.一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
7.Socket分类:
流套接字(stream socket):使用TCP提供可依赖的字节流服务
数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

复制代码
>     Socket类的常用构造器:
>     //创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
>     public Socket(InetAddress address,int port)
>     //创建一个流套接字并将其连接到指定主机上的指定端口号。
>     public Socket(String host,int port)
>  
>     Socket类的常用方法:
>     //返回此套接字的输入流。可以用于接收网络消息
>     public InputStream getInputStream()
>     //返回此套接字的输出流。可以用于发送网络消息
>     public OutputStream getOutputStream()
>     //此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
>     public InetAddress getInetAddress()
>     //获取套接字绑定的本地地址。 即本端的IP地址
>     public InetAddress getLocalAddress()
>     //此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
>     public int getPort()
>     //返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。
>     public int getLocalPort()
>     //关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。
>     public void close()
>     //如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
>     public void shutdownInput()
>     //禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据。
>     public void shutdownOutput()
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>

案例:客户端发送内容给服务端,服务端将内容打印到控制台上,并返回"发送成功" 给客户端

复制代码
>     //客户端:
>     public static void main(String[] args) throws IOException {
>      InetAddress inetAddress = InetAddress.getByName("192.168.20.66");
>      //自动要连接的服务端的端口
>      Socket socket = new Socket(inetAddress,8899);
>      //获取输出流
>      OutputStream outputStream = socket.getOutputStream();
>      //发送消息
>      outputStream.write("你好世界".getBytes());
>      //客户端把数据传输完毕 关闭数据的输出
>      socket.shutdownOutput();
>  
>      //客户端接受服务端的反馈消息并输出控制台
>      InputStream inputStream = socket.getInputStream();
>      //先可以把流中的数据存在该流
>      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
>      //读取数据
>      byte[] b=new byte[1024];
>      int len;
>      while ((len=inputStream.read(b))!=-1){
>          //全部读取到该流中,内置数组
>          byteArrayOutputStream.write(b,0,len);
>      }
>      System.out.println(byteArrayOutputStream.toString());
>      //关闭连接
>      byteArrayOutputStream.close();
>      inputStream.close();
>      outputStream.close();
>      socket.close();
>     }
>  
>     //服务端
>     public static void main(String[] args) throws IOException {
>      //创建服务端
>      ServerSocket serverSocket = new ServerSocket(8899);
>      //调用serverSocket.accept() 监听来自客户端的socket
>      //获取不到数据就会死等,阻塞
>      Socket accept = serverSocket.accept();
>      //获取输入流
>      InputStream inputStream = accept.getInputStream();
>      //获取数据
>      //先可以把流中的数据存在该流
>      ByteArrayOutputStream byteArrayOutputStream = new                                                                    ByteArrayOutputStream();
>      //读取数据
>      byte[] b=new byte[1024];
>      int len;
>      while ((len=inputStream.read(b))!=-1){
>          //全部读取到该流中,内置数组
>          byteArrayOutputStream.write(b,0,len);
>      }
>      //把ByteArrayOutputStream中的数组中的内容输出
>      System.out.println(byteArrayOutputStream.toString());
>      //反馈消息给客户端
>      OutputStream outputStream = accept.getOutputStream();
>      outputStream.write("发送成功".getBytes());
>      //释放资源
>      outputStream.close();
>      byteArrayOutputStream.close();
>      inputStream.close();
>      accept.close();
>      serverSocket.close();
>     }
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>

解析:

socket通讯是基于TCP/IP协议的连接,该协议是基于IP和端口的,socket服务端跑起来之后会一直监控发送到某个端口上的数据。
但是,socket所传输的内容不像文件一样,文件内容结尾有标志符。 socket传输的内容没有结尾标志符,所以socket传输内容如果不关闭流,read方法和readline方法在读取完数据后,会处于阻塞状态 ,一直等待数据传输过来,因为流没有关闭,会话还在进行。

注意事项:

复制代码
* 若提前关闭IO流,会导致整个socket都无法使用
*  _网络字节流会随socket一并关闭,无须手动close_

案例:通过网络传输本地文件

复制代码
>     客户端
>     public static void main(String[] args) throws IOException {
>         Socket socket = new Socket("127.0.0.1",10000);
>  
>         //是本地的流,用来读取本地文件的.
>         BufferedInputStream bis = new BufferedInputStream(new                                                                         FileInputStream("xxx\ xxx\ 1.jpg"));
>         //写到服务器 --- 网络中的流
>         OutputStream os = socket.getOutputStream();
>         BufferedOutputStream bos = new BufferedOutputStream(os);
>  
>         int b;
>         while((b = bis.read())!=-1){
>             bos.write(b);//通过网络写到服务器中
>         }
>         bos.flush();
>         //给服务器一个结束标记,告诉服务器文件已经传输完毕
>         socket.shutdownOutput();
>  
>  
>         BufferedReader br = new BufferedReader(new                                                                                            InputStreamReader(socket.getInputStream()));
>         String line;
>         while((line = br.readLine()) !=null){
>             System.out.println(line);
>         }
>         bis.close();
>         socket.close();
>     }
>  
>     服务端
>     public static void main(String[] args) throws IOException {
>         ServerSocket ss = new ServerSocket(10000);
>  
>     while (true) {//while循环不断接受数据
>         Socket accept = ss.accept();
>  
>         //网络中的流,从客户端读取数据的
>         BufferedInputStream bis = new                                                                                                 BufferedInputStream(accept.getInputStream());
>         //本地的IO流,把数据写到本地中,实现永久化存储
>         BufferedOutputStream bos = new BufferedOutputStream(new                                       FileOutputStream("UUID.randomUUID() +".jpg");
>  
>         int b;
>         while((b = bis.read()) !=-1){
>             bos.write(b);
>         }
>  
>         BufferedWriter bw = new BufferedWriter(new                                                                    OutputStreamWriter(accept.getOutputStream()));
>         bw.write("上传成功");
>         bw.newLine();
>         bw.flush();
>  
>         bos.close();
>         accept.close();
>         ss.close();
>     }
>     }
>  
>  
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>       
>  
>
  • 服务端也可通过多线程的方式接收多个客户端数据

三次握手 &四次挥手

三次握手:使用TCP协议前,须先建立TCP连接,形成传输数据通道。采用三次握手保证了双方对彼此的回应都有应答

四次挥手:

客户端和服务端均可以主动发起挥手动作,在socket编程中任何一方执行close()操作即可以产生挥手动作。但是一般都是客户端主动断开连接, 服务端是一直开着的

复制代码
1. 客户端发送一个信号用来关闭客户端到服务端的数据传输
2. 服务端接受这个信号,向客户端回发一个信号表示收到。
3. 当服务器将剩余数据处理完成后,服务端关闭与客户端的连接,然后再发一个信号给客户端
4. 客户端接受信号后, 客户端为了确认是否已经断开连接,再向服务端发信息验证是否已断开

日志Logback、枚举

Logback主要分为三个技术模块

复制代码
1. logback-core: 该模块为其他的两个模块提供基础代码 必须有
2. logback-classic: 完整实现了slf4j APi模块
3. logback-access模块与tomcat和jetty等Servlet容器集成 以提供Http访问日志功能

快速入门

复制代码
1. 导入jar包:在模块下新建Directory lib,放入jar包,右键lib,选择Add as Library
2. 编写配置文件:配置文件导入到src下
3. 在代码中获取日志对象
   * `private static final Logger logger= LoggerFactory.getLogger(LogBackDemo.class);`

4. 按照级别设置记录日志信息:`LOGGER.debug("debug级别的日志");`
   * 日志级别:ALL=TRACE<DEBUG<INFO<WARN<ERROR<FATAL<OFF
   * 只输出大于等于所选级别的日志信息
   * 默认的级别是debug (不区分大小写)

枚举

复制代码
1.

所有枚举类都是Enum的子类

复制代码
2.

我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项

复制代码
3.

每一个枚举项其实就是该枚举的一个对象

复制代码
4.

枚举也是一个类,也可以去定义成员变量

复制代码
5.

枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果
枚举类有其他的东西,这个分号就不能省略。建议不要省略

复制代码
6.

枚举类可以有构造器,但必须是private,空参构造也是默认private。

复制代码
   *

枚举项通过有参构造赋值:枚举(“”);

复制代码
   *

可以使用内部类的格式定义每一个枚举项

复制代码
7.

枚举类也可以有抽象方法,但是枚举项必须重写该方法

STEP15 类加载器、反射

概述:负责将.class文件加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后 在堆中生成一个代表这个类的java.lang.Class对象 ,作为方法区中类数据的访问入口

类加载时机:即用即加载

复制代码
 * 创建对象(隐式加载)
 * loaderClass, forName等(显式加载)
 * 调用或赋值类和接口的静态成员
 * 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
 * 初始化某个类的子类
 * 直接使用java.exe命令运行某个主类

不会加载类的情况:引用类时不会触发初始化,称为被动引用

复制代码
 * 通过子类引用父类的静态字段,不会导致子类初始化
 * 定义某个类类型的数组,不会触发此类的初始化
   * `MyClass[] cs = new MyClass[10];`

 * 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

流程

复制代码
1. 加载:通过包名+类名获取这个类的二进制字节流,用流将类加载到内存中,加载完毕后创建一个java.lang.Class类的对象表示这个类
2. 验证:链接阶段第一步,为了确保Class文件字节流 中包含的信息符合当前虚拟机要求,且不会危害虚拟机自身安全
3. 准备:为类的静态变量分配内存,并设置默认初始化值
4. 解析:将类的二进制数据流中的符号引用替换为直接引用
   * 如本类中用到了String类,解析前是由符号代替该类,解析时则需找到该类并替换符号

5. 初始化:根据代码中的主观计划初始化类变量和其他资源(变量赋值)

分类

启动类加载器

Bootstrap ClassLoader

虚拟机内置的类加载器

平台类加载器

Platform / Extension Classloader

负责加载JDK中的一些特殊模块

系统类加载器

System Classloader / AppClassLoader

负责加载用户类路径上所指定的类库

复制代码
 * 由下而上检查类是否已经加载
 * 由上而下尝试加载类

详解:

复制代码
 * 启动类加载器:没有父类  

用C++编写的,是JVM自带的用本地代码实现的类加载器,负责将JAVA_HOME/lib下面的核心类库或-Xbootclasspath选项指定的jar包等虚拟机识别的类库加载到内存中。简单讲就是负责Java平台核心库,用来装载核心类库。
* 扩展类加载器: 由Java语言实现,负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库,由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用。
* 系统类加载器:由Java语言实现。一般来说,Java 应用的类都是由它来完成加载的,负责将用户类路径java -classpath或-D java.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径下的类库加载到内存中。
开发者可以直接使用系统类加载器,具体可由系统类加载器加载到的路径可通过System.getProperty(“java.class.path”)查看
(自定义的类就是这个加载) 父类加载器肯定为AppClassLoader

双亲委派模式:避免类的重复加载

复制代码
 *

概述:当一个类收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后到达顶层的启动类加载器,如果父类能够完成类的加载任务,就会成功返回,倘若父类加载器无法完成任务,子类加载器才会尝试自己去加载

复制代码
 *

优势:
采用双亲委派模式使Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。如果我们在classpath路径下自定义一个名为java.lang.SingleInterge的自定义类,该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。

类加载器常用方法

复制代码
 *

public static ClassLoader getSystemClassLoader():获取系统类加载器

复制代码
   * `public ClassLoader getParent()`:获取该加载器的父类

 *

public ClassLoader getClassLoader():获取当前类的加载器

复制代码
 *

InputStream getResourceAsStream(String name):加载某一个字节文件

复制代码
>         Properties p=new Properties();

>         //配置文件默认识别为当前model下src下
>         InputStream in = Demo1.class.getClassLoader().getResourceAsStream("jdbc.properties");
>         p.load(in);
>  
>  
>                    
>                    
>                    
>

类加载器三大特性:

复制代码
1. 委托性:由下而上委托
2. 可见性:父加载器无法利用子加载器加载的类,而子加载器可以利用父加载器加载的类
3. 单一性:一个类只会被一个类加载器加载一次,不会被重复加载

反射

字节码文件在加载到内存中时为运行时类,也是Class这个类的对象,即 类对象 ;反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;是一种动态获取信息及动态调用对象方法的功能

反射特点

复制代码
 * 在运行时判断任意一个对象所属的类
 * 在运行时构造任意一个类的对象
 * 在运行时判断任意一个类所具有的成员变量和方法
 * 在运行时获取泛型信息
 * 在运行时调用任意一个对象的成员变量和方法
 * 在运行时处理注解
 * 生成动态代理

Class类总结

复制代码
 * Class本身也是一个类
 * 一个加载的类在 JVM 中只会有一个Class实例
 * 一个Class对象对应的是一个加载到JVM中的一个.class文件
 * 每个类的实例都会记得自己是由哪个 Class 实例所生成
 * 通过Class可以完整地得到一个类中的所有被加载的结构
 * Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

反射运作模式

复制代码
 * 正常方式: 引入需要的”包类”名称 --> 通过new实例化 --> 取得实例化对象
 * 反射方式: 获取Class对象–>获取constructor对象–>实例化对象–>getClass()方法–>得到完整的“包类”名称

1.

获取Class对象

复制代码
   * `public final Class getClass()`:已知某个类的实例时,获取该实例的类
   * `Class clazz = String.class;`:已知具体的类,通过类.class属性获取(此方法效能最高)
   * `Class clazz = Class.forName("java.lang.String");`:已知一个类的全类名,获取Class对象(使用频率最高)
   * `ClassLoader cl = this.getClass().getClassLoader();`
   * `Class clazz4 = cl.loadClass(“类的全类名”);`

有Class对象的类型:

① class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

② interface
③ 数组
④ enum
⑤ annotation:注解@interface
⑥ primitive type:基本数据类型
⑦ void

复制代码
2.

获取constructor对象

复制代码
   * `Constructor[] getConstructors()`:返回一个包含非私有Constructor对象的数组
   * `Constructor<?>[] getDeclaredConstructors()`:返回一个包含所有Constructor对象的数组
   * `Constructor<T> getConstructor(Class<?>... parameterTypes) `:返回一个指定参数的非私有Constructor对象
   * `Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)`:返回一个指定参数的任意Constructor对象

3.

利用constructor创建对象

复制代码
   * `T newInstance(Object... initargs)`:根据指定的构造方法创建对象
   * `setAccessible(boolean flag)`:设置为true,表示取消访问检查,可用于暴力访问私有成员
复制代码
>         Class<Demo1> clazz = Demo1.class;

>         //获取构造
>         Constructor<Demo1> constructor = clazz.getConstructor();
>         //暴力访问
>         constructor.setAccessible(true);
>         //通过newInstance()创建对象
>         //Java9之后推荐使用
>         Demo1 demo1 = constructor.newInstance();
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>

也可以通过类对象clazz.newInstance直接创建对象,但只能通过空参构造创建,已过时

复制代码
4.

利用Field赋值和获取值:一个成员变量的信息在反射中会专门封装到一个Field 对象中

复制代码
>         Field[] getFields() //返回所有公共成员变量对象的数组

>         Field[] getDeclaredFields() //返回所有成员变量对象的数组
>         Field getField(String name) //返回单个公共成员变量对象
>         Field getDeclaredField(String name) //返回单个任意成员变量对象
>  
>         void set(Object obj, Object value)
>         Object get(Object obj)
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>
复制代码
>         class  Person{

>                       private String name;
>                       int age;
>               //必须手动给出
>                 public Person() {
>                 }
>         }
>         //通过反射调用指定的属性
>             Class<Person> personClass = Person.class;
>  
>             Constructor<Person> constructor = personClass.getConstructor();
>             Person person = constructor.newInstance();
>  
>             Field field = personClass.getDeclaredField("name");
>             //私有的需要暴力访问
>             field.setAccessible(true);
>  
>             field.set(person,"张三");
>             Object o = field.get(person);
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>
复制代码
5.

获取Method的对象

复制代码
>         //返回所有公共成员方法对象的数组,==包括继承的==

>         Method[] getMethods()
>         //返回所有成员方法对象的数组,==不包括继承的==
>         Method[] getDeclaredMethods()
>         //返回单个公共成员方法对象
>         Method getMethod(String name, Class<?>... parameterTypes)
>         //返回单个任意成员方法对象
>         Method getDeclaredMethod(String name, Class<?>... parameterTypes)
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>

代码演示

复制代码
>         Object invoke(Object obj, Object... args):运行方法

>         参数一:用obj对象调用该方法
>         参数二:调用方法的传递的参数(如果没有就不写)
>         返回值:方法的返回值(如果没有就不写)
>  
>         public class Demo1 {
>             public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
>                 //通过反射调用指定的属性
>                 Class<Person> personClass = Person.class;
>                 Constructor<Person> constructor = personClass.getConstructor();
>                 Person person = constructor.newInstance();
>  
>                 Method method = personClass.getDeclaredMethod("m2",String.class);
>                 method.setAccessible(true);
>                 Object aaa = method.invoke(person,"aaa");
>                 System.out.println(aaa);
>             }
>         }
>         class  Person{
>             private String name;
>             int age;
>             //必须手动给出
>             public Person() {
>             }
>             public void m1(String name){
>                 System.out.println("m1(String)");
>             }
>             private void m2(String name){
>                 System.out.println("m2(String)");
>             }
>         }
>  
>  
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>                    
>

STEP16 XML、文档约束DTD&schema

XML

用于:存储数据和传输数据 或 作为软件的配置文件

在形式上,标记中可能包括注释、引用、字符数据段、起始标记、结束标记、空元素、文档类型声明(DTD)和序言

规则:

复制代码
1. 必须有声明语句,文档声明必须是第一行第一列  

<?xml version="1.0" encoding="UTF-8" standalone="yes”?>
version:该属性是必须存在的
encoding:该属性不是必须的打开当前xml文件的时候应该是使用什么字符编码表(一般取值都是UTF-8)
standalone: 该属性不是必须的,描述XML文件是否依赖其他的xml文件,
取值为yes/no
2. XML文档有且只有一个根元素
3. 所有的标记必须有相应的结束标记,所有的空标记也必须被关闭

复制代码
4. 属性值使用引号
5. XML中可存在的其他内容  

注释信息:
特殊字符
< 小于
> 大于
& 和号
' 单引号
&quot; 引号
CDATA区

xml解析

复制代码
>     <?xml version="1.0" encoding="utf-8" ?>

>     <!--描述学生信息的xml   注释快捷键 ctrl+/ -->
>     <students>
>         <student id="1">
>             <name>张三</name>
>             <age>23</age>
>             <info>
>                 你好< >世界
>             </info>
>         </student>
>         <student id="2">
>             <name>张三</name>
>             <age>23</age>
>         </student>
>         <aa></aa>
>     </students>
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>
复制代码
>     public static void main(String[] args) throws DocumentException {

>         //1 获得解析器对象 加载xml文件 获取文档对象
>         SAXReader saxReader = new SAXReader();
>         Document document = saxReader.read(new File("day17\ xml\ student.xml"));
>         //2 获取根标签
>         Element rootElement = document.getRootElement();
>         //3 获取子标签
>         //.elements()获取所有直接子标签
>         //elements("student") 获取所有指定名称的子直接子标签
>         List<Element> list = rootElement.elements("student");
>  
>         //得到每个子标签
>         for (Element element : list) {
>             //获取属性
>             Attribute id = element.attribute("id");
>             String idValue = id.getValue();
>             //获取子标签
>             String name = element.element("name").getText();
>             String age = element.element("age").getText();
>             System.out.println(idValue+"  "+name+"  "+"  "+age);
>         }
>     }
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>
复制代码
>     // 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)

>             // 加载xml
>             InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
>             // 解析xml
>             SAXReader saxReader = new SAXReader();
>             try {
>                 Document document = saxReader.read(resourceAsStream);
>                 Element rootElement = document.getRootElement();
>                 List<Element> beanList = rootElement.selectNodes("//bean");
>                 for (int i = 0; i < beanList.size(); i++) {
>                     Element element =  beanList.get(i);
>                     // 处理每个bean元素,获取到该元素的id 和 class 属性
>                     String id = element.attributeValue("id");        // accountDao
>                     String clazz = element.attributeValue("class");  // com.dabing.edu.dao.impl.JdbcAccountDaoImpl
>                     // 通过反射技术实例化对象
>                     Class<?> aClass = Class.forName(clazz);
>                     Object o = aClass.newInstance();  // 实例化之后的对象
>  
>                     // 存储到map中待用
>                     map.put(id,o);
>  
>                 }
>  
>                 // 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
>                 // 有property子元素的bean就有传值需求
>                 List<Element> propertyList = rootElement.selectNodes("//property");
>                 // 解析property,获取父元素
>                 for (int i = 0; i < propertyList.size(); i++) {
>                     Element element =  propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
>                     String name = element.attributeValue("name");
>                     String ref = element.attributeValue("ref");
>  
>                     // 找到当前需要被处理依赖关系的bean
>                     Element parent = element.getParent();
>  
>                     // 调用父元素对象的反射功能
>                     String parentId = parent.attributeValue("id");
>                     Object parentObject = map.get(parentId);
>                     // 遍历父对象中的所有方法,找到"set" + name
>                     Method[] methods = parentObject.getClass().getMethods();
>                     for (int j = 0; j < methods.length; j++) {
>                         Method method = methods[j];
>                         if(method.getName().equalsIgnoreCase("set" + name)) {  // 该方法就是 setAccountDao(AccountDao accountDao)
>                             method.invoke(parentObject,map.get(ref));
>                         }
>                     }
>  
>                     // 把处理之后的parentObject重新放到map中
>                     map.put(parentId,parentObject);
>                 }
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

DTD

概述:DTD为平面式文档,在写xml时要按照DTD指定的规则编写,即仅能使用约束中创建的元素及属性

步骤

复制代码
1. 创建一个文件,后缀为.dtd
2. 看xml文件中使用了哪些元素,使用<!ELEMENT>可以定义元素
3. 判断元素是简单元素还是复杂元素,即是否有子元素
复制代码
>     <!--指定DTD的语法,以“”结束;

>     students表示根元素;
>     SYSTEM表示dtd文件在本地;
>     “students.dtd”表示DTD文件路径-->
>     <!DOCTYPE students SYSTEM "students.dtd">
>     <!--students元素中可以包含1~n个student元素-->
>     <!ELEMENT students (student+)>
>     <!--student元素中须包含name、age、sex元素,且顺序也是固定的-->
>     <!ELEMENT student (name,age,sex)>
>     <!--name、age、sex元素内容分别为字符串文本、任意元素、空元素-->
>     <!ELEMENT name (#PCDATA)>
>     <!ELEMENT age ANY>
>     <!ELEMENT sex EMPTY>
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

引入方式

复制代码
1. `<!DOCTYPE 根元素名称 SYSTEM ‘DTD文件的路径'>`:本地引入
2. `<!DOCTYPE 根元素名称 [ dtd文件内容 ]>`:xml文件内部引入(放在内容上方)
3. `<!DOCTYPE 根元素的名称 PUBLIC "DTD文件名称" "DTD文档的URL">`:网络引入
   * 外部公共DTD表示由某个公司或权威组织发布

语法规则

复制代码
1.

定义元素:

复制代码
   *

类型:ANY、EMPTY、PCDATA

复制代码
   *

内容:文本数据、子元素

复制代码
   *

子元素出现次数:? * +

表示stu元素可以有0~1个name子元素,即name子元素可有可无。 表示stu元素可以有0~n个name子元素; 表示stu元素可以有1~n个name子元素
复制代码
   *

多个子元素<!ELEMENT stu (name,age,sex)>
子元素出现顺序要与声明顺序一致

复制代码
   *

枚举子元素<!ELEMENT stu (name | age | sex)>
表示stu只能有一个子元素,可以是三个枚举项中任意一个

复制代码
   *

复合声明:上述规则复合使用

复制代码
2.

定义属性

属性定义的格式

复制代码
>         <!ATTLIST student number CDATA #REQUIRED>

>         表示student元素的number为文本类型,这个属性是必须的。
>  
>  
>                    
>

最常见的属性类型:
CDATA,表示文本类型;
#REQUIRED,表示属性是必须的。
#IMPLIED,表示属性是可选的。即这个属性可以不给出;
#FIXED value:属性值是固定的

schema

与DTD的区别

复制代码
1. schema约束文件也是一个xml文件,符合xml的语法,这个文件的后缀名.xsd
2. 一个xml中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
3. dtd里面元素类型的取值比较单一常见的是PCDATA类型,但是在schema里面可以支持很多个数据类型
4. schema 语法更加的复杂
5. Schema文件用来约束一个xml文件,同时也被别的文件约束着

创建步骤

复制代码
>     <!--

>         1,创建一个文件,这个文件的后缀名为.xsd。
>         2,定义文档声明
>         3,schema文件的根标签为: <schema>
>         4,在<schema>中定义属性:xmlns=http://www.w3.org/2001/XMLSchema
>         约束别人的同时也被别人约束着
>         5,在<schema>中定义属性 :targetNamespace =唯一的url地址。
>     指定当前这个schema文件的名称空间, 以便其他xml文件找到这个约束
>         6,在<schema>中定义属性 :elementFormDefault="qualified",表示当前schema文件是一个质量良好的文件。
>         7,通过element定义元素
>         8,判断当前元素是简单元素还是复杂元素-->
>     <?xml version="1.0" encoding="UTF-8"?>
>     <schema xmlns="http://www.w3.org/2001/XMLSchema"
>             targetNamespace="http://www.xxz.org/user"
>             elementFormDefault="qualified">
>         <!--
>             element 根
>             complexType 复杂元素
>             sequence 子元素要按顺序定义
>  
>             name: 属性名
>             type 属性类型
>          -->
>         <element name="user">
>             <complexType>
>                 <sequence>
>                     <element name="id" type="int"/>
>                     <element name="username" type="string"/>
>                     <element name="birthday" type="date"/>
>                 </sequence>
>             </complexType>
>         </element>
>     </schema>  
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <user>
>       <id></id>
>       <username></username>
>       <birthday></birthday>
>     </user>
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

引入步骤

复制代码
>     <!--

>     1,在根标签上定义xmlns="http://www.w3.org/2001/XMLSchema-instance", 表示本文件是被别人约束的
>     2,通过xmlns引入约束文件的名称空间
>     3,给某一个xmlns属性添加一个标识,用于区分不同的名称空间
>         格式为: xmlns:标识=“名称空间地址”。
>         标识可以是任意的,但是一般取值都是xsi
>     4,通过xsi:schemaLocation指定名称空间所对应的约束文件路径
>          格式为:xsi:schemaLocation = "名称空间url 文件路径“-->
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>         xmlns="http://www.xxz.org/user"
>           xsi:schemaLocation="http://www.xxz.org/user">
>  
>         <id>1</id>
>         <username>zhangsan</username>
>         <birthday>1990-12-12</birthday>
>     </user>
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

定义属性

复制代码
>      <attribute name="id" type="int" use="required">:

>      表示所有的元素必须有id的属性
>                    name:属性名称
>                    type:属性类型
>                    use:是否必须出现 required(必须的) optional(可选的)
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <schema xmlns="http://www.w3.org/2001/XMLSchema"
>             targetNamespace="http://www.xxz.org/user"
>             elementFormDefault="qualified">
>         <!--
>             element 根
>             complexType 复杂元素
>             sequence 子元素要按顺序定义
>  
>             name: 属性名
>             type 属性类型
>          -->
>         <element name="users">
>             <complexType>
>                 <sequence>
>                     <element name="user">
>                         <complexType>
>                             <sequence>
>                                 <element name="id" type="int"/>
>                                 <element name="username" type="string"/>
>                                 <element name="birthday" type="date"/>
>                             </sequence>
>                             <attribute name="id" type="string" use="required"></attribute>
>                         </complexType>
>                     </element>
>                 </sequence>
>             </complexType>
>         </element>
>     </schema>
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <users  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>            xmlns="http://www.xxz.org/user"
>            xsi:schemaLocation="http://www.xxz.org/user use.xsd">
>  
>         <user id="001">
>             <id>1</id>
>             <username>33</username>
>             <birthday>1888-11-11</birthday>
>         </user>
>     </users>  
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

注解

注解是描述代码的代码,用于给编译器识别

生成文档相关的注解

@Author 标明开发该类模块的作者,多个作者之间用 , 分割
@Version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明如果没有参数就不能写
@return 对方法返回值的说明,如果是void 就不能写
@exception 对方法中可能抛出的异常进行说明,如果方法没有用throws显示的抛出异常就不能写

方法相关

@param a //格式要求: @param 形参名 形参类型 形参说明 (可有多个)
@return //格式要求 @return 返回值类型 返回值说明
@throws Exception //格式要求 异常类型 异常说明 (可有多个)

格式检查

@Override
@Deprecated:表示该方法是一个过期的方法
@suppressWarnings(“all”):压制警告

自定义注解&元注解 //TODO 见day16笔记

单元测试工具Junit

使用基本流程:

复制代码
1. 将junit的jar包导入到工程中
2. 编写测试方法,该测试方法必须是 _公共的无参数无返回值的非静态方法_
3. 在测试方法上使用@Test注解标注该方法是一个测试方法
   * @Test:表示测试该方法
   * @Before:在测试的方法前运行
   * @After:在测试的方法后运行

4. 选中测试方法右键通过junit运行该方法

注意事项:

复制代码
* 使用单元测试时,相对路径为Junit包所在的模块下  

–>

复制代码
>      <id>1</id>

>      <username>zhangsan</username>
>      <birthday>1990-12-12</birthday>
>  
>  
>             
>             
>
复制代码

定义属性

复制代码
>      <attribute name="id" type="int" use="required">:

>      表示所有的元素必须有id的属性
>                    name:属性名称
>                    type:属性类型
>                    use:是否必须出现 required(必须的) optional(可选的)
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <schema xmlns="http://www.w3.org/2001/XMLSchema"
>             targetNamespace="http://www.xxz.org/user"
>             elementFormDefault="qualified">
>         <!--
>             element 根
>             complexType 复杂元素
>             sequence 子元素要按顺序定义
>  
>             name: 属性名
>             type 属性类型
>          -->
>         <element name="users">
>             <complexType>
>                 <sequence>
>                     <element name="user">
>                         <complexType>
>                             <sequence>
>                                 <element name="id" type="int"/>
>                                 <element name="username" type="string"/>
>                                 <element name="birthday" type="date"/>
>                             </sequence>
>                             <attribute name="id" type="string" use="required"></attribute>
>                         </complexType>
>                     </element>
>                 </sequence>
>             </complexType>
>         </element>
>     </schema>
>  
>     <?xml version="1.0" encoding="UTF-8"?>
>     <users  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>            xmlns="http://www.xxz.org/user"
>            xsi:schemaLocation="http://www.xxz.org/user use.xsd">
>  
>         <user id="001">
>             <id>1</id>
>             <username>33</username>
>             <birthday>1888-11-11</birthday>
>         </user>
>     </users>  
>  
>  
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>              
>

注解

注解是描述代码的代码,用于给编译器识别

生成文档相关的注解

@Author 标明开发该类模块的作者,多个作者之间用 , 分割
@Version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明如果没有参数就不能写
@return 对方法返回值的说明,如果是void 就不能写
@exception 对方法中可能抛出的异常进行说明,如果方法没有用throws显示的抛出异常就不能写

方法相关

@param a //格式要求: @param 形参名 形参类型 形参说明 (可有多个)
@return //格式要求 @return 返回值类型 返回值说明
@throws Exception //格式要求 异常类型 异常说明 (可有多个)

格式检查

@Override
@Deprecated:表示该方法是一个过期的方法
@suppressWarnings(“all”):压制警告

自定义注解&元注解 //TODO 见day16笔记

单元测试工具Junit

  • 使用基本流程:

    1. 将junit的jar包导入到工程中

    2. 编写测试方法,该测试方法必须是 公共的无参数无返回值的非静态方法

    3. 在测试方法上使用@Test注解标注该方法是一个测试方法

      • @Test:表示测试该方法
      • @Before:在测试的方法前运行
      • @After:在测试的方法后运行
    4. 选中测试方法右键通过junit运行该方法

  • 注意事项:

    • 使用单元测试时,相对路径为Junit包所在的模块下

*[增加注释]: 鼠标放置到该位置自动出现
*[T o1,T o2]: o1对象为将要存入集合的元素,o2为已存入的元素
*[哈希表]: 特点是存储快
*[native]: 表示调用的本地操作系统的方法
*[一般情况下,不同对象的哈希值是不同的]: 某些特殊情况,如“重地”和“通话”的哈希值相同
*[默认加载因]: 当数组中存了16 * 加载因个元素后,数组就会自动扩容为原先的两倍
*[七上八下]: JDK7新元素在上部添加,8以后在下部添加
*[Collections]: //TODO
*[处理(高级)流]: 给节点流增强功能,并不能传输数据
笔实际写字的是笔芯,无论你外边怎么包装,

笔杆多么好,它之所以敢叫笔就是因为里边有笔芯,

笔杆的存在只是让你写字的时候更好握一些。

这种设计来源于java的一种设计模式:装饰者模式

*[ASCII字符表]: American Standard Code for Information Interchange 美国信息交换标准代码
*[自动计算出一个序列号]: 根据包名,类名,继承关系,非私有的方法和属性,
以及参数,返回值等诸多因子计算得出的一个64位的哈希字段。

这个值基本上是唯一的。

全部评论 (0)

还没有任何评论哟~