跟学尚硅学习,趴的老师笔记仅供自己学习和复习无他用途-JavaSE复习笔记

JavaSE复习笔记

第一章 Java概述

一、计算机语言

  1. 机器语言

  2. 汇编语言

  3. 高级语言:更接近人类语言,方便编写与维护,但相对机器语言执行效率低。

二、跨平台原理

一处编写,到处运行。

Java程序借助JVM实现跨平台,在不同平台上需要先有JVM

在这里插入图片描述

三、JVM、JRE和JDK关系

  1. JVM:Java虚拟机
  2. JRE :Java运行环境 = JVM和核心类库
  3. JDK :Java开发工具集 = JRE + 一些开发工具,比如java、javac等

四、开发环境搭建

  1. 安装JDK

  2. 配置环境变量

    • 目的:

      在任意路径下都可以使用JDK安装目录下的java等命令

    • 配置方式:

      右键点击我的电脑-》属性-》高级系统设置-》系统环境变量-》新建:
      			变量名:JAVA_HOME
      			变量值:JDK的安装路径,比如C:\Program Files\Java\jdk1.8.0_202\bin	-》保存-》编辑Path变量-》新建:%JAVA_HOME%\bin  -》保存确认-》确认-》确认-》重新打开CMD命令行-》输入java或javac命令进行测试
      

五、Java程序开发3个步骤

  1. 编写源代码,保存问后缀为.java的源文件

  2. 编译源文件,使用javac命令编译,产生后缀为class的字节码文件

  3. 运行编译后的字节码文件,产生结果

在这里插入图片描述

第二章 Java基础语法

一、注释

给程序员看的说明性文字,不会影响程序的编译执行

  1. 单行注释://注释内容
  2. 多行注释:/* 注释内容 */
  3. 文档注释:/** 注释内容 */

二、关键字

Java赋予了特殊含义的字符串(单词),比如public、class、static、void等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FCgBezdm-1684333193172)(JavaSE复习笔记_imgs/image-20220610085240701.png)]

保留字:const、goto

特殊值:true、false、null 类似关键字

三、标识符

程序用来给类、接口、变量等命名使用的字符串就是标识符

  1. 命名规则-必须遵守
    • 由英文大小写字母、数字、下划线_、美元符号组成。
    • 并且不能由数字开头
    • 不能使用关键字和特殊值
    • 严格区分大小小写
  2. 命名规范 -建议遵守
    • 见名知意
    • 给类、接口等命名一般使用大驼峰发则,XxxYyyZzz,比如:HelloWorld,Hello
    • 给方法,变量等起名一般小驼峰发则,xxxYyyZzz,比如:name,firstName,getName
    • 给常量起名,通常全部大写字母,多个单词使用下划线分割,XXX_YYY_ZZZ,比如:MAX_VALUE
    • 给包起名,通常全部小写使用点来分割,xxx.yyy.zzz,比如:com.atguigu.test

四、常量

程序运行过程中其值不会发生改变的量

  1. 自定义常量,使用关键字final来修饰变量,例如:final int x = 123;

  2. 字面值、字面量(literal):

    字面量分类举例
    字符串字面量“HelloWorld”
    整数字面量12,-23
    浮点字面量12.34
    字符字面量‘a’,‘A’,‘0’,‘好’
    布尔字面量true,false
    空值字面量null

五、变量

程序运行过程中,其值可以发生改变的量

  1. 变量的作用:

    存储一个数据,本质是内存中的一块内存区域

  2. 变量的声明

    数据类型 变量名;

    int age;//声明整型变量age
    age = 18;//给age变量赋值
    

    数据类型 变量名 = 值;

    int x = 123;
    

    数据类型 变量1=值1,变量2=值2,…;

    同时声明多个同类型变量

    int a,b,c;
    a = 1;
    b = 2;
    c = 3;
     
    int x=1,y=2,z=3;
    
  3. 变量的使用

    通过变量名来使用变量中存储的数据

    int age = 18;
    age = 19;
    System.out.println("age = "+age);//age = 19;
    
  4. 变量的使用注意事项

    • 变量必须声明才能使用,否则编译失败
    • 变量必须先初始化值才能使用,否则编译失败
    • 变量有作用域,其作用域是声明变量所在的大括号内
    • 同一个作用域内不能声明同名变量

六、计算机如何存储数据

  1. 进制

    • 二进制
    • 十进制
    • 八进制
    • 十六进制
  2. 计算机存储单位

    • **位(bit):**是数据存储的最小单位,也就是一个二进制位。其中8 bit 就称为1个字节(Byte)。
    • **字节(Byte):**是计算机信息技术用于计量存储容量的一种计量单位,1字节等于8bit。
    • 转换关系:
      • 8 bit = 1 Byte
      • 1024 Byte = 1 KB
      • 1024 KB = 1 MB
      • 1024 MB = 1 GB
      • 1024 GB = 1 TB
  3. 不同类型数据的存储形式

    • 整数使用补码的形式存储

      **原码:**最高位为符号为,0表示正数,1表示负数,其他位为数值位。

      正整数的原码、反码与补码完全一致

      负整数的反码:原码的符号为不变,其他位按位取反。

      **负整数的补码:**反码基础上加1

    • 浮点数的存储形式

      Java的float、double类型存储的数据是一个不精确的近似值

      同样的字节数存储浮点数的范围要远大于存储整数。

    • 字符数据的存储

      使用编码表:字符与整数的对应表

      ASCII码表:使用一个字节来存储常用的英文字符等,比如’a’ -> 97

      Unicode:统一码,Java的字符类型支持Unicode

七、数据类型

Java是强类型语言,分别为不同的数据定义了不同的数据类型,因为不同类型的数据存储形式不同或者存储数据范围不同。

  1. 分类

    1. 引用数据类型:比如String、类、接口、数组等

    2. 基本数据类型:分为4类八种

在这里插入图片描述

  1. 数据类型转换

    基本数据类型中除布尔类型外的7种数值类型之间可以进行类型转换。

在这里插入图片描述

  • 隐式自动转换

    • 通常数据范围小类型转换数据范围大的类型会自动转换。
    int x = 'a';//char自动转换为int类型
    double y = x;//int类型自动转换为double类型
    
    • 不同类型之间进行混合运算时,全部自动提升为其中最大的类型
    byte a = 1;
    int b = 2;
    float c = 3;
    float d = a+b+c;//a+b+c的结果是float类型
    
    • byte、short和char三者之间运算全部自动转换为int类型
    byte a = 1;
    byte b = 2;
    int c = a + b;//a+b的结果为int类型
    
  • 显示强制转换

    格式:(目标类型)变量或值

    注意:强制类型转换可能损失数据,不要随便使用。

    • 通常把范围大的类型转换为范围小的类型需要进行强制转换
    double d = 12.34;
    int i = (int)d;//12,把double类型的变量d强制转换为int类型
    
    byte a = (byte)128;//-128,把int类型的128强制转换为byte类型
    
    • 强制类型提升

      int a = 1;
      int b = 2;
      //int c = a/b;//0
      double c = (double)a/b;//0.5
      
  • 特殊类型转换

    字符串与任意类型使用+拼接,结果都是字符串类型

    String s = "abc"+1+true;
    

八、运算符

是一种特殊的符号,用以表示数据的运算、赋值和比较等。

  1. 算术运算符

    +、-、*、/、%

    自增++、自减–

    • 单独使用自增自减运算符时

      前置自增自减与后置自增自减效果相同

      int a = 10;
      int b = 10;
      a++;
      ++b;
      System.out.println(a);//11
      System.out.println(b);//11
      
    • 组合使用自增自减运算符时

      后置自增自减效果:先使用,再自增自减

      int a = 10;
      int b = a++;
      System.out.println(a);//11
      System.out.println(b);//10
      

      前置自增自减效果:先自增自减,再使用

      int a = 10;
      int b = ++a;
      System.out.println(a);//11
      System.out.println(b);//11
      
  2. 赋值运算符

    =、+=、-=、*=、/=等

    byte a = 10;
    a += 2;//正常
    //a = a + 2;//编译失败,a+2结果为int类型,再赋值给byte类型的a报错。
    System.out.println(a);
    
  3. 关系运算符、比较运算符

    >、<、<=、>=、==、!=

    运算结果一定是布尔类型

  4. 逻辑运算符

    特点:运算符左右两边都是布尔类型,结果也一定是布尔类型

    与& :并且的意思,两边都为真结果才为真

    或| :或者的意思,一边为真结果就为真。

    非!:取反,真变假,假变真

    异或^:两边相同结果为假,两边不同结果为真。

    **短路与&&、短路或|| :**最终结果和&、|相同,但是执行过程不同,短路与和短路或执行过程是:

    如果通过运算符的左边能判断出最终结果,那么运算符的右边不再执行,效率高,推荐使用。

  5. 条件运算符

    格式:布尔表达式 ? 结果1 : 结果2;

    执行过程:表达式结果为true,最终结果为结果1,否则为结果2.

    int x = 10;
    int y = 5;
    int max = x>y?x:y;
    
  6. 位运算符(了解)

    按照二进制位进行运算的符号,执行效率高。

    按位与&、按位或|、按位取反~、按位异或^

    左移<< :左移n位,结果为乘以2的n次方

    右移>> :右移n位,结果为除以2的n次方,除不尽时向下取整。左边最高位是1补1,是0补0.

    无符号右移>>>:左边最高位永远补0,所以最终结果一定是正数。

第三章 流程控制

一、键盘录入

java.util.Scanner键盘扫描器,可以接收键盘录入的数据

//创建扫描器
import java.util.Scanner;//导入
public class Demo{
    public static void main(String[] args){
		//1.创建扫描器
        Scanner in = new Scanner(System.in);
        //2.提示信息
        System.out.println("请输入数据:");
        //3.接收键盘录入的数据
        int a = in.nextInt();//接收一个int类型数据
        
        //接收不同类型数据
        double d = in.nextDouble();
        boolean b = in.nextBoolean();
        //....
        //接收字符串
        String s = in.next();//接收一个字符串,遇到空白字符结束。
        String s2 = in.nextLine();//接收一行字符串,遇到回车键结束。
        //注意:nextLine使用之前有其他方式接收数据,会把其他方式输入的回车键接收到。
    }
}

二、流程控制语句分类

  1. 顺序结构:代码自上而下顺序执行
  2. 分支结构-选择结构:根据条件执行某些代码
  3. 循环结构:重复执行某些代码

三、分支结构语句

  1. if语句

    • 格式一:

      if(关系表达式){
        	语句体;
      }
      
    • 格式二:

      if(关系表达式) { 
        	语句体1;
      }else {
        	语句体2;
      }
      
    • 格式三:

      if (判断条件1) {
        	执行语句1;
      }else if (判断条件2) {
        	执行语句2;
      }
      ...
      }else if (判断条件n) {
       	执行语句n;
      }else {
        	执行语句n+1;
      }
      
    • if语句可以嵌套使用

  2. switch语句

    • 格式:

      switch(表达式){
          case 常量值1:
              语句块1;
              break;
          case 常量值2:
              语句块2;
              break;  
          ...
         	default:
              语句块n+1;
              break;  
      }
      
    • 格式说明与使用注意实现

      • 小括号内结果类型必须是6种之一:byte、short、char、int、String、枚举
      • case后必须是常量值,并且多个case后常量值不能重复
      • break用于跳出Switch语句,如果没有break会继续执行下一个分支中代码。
      • default分支:当所有case后常量都不匹配时,执行default分支。
  3. if语句与switch语句的比较

    • if和switch在一些情况下可以互换,都是根据条件执行某部分代码,不同的时if可以根据范围或常量来执行分支代码,switch不适合根据范围来执行分支。
    • 当条件是固定的几个值时,并且数量超过3时建议使用switch语句,效率高。

四、循环语句

  1. for循环

    • 格式:

      for(初始化语句①; 循环条件语句②; 迭代语句④){
          循环体语句③
      }
      //执行过程:1234 234 234 ....2
      for(;;){
          循环体语句块;//如果循环体中没有跳出循环体的语句,那么就是死循环
      }
      
    • 示例:

      for(int i=0;i<5;i++){
          System.out.println(i);
      }
      //0,1,2,3,4
      
  2. while循环

    • 格式:

      while (循环条件语句①) {
          循环体语句②;
      }
      //执行流程:12 12 12 ...1
      while(true){
           循环体语句;//如果此时循环体中没有跳出循环的语句,也是死循环
      }
      
    • 示例:

      int i = 0;
      while(i<5){
          System.out.println(i);
        	i++;
      }
      
      //0,1,2,3,4
      
      
  3. do…while循环

    • 格式

      do{
          ①循环体语句
      }while(循环条件语句②);
      //执行流程:12121....2
      
    • 示例

      int i = 0;
      do{
          System.out.println(i);
          i++;
      }while(i<5);
      //0,1,2,3,4
      
  4. 循环语句的区别

    1. 从循环次数角度分析

      • do…while循环至少执行一次循环体语句

      • for和while循环先判断循环条件语句是否成立,然后决定是否执行循环体,至少执行零次循环体语句

    2. 从循环变量的生命周期角度分析

      • for循环的循环变量在for()中声明的,在循环语句结束后,不可以被访问;

      • while和do…while循环的循环变量因为在外面声明的,所以while和do…while结束后可以被继续使用的;

    3. 如何选择

      • 遍历有明显的循环次数(范围)的需求,选择for循环

      • 遍历没有明显的循环次数(范围)的需求,循环while循环

      • 如果循环体语句块至少执行一次,可以考虑使用do…while循环

      • 本质上:三种循环之间是可以互相转换的,都能实现循环的功能

    4. 三种循环结构都具有四要素:

    • (1)循环变量的初始化表达式

    • (2)循环条件

    • (3)循环变量的修改的迭代表达式

    • (4)循环体语句块

    1. 死循环比较
    • ​ for(;😉{循环体} ,除循环体外不需要执行其他语句,性能略高
    • ​ while(true){ 循环体},除循环体外还需要执行小括号里的表达式
  5. break与continue关键字

    1. break关键字的应用场景与作用
      • 用在switch语句中,用于跳出switch语句
      • 用在循环语句中,用于跳出当前循环体
    2. continue关键字的应用场景与作用
      • 用在循环语句中,用于跳出本次循环,继续下一次循环
  6. 循环的嵌套使用

    //外循环控制行数
    for(int i=0;i<5;i++){
        for(int j=0;j<5;j++){
            if(j>i)
                break;
            System.out.print(i);
        }
        System.out.println();//换行
    }
    

第四章 数组

一、数组概述

  1. 数组:数据容器,可以存储多个同类型的数据

  2. 相关概念:

    • 数组名:数组的名字

    • 元素:数组存储的每个数据

    • 索引、脚标:用于访问元素的编号,从0开始

    • 长度:数组可以存储的元素个数

    在这里插入图片描述

  3. 数组的特点:

    • 数组一旦创建长度不可改变
    • 数组是一块连续的内存空间,可以通过索引快速访问每个元素
    • 数组元素类型可以是基本数据类型或引用数据类型

二、数组的声明与初始化

  1. 声明格式:

    数据类型[] 数组名;

    int[] ages;
    String[] names;
    
  2. 数组初始化

    • 静态初始化

      程序给出数组元素的初始值,长度有系统决定

      数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
      或
      数据类型[] 数组名;
      数组名 = new 数据类型[]{元素1,元素2,元素3...};
      
      简化方式:
      数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中完成,不能分开两个语句写
      
      int[] arr = {11,22,33};
      
    • 动态初始化

      程序员指定数组的长度,元素初始值由系统给出

       数据类型[] 数组名字 = new 数据类型[长度];
      
        或
      
       数据类型[] 数组名字;
       数组名字 = new 数据类型[长度];
      
      int[] arr = new int[3];
      

三、数组元素的访问与遍历

  1. 访问数组元素格式

    获取元素:数组名[索引] ;

    设置元素值:数组名[索引]=值 ;

    int[] arr = {11,22,33};
    int a = arr[0];//获取数组指定位置的元素
    arr[0] = 10;//给指定位置的元素设置值
    
  2. 数组的长度

    获取数组的长度(int类型):数组名.length

  3. 数组的循环遍历

    for(int i=0;i<arr.length;i++){
        System.out.println(arr[i]);
    }
    

四、数组元素的默认初始值

动态初始化时,程序员指定数组的长度,元素初始值由系统给出,不同类型的数组元素初始值不同。

在这里插入图片描述

五、数组的内存分析

在这里插入图片描述

六、数组相关算法

  1. 统计相关算法:求和、求平均值、求偶数个数等

  2. 查找相关算法:在查找数组中指定元素及位置,查找最大最小值及位置

    • 顺序查找指定元素
    • 二分查找指定元素
  3. 排序算法:元素从小大的或从大到小进行排序

    • 冒泡排序:相邻元素两两比较,大的交换到后面,每轮确定一个最大元素到最后位置。
  4. 数组的反转:把元素前后倒置

  5. 数组的扩容:本质上是通过创建新数组实现扩容

  6. 数组元素的插入:在指定位置插入指定元素,保证插入后原数据的有序性。

  7. 数组元素的删除:删除指定位置的元素,保证删除后原数据的有序性和连续性。

七、Arrays数组工具类

java.util.Arrays工具类,封装了一些方便操作数组的功能。

int[] arr = {11,2,33,4,55,6};
//排序
Arrays.sort(arr);
//二分查找
int index = Arrays.binarySearch(arr,11);//11为要查找的元素
//复制一个新数组
int[] arr2 = Arrays.copyOf(arr,10);//10为新数组的长度
//把数组转换为一个字符串
String s = Arrays.toString(arr);

八、二维数组

  1. 概念:二维数组本质上是一个元素为一维数组的数组

  2. 声明格式:

    数据类型[][] 数组名;
    
    int[][] arr;
    
  3. 二维数组初始化

    静态初始化:我们指定元素初始值

    数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2,...},{元素1,元素2,...},...};
    或
    数据类型[][] 数组名 ={{元素1,元素2,...},{元素1,元素2,...},...};
    
    int[][] arr = new int[][]{{1,2,3},{4,5},{6}};
    //简化
    int[][] arr ={{1,2,3},{4,5},{6}};
    

    动态初始化:我们指定数组长度

    //(1)确定行数和列数
    元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
    //	m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
    //	n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格
    
    //此时创建完数组,行数、列数确定,而且元素也都有默认值
    
    //(2)再为元素赋新值
    二维数组名[行下标][列下标] =;
    
    int[][] arr = new int[3][5];
    arr[0][0]= 1;
    //或者
    int[][] arr = new int[3][];  
    //arr[0][0] = 1;//会抛空指针异常
    arr[0]= new int[]{1,2,3};
    
  4. 二维数组的元素访问与遍历

    int[][] arr ={{1,2,3},{4,5},{6}};
    //获取二维数组的长度
    int len = arr.length;
    //遍历二维数组,获取每个一维数组 :arr[i]
    for(int i=0;i<len;i++){
       //遍历一维数组arr[i]
        for(int j=0;j<arr[i].lenght;j++){
            System.out.print(arr[i][j]+" ");
        }
        System.out.println();//换行
    }
    
  5. 二维数组的内存分析

第五章 面向对象基础(上)

一、概述

  1. 面向对象:面向对象和面向过程都是一种编程思想,基于不同的思想会产生不同的程序设计方法。Java语言是在面向对象思想的指引下去设计、开发计算机程序的,所以Java语言是一种面向对象的程序设计语言。而C语言是一种面向过程的程序设计语言。

    参照现实生活中事物的组织规则,体现到程序中来。

  2. 面向对象与面向过程比较:

    • 面向过程:
      • 关注点是步骤,怎么做,程序员是执行者
      • 缺点:不利于程序的扩展和维护
    • 面向对象:
      • 关注点是对象,考虑谁来着,程序员是指挥者
      • 优点:有利于程序的扩展和维护

二、类与对象

  1. 类与对象的概念理解

    类:一类具有共同特征的事物的抽象描述

    对象:是类的实例,是一个具体的事物(从两个维度描述:属性和功能)。

    类是对象的模板,通过类来创建对象,对象是类的具体实例。

  2. 类的定义格式:

    修饰符 class 类名{
        //成员
        //属性-成员变量
        //功能-成员方法
    }
    
    public class Student{
    	//属性-成员变量
    	String name;
    	int age;
        //功能-方法
        public void study(){  
            
        }
    }
    
  3. 创建对象并使用

    //创建对象
    类名 对象名 = new 类名();
    
    Student s = new Student();
    System.out.println(s);//s变量中存储的是对象的内存地址
    
    System.out.println(s.name);
    s.study();
    
  4. 对象的内存分析

在这里插入图片描述

三、实例变量

  1. 变量的分类:

    1. 成员变量:定义在类的直接成员位置
      • 静态变量-类变量:有static修饰的变量(后面讲)
      • 非静态变量-实例变量:没有static修饰
    2. 局部变量:定义在方法内或形参上等局部区域内
  2. 成员变量的声明

    定义在类的直接成员位置

    public class Student{
        //静态变量-类变量
        static int x;
        //实例变量-非静态变量
        String name;
        int age;
    }
    
  3. 实例变量特点:

    • 必须通过对象名来访问,即对象名.变量名
    • 每个对象独有一份实例变量值。
    • 所有成员变量都有默认初始值,类似数组元素的默认初始值。
  4. 实例变量的内存分析

    在这里插入图片描述

  5. 实例变量与局部变量的比较

    实例变量局部变量
    声明的位置直接声明在类的成员位置声明在方法体中或其他局部区域内(方法声明上,构造方法,代码块等)
    修饰符public、private、final等不能使用访问权限修饰符,可以使用final
    内存加载位置
    初始化值有默认初始化值无默认初始化值
    生命周期同对象的生命周期随着方法的调用而存在,方法调用完毕即消失

四、方法

  1. 方法:也叫函数,一个特定功能的代码块。把功能封装到方法中的目的是为了重复利用代码。成员变量用于描述对象的属性信息,方法用于描述对象的功能。

  2. 方法的分类

    • 静态方法-类方法:有static修饰(后面讲)
    • 非静态方法-实例方法:没有static修饰的方法
  3. 方法的定义格式

    方法定义在类的成员位置,多个方法之间是并列定义的,不能嵌套定义。

    【修饰符】 返回值类型 方法名(【形参列表】){
        //方法体return;}
    

    格式说明:

    • 修饰符通常是权限修饰符、final、static等
    • 返回值类型:方法返回的数据的类型声明,如果使用void表示不能返回数据。这里与return语句配合使用。
    • 方法名:符合标识符的命名规则与规范
    • 形参列表:声明了调用方法时需要传入参数的个数和类型。
    • 方法体:实现功能的代码
    • return语句:①结束方法 ②返回数据。如果return后跟返回值,必须与方法声明上的返回值类型一致。
    public class Student{
        //声明方法:
        public void show(){
           System.out.pritln("show...");
        }
        
        public int add(int a,int b){
            return a+b;
        }
    }
    
  4. 实例方法的调用

    方法不调用不执行,调用一次执行一次

    实例方法必须通过对象来调用

    对象名.方法名(【实参列表】);
    
    Student s  =  new Student();
    s.show();//调用空参无返回值方法
    int sum = s.add(1,2);//调用有参有返回值方法,使用变量接收返回值
    

    方法调用注意事项:

    • 形参:方法声明上的参数列表是形式上的参数。
    • 实参:方法调用是传入的实际参数
    • 方法调用时,实参与形参的类型,个数及顺序必须一致。
    • 方法如果有返回值,可以使用变量接收或直接输出,否则返回的数据会丢失。方法如果没有返回值,那么不能使用变量接收或直接输出。
  5. 一个类内部实例变量和方法的访问

    public class Student{
        //实例变量
        String name;
        int age;
        //实例方法:
        public void show(){
           System.out.pritln("show..."+name);//直接访问本来的实例变量(省略了this)
           System.out.pritln("show..."+this.name);//this代表当前对象
           int sum = add(1,2);//直接调用本类的其他实例方法(省略了this)
           int sum = this.add(1,2);//同上句
        }
        
        public int add(int a,int b){
            return a+b;
        }
    }
    
  6. 方法调用的内存分析

    方法调用时会在栈内存中开辟空间,主要用于存储方法内局部变量值,这个过程称为方法入栈,当方法执行完后会立即是否这块空间,称为方法出栈,如果方法有返回值先把返回值返回到方法的调用处。

    在这里插入图片描述

五、方法的参数传递机制

方法只有值传递,当形参为基本数据类型是传递的是数据值,当形参为引用数据类型时传递的是地址值。

方法的形参的修改会不会影响实参?

  1. 方法的形参是基本数据类型:形参的改变不会影响实参
  2. 方法的形参为引用数据类型:形参的改变可能会影响实参
  3. 特殊情况:形参为引用数据类型的String和包装类时,形参改变不会影响实参。
public class Demo{
    public static void main(String[] args){
        Demo d = new Demo();
        int a = 10;
        d.test1(a);
        System.out.println(a);//10
        
        int[] arr = {1,2,3};
        d.test2(arr);
        System.out.println(arr[0]);//2
        
    }
    
    public void test1(int a){
        a++;
    }
    
    public void test2(int[] arr){
        arr[0]++;
    }
}

六、方法重载

同一个类中方法名相同,参数列表不同的情况就成为方法重载。

参数列表不同指的是:参数的类型不同或个数不同或类型顺序不同。

public class Demo{
    public void show(int a,String b){}
    public int show(int a,int b){}
    String show(String b,int a){}
    void show(){}
   // int show(){}//与上一个方法声明完全相同
}

七、可变参数

如果一个方法的参数是不确定个数的同类型参数,那么可以定义成可变参数。

public void show(int... a){//形参a是可变参数,表示传入的实际参数可以是任意个int类型整数
    
}
//--------------------------------
show();
show(1);
show(1,2,3);

注意:

  • 可变参数本质上是一个数组
  • 一个方法最多只能有一个可变参数,并且只能在最后一个。
  • 方法的形参是可变参数,可以传入任意个数的同类型参数或者直接传入一个同类型的数组。

八、递归

  1. 方法的递归:指的是方法调用自身的情况

  2. 分类:

    • 直接递归:方法直接调用自己
    • 间接递归:方法间接调用自己,比如方法A调用B,B调用A
  3. 注意实现:

    • 递归必须有出口(结束条件),否则就是无穷递归,类似死循环,会导致内存溢出
    • 递归即使有结束条件,也不宜递归此次太多,否则还会导致内存溢出。
  4. 示例:

    //求n的阶乘
    public int jiecheng(int n){
        if(n==0||n==1)
            return 1;
        return jiecheng(n-1)*n;
    }
    
  5. 内存分析:

九、对象数组

数组的元素是对象(引用数据类型)

Student[] stus = new Student[3];
stus[0] = new Student();
stus[1] = new Student();
stus[2] = new Student();

在这里插入图片描述

第六章 面向对象基础(中)

一、权限修饰符

权限修饰符共有4种,分别为public,protected、缺省、private;权限修饰符可以使得数据在一定范围内可见或者隐藏。

修饰符本类本包其他包子类任意位置
private×××
缺省××
protected×
public

权限修饰符可以修饰:

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

二、封装性

  1. 概念理解:把该隐藏的隐藏,该暴露的暴露出来。

  2. 类的封装:

    可以借助权限修饰符实现类的封装。

    • 私有化属性private
    • 提供公共的get、set方法
    public class Student{
        //私有属性
        private String name;
        private int age;
        //公共的get/set方法
        public void setName(String name){
            this.name = name;//this代表当前对象,在这里用于区分局部变量和成员变量
        }
        
        public String getName(){
            return name;
        }
        //可以是IDEA模块及快捷键ALT+INSERT 快速生成getter\setter方法
        //....
    }
    

三、继承

  1. 继承:Java类有父类与子类之分,子类可以继承父类的所有成员变量和方法。

  2. 继承的好处:

    • 提高了代码的复用性

    • 提高了代码的扩展性

    • 多态的前提

      弊端:增强了类与类之间的耦合度。

  3. 继承的格式:

    在定义子类时,使用关键字extends来确定与父类的关系。

    【修饰符】 class  子类 extends 父类 {}
    
    //父类-超类-基类
    public class Animal{
        String name;
        private int age;
        void eat(){
            System.out.println("吃...")
        }
    }
    //子类-派生类
    public class Cat extends Animal{
        void catchMouse(){
            
        }
    }
    
  4. 继承的特点:

    • 子类可以继承父类的所有成员变量和方法
    • 但是子类不能直接使用父类的私有成员,如果非要访问可以通过父类继承公共的方法来访问
    • Java类只支持单继承,即一个类只能有一个父类。
    • Java类支持多层继承,即一个类可以有子类,子类还可以有子类
    • 一个类可以有多个子类

四、方法重写

  1. 概念理解:方法重写Override指定的是子类中声明了与父类相同的方法,通常方法体不同。

  2. 具体要求:

    • 必须保证父子类之间重写方法的名称相同。

    • 必须保证父子类之间重写方法的参数列表也完全相同。

    • 子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Cat< Animal)。

      注意:如果返回值类型是基本数据类型和void,那么必须是相同

    • 子类方法的权限必须【大于等于】父类方法的权限修饰符。

      注意:public > protected > 缺省 > private

      父类私有方法不能重写

      跨包的父类缺省的方法也不能重写

    • 子类方法声明的异常类型不能比父类的大。

    • 静态方法不能被重写,方法重写指的是实例方法重写,静态方法属于类的方法不能被重写,而是隐藏。

      final修饰的方法不能被重写

      private方法不能被重写, 因为子类不可见

  3. 使用注解@Override加在方法上,可以验证当前方法是否是重写方法。如果不是编译失败。

五、多态

  1. 理解:事物在不同的条件下呈现不同的状态

  2. 多态的语法形式:

    父类类型的引用指向其子类对象

    父类类型 变量 = 子类对象或变量
    
    Animal a = new Cat();
    
  3. 多态的表现

    编译看左边,运行看右边

    Animal a = new Cat();
    a.eat();//编译时a看做左边Animal类型,运行时执行右边子类Cat的eat方法
    
  4. 多态的好处

    • 提高了代码的扩展性
    • 降低了类与类之间的耦合度
  5. 多态的常用方式

    • 用在方法的参数上或成员变量
    • 用在数组
    • 用在方法的返回值类型
  6. 向上转型与向下转型:

    多态的弊端:多态引用形式时,不能调用子类的特有方法,怎么办可以向下转型为子类类型。

    • 向上转型:子类类型自动转换为父类类型

    • 向下转型:父类类型转为子类类型时需要强制转换

      (目标类型)父类类型变量。

      强制向下转换有风险,可能发生ClassCastException问题,怎么办?可以先使用instanceof关键字进行判断。

  7. instanceof关键字

    用于判断某个变量或对象运行时类型是否属于某种类型。

    Animal  a;
    a = new Cat();
    a = new Dog();
    if(a instanceof Cat){//判断a的运行时类型是否属于Cat类型。
        Cat cat  =(Cat)a;
    }else if(a instanceof Dog){
        Dog dog = (Dog)a;
    }
    
    
  8. 虚方法

    指的是可以被重写的方法。

    多态引用时,成员变量和非虚方法不具有多态性。

    虚方法引用原则是:编译看左边,运行看右边。

    • 静态分派:编译期在左边父类类型中找到最兼容最匹配的方法。
    • 动态绑定:运行时执行之前最匹配的方法的在右边子类中重写方法。

六、构造器

  1. 作用:主要是为实例变量赋值

  2. 格式:

    构造器定义在类的成员位置,与方法并列,格式与方法类似,所以也称为构造方法

    【权限修饰符】 构造器名(【参数列表】){
        //构造体
    }
    
    public class Student{
        private String name;
        private int age;
        //空参构造器
        public Student(){}
        //有参构造器
        public Student(String name){
            //this();//调用空参构造器
            this.name = name;
        }
        //有参构造器
        public Student(String name,int age){
            this(name);//调用一个参数的构造器,必须在第一行
            this.age = age;
        }
    }
    
  3. 格式说明与使用注意事项

    • 构造器只能使用权限修饰符

    • 构造器名称必须与所在的类的类名相同

    • 构造器与方法类似,但是没有返回值类型,也不能有void。

    • 构造器可以重载,名称都与类名相同,参数列表不同。

    • 每个类都默认自带一个空参构造器,但是当手动显示添加任意一个构造器时,默认的空参构造器不再存在。

    • this(【参数】)可以调用本类的其他构造器,并且这句必须在构造体第一行

    • super(【参数】)用于调用父类的构造器,

    • 每个子类的每个构造器都默认在第一行自带一个super()用于调用父类的空参构造器。

    • 当手动显示在构造体中给出this(【参数】)或super(【参数】)时,默认的super()不再存在

    • 注意:使用构造器new子类对象时,一定会通过某个构造器中的super(【参数】)来调用父类的构造器,先初始化父类数据。

七、非静态代码块

  1. 作用:跟构造器一样主要是为实例变量赋值

  2. 格式:{}

    定义在类的成员位置,与方法并列

    public class Student{
        private String name;
        private int age;
        //非静态代码块-构造代码块-实例代码块
        {
            this.name = "tom";
        }
    }
    
  3. 执行特点:

    每次使用任意构造器创建对象时都会执行,并且先于构造器执行。

    经常用来写多个构造器中相同的代码。

八、实例初始化过程

实例初始化过程是为实例变量赋予有效值的过程。

  1. 实例变量初始化值得方式

    • 直接显示赋值语句
    • 非静态代码块
    • 构造器
  2. 当使用构造器new对象时,Java底层会执行一个实例初始化方法{},类中有几个构造器就有几个方法,即调用不同的构造器就会执行对应的方法。这个方法包含4部分内容:

    1. super(【参数】) —构造器中第一行

    2. 直接显示赋值语句

    3. 非静态代码块中语句

    4. 构造器中其余代码

      以上四部分代码的执行顺序是:第一部分最先执行,其中2.3部分按照书写的先后顺序执行,最后执行构造器中其余代码。

  3. 实例初始化执行特点:

    • 创建对象时,才会执行
    • 每new一个对象,都会完成该对象的实例初始化
    • 调用哪个构造器,就是执行它对应的实例初始化方法
    • 子类super()还是super(实参列表)实例初始化方法中的super()或super(实参列表) 不仅仅代表父类的构造器代码了,而是代表父类构造器对应的实例初始化方法。

九、一些关键字

  1. this和super关键字

    • this关键字,代表当前对象

      • 应用场景:实例方法、构造器、非静态代码块中
      • 使用方式:this.实例变量或方法 ;this(【参数】)
    • super关键字,用于访问父类的数据的关键字

      • 应用场景:实例方法中,构造器中,非静态代码块中
      • 使用方式:super.实例变量或方法;super(【参数】)
    • this和super可以用于区分父子类中同名的成员变量或方法。

      this和super调用实例变量和方法时,遵从追根溯源原则。

    • this和super在继承关系中的内存分析

  2. final关键字

    final最终的不可修改的

    • 修饰的类不能被继承-太监类
    • 修饰的方法不能被重写
    • 修饰的变量是常量

十、Object类

java.lang.Object类是所有Java的超类

相关API:

  1. int hashCode() 返回该对象的哈希码值。
此方法默认返回的是对象的内存地址转换而来的一个整数。
一般建议所有的对象都重写此方法。
这个算法的常规协定:
相同的对象哈希值一定相同
不同对象的哈希值尽量不同
哈希值不同,一定不是同一个对象。
123456---%10--->6
5667987---%10--->7
  1. Class<?> getClass() 返回此 Object 的运行时类。

​ getClass().getName()返回对象到的运行时类型名称

  1. String toString() 返回该对象的字符串表示。

​ 默认返回的是对象的运行时类型名称+‘@’+对象的hashCode值得16进制表示。

​ 建议所有类都重写此方法,用于返回对象的属性信息。

  1. boolean equals(Object obj) 指示其他某个对象是否与此对象“相等”。

​ 此方法默认比较的是两个对象的内存地址,

​ 通常建议类重新此方法用于比较两个对象的内容是否相同。

  1. protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

十一、标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。标准的 JavaBean —般需遵循以下规范:

(1)类必须是具体的和公共的,

(2)并且具有无参数的构造方法,

(3)成员变量私有化,并提供用来操作成员变量的setget 方法。

(4)实现 java.io.Serializable 接口 ?

第七章 面向对象基础(下)

一、静态static

  1. 静态变量:有static修饰的成员变量,也称为类变量

    所有对象共享的一份数据,存储在方法区,推荐使用类名直接访问。

  2. 静态方法:有static修饰的方法,也称为类方法

    推荐使用类名访问。通常工具类中的方法都是静态方法。

    静态方法可以被继承,但是不能重写

  3. 静态代码块:主要作用是为静态变量初始化值。

    格式:

    //静态代码块
    static{
        //通常为类变量赋值的语句
    }
    

    执行特点:类加载时只执行一次。先于非静态代码块和构造器执行。

  4. 类的初始化

    类初始化的过程主要是为静态变量赋值的一个过程。

    类初始化时底层会执行一个方法,这个方法的执行主要包含2部分内容:

    • 静态变量的直接显示赋值语句

    • 静态代码块中语句

      这两部分内容根据书写的先后顺序执行。

    每个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。

    类的初始化一定优先于实例初始化。

  5. 静态与非静态的访问问题

    同一个类中,静态不能直接访问非静态

    public class MyClass{
        static int x = 10;//静态变量-类变量
        int y = 20;//非静态变量-实例变量
        //静态方法-类方法
        public static void testStaticMethod(){
            System.out.println("静态方法...");
        }
         //非静态方法-实例方法
        public  void testInstanceMethod(){
            System.out.println("非静态方法...");
        }
        
        //实例方法
        public void test1(){
            System.out.println(x);
            System.out.println(y);
            testStaticMethod();
            testInstanceMethod();
        }
        //静态方法
        public static void test2(){
            System.out.println(x);
            //System.out.println(y);//不可以访问实例变量
            testStaticMethod();
            //testInstanceMethod();//不可以访问实例方法
        }
    }
    

    在类的外部:访问静态成员推荐使用类名来访问,访问非静态变量或方法必须通过对象访问。

二、抽象类

  1. 概念理解:有abstract修饰的类是抽象类。

  2. 格式:

    修饰符 abstract class 类名{}
    
    //抽象类
    public abstract class Animal{
        //抽象方法:有abstract修饰的方法,没有方法体
        public abstract void eat();
    }
    
  3. 抽象类的特点(区别具体的类)

    • 抽象类有构造器但是不能创建对象
    • 抽象类中可以有抽象方法也可以没有
    • 有抽象方法的类必须是抽象类
    • 抽象类的子类必须重写父类的抽象方法,除非子类也是抽象类。

三、接口

  1. 概念理解:本质是一种规范。

  2. 语法格式:

    修饰符  interface 接口名{
        //接口的成员:
        //JDK8之前:1.静态常量2.抽象方法
        //JDK8时:3.默认方法 4.静态方法
        //JDK9时: 5.私有方法
    }
    
    //接口
    public interface Jumpable{
        //1.静态常量
        int x = 123;//默认自带修饰符public static final
        //2.抽象方法
        void jump();//默认自带修饰符public abstract
    	//3.默认方法-扩展方法-实现类可以重写或不重写-可以被继承
        default void test1(){}
        //4.静态方法,不能被实现类继承,也不能重写
        static void test2(){}
        //5.私有方法
        private void test3(){}
    }
    
  3. 接口的实现

    类与接口的关系是实现关系,一个类可以在继承另一个类的同时实现多个接口

    【修饰符】 class 实现类  implements 接口{
    	// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
      	// 重写接口中默认方法【可选】
    }
    
    【修饰符】 class 实现类 extends 父类 implements 接口{
        // 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
      	// 重写接口中默认方法【可选】
    }
    
    //类实现接口
    publc class Cat implements Jumpable{
        //重写抽象方法
        public void jump(){
            System.out.println("猫跳高...");
        }
        //可以选择是否重新默认方法
         public  void test1(){}
        //不能继承和重写接口的静态方法
         public static void test2(){}
    }
    //类实现接口
    publc class Dog extends Animal implements Jumpable{
        //重写父类和接口中的抽象方法
    }
    
  4. 接口的成员的访问

    • 访问接口的抽象方法和默认方法,必须通过创建实现类对象来访问

    • 访问接口的静态常量,推荐使用接口名来访问

    • 访问接口的静态方法,必须使用接口名来访问

      //创建接口的实现类对象
      Cat cat = new Cat();
      cat.jump();//访问重写后的抽象方法
      cat.test1();//访问默认方法
      System.out.println(Jumpable.x);//访问静态常量
      Jumpable.test2();//访问静态方法
      
  5. 接口的多态形式

    Jumpable ja = new Dog();
    
    ja.jump();//编译看左边运行看右边
    
    Animal a = (Animal)ja;
    a.eat();
    Dog d = (Dog)ja;
    d.eat();
    
  6. 接口的多继承与多实现

    • 类与类之间的关系:继承关系,一个类只能继承自一个类
    • 接口的与接口的关系:继承关系,一个接口可以同时继承自多个接口
    • 类与接口的关系:实现关系,一个类可以同时实现多个接口
  7. 接口特点总结(区别抽象类)

    • 接口没有构造器也不能创建创建
    • 接口的成员只能是:1.静态常量,2.抽象方法,3.默认方法,4.静态方法,5私有方法
    • 接口支持多实现和多继承
    • 接口的实现类必须重写接口的所有抽象方法,除非实现类也是抽象类
    • 接口的默认方法实现类可以继承,也可以重写,必须通过实现类对象来调用
    • 接口的静态方法不能被继承和重写,并且只能通过接口名来调用
  8. 经典接口

    • java.lang.Comparable接口,强行对象实现类此接口的类的对象进行排序,自然排序。
    • java.util.Comparator接口,接口的实现类可以对任意类型的对象进行排序,定制排序。
    • java.lang.Cloneable接口,实现了此接口的类的对象,可以被Object类的clone方法进行克隆

四、单例模式

设计模式:根据特定需求,一套开发实践经验的总结

单例模式:要求内存中只能存在某个类的一个实例。

  • 饿汉式

    public class Singleton{
        //静态常量--只执行一次。
        private static final Singleton instance = new Singleton();
        //私有构造器
        private Singleton(){}
        //公共的静态方法
        public static Singleton getInstance(){
            return instance;
        }
    }
    
  • 懒汉式

    有线程安全问题,后期解决

    public class Singleton{
        //静态变量--只执行一次。
        private static Singleton instance = null;
        //私有构造器
        private Singleton(){}
        //公共的静态方法
        public static Singleton getInstance(){
            if(instance == null)
                instance == new Singleton();
            return instance;
        }
    }
    

五、内部类

  1. 概念理解:定义在一个类的内部的类就是内部类

  2. 内部类分类:

    • 成员内部类:定义在外部类的成员位置,与外部类方法并列

      • 静态内部类:有static修饰

        可以使用四种权限修饰符

        静态内部类不能访问外部类的非静态成员

        public class Outer{
            //静态内部类
            static class Inner{
                
            }
        }
        
      • 非静态内部类:没有static修饰

        可以使用四种权限修饰符

        可以直接访问外部类的任何数据,包括私有、静态的、非静态的

        不能定义静态成员,但是可以定义静态常量

        public class Outer{
            //非静态内部类
             class Inner{
                
            }
        }
        
    • 局部内部类:定义在外部类的方法中等局部区域内

      • 普通局部内部类

        public class Outer{
            //外部类的方法
            public void show(){
                int x = 10;//局部变量,如果被内部类访问到时,此变量将是final效果
                //局部内部类
                class Inner{
                    
                }
            }
        }
        
      • 匿名内部类

        没有名字的内部类,通过一个类只使用一次就不再使用时,可以使用匿名内部类。

        格式:

        //创建匿名内部类对象
        new 父类或接口(){
            //子类或实现类的成员
        };
        
        //接口
        public interface Danceable{
            void dance();
        }
        //---------------------------------------
        //创建此接口的实现类对象
        new Danceable(){
            public void dance(){
                System.out.println("跳舞...");
            }
        }.dance();
        

六、枚举

  1. 枚举类型:一个类只能有固定的几个对象。

  2. 定义方式:

    JDK5之后Java使用关键字enum来定义枚举类型,格式:

    public enum Season{
        SPRING,SUMMER,AUTUMN,WINNTER
    }
    
    //测试
    Season s = Seanson.SPRING;
    switch(s){
        case SPRING:
            System.out.println("春天");
            break;
        case SUMMER:
            System.out.println("夏天");
            break;
    }
    
  3. 枚举类的要求和特点

    • 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
    • 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
    • 编译器给枚举类默认提供private的无参构造
    • 如果需要也可以定义有参构造,默认也是private修饰,常量对象名后面加(实参列表)调用有参构造器。
    • 枚举类默认继承自java.lang.Enum类,不能再继承其他的类型;枚举类默认是final修饰的也不能被继承。
    • JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
    • 枚举类型如有其它属性,建议这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。
  4. 枚举类型常用方法

    • String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
    • String name():返回的是常量名(对象名)
    • int ordinal():返回常量的次序号,默认从0开始
    • 枚举类型[] values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
    • 枚举类型 valueOf(String name):静态方法,根据枚举常量对象名称获取枚举对象

七、注解

  1. 注解:一种给程序看的标记符号

  2. 三个基本注解:

    • @Override 用在方法上用于验证方法是否是重写的方法
    • @Deprecated 表示过时的,不推荐使用。
    • @SupressWarning(“all”) 抑制警告
  3. 自定义注解

    public @interface MyAnno{
        
    }
    
    @MyAnno
    public class Student{
        @MyAnno
        private String name;
    }
    

第八章 异常

一、异常的概述

  1. 异常理解:程序运行过程中发生的不正常情况,如果没有处理,一旦发生JVM就会终止。

二、Java异常体系

Java程序使用不同的Java类表示不同的异常种类

  • Throwable异常和错误的根类

    • Error错误:严重的问题,无法处理,发生后JVM只能终止

    • Exception异常:一般性问题,可以进行处理,以保证JVM可以继续运行。

      • 受检异常:如果不处理无法编译通过,更无法运行,所以也称为编译异常。

        排除RuntimeException及其子类之外的异常。

      • 非受检异常:可以选择处理或不处理。通常因为代码不够严谨造成。也称为运行时异常。

        RuntimeException及其子类表示的异常

在这里插入图片描述

三、异常的生成与抛出机制

JVM会在程序的异常反生处理自动创建一个对应的异常类对象。并向方法的调用处逐层抛出,直到抛到main方法还未处理,最终抛给JVM,JVM会在控制台打印输出异常对象中包含的异常信息,然后终止。

异常抛出的方式:

  • JVM自动创建异常对象并抛出

  • 手动创建异常对象并使用throw关键字抛出

    throw new Exception("异常发生了");
    

四、异常的处理机制

异常发生后,如果未处理JVM就会终止,导致程序无法继续运行。

如何处理异常?

  • 使用try…catch语句捕获异常。在异常对象的抛出过程中将其捕获,就可以阻止JVM终止。

    try{
        //可能发生异常的代码
    }catch(要捕获的异常类型1|异常类型2 变量){
        //捕获异常后的处理
    }catch(要捕获的异常类型3|异常类型4 变量){
        //捕获异常后的处理
    }finally{
        //最终要执行的代码,通常用于释放资源
    }
    
  • 针对运行时异常,我们要尽量保证代码的严谨,尽量保证不会发生异常。针对受检异常必须进行处理,否则无法运行。可以选择的处理方式2种:

    • try…catch捕获异常–真正的处理。

    • 在方法上使用关键字throws声明可能发生的异常类型,由方法的调用者来处理(甩锅)。

      public void show()throws FileNotFoundException{
          FileInputStream fis = new FileInputStream("D:/text.txt");//这里可能会发生编译期异常,必须处理
      }
      

五、自定义异常

  1. 为什么需要自定义异常类:

    异常表示程序运行中出现的不正常的情况,我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,我们在使用也是尽量使用Java这些异常类型。但在大型系统的开发中总是有些不正常的情况是Java没有定义好的类不好表示的,此时我们可以根据自己业务的异常情况来自定义异常类。例如年龄负数问题,考试成绩负数问题等等,我们都可以通过自定义异常类来表示。

  2. 异常类如何定义:

    保持一个合理的异常体系是很重要的,一般自定义一个异常比如UserException作为“根异常”,然后在此基础上再派生出不同的异常类型,自定义的“根异常”需要从一个合适的现有异常中派生出来,通常建议派生自java.lang.RuntimeException

六、异常关键字和注意事项总结

  1. 异常处理中的5个关键字

  2. 异常处理注意事项

    • 编译期异常必须处理,要么捕获处理,要么声明在方法上,让调用者处理。
    • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。
    • try语句范围要尽量小的包围在可能出现异常的一行或几行代码上,不要把大量无异常的代码一起包起来,虽然这样很省事。
    • catch语句捕获的异常类型要尽量小,尽量精准,好针对性的做处理。
    • 如果finally有return语句,永远返回finally中的结果,但要避免该情况.
    • 如果父类方法抛出了多个异常,子类重写父类方法时不能抛出更大异常,可以抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
    • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。

第九章 常用类

一、String类

java.lang.String类表示字符串,因为字符串太常用,所以字符串字面量"abc"也表示这个类的实例。

  1. 字符串的特点

    • 字符串是个常量,字符串一旦创建不可改变。
    • 字符串字面量“abc"存储在字符串常量池中,每个字符串在常量池中只有一份,方便共享。
    • String类底层使用char[]存储字符数据,JDK9之后使用byte[]
    • String类是final修饰的,不能被继承。
  2. 字符串对象的创建方式

    • String s = “hello”;
    • public String() :初始化新创建的 String对象,以使其表示空字符序列。
    • String(String original): 初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
    • public String(char[] value) :通过当前参数中的字符数组来构造新的String。
    • public String(char[] value,int offset, int count) :通过字符数组的一部分来构造新的String。
    • public String(byte[] bytes) :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
    • public String(byte[] bytes,String charsetName) :通过使用指定的字符集解码当前参数中的字节数组来构造新的String。
    • String s = “”+123;
  3. 常用方法

    系列1

    (1)boolean isEmpty():字符串是否为空

    (2)int length():返回字符串的长度

    (3)String concat(xx):拼接,等价于+

    (4)boolean equals(Object obj):比较字符串是否相等,区分大小写

    (5)boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写

    (6)int compareTo(String other):比较字符串大小,区分大小写,按照Unicode编码值比较大小

    (7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写

    (8)String toLowerCase():将字符串中大写字母转为小写

    (9)String toUpperCase():将字符串中小写字母转为大写

    (10)String trim():去掉字符串前后空白符

    系列2:查找

    (11)boolean contains(xx):是否包含xx

    (12)int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1

    (13)int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1

    系列3:字符串截取

    (14)String substring(int beginIndex) :返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。

    (15)String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

    系列4:和字符相关

    (16)char charAt(int index):返回[index]位置的字符

    (17)char[] toCharArray(): 将此字符串转换为一个新的字符数组返回

    (18)String(char[] value):返回指定数组中表示该字符序列的 String。

    (19)String(char[] value, int offset, int count):返回指定数组中表示该字符序列的 String。

    (20)static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String

    (21)static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String

    (22)static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String

    (23)static String valueOf(char[] data) :返回指定数组中表示该字符序列的 String

    系列5:编码与解码

    (24)byte[] getBytes():编码,把字符串变为字节数组,按照平台默认的字符编码进行编码

    ​ byte[] getBytes(字符编码方式):按照指定的编码方式进行编码

    (25)new String(byte[] ) 或 new String(byte[], int, int):解码,按照平台默认的字符编码进行解码

    ​ new String(byte[],字符编码方式 ) 或 new String(byte[], int, int,字符编码方式):解码,按照指定的编码方式进行解码

    系列6:开头与结尾

    (26)boolean startsWith(xx):是否以xx开头

    (27)boolean endsWith(xx):是否以xx结尾

    系列7:正则匹配

    正则表达式:用字符串表示的一个规则。

    (28)boolean matches(正则表达式):判断当前字符串是否匹配某个正则表达式

    系列8:替换

    (29)String replace(xx,xx):不支持正则

    (30)String replaceFirst(正则,value):替换第一个匹配部分

    (31)String repalceAll(正则, value):替换所有匹配部分

    系列9:拆分

    (32)String[] split(正则):按照某种规则进行拆分

  4. 字符串的内存分析:

在这里插入图片描述

二、StringBuilder与StringBuffer类

String类表示不可变的字符串,如果要频繁修改字符串那么就需要不断创建新的String对象,所以Java提供了表示可变字符串的类StringBuffere和StringBuilder。

**StringBuffer:**老的,线程安全的(因为它的方法有synchronized修饰),效率低

**StringBuilder:**线程不安全的,效率高

常用API

常用的API,StringBuilder、StringBuffer的API是完全一致的

(1)StringBuffer append(xx):拼接,追加

(2)StringBuffer insert(int index, xx):在[index]位置插入xx

(3)StringBuffer delete(int start, int end):删除[start,end)之间字符

StringBuffer deleteCharAt(int index):删除[index]位置字符

(4)void setCharAt(int index, xx):替换[index]位置字符

(5)StringBuffer reverse():反转

(6)void setLength(int newLength) :设置当前字符序列长度为newLength

(7)StringBuffer replace(int start, int end, String str):替换[start,end)范围的字符序列为str

(8)int indexOf(String str):在当前字符序列中查询str的第一次出现下标

​ int indexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的第一次出现下标

​ int lastIndexOf(String str):在当前字符序列中查询str的最后一次出现下标

​ int lastIndexOf(String str, int fromIndex):在当前字符序列[fromIndex,最后]中查询str的最后一次出现下标

(9)String substring(int start):截取当前字符序列[start,最后]

(10)String substring(int start, int end):截取当前字符序列[start,end)

(11)String toString():返回此序列中数据的字符串表示形式

//创建对象
StringBuilder sb = new StringBuilder("hello");
//拼接字符串
sb.append("java");
sb.append("world").append("abc");
//转换String对象
String s = sb.toString();

三、数学相关类

  1. java.lang.Math类

    这个类提了一些基础数学运算功能

    • public static final double PI:返回圆周率
    • public static double abs(double a) :返回 double 值的绝对值。
    • public static double ceil(double a) :返回大于等于参数的最小的整数。
    • public static double floor(double a) :返回小于等于参数最大的整数。
    • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)
    • public static double pow(double a,double b):返回a的b幂次方法
    • public static double sqrt(double a):返回a的平方根
    • public static double random():返回[0,1)的随机值
    • public static double max(double x, double y):返回x,y中的最大值
    • public static double min(double x, double y):返回x,y中的最小值
  2. java.util.Random类

    用于生成随机数

    //创建Random对象
    Random  r = new Random();
    //生成随机数
    int  num = r.nextInt();//产生一个随机整数
    int  num = r.nextInt(100);//产生一个[0,100)的随机整数
    
  3. BigInteger类

    不可变的任意精度的整数。

    • BigInteger(String val)

    • BigInteger add(BigInteger val)

    • BigInteger subtract(BigInteger val)

    • BigInteger multiply(BigInteger val)

    • BigInteger divide(BigInteger val)

    • BigInteger remainder(BigInteger val)

    • int intValue():将此 BigInteger 转换为 int。

    • long longValue():将此 BigInteger 转换为 long。

    • float floatValue():将此 BigInteger 转换为 float。

    • BigInteger b1 = new BigInteger("1234567890");
      BigInteger b2 = new BigInteger("1234567890");
      BigInteger add = b1.add(b2);
      
  4. BigDecimal类

    不可变的、任意精度的有符号十进制数。

    • BigDecimal(String val)
    • BigDecimal add(BigDecimal val)
    • BigDecimal subtract(BigDecimal val)
    • BigDecimal multiply(BigDecimal val)
    • BigDecimal divide(BigDecimal val)
    • BigDecimal divide(BigDecimal divisor, int roundingMode)
    • BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
    • BigDecimal remainder(BigDecimal val)
    • double doubleValue():将此 BigDecimal 转换为 double。

四、日期时间API

  1. java.util.Date日期类

    这个类的对象表示一个特定时间瞬间,即包含日期时间信息。

    //空参构造器
    Date d = new Date();//获取当前系统日期时间对象
    //方法
    long time = d.getTime();//获取从1970-1-1 8:0:0到现在的毫秒值。
    //有参构造器
    Date date = new Date(0L);//根据指定的毫秒值获取日期对象
    
  2. java.util.Calendar日历类

    //获取Calendar对象
    Calendar c = Calendar.getInstance();
    //方法
    int year = c.get(Calendar.YREA);
    int month = c.get(Calendar.MONTH);
    
  3. java.text.SimpleDateFormat日期时间格式化类

    用于格式化Date对象,获取解析指定格式的字符串

    //创建格式化类对象,指定一种格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //格式化:把Date对象转换为指定格式的字符串
    String str = sdf.format(new Date());
    //解析:把指定格式的字符串转换为Date对象
    Date date = sdf.parse("1999-09-09 10:10:10");
    
  4. JDK8中的日期时间API

    1、LocalDate日期类、LocalTime时间类、LocalDateTime日期时间类

    本地日期时间类

    方法描述
    now() / now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
    of()静态方法,根据指定日期/时间创建对象
    getDayOfMonth()/getDayOfYear()获得月份天数(1-31) /获得年份天数(1-366)
    getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
    getMonth()获得月份, 返回一个 Month 枚举值
    getMonthValue() / getYear()获得月份(1-12) /获得年份
    getHours()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
    withDayOfMonth()/withDayOfYear()/withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
    with(TemporalAdjuster t)将当前日期时间设置为校对器指定的日期时间
    plusDays(), plusWeeks(), plusMonths(), plusYears(),plusHours()向当前对象添加几天、几周、几个月、几年、几小时
    minusMonths() / minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
    plus(TemporalAmount t)/minus(TemporalAmount t)添加或减少一个 Duration 或 Period
    isBefore()/isAfter()比较两个 LocalDate
    isLeapYear()判断是否是闰年(在LocalDate类中声明)
    format(DateTimeFormatter t)格式化本地日期、时间,返回一个字符串
    parse(Charsequence text)将指定格式的字符串解析为日期、时间

    2、指定时区日期时间:ZonedDateTime

    3、持续日期/时间:Period和Duration

    4、DateTimeFormatter:日期时间格式化

    该类提供了三种格式化方法:

    预定义的标准格式。如:DateTimeFormatter.ISO_DATE_TIME; ISO_DATE

    本地化相关的格式。如:DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)

    自定义的格式。如:DateTimeFormatter.ofPattern(“yyyy-MM-dd hh:mm:ss”)

    //创建格式化类对象,指定一种格式
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    //格式化
    //方式一:
    String str = dtf.format(LocalDateTime.now());
    //方式二:
    String str = LocalDateTime.now().format(dtf);
    //解析:
    LocalDateTime datetime = LocalDateTime.parse("1999-09-09 10:10:10",dtf);
    

五、系统相关类

  1. java.lang.System类

    系统类中很多好用的方法,其中几个如下:

    • static long currentTimeMillis() :返回当前系统时间距离1970-1-1 0:0:0的毫秒值
    • static void exit(int status) :退出当前系统
    • static void gc() :运行垃圾回收器。
    • static String getProperty(String key):获取某个系统属性
    • static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
  2. java.lang.Runtime运行时类

    每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。 应用程序不能创建自己的 Runtime 类实例。

    public static Runtime getRuntime(): 返回与当前 Java 应用程序相关的运行时对象。

    public long totalMemory():返回 Java 虚拟机中的内存总量。此方法返回的值可能随时间的推移而变化,这取决于主机环境。

    public long freeMemory():回 Java 虚拟机中的空闲内存量。调用 gc 方法可能导致 freeMemory 返回值的增加。

    public long maxMemory(): 返回 Java 虚拟机试图使用的最大内存量。

    Process exec(String command):在单独的进程中执行指定的字符串命令。

六、数组工具类

java.util.Arrays数组工具类,提供了很多静态方法来对数组进行操作,而且如下每一个方法都有各种重载形式,以下只列出int[]类型的,其他类型的数组类推:

  • static int binarySearch(int[] a, int key) :要求数组有序,在数组中查找key是否存在,如果存在返回第一次找到的下标,不存在返回负数

  • static int[] copyOf(int[] original, int newLength) :根据original原数组复制一个长度为newLength的新数组,并返回新数组

  • static int[] copyOfRange(int[] original, int from, int to) :复制original原数组的[from,to)构成新数组,并返回新数组

  • static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同

  • static void fill(int[] a, int val) :用val填充整个a数组

  • static void fill(int[] a, int fromIndex, int toIndex, int val):将a数组[fromIndex,toIndex)部分填充为val

  • static void sort(int[] a) :将a数组按照从小到大进行排序

  • static void sort(int[] a, int fromIndex, int toIndex) :将a数组的[fromIndex, toIndex)部分按照升序排列

  • static String toString(int[] a) :把a数组的元素,拼接为一个字符串,形式为:[元素1,元素2,元素3。。。]

七、包装类

  1. 概念理解:Java针对每种基本数据类型都提供了对应的引用数据类型,即包装类。

    Java提供了两大类数据类型,基本类型与引用类型,使用基本类型在于效率,但是缺少像引用数据类型一样的丰富API,那么Java提供了针对基本数据类型的保证类,以提供更加便捷的操作功能,包装类就是把基本数据类型包装成对应的引用数据类型。

    序号基本数据类型包装类(java.lang包)
    1byteByte
    2shortShort
    3intInteger
    4longLong
    5floatFloat
    6doubleDouble
    7charCharacter
    8booleanBoolean
    9voidVoid
  2. 装箱与拆箱

    自动装箱:基本数据类型转换为对应的包装类类型

    自动拆箱:包装类类型自动转换为对应的基本数据类型

    Integer i = 10;//自动装箱
    int a = i;//自动拆箱
    
  3. 相关API

    整数与字符串相互转换

    //字符串转整数的方法
    Integer i1 = new Integer("123");
    System.out.println("i1 = " + i1);
    int i2 = Integer.parseInt("456");
    System.out.println("i2 = " + i2);
    Integer i3 = Integer.valueOf("789");
    System.out.println("i3 = " + i3);
    //整数转字符串的方法
    String s = "" + 123;
    String s1 = String.valueOf(456);
    String s2 = Integer.toString(789);
    Integer i = 111;
    String s3 = i.toString();
    

    数据类型的最大最小值

    Integer.MAX_VALUE和Integer.MIN_VALUE
    Long.MAX_VALUE和Long.MIN_VALUE
    Double.MAX_VALUE和Double.MIN_VALUE
    

    字符转大小写

    Character.toUpperCase('x');
    Character.toLowerCase('X');
    

    整数转进制

    Integer.toBinaryString(int i) 
    Integer.toHexString(int i)
    Integer.toOctalString(int i)
    
  4. 包装类的缓存问题

    Integer i = 10;
    Integer i1 = 10;
    System.out.pritln(i == i1);//true
    Integer i = 200;
    Integer i1 = 200;
    System.out.pritln(i == i1);//false
    

    包装类的数据在缓存数值范围内时,直接从内存中取出对象,超过范围会创建新的对象

    包装类缓存对象
    Byte-128~127
    Short-128~127
    Integer-128~127
    Long-128~127
    Float没有
    Double没有
    Character0~127
    Booleantrue和false

第十章 集合

一、集合的概述

  1. 概念理解:集合是数据容器

  2. 集合的特点(对比数组)

    • 数组一旦创建长度不可改变,集合长度可变。
    • 数组的功能单一,集合提供增删改查元素的等丰富API功能
    • 数组的存储特点单一(有序可重复),不同的集合提供了不同的存储的特点。
    • 数组可以存储基本数据类型和引用数据类型元素,集合只能存储引用数据类型。
  3. 集合框架体系

二、Collection接口

java.util.Collection接口是一个顶层接口

常用方法:

1、添加元素

(1)add(E obj):添加元素对象到当前集合中

(2)addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other

2、删除元素

(1) boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。

(2)boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll

​ (3) void clear(); 清空集合

3、判断

(1)boolean isEmpty():判断当前集合是否为空集合。

(2)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。

(3)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。

4、获取元素个数

(1)int size():获取当前集合中实际存储的元素个数

5、交集

(1)boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集,即this = this ∩ coll;

6、转为数组

(1)Object[] toArray():返回包含当前集合中所有元素的数组

三、Iterator迭代器

  1. Iterator是一个接口,用于遍历Collection集合,Iterator迭代器的API

    boolean hasNext();//判断是否有下一个元素
    Object next();//取出下一个元素
    void remove();//删除当前元素
    
  2. Iterator迭代器的使用步骤

    Collection<String> c  = new ArrayList<>();
    c.add("abc");
    c.add("efg");
    //获取迭代器
    Iterator<String> it = c.iterator();
    while(it.hasNext()){
        String s = it.next();
    }
    
    
  3. foreach循环

    也称为增强for循环,底层使用Iterator迭代器遍历集合

    • 语法格式:

      for(元素的类型 变量 : 数组或集合){//这个变量即代表每个元素
          //循环体
      }
      
    • 示例

      int[] arr = {11,22,33,44};
      //foreach循环遍历数组
      for(int i : arr){
          System.out.println(i);
      }
      
      Collection<Integer> coll = new ArrayList<>();
      coll.add(123);
      coll.add(456);
      coll.add(789);
      //foreach循环遍历集合
      for(Integer i : coll){
          System.out.println(i);
      }
      
    • foreach循环与Iterable接口的关系

      实现了Iterable接口的类的实例都可以使用foreach循环遍历。

    • 快速失败机制

      使用迭代器遍历集合时,如果使用迭代器之外的方法来修改集合,迭代器会立即抛出并发修改异常,来阻止此操作,因为这样操作有风险。

      快速失败机制不会得到保证。

      快速失败机制借助集合的modCount(记录集合的修改次数)变量来实现。

      如果使用迭代器遍历集合时就要修改集合数据,可以使用迭代器自身提供的方法修改。

四、List集合

  1. 概述:

    • java.util.List接口

      是Collection的一个典型子接口。List集合的特点:

      • 元素有序、可重复

      • 都可以使用索引访问元素

    • 特有方法(都与索引相关)

      添加元素

      • void add(int index, E ele)
      • boolean addAll(int index, Collection<? extends E> eles)

      获取元素

      • E get(int index)
      • List subList(int fromIndex, int toIndex)

      获取元素索引

      • int indexOf(Object obj)
      • int lastIndexOf(Object obj)

      删除和替换元素

      • E remove(int index)
      • E set(int index, E ele)
    • List集合的遍历方式:

      • 迭代器遍历
      • foreach循环遍历
      • 普通for循环遍历
  2. ArrayList实现类

    java.util.ArrayList实现类是List接口的典型实现类

    • 特点:

      • 元素有序,可重复
      • 使用索引查询效率高,增删效率低
      • 底层数据数据结构:数组
    • 源码分析

      ArrayList底层实现:可变长的数组,有索引,查询效率高,增删效率低
          构造方法:
          new ArrayList():
          jdk6中,空参构造直接创建10长度的数组
          jdk7(新版)jdk8中,默认初始容量0,在添加第一元素时初始化容量为10
          new ArrayList(int initialCapacity):
      指定初始化容量
          添加元素:add(E e);
      首次添加元素,初始化容量为10
          每次添加修改modCount属性值
          每次添加检查容量是否足够,容量不足时需要扩容,扩容大小为原容量的1.5倍
          移除元素:remove(E e);
      每次成功移除元素,修改modCount值
          每次成功移除需要需要移动元素,以保证所以元素是连续存储的(删除操作效率低的原因)
      
  3. LinkedList实现类

    List接口的典型实现类

    • 特点:

      • 元素有序、可重复,可使用索引访问

      • 底层结构:双向链表

      • 效率:增删快,查询慢(首位元素操作效率高)

在这里插入图片描述

  • 特有方法:(与首位元素相关的方法)

    • void addFirst(Object obj )
    • void addLast(Object obj )
    • Object getFirst()
    • Object getLast()
    • Object removeFirst()
    • Object removeLast ()
  1. ListIterator迭代器

    List 集合额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象, ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:

    • void add():通过迭代器添加元素到对应集合
    • void set(Object obj):通过迭代器替换正迭代的元素
    • void remove():通过迭代器删除刚迭代的元素
    • boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
    • Object previous():返回列表中的前一个元素。
    • int previousIndex():返回列表中的前一个元素的索引
    • boolean hasNext()
    • Object next()
    • int nextIndex()

五、Set集合

  1. 概述:

    Set是一个接口,是Collection的一个典型接口,

    Set集合的特点:元素唯一,通常无序

  2. HashSet实现类

    • 特点:元素唯一,无序

    • 底层结构:哈希表

    • 效率:综合效率高

    • 判断元素重复的依据:元素的hashCode值和equals比较结果都相同。

  3. LinkedHashSet实现类

    是HashSet的子类。

    特点:元素唯一,有序

    底层结构:哈希表基础上维护一个链表以保证元素的迭代顺序

    效率:略低于HashSet

  4. TreeSet实现类

    特点:元素唯一,无序,实现了排序(大小顺序)

    底层结构:红黑树-一种相对平衡的二叉树
    效率:高于链表

    排序原理:存储过程:大的放右边,小的放左边。遍历顺序:中序遍历,即左-中-右

    TreeSet要求存储的元素必须可以比较大小,那么要求元素类型必须实现Comparable接口或者给TreeSet集合传入Comparator比较器,用于比较元素的大小。

六、Map集合

  1. Map概述:

    • Map是一个顶层接口,表示一组键值对
    • Map集合的特点:
      • key唯一,通过key可以映射到一个value
  2. Map集合的方法:

    1、添加操作

    • V put(K key,V value)
    • void putAll(Map<? extends K,? extends V> m)

    2、删除

    • void clear()
    • V remove(Object key)

    3、元素查询的操作

    • V get(Object key)
    • boolean containsKey(Object key)
    • boolean containsValue(Object value)
    • boolean isEmpty()

    4、元视图操作的方法:

    • Set keySet()
    • Collection values()
    • Set<Map.Entry<K,V>> entrySet()

    5、其他方法

    • int size()
  3. Map集合的遍历方式:

    Map<String,Integer> map = new HashMap<>();
    map.put("tom",18);
    map.put("jack",19);
    //遍历方式一:
    Set<String> keys = map.keySet();//获取所有key封装到一个set集合
    for(String key : keys){//遍历set集合获取每个key
        Integer value = map.get(key);//根据每个key获取每个value
        System.out.println(key+" = "+value);
    }
    //遍历方式二:
     Set<Map.Entry<String,Integer>> set = map.entrySet();//把每个键值对封装到Set集合中
    for(Map.Entry<String,Integer>  entry : set){//遍历set集合获取每个键值对
        String key = entry.getKey();//从每个键值对中获取key
        Integer value = entry.getValue();//从每个键值对中获取value
    }
    
    
  4. HashMap实现类

    • 特点:key唯一,无序,key和value都可以为null

    • 底层结构:哈希表=数组+链表+红黑树(JDK8)

    • 效率:综合效率高

    • 底层原理:

      1.HashMap初始容量?
        	空参构造器初始容量为空,第一次添加元素时,初始容量为16
       	指定容量的有参构造器,第一次添加元素时,初始化容量为比指定容量大的最小的2的次幂
      2.添加元素时,如何计算元素的位置?
        	1)获取key的hashCode值
          2)二次哈希,进行高低16位异或运算
          3)对容量取模得到存储的索引位置
      3.链表与红黑树的转换时机?
          当链表长度达到8时,并且容量达到64,链表转换为红黑树,当红黑树节点降为6时退化为链表
      4.扩容机制?
          当元素个数达到扩容临界值(即容量*加载因子0.75)时,进行扩容,新容量为原容量的2倍。
      
    • Hashtable类底层结构哈希表,key和value都不能为null,是线程安全的。

  5. LinkedHashMap实现类

    • 是HashMap的子类
    • key唯一,有序
    • 底层结构:哈希表基础上维护一个链表,用于保证元素的迭代顺序。
    • 效率:略低于HashMap
  6. TreeMap实现类

    • key唯一,无序,实现了排序
    • 底层结构:红黑树
    • 效率:高于链表
    • 排序原理:存储元素时,大的放右边,小的放左边,遍历顺序:中序遍历,即左中右。
    • 要求元素的key必须可以比较大小,那么就要求元素的key要么实现了Comparable接口,或者创建集合时传入一个Comparator比较器,用于比较key的大小。
  7. Properties集合

    • 表示一个属性集,是Hashtable的子类
    • key和value都是字符串
    • 特有方法:
      • void setProperty(String key,String value);存储元素
      • String getProperty(String key);

七、Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法:

  • public static boolean addAll(Collection<? super T> c,T… elements)将所有指定元素添加到指定 collection 中。
  • public static int binarySearch(List<? extends Comparable<? super T>> list,T key)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
  • public static int binarySearch(List<? extends T> list,T key,Comparator<? super T> c)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。
  • public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
  • public static T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者
  • public static void reverse(List<?> list)反转指定列表List中元素的顺序。
  • public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
  • public static <T extends Comparable<? super T>> void sort(List list)根据元素的自然顺序对指定 List 集合元素按升序排序
  • public static void sort(List list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
  • public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换
  • public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数
  • public static void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中
  • public static boolean replaceAll(List list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值
  • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
  • Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。

第十一章 泛型

一、泛型概述

  1. 泛型:一种类型参数
  2. 语法形式:<引用数据类型>
  3. 好处:把运行时可能存在的强制类型转换风险提前到编译期解决,简化了代码
  4. 相关术语:
    • TypeVariable:类型变量,例如:ArrayList<E>中的E,Map<K,V>中的K,V
    • ParameterizedType:参数化类型,例如:Comparator<T>Comparator<String>
    • GenericArrayType:泛化的数组类型,即T[]
    • WildcardType:通配符类型,例如:Comparator<?>

二、自定义泛型结构

  1. 自定义泛型类

    类型变量声明在类上面

    • 格式示例:

      //泛型类
      public class MyClass<T>{
          private T obj;
          public void setObj(T t){
              this.obj = t;
          }
          public T getObj(){
              return obj;
          }
          //静态域中不能使用泛型类的类型变量
         // public static void test(T t){    
         // }
          
      }
      
    • 泛型类的使用

      MyClass<String> mc = new MyClass<>();
      mc.setObj("hello");
      String s = mc.getObj();
      //--------------------------
      MyClass mc2 = new MyClass();
      mc2.setObj("hello");
      Object s = mc2.getObj();
      
      
    • 泛型类的使用与注意实现

      • 使用泛型类时需要明确泛型的类型
      • 使用泛型类创建对象是,左右两边泛型类型必须一致,JDK7之后右边可以省略
      • 使用泛型是不明确泛型类型,那么默认为Object类型
      • 泛型类上声明的类型变量不能使用在静态域中
  2. 泛型接口

    类型变量声明在接口上

    • 格式示例

      //泛型接口
      public interface MyInter<T>{
          void show(T t);
      }
      
    • 泛型接口的使用方式:

      • 实现类实现接口时明确接口的泛型类型,那么实现类不再是泛型类

        public class ClassA implements MyInter<String>{
            public void show(String s){
                
            }
        }
        
      • 实现类实现接口时不明确接口的泛型类型,实现类应该还是泛型类

        public class ClassB<T> implements MyInter<T>{
            public void show(T t){    
            }
        }
        
  3. 泛型方法

    当一个方法内使用的类型不确定,但是与类的其他成员无关,这时可以把类型变量声明在方法上,即泛型方法。

    • 格式示例:

      public class MyClass  {
          //泛型方法
          public static <T> T show(T t){
              return t;
          } 
      }
      

三、通配符

  1. <?> 表示任意的类型

    应用场景:不适合获取,也不能添加数据,通常用于复制,翻转,等操作。

    //List<Animal> list = new ArrayList<Animal>();
    //list.add(new Dog());
    
    List<?> list;
    
    list = new ArrayList<Object>();
    list = new ArrayList<Animal>();
    list = new ArrayList<Cat>();
    
  2. <? extends T> 设定通配符上限

    应用场景:使用获取数据,不能添加数据

    List<? extends Animal> list;
    
    //list = new ArrayList<Object>();//编译失败
    list = new ArrayList<Animal>();
    list = new ArrayList<Cat>();
    
  3. <? super T> 设定通配符下限

    应用场景:适合添加数据,不适合获取数据

    List<? super Animal> list;
    
    list = new ArrayList<Object>();
    list = new ArrayList<Animal>();
    //list = new ArrayList<Cat>();//编译失败
    

第十二章 File类与IO流

一、File类

  1. java.io.File类:表示系统中文件或目录

  2. 构造方法:

    File file = new File("D:/test/abc.txt");
    File file1 = new File("D:/test/");
    
    File file2 = new File("D:/test/","abc.txt");
    File file3 = new File(file1,"abc.txt");
    
  3. 相关方法:

    获取文件和目录基本信息的方法
    • public String getName() :返回由此File表示的文件或目录的名称。
    • public long length() :返回由此File表示的文件的长度。
    • public String getPath() :将此File转换为路径名字符串。
    • public long lastModified():返回File对象对应的文件或目录的最后修改时间(毫秒值)
    各种路径问题
    • public String getPath() :将此File转换为路径名字符串。

    • public String getAbsolutePath() :返回此File的绝对路径名字符串。

    • String getCanonicalPath():返回此File对象所对应的规范路径名。

      File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。

      默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。

      • 绝对路径:从盘符开始的路径,这是一个完整的路径。
      • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
      • 规范路径:所谓规范路径名,即对路径中的“…”等进行解析后的路径名
    判断功能的方法
    • public boolean exists() :此File表示的文件或目录是否实际存在。
    • public boolean isDirectory() :此File表示的是否为目录。
    • public boolean isFile() :此File表示的是否为文件。
    创建删除功能的方法
    • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
    • public boolean delete() :删除由此File表示的文件或目录。 只能删除空目录。
    • public boolean mkdir() :创建由此File表示的目录。
    • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
    目录操作
    • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
    • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
    • public File[] listFiles(FileFilter filter):返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(java.io.File) 方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。

二、IO流概述

  1. IO流:即输入输出流,表示数据的传输
  2. IO流的分类
    • 根据流向划分(参照物为内存或程序)
      • 输入流:把数据从其他介质传输到内存中
      • 输出流:把内存中的数据传输到其他介质
    • 根据传输单位
      • 字节流
      • 字符流
    • 按角色划分
      • 节点流:明确了数据源
      • 处理流:对其他流的包装处理
  3. IO流的超类
    • InputStream : 字节输入流的超类
    • OutputStream:字节输出流的超类
    • Reader :字符输入流的超类
    • Writer : 字符输出流的超类

三、文件字节流

  1. FileInputStream文件字节输入流

    用于读取文件中的字节数据

    //使用步骤:
    //1.使用构造器创建流对象
    //FileInputStream fis = new FileInputStream(new File("abc.txt"));
    FileInputStream fis = new FileInputStream("abc.txt");
    //2.读数据
    int b = fis.read();//读取一个字节
    byte[] bys = new byte[1024];
    int len = fis.read(bys);//读取数据到字节数组中,返回读取到的字节个数
    //3.关闭流
    fis.close();
    
  2. FileOutputStream文件字节输出流

    用于把数据写出到指定的文件中

    //使用步骤:
    //1.使用构造器创建流对象。如果文件不存在会自动创建
    FileOutputStream fos = new FileOutputStream("abc.txt");
    //FileOutputStream fos = new FileOutputStream("abc.txt",true);//第二个参数true表示追加写入
    //2.写数据
    fos.write(97);//写出一个字节数据
    byte[] bys = "hello".getBytes();
    fos.write(bys,0,3);//写出一个字节数组的一部分
    //3.释放资源
    fos.close();
    
    

四、文件字符流

  1. FileReader文件字符输入流

    //构造器:
    FileReader(File file);
    FileReader(String path);
    //读数据
    int read();//读取一个字符
    int read(char[] chs);//读取数据到字符数组中,返回读取到的字符个数
    //关闭流
    void close();
    
  2. FileWriter文件字符输出流

    //构造器:
    FileWriter(File file);
    FileWriter(String file);
    FileWriter(String file,boolean append);//第二个参数true表示追加
    //写数据
    void write(int ch);//写一个字符
    void write(char[] chs,int off,int len);//写一个字符数组的一部分
    //关闭流
    void close();
    
  3. 文件字符输出流的flush方法和close方法

    文件字符输出流内置一个8k大小的缓冲区,写出的字符首先进入缓冲区,当缓冲区满后会自动一次性写出数据,提高IO性能。

    • flush()方法:刷新缓冲区,直接把缓冲区中数据写出。
    • close()方法:先刷新缓冲区,再关闭流

五、缓冲流

缓冲流,也称为高效流,是一种处理流,内置默认大小为8的缓冲区,可以对其他流进行包装处理,使其读写效率更高。

  • BufferedInputStream高效字节输入流

    //构造器:
    BufferedInputStream(InputStream in);//默认缓冲区大小8k
    BufferedInputStream(InputStream in,int bufferSize);//指定缓冲区大小
    //读取数据
    int read();//读一个字节
    int read(byte[] bys);//读取数据到字节数组中,返回读取到的字节个数
    //关闭流
    void close();
    
  • BufferedOutputStream高效字节输出流

    //构造器:
    BufferedOutputStream(OutputStream out);//默认缓冲区8k
    BufferedOutputStream(OutputStream out,int bufferSize);//指定缓冲区大小
    //写出数据
    void write(int b);//写一个字节
    void write(byte[] bys,int off,int count);//写出字节数组的一部分
    //关闭流:
    void close();
    
  • BufferedReader高效字符输入流

    //构造器:
    BufferedReader(Reader reader);//默认缓冲区8k
    BufferedReader(Reader reader,int bufferSize);//指定缓冲区大小
    //读取数据:
    int read();//读取一个字符
    int read(char[] chs)//读取数据到字符数组返回读取到的字符个数
    String readLine();//读取一行字符串
    //关闭流:
    void close();
    
  • BufferedWriter高效字符输出流

    //构造器:
    BufferedWriter(Writer writer);//默认缓冲区8k
    BufferedWriter(Writer writer,int bufferSize);//指定缓冲区大小
    //写出数据
    void write(int b);//写一个字符
    void write(char[] chs,int off,int count);//写出字符数组的一部分
    void write(String s);//写一个字符串
    void newLine();//写一个换行符
    //关闭流
    void close();
    

六、转换流

转换流一种处理流,把字节流转换为字符流。

字符流的本质:字节流+编码方式

转换流:指定字节流+指定的编码方式

  • InputStreamReader转换输入流:把字节输入流转换为字符输入流
FileInputStream fis = new FileInputStream("abc.txt");
//创建转换流对象
//InputStreamReader isr = new InputStreamReader(fis);//使用平台默认编码方式
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用指定的编码方式
//读取字符数据
int ch = isr.read();//读取一个字符
System.out.println((char)ch);
//关闭流
isr.close();

  • OutputStreamWriter转换输出流:把字节输出流转换为字符输出流

    FileOutputStream fos = new FileOutputStream("abc.txt");
    //创建转换输出流
    //OutputStreamWriter osw = new OutputStreamWriter(fos);//使用平台默认编码方式
    OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");//指定编码方式
    //写字符
    osw.write('中');
    //关闭流
    osw.close();
    
    

七、对象流

  1. 序列化与反序列化

    • 序列化:把内存中Java对象转换为字节序列用于持久存储或传输到网络中。
    • 反序列化:把Java对象的字节序列还原为内存中的Java对象。
    • 一个类的对象要被序列化,这个类必须实现序列化接口Serializable
  2. 对象流

    • ObjectInputStream对象输入流,用于反序列化操作,即读取Java对象到内存中

    • ObjectOutputStream对象输出流,用于序列化操作,把Java对象写出内存持久存储

      //创建对象输出流
      ObjectOutputStream oos 
          = new ObjectOutputStream(new FileOutputStream("oos.txt"));
      //写出对象
      oos.writeObject(new Student());
      //关闭流
      oos.close();
      //----------------------
      //创建对象输入流
      ObjectInputStream ois 
          = new ObjectInputStream(new FileInputStream("oos.txt"));
      //读取对象
      Object obj = ois.readObject();
      //关闭流
      ois.close();
      
      //这个类的对象要被序列化,必须实现序列化接口
      class Student implements Serializable{
          private static final long seriaVersionUID = 1L;//序列化版本号
          
      }
      
  3. transient关键字

    transient修饰的不会被序列化

    类的静态变量不会被序列化

八、打印流与标准输入输出流

  1. 打印流

    • PrintStream字节打印流

    • PrintWriter字符打印流

      //特有的方法-打印数据
      print(Object obj);
      println(Object obj);
      
  2. 标准输入输出流

    • System.in 标准输入流,从键盘读取数据
    • System.out 标准输出流,输出数据到控制台

九、JDK7的trycatch语句

JDK7之后的try…catch可以自动释放资源,无需手动释放。

格式:

try(
	//需要释放资源的对象的声明
){
    //可能发生异常的业务代码
}catch(要捕获的异常类型 变量){
    //捕获异常后的处理逻辑
}

第十三章 多线程

一、 相关概念

  1. 并行与并发概念

    • 并行:同一个时间点,多个事件正在发生
    • 并发:同一个微小的时间段(时间片)内,多个程序正在执行。
    • 单核cpu不支持并行操作,只支持并发操作。
  2. 线程与进程

    • 进程:程序运行过程的描述,系统以进程为单位分配独立的系统资源。比如内存

    • 线程:是执行程序任务的最小单位,一个进程至少有一个线程,一个程序有多个线程就是多线程程序。

      面试题:进程是操作系统调度和分配资源的最小单位,线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。

  3. 多线程的好处与应用场景

    • 充分利用CPU资源
    • 多个线程执行相同任务,共同完成一个大的任务
    • 多个线程执行不同的任务,共同完成一个大的任务
  4. CPU的调度方式

    • 分时调度:平均分配CPU资源
    • 抢占式调度:随机分配CPU资源,Java支持抢占式调度方式

二、创建新线程的方式

  1. 方式一:继承Thread类

    • 创建Thread类的子类,并重写run方法
    • 创建子类对象
    • 调用start方法启动线程
  2. 方式二:实现Runnable接口

    • 创建Runnable接口的实现类(任务类),并重写run方法
    • 创建任务类对象
    • 创建Thread线程类对象,通过构造器传入任务类对象
    • 调用start方法启动线程
  3. 两种创建方式的比较

    • 方式一有单继承限制,方式二实现接口的同时可以再继承其他类
    • 方式二方便共享数据
  4. 匿名内部类创建方式创建线程

    //方式一匿名内部类创建方式
    new Thread(){
        @Override
        public void run(){
            System.out.println("线程任务....")
        }
    }.start();
    //方式二匿名内部类创建方式
    new Thread(new Runnable(){
         @Override
        public void run(){
            System.out.println("线程任务....")
        }
    }).start();
    

三、Thread类

  1. 构造方法
  • public Thread() :分配一个新的线程对象。

  • public Thread(String name) :分配一个指定名字的新的线程对象。

  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。

  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

  1. 线程使用基础方法

    • public void run() :此线程要执行的任务在此处定义代码。

    • public String getName() :获取当前线程名称。

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

    • public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

    • public final int getPriority() :返回线程优先级

    • public final void setPriority(int newPriority) :改变线程的优先级

      • 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
      • MAX_PRIORITY(10):最高优先级
      • MIN _PRIORITY (1):最低优先级
      • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
  2. 线程控制常见方法

    • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

    • public static void sleep(long millis) :线程睡眠,使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

    • public static void yield():线程礼让,yield只是让当前线程暂时失去执行权,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。

    • void join() :加入线程,当前线程中加入一个新线程,等待加入的线程终止后再继续执行当前线程。

      void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。

      void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

    • public final void stop():强迫线程停止执行。 该方法具有不安全性,已被弃用,最好不要使用。

      • 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
      • 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
    • public void interrupt():中断线程,实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。

    • public static boolean interrupted():检查线程的中断状态,调用此方法会清除中断状态(标记)。

    • public boolean isInterrupted():检查线程中断状态,不会清除中断状态(标记)

    • public void setDaemon(boolean on):将线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。

      • 守护线程,主要为其他线程服务,当程序中没有非守护线程执行时,守护线程也将终止执行。JVM垃圾回收器也是守护线程。
      • public boolean isDaemon():检查当前线程是否为守护线程。

四、线程的生命周期状态

在这里插入图片描述

五、线程安全问题

  1. 线程安全问题的原因(条件)

    • 多线程并发执行
    • 多个线程共享数据
    • 理解为多条语句操作共享数据
  2. 线程安全问题的解决

    Java使用synchronized关键字实现同步机制,来解决线程安全问题。

    • 同步代码块

      synchronized(同步锁对象){
          //同步执行的代码
      }
      
    • 同步方法

      public synchronized void sale(){
          //同步执行的代码
      }
      

    同步代码执行机制:当线程获取到同步锁对象才能执行同步代码,在执行同步代码的同时持有锁对象,当执行完同步代码后会释放锁对象,其他线程有机会获取锁对象进而执行同步代码。即同一个时刻只有一个线程在执行同步代码。

  3. 同步锁对象的问题

    • 锁对象可以是任意的Java对象
    • 但是,必须保证所有线程使用同一把锁,才能解决线程安全问题
    • 锁对象的选择:
      • 同步代码块通常使用this作为锁对象
      • 同步实例方法默认使用this作为锁对象
      • 静态域中的同步代码块通常使用当前类的Class实例作为锁对象
      • 同步静态方法默认使用的当前类的Class实例作为锁对象
  4. 编写多线程程序的一般原则

    • 单独创建共享资源操作类

    • 资源类与线程相关类解耦合

六、单例模式懒汉式的线程安全问题解决

public class Singleton{
    private static  Singleton instance = null;
    private Singleton(){}
    public static Singleton  getInstance(){
        if(instance == null)
            synchronized(Singleton.class){
            	 if(instance == null)
                     instance = new Singleton();
        	}
        return instance;
    }
}

七、等待唤醒机制

  1. 线程间通信:多个线程之间互相交换数据,比如A线程的执行结果需要告诉B线程,B线程的执行结果需要告诉A线程。
  2. 如果两个线程需要互相通信,并实现协调工作,可以使用Object类提供wait和notify方法实现,即等待唤醒机制。
  3. 经典的问题生成与消费者问题可以使用等待唤醒机制实现。

八、释放锁操作与死锁

任何线程进入同步代码块、同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?

1、释放锁的操作

当前线程的同步方法、同步代码块执行结束。

当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。

当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

2、不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行。

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()这样的过时来控制线程。

3、死锁

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

4、面试题:sleep()和wait()方法的区别

(1)sleep()不释放锁,wait()释放锁

(2)sleep()指定休眠的时间,wait()可以指定时间也可以无限等待直到notify或notifyAll

(3)sleep()在Thread类中声明的静态方法,wait方法在Object类中声明

因为我们调用wait()方法是由锁对象调用,而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法,只能声明在Object类中。

第十四章 网络编程

一、B/S与CS软件结构

  1. C/S 客户端与服务器

    程序员需要开发客户端和服务端程序,用户需要下载、升级客户端程序

    对网络带宽要求相对低

    数据相对安全

  2. B/S 浏览器与服务器

    程序员只需要开发服务端程序,用户无需下载客户端,只需要浏览器

    对网络带宽要求相对高

    数据相对不安全

二、TCP与UDP协议

网络通信协议指计算机网络中计算机直接通信的规则。

  1. UDP协议(发短信)

    非面向连接,数据不可靠

    效率高

    传输数据量有限制64k

  2. TCP协议

    面向连接,数据可靠

    效率相对低(建立连接:三次握手,断开连接:四次挥手)

    理论传输数据量无限制

三、网络编程三要素

  1. 协议

  2. ip地址:计算机网络中每台计算机的唯一标识。例如:192.168.1.123

    ip地址由:网络地址+主机地址组成
    子网掩码: 255.255.255.0 是配合ip地址使用,用于标识网络地址
    127.0.0.1 回环地址
    localhost 本地主机名
    相关命令
    ipconfig /all
    ping IP地址 – 测试连通性
    域名:是为了方便记忆计算机地址的产物。每个服务器ip地址都与一个域名进行绑定。
    DNS:域名解析服务器,用于把域名转换为对应的ip地址

  3. 端口:是计算机中进程(运行的网络程序)的唯一标识。

    端口是一个2个字节表示的整数:0~65535
    ip地址+端口 ---- 用于确定计算机网络中的唯一的一个进程。
    同一个ip地址主机不能有两个程序使用相同的端口并运行,会导致端口冲突,启动失败。
    0~1024之间的端口一般留给系统程序用的,不要随便使用。
    1024~65535之间的端口都可以使用,只要保证没有其他程序在使用就行。

四、网络编程

  1. TCP程序

    TCP客户端程序

    public class TcpClient {
        public static void main(String[] args) throws IOException {
            //创建Socket
            Socket socket = new Socket("192.168.17.81", 12345);//指定服务器地址和端口
            //通过socket获取输出流
            OutputStream os = socket.getOutputStream();
            //写出数据
            os.write("Hello TCP!!!!".getBytes());
            //释放资源
            os.close();
            socket.close();
        }
    }
    

    TCP服务端程序

    public class TcpServer {
        public static void main(String[] args) throws IOException {
            //创建ServerSocket对象
            ServerSocket ss = new ServerSocket(12345);//绑定端口
            //通过ServerSocket接收Socket
            Socket socket = ss.accept();//阻塞方法,接收客户端连接请求
            //通过Socket获取输入流
            InputStream is = socket.getInputStream();
            //读取数据
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            //获取对方ip
            InetAddress inetAddress = socket.getInetAddress();
            String ip = inetAddress.getHostAddress();
            System.out.println(ip + "发来的信息:" + new String(bys, 0, len));
            //释放资源
            is.close();
            socket.close();
            ss.close();
        }
    }
    
    
  2. UDP程序

    UDP发送端程序

    public class UdpSend {
        public static void main(String[] args) throws IOException {
            //创建DatagramSocket
            DatagramSocket ds = new DatagramSocket();
            //创建数据包裹
            byte[] bytes = "Hello UDP".getBytes();
            InetAddress ip = InetAddress.getByName("192.168.17.103");
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,ip,12345);
            //发送数据
            ds.send(dp);
            //释放资源
            ds.close();
        }
    }
    
    

    UDP接收端程序

    public class UdpReceive {
        public static void main(String[] args) throws IOException {
            //创建DatagramSocket
            DatagramSocket ds = new DatagramSocket(12345);//绑定端口
            //创建空包裹
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);
            //接收数据包裹
            ds.receive(dp);
            //解析包裹中的数据
            byte[] data = dp.getData();//获取包裹中的数据
            InetAddress address = dp.getAddress();//获取对方ip
            String ip = address.getHostAddress();
            int length = dp.getLength();//获取数据的长度
            System.out.println(ip+"-----"+new String(data, 0, length));//控制台输出对方发送的数据
            //释放资源
            ds.close();
    
        }
    }
    

第十五章 反射

一、类的加载过程

类的加载过程

主动使用一个类时,这个类要被加载到内存中并初始化,这个过程大致分为3阶段:

  1. 加载

    就是指将类型的class字节码数据读入内存

  2. 链接

    • 验证:校验被加载的class文件的合法性,并且不会危害虚拟机的自身安全(文件格式验证,语义分析等)

    • 准备:为静态变量赋予默认初始值,为静态常量赋予正确的有效值

      static int x = 10;static final int y = 20;此时,x=0,y=20;

    • 解析:把字节码中的符号引用替换为对应的直接地址引用

  3. 初始化:

    主要执行两部分代码①静态变量的直接显示赋值语句②静态代码块中语句,这两部分按照书写的先后顺序执行。

二、类的初始化

类初始化时一定会执行静态代码块中语句,并且只执行一次,即类只初始化一次。

  • 会导致类初始化的操作有哪些?

    (1)运行主方法所在的类,要先完成类初始化,再执行main方法

    (2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化

    (3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化

    (4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类

    (5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化

  • 不会导致类初始化的操作有哪些?

    (1)使用某个类的静态的常量(static final)

    (2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化

    (3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化

    (4)使用某个类型声明变量,不会导致类的初始化,比如Student stu;

三、类加载器

Java编译后的字节码文件是通过类加载器ClassLoader加载到内存中的。不同的类加载器主要加载不同路径下的class文件。

  1. 根类加载器(Bootstrap ClassLoader)

    主要负责加载jre/lib/rt.jar等核心类,此类加载器由c++编写

  2. 扩展类加载器(Extension ClassLoader)

    主要负责加载jre/lib/ext/路径下的jar,由Java编写

  3. 应用程序类加载器(Application ClassLoader)

    主要加载classpath路径下的class文件,由Java编写

  4. 自定义类加载器

    需要继承ClassLoader

    用于加载指定路径下的类

类加载器的双亲委派模式:

当某个类加载器收到一个类的加载请求时,会先委托父级类加载器进行加载,以及根类加载器进行加载,如果都加载不了再尝试自己来加载,如果还是无法加载会抛ClassNotFountException异常。

双亲委派模式的好处:

  • 避免类的重复加载
  • 安全考虑

获取类加载器的方式:

//获取当前类的加载器
ClassLoader classLoader = Demo.class.getClassLoader();
//获取当前线程上下文类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

四、Class类

java.lang.Class类的实例表示运行时类型。每个类被类加载器加载到内存中之后都会产生一个唯一的Class实例。

这个类的Class实例包含了这个类的所有信息,我们可以通过这个Class实例在运行时任意操作这个类。

获取一个类的Class实例的方式:

  1. 类型名.class
  2. 对象名.getClass方法
  3. Class.forName(String s);
  4. 类加载器的loadClass方法

五、反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

要想解剖一个类,必须先要获取到该类的Class对象。所以,Class对象是反射的根源。

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)

第十六章 Java8新特性

一、Lambda表达式

  1. 函数式接口

    只有一个抽象方法的接口,也称为SAM接口。SAM接口可以使用注解@FunctionalInterface

  2. lambda表达式:本质是SAM接口的匿名内部类对象。

    new Thread(new Runnable(){
        @Override
        public void run(){
            System.out.println("线程任务...");
        }
    }).start();
    //----------lambda-----------
    new Thread(()->System.out.println("线程任务...")).start();
    
  3. lambda表达式语法:

    格式:(形参列表)->{lambda体}

    说明:

    • -> 是lambda的操作符
    • 操作符左边是形参列表,形参可以省略数据类型,如果只有一个参数,可以省略小括号
    • 右边是lambda体,如果只有一条语句,大括号可以省略,而且如果有return也可以省略
  4. Java提供的函数式接口

    消费型接口**

    消费型接口的抽象方法特点:有形参,但是返回值类型是void

    接口名抽象方法描述
    Consumervoid accept(T t)接收一个对象用于完成功能
    BiConsumer<T,U>void accept(T t, U u)接收两个对象用于完成功能
    DoubleConsumervoid accept(double value)接收一个double值
    IntConsumervoid accept(int value)接收一个int值
    LongConsumervoid accept(long value)接收一个long值
    ObjDoubleConsumervoid accept(T t, double value)接收一个对象和一个double值
    ObjIntConsumervoid accept(T t, int value)接收一个对象和一个int值
    ObjLongConsumervoid accept(T t, long value)接收一个对象和一个long值

    示例:

    List<Integer> list = new ArrayList<>();
    list.add(11);
    list.add(22);
    list.add(33);
    //遍历集合
    list.forEach(e->System.out.println(e));
    
    供给型接口**

    这类接口的抽象方法特点:无参,但是有返回值

    接口名抽象方法描述
    SupplierT get()返回一个对象
    BooleanSupplierboolean getAsBoolean()返回一个boolean值
    DoubleSupplierdouble getAsDouble()返回一个double值
    IntSupplierint getAsInt()返回一个int值
    LongSupplierlong getAsLong()返回一个long值

    示例:

    //生成一个Stream无限流,数据产生使用随机数
    Stream.generate(()->Math.random());
    
    判断型接口**

    这里接口的抽象方法特点:有参,但是返回值类型是boolean结果。

    接口名抽象方法描述
    Predicateboolean test(T t)接收一个对象
    BiPredicate<T,U>boolean test(T t, U u)接收两个对象
    DoublePredicateboolean test(double value)接收一个double值
    IntPredicateboolean test(int value)接收一个int值
    LongPredicateboolean test(long value)接收一个long值

    示例:

    List<Integer> list = new ArrayList<>();
    list.add(11);
    list.add(22);
    list.add(33);
    //删除符合条件的元素:删除偶数
    list.removeIf(e -> e%2==0);
    
    功能型接口**

    这类接口的抽象方法特点:既有参数又有返回值

    接口名抽象方法描述
    Function<T,R>R apply(T t)接收一个T类型对象,返回一个R类型对象结果
    UnaryOperatorT apply(T t)接收一个T类型对象,返回一个T类型对象结果
    DoubleFunctionR apply(double value)接收一个double值,返回一个R类型对象
    IntFunctionR apply(int value)接收一个int值,返回一个R类型对象
    LongFunctionR apply(long value)接收一个long值,返回一个R类型对象
    ToDoubleFunctiondouble applyAsDouble(T value)接收一个T类型对象,返回一个double
    ToIntFunctionint applyAsInt(T value)接收一个T类型对象,返回一个int
    ToLongFunctionlong applyAsLong(T value)接收一个T类型对象,返回一个long
    DoubleToIntFunctionint applyAsInt(double value)接收一个double值,返回一个int结果
    DoubleToLongFunctionlong applyAsLong(double value)接收一个double值,返回一个long结果
    IntToDoubleFunctiondouble applyAsDouble(int value)接收一个int值,返回一个double结果
    IntToLongFunctionlong applyAsLong(int value)接收一个int值,返回一个long结果
    LongToDoubleFunctiondouble applyAsDouble(long value)接收一个long值,返回一个double结果
    LongToIntFunctionint applyAsInt(long value)接收一个long值,返回一个int结果
    DoubleUnaryOperatordouble applyAsDouble(double operand)接收一个double值,返回一个double
    IntUnaryOperatorint applyAsInt(int operand)接收一个int值,返回一个int结果
    LongUnaryOperatorlong applyAsLong(long operand)接收一个long值,返回一个long结果
    BiFunction<T,U,R>R apply(T t, U u)接收一个T类型和一个U类型对象,返回一个R类型对象结果
    BinaryOperatorT apply(T t, T u)接收两个T类型对象,返回一个T类型对象结果
    ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)接收一个T类型和一个U类型对象,返回一个double
    ToIntBiFunction<T,U>int applyAsInt(T t, U u)接收一个T类型和一个U类型对象,返回一个int
    ToLongBiFunction<T,U>long applyAsLong(T t, U u)接收一个T类型和一个U类型对象,返回一个long
    DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个double值,返回一个double结果
    IntBinaryOperatorint applyAsInt(int left, int right)接收两个int值,返回一个int结果
    LongBinaryOperatorlong applyAsLong(long left, long right)接收两个long值,返回一个long结果
  5. 方法引用与构造器引用

    是对lambda表达式的简化

    Lambda表达式是可以简化函数式接口的变量与形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。当Lambda表达式满足一些特殊的情况时,还可以再简化:

    (1)Lambda体只有一句语句,并且是通过调用一个对象或类的现有的方法来完成的

    例如:System.out对象,调用println()方法来完成Lambda体

    ​ Math类,调用random()静态方法来完成Lambda体

    (2)并且Lambda表达式的形参正好是给该方法的实参

    例如:t->System.out.println(t)

    ​ () -> Math.random() 都是无参

    方法引用

    方法引用的语法格式:

    (1)实例对象名::实例方法

    (2)类名::静态方法

    (3)类名::实例方法

    说明:

    • ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)
    • Lambda表达式的形参列表,全部在Lambda体中使用上了,要么是作为调用方法的对象,要么是作为方法的实参。
    • 在整个Lambda体中没有额外的数据。
    构造器引用

    (1)当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表。

    (2) 当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度

    构造器引用的语法格式:

    • 类名::new
    • 数组类型名::new

二、Stream

“集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤:

1- 创建 Stream:通过一个数据源(如:集合、数组),获取一个流

2- 中间操作:中间操作是个操作链,对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。

3- 终止操作:一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。

  1. 创建Stream流的方式:

    • 通过集合创建

      List<String> list = new ArrayList<>)();
      Stream<String> stream = list.stream();
      
    • 通过数组创建

      Integer[] arr = {1,2,3,4};
      Stream<Integer> stream = Arrays.stream(arr);
      
    • Stream的of方法

      Stream<Integer> stream = Stream.of(1,2,3,4);
      
    • Stream的generate方法创建无限流

      Stream<Double> stream = Stream.generate(Math::ramdon);
      
  2. 中间操作

    多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

    方 法描 述
    filter(Predicate p)接收 Lambda , 从流中排除某些元素,保留符合条件的元素
    distinct()筛选,通过流所生成元素的equals() 去除重复元素
    limit(long maxSize)截断流,使其元素不超过给定数量
    skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
    peek(Consumer action)接收Lambda,对流中的每个数据执行Lambda体操作
    sorted()产生一个新流,其中按自然顺序排序
    sorted(Comparator com)产生一个新流,其中按比较器顺序排序
    map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
    mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
    mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
    flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
  3. 终结操作

    终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

    方法描述
    boolean allMatch(Predicate p)检查是否匹配所有元素
    boolean anyMatch(Predicate p)检查是否至少匹配一个元素
    boolean noneMatch(Predicate p)检查是否没有匹配所有元素
    Optional findFirst()返回第一个元素
    Optional findAny()返回当前流中的任意元素
    long count()返回流中元素总数
    Optional max(Comparator c)返回流中最大值
    Optional min(Comparator c)返回流中最小值
    void forEach(Consumer c)迭代
    T reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
    U reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional
    R collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

三、Optional

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

16.3.1 API

1、如何创建Optional对象?或者说如何用Optional来装值对象或null值

(1)static Optional empty() :用来创建一个空的Optional

(2)static Optional of(T value) :用来创建一个非空的Optional

(3)static Optional ofNullable(T value) :用来创建一个可能是空,也可能非空的Optional

2、如何从Optional容器中取出所包装的对象呢?

(1)T get() :要求Optional容器必须非空

T get()与of(T value)使用是安全的

(2)T orElse(T other) :

orElse(T other) 与ofNullable(T value)配合使用,

如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

(3)T orElseGet(Supplier<? extends T> other) :

如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替

(4) T orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

3、其他方法

(1)boolean isPresent() :判断Optional容器中的值是否存在

(2)void ifPresent(Consumer<? super T> consumer) :

判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做

(3) Optional map(Function<? super T,? extends U> mapper)

判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做

                   | **描  述**                                                   |

| ----------------------------------- | ------------------------------------------------------------ |
| filter(Predicate p) | 接收 Lambda , 从流中排除某些元素,保留符合条件的元素 |
| distinct() | 筛选,通过流所生成元素的equals() 去除重复元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
| peek(Consumer action) | 接收Lambda,对流中的每个数据执行Lambda体操作 |
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator com) | 产生一个新流,其中按比较器顺序排序 |
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |

  1. 终结操作

    终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。流进行了终止操作后,不能再次使用。

    方法描述
    boolean allMatch(Predicate p)检查是否匹配所有元素
    boolean anyMatch(Predicate p)检查是否至少匹配一个元素
    boolean noneMatch(Predicate p)检查是否没有匹配所有元素
    Optional findFirst()返回第一个元素
    Optional findAny()返回当前流中的任意元素
    long count()返回流中元素总数
    Optional max(Comparator c)返回流中最大值
    Optional min(Comparator c)返回流中最小值
    void forEach(Consumer c)迭代
    T reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
    U reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional
    R collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

三、Optional

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

16.3.1 API

1、如何创建Optional对象?或者说如何用Optional来装值对象或null值

(1)static Optional empty() :用来创建一个空的Optional

(2)static Optional of(T value) :用来创建一个非空的Optional

(3)static Optional ofNullable(T value) :用来创建一个可能是空,也可能非空的Optional

2、如何从Optional容器中取出所包装的对象呢?

(1)T get() :要求Optional容器必须非空

T get()与of(T value)使用是安全的

(2)T orElse(T other) :

orElse(T other) 与ofNullable(T value)配合使用,

如果Optional容器中非空,就返回所包装值,如果为空,就用orElse(T other)other指定的默认值(备胎)代替

(3)T orElseGet(Supplier<? extends T> other) :

如果Optional容器中非空,就返回所包装值,如果为空,就用Supplier接口的Lambda表达式提供的值代替

(4) T orElseThrow(Supplier<? extends X> exceptionSupplier)

如果Optional容器中非空,就返回所包装值,如果为空,就抛出你指定的异常类型代替原来的NoSuchElementException

3、其他方法

(1)boolean isPresent() :判断Optional容器中的值是否存在

(2)void ifPresent(Consumer<? super T> consumer) :

判断Optional容器中的值是否存在,如果存在,就对它进行Consumer指定的操作,如果不存在就不做

(3) Optional map(Function<? super T,? extends U> mapper)

判断Optional容器中的值是否存在,如果存在,就对它进行Function接口指定的操作,如果不存在就不做

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值