Java语言基础

121 阅读1小时+

第1章 java基础

1.0 计算机基础

  1. 现代计算机基础:冯诺依曼体系结构
  2. IT定律(硬件):
    • 摩尔定律:18-24个月,计算机性能提升一倍。
    • 反摩尔定律:如果现在和18个月前卖掉同样多的同样的产品,营业也就会下降一半。
    • 安迪-比尔定律:硬件性能的提高,很快会被软件消耗掉。
  3. DOS命令:
    • md:创建文件夹
    • rd:删除文件夹
    • del:删除文件
    • cd:切换路径
    • dir:查看当前路径下的文件和文件夹
    • cd:切换路径
      • cd \:切换到根路径。
    • tree:指定目录下的所有文件(显示文件树)
    • cls:清屏
    • exit:退出dos
  4. 编码:
    1. ASCII:一个字节表示一个字符,共128个。
    2. Unicode:两个字节表示一个字符,字母和汉字都用2个字符表示,存在浪费空间问题。兼容ASCII编码
    3. UTF-8:大小可变编码,字母用一个字节,汉字用3个字节
    4. gbk:字母用1个字节,汉字用2个字节。
    5. gb2312:同gbk,表示范围小于gbk
    6. big5:繁体中文

1.1 java简介

  1. java SE、Java EE、Java ME的关系

    image.png

  2. JDK和JRE:

    • JDK: Java Development Kit,把Java源码编译成Java字节码。
      • JDK=JRE+Java开发工具
    • JRE: Java Runtime Environment,运行Java字节码的虚拟机。
      • JRE=JVM+核心类库

    image.png

  3. JSR和JCP:

    • JSR规范:Java Specification Request
    • JCP组织:Java Community Process,负责JSR。
  4. JDK的可执行文件:

    • java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码;
    • javac:这是Java的编译器,它用于把Java源码文件(以.java后缀结尾)编译为Java字节码文件(以.class后缀结尾);
    • jar:用于把一组.class文件打包成一个.jar文件,便于发布;
    • javadoc:用于从Java源码中自动提取注释并生成文档;
    • jdb:Java调试器,用于开发阶段的运行调试。
  5. Java的特点:

    • 面向对象(oop)
    • 健壮性:强类型机制、异常处理、垃圾自动收集等。
    • 跨平台的。
    • 解释性的:
      • 解释性语言编译后不能直接被机器运行,需要解释器执行。
      • 编译性语言编译后的代码可以直接被机器执行,如C,C++
  6. 运行Java程序:

    • javac 文件名.java编译代码,生成文件名.class字节码文件。
    • java 文件名自动查找对应的文件.class文件并执行。
    • java11后,**java 文件名.java**可以直接运行一个单文件源码,但多数情况下,无法直接这样操作,因为**文件名.java**文件需要依赖其他库。
  7. 由于发展原因:推荐下载jdk8U201版本(最后一稳定版本、长期支持版本)repo.huaweicloud.com/java/jdk/8u…

    • 安装jdk8时,会额外安装jre8,为了避免开发工具配置出错,建议安装。

1.2 Java程序基础

1.2.1 Java程序基本结构

  1. Java编程的特点:

    • Java源码的缩进不是必须的。
    • Java代码保存为文件时,文件名和main方法外部的类名要完全一致。
    • 一个源文件最多只能有一个public类,其他类的个数不限。
      • 也可以没有public类。
      • 编译后每一个类都对应生成一个.class文件。
      • 每个类都可以有main方法,且都可以被执行。
        • 被执行的前提是同文件内有一个public类。
        • 执行方式是**java 类名**
        • main方法必须按java程序规定的入口语法编写,即public static void main(String[] args)
    • 类名必须以英文字母开头(习惯首字母大写),后接字母、数字、下划线的组合。
    • public static void main(String[] args)规定的java程序入口
      • main方法名必须以英文字母开头(习惯首字母小写),后接字母、数字、下划线。
    • Java的每一行语句必须以分号结束。
  2. java注释

    • 单行注释://
    • 多行注释:
      • 多行注释不能嵌套多行注释。
    /*
    xxxx
    */
    
    • 文档注释:注释内容可以被JDK提供的javadoc所解析,生成一套以网页文件形式体现在该程序
    /** 
    xxx
    */
    
    • 拓展:javadoc命令。
  3. 转义字符

    • \t:一个制表符,实现对齐功能。
    • \n:换行符
    • \\:\
    • \r:回车(光标移至本行前面)
      • 霜冷长河\r寒冬--------->输出后寒冬长河
  4. Java代码规范:

    • 类、方法的注释,要以javadoc的方式书写。
    • 非javadoc的注释,写给代码维护者看,为什么这样写、如何修改、注意什么问题。
    • 使用tab缩进。
    • 运算符“+-*/=”两边各使用一个空格增加代码的可读性。
    • 源文件使用UTF-8编码。
    • 每行字符不要超过80字符。
    • 代码编写行尾风格(起始大括号在同一行)或次行风格(起始大括号在下一行)

1.2.2 变量和数据类型

  1. 变量:
    • 概念:相当于内存中一个数据存储空间的表示。
      • 不同类型的变量,占用的内存空间大小不同。
      • 该存储空间的值,可以在同种类型范围内不断变化。
    • 三个基本要素:类型、名称、值。
      • java中变量的声明和初始化可以是两条语句,但使用前必须必须赋值进行初始化。
        • **int num; num = 10;**
    • 变量必须先声明,后使用。
    • 同一作用域内,变量不能重名。
    • int a = b = c;这种初始化方式会报错。
    • int a, b, c;这种初始化方式不会报错。
  2. 输出语句中+的含义:
    • 左右两边都是数值时,做加法运算。
    • 左右一边有字符串时,做拼接运算。
  3. 数据类型:

  1. 基本数据类型
    1. 整数型:

      1. 整数型默认使用int大小的存储空间,使用long大小的存储空间时结尾加L。
        类型占用存储空间表示范围
        byte1字节-218-1~218-1-1
        short2字节-228-1~228-1-1
        int4字节-248-1~248-1-1
        long8字节-288-1~288-1-1
    2. 浮点类型:

      1. 浮点类型都是近似值,因为存在小数位丢失的问题。
      2. 内存中的存放形式:浮点数=符号位+指数位+尾数位。
      3. 浮点型默认使用double大小的存储空间,使用float大小的存储空间时末尾加f或F。
      4. 浮点型除以整形存在精度问题,在编程中,应当注意对运算结果是小数的运算、判断!应该以两个数差值的绝对值在某个精度范围内判断。
        类型占用存储空间表示范围
        float4字节
        double8字节
    3. 字符:

      1. 字符型要用单引号,不能使用双引号。
      2. 字符型也可以直接赋值(整数),不需要用单引号,输出时显示对应的Unicode编码。
      3. 字符型本质上是一个整数,可以参与运算。
      4. 字符型还可以赋值为转义字符,需要单引号。
      5. 字符型也可以赋值为单个汉字,需要单引号。
    4. 布尔类型:

      1. Java对布尔类型的存储占用空间大小没有做规定,理论上只需要1bit(0或者1),但由于计算机中最小存储单元为字节(Byte),所以占用应为1字节,同时又因为编译后虚拟机(JVM)中布尔类型的值会被转换为int类型的数据代替,所以会占用4个字节。
      2. Java中不可以用0或者非0的整数代替 false或true,即Java中布尔类型的值只能为true或false。
  2. 自动类型转换:
    • Java中程序在进行赋值或运算时,精度小的类型会自动转换为精度大的类型。
      • char→int→long→float→double
      • byte、short→int→long→float→double
    • 几点规律:
      • 多种类型的数据混合运算时,系统会将所有数据自动转换为容量最大的那种数据类型,然后再进行计算。
      • (byte、short)和char之间不会相互自动转换。
      • 无法进行容量大的类型向容量小的类型自动转换,会报错。
      • byte、short、char在进行运算时,会自动转换为int,所以需要存储空间大于等于int的类型。
        • int=byte+byte(对),short=byte+byte(错),其他同理。
      • 布尔类型不参与自动类型转换。
  3. 强制类型转换
    • 自动类型转换的逆过程。
    • 转换过程中会造成精度降低或溢出。
    • char类型可以保存int的常量值(自动类型转换,int自动转char),但不能保存int的变量值。【原因:类型转换问题】
  4. 基本数据类型和String类型的转换
    • 基本数据类型--->String类型:基本数据类型 + ""
    • String类型--->基本数据类型:
      • Byte.parseByte()
      • Short.parseShort()
      • Integer.parseInt()
      • Long.parseLong()
      • Float.parseFloat()
      • Double.parseDouble()
      • Boolean.parseBoolean()
      • char类型没有上述的方法,s.charAt(n)可以获取字符串s指定位置的字符。
      • 注意点:
        • String类型转换为基本类型时,必须先确保可以转换为对应的类型。如123hello不能转换为int
        • 如果格式不正确,会抛出异常,程序终止。
  5. 变量的赋值:
    • 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
    • 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。

1.2.3 运算符

  1. 算术运算符:
    • %本质是a % b = a - a / b *b,所以**10 % -3** = 10 - 10 / (-3) * (-3) = **1**
    • ++的经典面试题:
      • i = i++:(1)temp=i,(2)i=i+1,(3)i=temp,结果为i
      • i = ++i:(1)i=i+1,(2)temp=i,(3)i=temp,结果为i+1
    • 进行b++时不会进行强制类型转换,如:
      • byte b=127;b++不会报错,输出-128。
    • 注意:前减减/前加加的优先级高于=,后加加/后减减的优先级低于=
    • 整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:
      • NaN表示Not a Number,0.0 / 0
      • Infinity表示无穷大,1.0 / 0
      • -Infinity表示负无穷大,-1.0 / 0
  2. 关系运算符:
    • 关系运算符的结果都是boolean类型,要么是true,要么是false。
    • ==判断引用数据类型时,比较的是地址值。
      • 左右两侧数据类型要一致,否则编译报错。
    • ==判断基本数据类型时,比较的是具体值。
      • 左右两侧不一定类型要相同,会进行自动数据类型提升
        • boolean除外,boolean与不是boolean的比较时会报错。
    • 判断字符串时:
      • 字符串以字面量形式声明:比较内容
      • 字符串以new关键字声明:比较地址值
      • 规范情况:须使用isequals()方法
  3. 逻辑运算符:
    • 逻辑运算符的结果都是boolean类型,要么是true,要么是false。
    • &&:第一个条件为false时,立即得出结果为false,效率高,而且后面部分不会被执行
    • &:即使第一个条件为false,第二个条件的计算还会执行。
      • image.png
    • ||:第一个条件为true时,立即得出结果为true,效率高,而且后面部分不会被执行
    • |:即使第一个条件为true,第二个条件的计算还会执行。
    • 开发中常用&&||
  4. 赋值运算符:
    • 赋值运算符左边只能是变量,右边可以是变量、表达式、常量值。
    • 复合运算符会进行强制类型转换。
  5. 三元运算符:本质实际上是if-else语句。
    • 条件表达式1 ? 表达式1 : 表达式2
      • 表达式1和表达式2要求类型一致,所以会自动类型提升。
  6. 运算符优先级:
    • ++优先级的问题:
      • 算术运算(赋值):
      • 逻辑运算:
      • 三元运算:
  7. 位运算符:暂无。
  8. 优先级
    • image.png

1.2.4 标识符规范

  1. 包名:多单词组成时全部写成小写。
  2. 文件名、类名、接口名:使用大驼峰形式。
  3. 变量名、方法名:小驼峰。
  4. 常量名:所有字母都大写,多个单词用下划线连接。

1.2.5 键盘输入语句

  1. 使用步骤:
    • 导入Scanner所在的包:import java.util.Scanner;
    • 创建Scanner实例对象:Scanner myScanner = new Scanner(System.in);
    • 使用myScanner的实例对象方法:
      • 接收字符串:String str = myScanner.next();
      • 接收整数:int num = myScanner.nextInt();
      • 接收浮点数:double sth = myScanner.netDouble();
      • 接收字符:char charNum = myScanner.next().charAt(0);

1.2.6 进制

  1. 整数的四种表示方式:
    • 二进制:0和1,以0b或0B开头。
    • 八进制:0-7,以0开头。
    • 十进制:0-9。
    • 十六进制:0-9以及A-F(a-f),以0x或0X开头。
  2. 进制转换:
    • 其他进制转十进制:
      • 二进制转十进制:每个位上的数 * 2^indx的和,index从右向左依次为0至n。
      • 八进制转十进制:每个位上的数 * 8^indx的和,index从右向左依次为0至n。
      • 十六进制转十进制:每个位上的数 * 16^indx的和,index从右向左依次为0至n。
    • 十进制转其他进制:
      • 十进制转二进制:该数不断除2,直到商为0,将余数倒序拼接。
      • 十进制转八进制:该数不断除8,直到商为0,将余数倒序拼接。
      • 十进制转十六进制:该数不断除16,直到商为0,将余数倒序拼接。
    • 二进制转其他进制:
      • 二进制转八进制:从右向左,3个数一组,对应的十进制数组合后即为八进制。
      • 二进制转十六进制:从右向左,4个数一组,对应的十进制数组合后即为十六进制。
    • 其他进制转二进制:
      • 八进制转二进制:将八进制的每一个数,转成对应的3位数二进制即可。
      • 十六进制转二进制:将十六进制的每一个数,转成对应的4位数二进制即可。
  3. 位运算:
    • 原码、反码、补码:
      • 二进制数的最高位为符号位,0表示正数,1表示负数。
      • 正数的原码、反码、补码都一样。
      • 负数的反码 = 原码符号位不变,其他位的数取反。
      • 负数的补码 = 反码 + 1,负数的反码 = 补码 - 1。
      • 0的反码,补码都是0。
      • java没有无符号数 ,即java中的数都是有符号的。
        • 0在java中表示正数。
      • 计算机运算的时候,都是以补码进行运算。
      • 使用运算结果的时候,需要看它的原码。
    • 算数右移>>:符号位不变,低位溢出,高位补0。
      • m >> n本质是m / 2^n
      • 注意:负数跨越0时输出均为-1。
      • 负数的算数右移?
    • 算术左移<<:符号位不变,高位溢出,低位补0。
      • m << n本质是m * 2^n
    • 逻辑右移或无符号右移>>>:低位溢出,高位补0(符号位也跟着移动)。
    • 没有<<<

1.3 流程控制

1.3.1 顺序控制

  1. java程序默认的执行流程,从上到下逐行执行,所以 变量必须先声明后使用。

1.3.2 分支控制

  1. 单分支【if】:
    • 即使执行的代码块语句只有一条,也建议加上{}
    • 当只有一条语句时,可以不用{}直接换行写,也可以不用换行,与if写在同一行。
  2. 双分支【if……else】:
  3. 多分支【if……else if……else】:
    • 多分支可以没有最后的else。
    • 多分支只有一个执行入口,即从这个入口执行了语句,其余入口的语句不会被执行 。【先入为主】
      • 如下述的伪代码,最高分为80时,考了90分的只会从第一个入口进,输出A,而不会再去判断后面的B、C、D
if(成绩成绩>=最高分-10){
    等级为A 
}else if(成绩>=最高分-20){
    等级为B
}else if(成绩>=最高分-30){
    等级为C
}else{
    等级为D
}
  1. 嵌套分支:

    • 一个分支结构中嵌套了另一个完整的分支结构。
    if(){
            if(){
            else if(){
            xxx;
            }else{
            xxx;
            }
        }else{
        xxx;
        }
    }
    
    • 为了保证可读性,不要超过三层。
  2. switch分支:

    switch(表达式){
        case 常量1:
            语句1;
            break;
            ……
        case 常量n:
            语句n;
            break; 
        default:
            default语句块;
            break;
    }
    
    • 穿透:如果case语句没有break语句,程序会继续执行下一个case的语句,而不会进行case判断。
    • image.png
    • switch表达式的数据类型,应和case后面的常量类型一致,或者表达式的数据类型可以自动转换为常量类型。
    • switch表达式的返回值必须是byte、short、int、char、enum【枚举】、String。
    • case语句的值必须是常量或常量表达式,不能是变量或带变量的表达式。
  3. switch和if的选择:

    • 运算的结果数值不多,且是byte、short、int、char、enmu、String的某种类型,建议使用swtich。
    • 对于区间判断、结果为boolean的判断,建议使用if。

1.3.3 循环控制

  1. for循环:

    • 基本语法:
    for(循环变量初始化;循环条件;循环变量迭代){
        循环操作;
    }
    
    • 四要素:循环变量初始化、循环条件、循环操作、循环变量迭代。
      • 循环初始值可以有多条初始化语句,但要求类型一样,并用逗号隔开int i = 0, j = 0
      • 循环变量迭代可以有多条变量迭代语句,中间用逗号隔开i++, j++
    • 如果循环操作只有一条语句,可以省略{},建议不要省略。
    • for(;循环条件;)
      • 循环变量初始化和循环变量迭代可以写到其他地方,但是;不能省略。
      • 循环变量初始化写到其他地方时,作用域再循环体外。否则只能作用域循环体内。
    • for(;;):表示无限循环。
  2. while循环:

    • 基本语法:
    循环变量初始化;
    while(循环条件){
     循环体;
     循环变量迭代;
    }
    
    • 四要素:同for循环。
  3. do…while循环

    • 基本语法
    循环变量初始化;
    do{
        循环体;
        循环变量迭代;
    }while(循环条件);
    
    • 四要素:同for循环。
    • 注意while后面的;
  4. 增强for循环

    for(int i : nums) {
       System.out.println("i=" + i);
    }
    
    • 依次从nums数组、枚举类等中取出数据,赋给i

1.3.4 多重循环控制

  1. 建议循环嵌套不要超过2层,否则可读性非常差。

  2. 外层循环m次,内层循环n次,则内层循环体实际上需要执行m*n次。

  3. break:结束当前循环。

    label1:
    for(...){
        label2:	
        for(...){
            label3:
            for(...){
                break label1;
             }
        }
    }
    
    • break配合label标签可以指定跳出哪层循环。
    • label是标签名,由程序员指定,实际开发中,尽量不要使用标签。
    • 如果break后没有指定的label标签名,则表示跳出最近的循环体.
  4. continue:结束当次循环

    label1:
    for(...){
        label2:	
        for(...){
            label3:
            for(...){
                continue label1;
             }
        }
    }
    
    • continue配合label标签可以指定跳出哪层循环。
    • label是标签名,由程序员指定,实际开发中,尽量不要使用标签。
    • 如果contineu后没有指定的label标签名,则表示跳出最近的循环体。
    • continue不能用于switch-case语句中
  5. return:

    • return用在方法内时,表示跳出方法,用在main方法中表示退出程序。本质上不是退出循环。

1.4 数组和数组操作

1.4.1 数组介绍

  1. 定义:存放同一类型数据的组合。是一种引用数据类型。
  2. 数组的定义:
    1. 动态初始化——方式一:
      1. 声明、创建、定义:
      • 数据类型 数组名[] = new 数据类型[大小]
      • 数据类型[] 数组名 = new 数据类型[大小]
      1. 初始化:
      • a[0] = 1;……
    2. 动态初始化——方式二:
      1. 声明数组:
      • 数据类型 数组名[];
      • 数据类型[] 数组名;
      • 声明数组时,[]内不能写数组大小。
      1. 创建数组(分配空间):
      • 数组名 = new 数据类型[大小];
      1. 初始化:
      • a[0] = 1;……
    3. 静态初始化——方式一:
      1. 声明、创建并初始化:
      • 数据类型 数组名[] = {值1, 值2,……}
      • 数据类型[] 数组名 = {值1, 值2,……}
      • 静态初始化时,[]内不能写数组大小。
    4. 静态初始化——方式二:
      • 数据类型 数组名[] = new 数据类型[]{值1, 值2,……}
      • 数据类型[] 数组名 = new 数据类型[]{值1, 值2,……}
      • 静态初始化时,[]内不能写数组大小。
  3. 注意事项:
  • 数组创建后,如果没有进行初始化,默认初始值如下:
    • byte、short、int、long:0
    • float、double:0.0
    • char:\u0000(空字符)
    • boolean:false
    • String:null
  • 数组是引用类型,是对象类型的一种。
  • 数组定义后不能直接改变大小(容量),但可以通过以下两种方式改变大小
    • 数组的拷贝:将一个大容量的数组赋值给小容量的数组
    • 通过new关键字重新确定容量大小。
    • 动态初始化情况下改变传入变量的大小控制数组的大小。

1.4.2 数组操作

  1. 拷贝数组:
    1. 基本数据类型:b=0;a=b;,两个数值互不影响,拷贝的是数据。
    2. 引用数据类型:int[] arr1 = {1,2……};arr2 = arr1;
      1. 默认情况下:拷贝的是内存中的地址,arr1和arr2不论哪个的值变化,都会引起另外一个的变化。
      2. 值拷贝方式:给arr1和arr2分别开辟独立的内存空间。int[] arr1 = {1,2……};int[] arr2 = new int[arr1.length];
  2. 反转数组:
    1. 交换数据法:
    2. 逆序赋值法:
  3. 数组扩容:
    1. 创建新数组,新数组添加元素,将新数组赋值给旧数组。
  4. 数组缩减:
    1. 创建新数组,新数组长度比旧数组少1,循环赋值。
  5. 数组排序:
    1. 内部排序:将所有数据加载到内存中进行排序。
      1. 冒泡排序:
    2. 外部排序:数据量过大,无法全部加载到内存中,借助外部的存储空间进行排序。
  6. 数组查找:
    1. 顺序查找:
    2. 二分查找:

1.4.3 二维数组

  1. 初始化:
    1. 动态初始化——方式一:
      1. 声明、创建、定义:
      • 数据类型 数组名[][] = new 数据类型[大小][大小]
      • 数据类型[][] 数组名 = new 数据类型[大小][大小]
      • 数据类型[] 数组名[] = new 数据类型[大小][大小]
      1. 初始化:
      • a[0][0] = 1;……
      • 或者a[0] = {1,3……}
      1. 未初始化的默认值:
      • a[0]:指向内存地址。
      • a[0][0]:输出该数组对应数据类型的默认值。
    2. 动态初始化——方式二:
      1. 声明数组:
      • 数据类型 数组名[][];
      • 数据类型[][] 数组名;
      • 数据类型[] 数组名[];
      1. 创建数组(分配空间):
      • 数组名 = new 数据类型[大小][大小];
      1. 初始化:
      • a[0][0] = 1;……
      • 或者a[0] = {1,3……}
      1. 未初始化的默认值:
      • a[0]:指向内存地址。
      • a[0][0]:输出该数组对应数据类型的默认值。
    3. 动态初始化——列数不确定:
      1. 声明数组:
      • 数据类型 数组名[][] = new 数据类型[大小][]
      • 数据类型[][] 数组名 = new 数据类型[大小][]
      • 数据类型[] 数组名[] = new 数据类型[大小][]
      • 数据类型 数组名[][];数组名 = new 数据类型[大小][];
      • 数据类型[][] 数组名;数组名 = new 数据类型[大小][];
      • 数据类型[] 数组名[];数组名 = new 数据类型[大小][];
      1. 初始化:
      • a[0] = new int[1];……
      • 或者a[0] = {1,3……}
      1. 注意点:
      • 数据类型 数组名[][] = new 数据类型[a][]声明了具有a个一维数组的二维数组,但一维数组内部的元素个数还不确定。
      • 数据类型 数组名[][] = new 数据类型[][a]这种声明方式非法。
      1. 未初始化的默认值:
      • a[0] = new int[3];a[0]:指向内存地址。
      • 没有a[0] = new int[3];直接a[0]:null。
      • a[0] = new int[3];a[0][0]:输出该数组对应数据类型的默认值,本例为0。
      • 没有a[0] = new int[3];直接a[0]:报错。
    4. 静态初始化——方式一:
      1. 声明、创建并初始化:
      • 数据类型 数组名[][] = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 数据类型[][] 数组名 = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 数据类型[] 数组名[] = {{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 静态初始化时,[]内不能写数组大小。
    5. 静态初始化——方式二:
      1. 声明、创建并初始化:
      • 数据类型 数组名[][] = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 数据类型[][] 数组名 = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 数据类型[] 数组名[] = new int[][]{{值1, 值2,……},{值1, 值2,……},{值1, 值2,……}}
      • 静态初始化时,[]内不能写数组大小。
    6. 特殊写法情况:int[] x,y[]; x是一维数组,y是二维数组。

1.4.4 Arrays工具类(常用)

  1. Arrays.equals(int[] a, int[] b)
    • 返回booleanboolean isEquals = Arrays.equals(a, b);
    • 返回true的条件:两数组指向同一个内存地址(a=b)、a和b内部的元素值相等
    • 返回false的条件:两数组均为null、a和b内部的元素值不想等
    • 除int外,其余各种类型的对象都可判断
  2. Arrays.toString(int[] a)
    • 返回StringString aStr = Arrays.toString(a);
    • 输出为[值1,,……]的形式,实际为字符串的拼接
  3. Arrays.fill(int[] a,10)
    • 返回为空,所以不能赋值给其他语句。
    • 表示将数组a的所有元素替换为10。
  4. Arrays.sort(int[] a)
    • 返回为空,所以不能赋值给其他语句。
    • 表示将数组a按照从小到大的顺序排序,底层排序方式为快速排序。
  5. Arrays.binarySearch(int[] a, 10)
    • 返回intint index = Arrays.binarySearch(a, 10);
    • 返回值为正数时,为目标数据(10)所在的元素下标。返回值为负数时,表示没找到。
    • 底层排序方式为二分法查找。
  6. 使用上述方法需要引入对应的工具类:import java.util.Arrays;

1.4.5 常见异常

  1. 编译时不报错,运行出错。
  2. ArrayIndexOutOfBoundsExcetion
    • 角标越界:
      • 角标为负数
      • 角标为数组长度(应为数组长度-1)
  3. NullPointerException
    • 数组指向null
      • 数组只声明、未初始化
      • 数组赋值为null
    • 不定列二维数组某个一维数组未初始化,访问了该一维数组的具体值。
    • 一维字符串数组的某个值赋值为空。

1.4.6 数组输出问题:

  1. 详见(详见Java问答,1.4.14).
  2. 一维数组:
    • 声明未创建(没有指定数组大小):编译报错
    • 声明并初始化(指定数组大小):输出数组显示内存地址、输出元素显示默认值或指定值
  3. 二维数组:
    • 声明未创建(没有指定数组大小):编译报错
    • 声明并初始化(指定数组大小):
      • 指定两个大小:输出数组显示内存地址、输出一维元素显示内存地址或一维数组、输出二维元素为默认值或指定值
      • 指定一个大小:输出数组显示内存地址、输出一位元素显式为空、输出二维元素编译不报错,运行报错

第2章 面向对象(基础部分)

2.0 面向对象的学习内容

2.1 类和对象

  1. 类和对象是面向对象的核心概念。
    • 类是对一类事物的描述,是抽象、概念上的定义。
    • 对象是实际存在的该类事物的每个个体,因而也被称为实例。
  2. 类和对象的书面理解:
    • Java语言范畴中,将功能、结构等封装到类中,通过类的实例化,来调用具体的功能、结构。
      • Scanner、String等
      • 文件:File
      • 网络资源:URL
    • 涉及到Java语言与HTML、后端数据库交互时,前后端的结构在java层面交互时,都体现为类、对象。

2.1.1 类

  1. 类的语法格式:

    修饰符 class 类名{
        属性声明;
        方法声明;
    }
    
    public class Person{
        private int age ; //声明私有变量 age
        public void showAge(int i) { //声明方法showAge( )
            age = i; 
        } 
    }
    
  2. 类的成员:

    • 属性:对应类中的成员变量
      • Field = 属性 = 成员变量
    • 行为:对应类中的成员方法
      • Method = 方法 = 函数
  3. 类权限修饰符只能缺省或public

2.1.2 类的成员——属性

  1. 属性的定义(声明):语法同变量:访问修饰符 属性类型 属性名;
  2. 细节和注意事项:
    • 访问修饰符:public、proctected、默认、private。
    • 属性的类型:可以是任意类型,包括基本数据类型和引用数据类型。
    • 属性的默认值:属性不赋值时,有默认值,规则同数组
      • byte、byte、short、int、long:0
      • float、double:0.0
      • char:\u0000(空字符)
      • boolean:false
      • String:null
  3. 属性赋值:遵循变量赋值的规定,即:
    • 如果变量是基本数据类型,此时赋值的是变量所保存的数据值。
    • 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值。

2.1.3 类的成员——方法

  1. 方法的定义:

    访问修饰符 返回数据类型 方法名(形参列表……){
        语句;
        return 返回值;
    }
    
    • 形参列表:表示成员方法的输入
    • 返回数据类型:表示成员变量的输出类型。void表示没有返回值。
    • {}:表示方法体,实现某一功能的代码块
    • return语句:不是必须的。
      • 无返回值类型的方法也可以用return,用于结束方法。
  2. 细节和注意事项

    • 访问修饰符:
      • public、proctected、默认、private
      • 控制方法的使用范围。
    • 返回数据类型:
      • 一个方法只能有一个返回值。返回多个值可以使用数组接收。
      • 返回类型可以是任意类型包含基本类型或引用类型(数组、对象)
      • 如果要求有返回值,则必须有return语句,且return语句的数据类型与方法的返回数据类型一致或兼容。
      • 如果方法时void,则方法体中可以没有return语句,或者只写return。
    • 方法名:
      • 见名知义
      • 小驼峰命名方法,遵循标识符的规则、规范
    • 形参列表:
      • 一个方法可以有0个参数,也可以有多个参数,多个参数用逗号分开。
      • 参数类型可以是任意类型,包括基本类型和引用类型。
      • 调用带参数的方法时,必须传入对应类型或兼容类型的实参。
      • 调用方法时,实参和形参的数量、类型、顺序必须一致。
    • 方法体:
      • 方法体的语句可以为输入、输出、变量、运算、分支、循环、方法调用,但是不能嵌套定义——方法中不能再声明方法。
  3. 方法调用的细节

    • 同一个类中的方法A(非main方法)中调用方法B时,可以直接调用方法名()
      • main方法需要调用同一个类中的方法B时,需要通过实例化类,对象名.B方法名()调用
    • 跨类中的方法A调用方法B时,需要通过对象名(实例化对象)调用类名 对象名 = new 类名();对象名.方法名()
    • 跨类调用的方法和它的访问修饰符有关。
  4. 方法的调用机制:

    • 当程序执行(main方法)到方法时,会在jvm的栈内存内开辟一个独立的空间
    • 当方法执行完毕时,或执行到return语句时,就会将方法的运行结果返回给栈内存中调用方法的地方。同时销毁开辟的独立空间。
    • 返回后,程序继续执行后面的代码
    • 当jvm内存内的main方法执行完毕,整个程序退出。

    image.png

  5. 使用方法的优点:

    • 提高了代码的复用性。
    • 将实现的细节封装起来,供其他用户调用。
  6. 方法的传参机制(值传递):

    • 基本数据类型:传递的是值(值拷贝),形参的任何改变不影响实参。
    • 引用数据类型:传递的是地址,可以通过形参影响实参。
      • 形参不影响实参的情况:形参在方法内开辟了新的内存空间(需要在方法体内的语句实现)。
      • 字符串的传参?
  7. 克隆对象:创建两个独立的对象,只是属性、方法一致。

    • 利用引用数据类型传参的机制(在方法体内创建新对象,逐一赋值)。

2.1.4 类的实例化——对象

  1. 创建对象:
    • 方式一:类名 对象名 = new 类名();
    • 方式二:类名 对象名;对象名 = new 类名();
    • Person p1 = new Person();
      • p1是对象的名(对象的引用),保存实例化对象的内存地址
      • new Person()创建的对象空间(数据)才是真正的对象
    • 方式三:创建对象数组类名[] 对象数组名 = new 类名[n];
  2. 访问成员:对象名.对象成员
    • 访问属性:对象名.属性
    • 访问方法:对象名.方法()
  3. 匿名对象:
    • 含义:创建对象时,如果没有显式地给对象起名,只是进行了new 类名(),则new 类名()为匿名对象。
    • 特征:匿名对象只能调用一次。
      • 第二次调用时已经是新对象了,跟前一个对象没有关系。
    • 使用:
      • 临时使用、不需要保留
      • 作为方法的参数

2.1.5 方法重载(OverLoad)

  1. 定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
  2. 判断是否是重载:
    • “两同一不同”:
      • 在同一个类中
      • 相同方法名
      • 参数列表不同:
        • 参数个数不同
        • 参数类型不同
        • 同类型参数顺序不同
    • 跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
  3. 在通过对象调用方法时,如何确定某一个指定的方法:
    • 判断方法名是否一致
    • 判断参数列表是否一致:参数类型、参数个数、参数顺序一致
  4. 注意点:子类可以重载父类同名不同参的方法。

2.1.6 方法递归(Recrusion)

  1. 含义:方法自己调用自己,每次调用时传入的参数不同
  2. 调用规则:
    • 递归执行一次,会在栈内存内创建一个受保护的独立空间(栈空间)
    • 方法的局部变量相互独立,不会相互影响
    • 方法中使用引用数据类型的变量时,就会共享该引用类型的数据。
    • 递归必须向退出递归的条件逼近,否则就是无限递归。
    • 当一个方法执行完毕,或者遇到return语句,就会返回,遵循谁调用,结果就返回给谁。同时该方法执行完毕或返回时,该方法也就执行完毕。

2.1.7 构造方法/构造器(Constructor)

  1. 基本语法:[修饰符] 方法名(形参列表){}
  2. 语法说明:
    • 修饰符可以默认(无),也可以用private、protected、public。
      • 修饰符为默认(无)时,与类的修饰符一致。
    • 构造器没有返回值,也不可以写void
    • 方法名必须和类名一模一样
    • 参数列表和方法的规则一致
    • 在创建对象时,系统会自动调用该类的对象完成属性的初始化。
      • 完成对象的初始化不是创建对象
    • 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
  3. 主要作用:完成新对象(属性)的初始化。
  4. 构造器重载:
    • 一个类可以定义多个构造器
    • 如果一个构造器没写,系统会自动生成一个无参、默认的构造器(该构造器的修饰符与类一致)。
    • 一旦定义了一个构造器,默认的无参构造器就不能使用了,使用了会报错。
      • 如果想继续使用,需要再重新显式定义一下。
  5. 对象创建流程:
    • 加载类信息,加载到方法区,只会加载一次
    • 在堆空间中分配空间(地址)
    • 完成对象的初始化
      • 默认初始化
      • 显式初始化
      • 构造器初始化
    • 将对象在堆空间的地址返回给创建对象时定义的对象
  6. 父类的构造器不可被子类继承
  7. 注意点:使用了构造器后,实例化对象要用构造器的方法,否则会报错。
    • 原因为:显式声明构造器后,默认的无参构造器会不可用。

2.1.8 方法重写(override/overwrite)

  1. 定义:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
  2. 应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
  3. 语法:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{}
    • 方法名:父子一致。
    • 形参列表:父子一致。
    • 权限修饰符:子类不小于父类
      • 不能重写父类中private修饰的方法,因为private修饰的方法对外部不可见。
    • 返回值类型:
      • void:父子一致。
      • 基本数据类型:父子一致。
      • 引用数据类型:子类不大于父类
    • 异常类型:子类不大于父类
  4. 注意点:子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。

2.1.9 代码块

  1. 作用:用来初始化类、对象
  2. 修饰:
    • 权限修饰:只能缺省。
    • 关键字:只能用static或没有。
  3. 分类:静态代码块与非静态代码块,
    • 相同点:
      • 都可以用于对类的属性、声明初始化
      • 都可以声明多个代码块,但一般每种最多写一个
      • 多个代码块默认执行顺序都是先上后下
    • 不同点:
      • 静态代码块:
        • 只能调用静态的属性和方法
        • 随类的加载而执行,只执行一次
          • 类加载的三个时机:
            • 创建对象实例时(new)
            • 创建子对象实例时,父类会被加载
            • 使用类的静态成员时。
      • 非静态代码块:
        • 既可以调用静态的属性和方法,也可以调用非静态的属性方法
        • 随对象的创建而执行,创建一次执行一次。先于构造器执行
          • 可以将构造器相同的部分写到代码块内,减少不同构造器间的代码冗余。
  4. 创建对象时,类的调用顺序:
    • 静态代码块和静态属性。取决于书写顺序。
    • 普通代码块和普通属性。取决于书写顺序。
    • 构造器。
  5. 创建子类对象时,类的调用顺序:
    • 父类的静态代码块和静态属性。取决于书写顺序。
    • 子类的静态代码块和静态属性。取决于书写顺序。
    • 父类的普通代码块和普通属性。取决于书写顺序。
    • 父类的构造器。
    • 子类的普通代码块和普通属性。取决于书写顺序。
    • 子类的构造器。

2.1.10 内部类(InenerClass)

  1. 含义:定义在类内部的一个类,包含内部的类叫外部类

    • 外部引用时需要完整写出类名称(含包名)
    • 编译以后生成OuterClass$InnerClass.class字节码文件
  2. 分类:

    • 局部内部类:定义在方法、代码块、构造器中。
    • 成员内部类(static修饰和无修饰):定义在成员位置,类内可定义类的五大组成部分(属性、方法、构造器、代码块、内部类)
      • 可以被final修饰,表示此类不能被继承。
      • 可以被abstract修饰,表示不能被实例化
  3. 局部内部类:

    • 可以直接访问外部类的所有成员,包含私有的
      • 但是如果调用局部内部类所在方法中的局部变量时,要求该方法中的局部变量为final修饰,JDK8之前显式声明,JDK8之后可省略。
    • 不能添加权限修饰符(只能缺省,同局部变量的修饰符范围),可以被final修饰,修饰后不可被继承
      • 不能被static修饰,也不能包含static成员
    • 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过外部类.this.成员名的方式
    • 由于局部内部类定义在方法、代码块、构造器中,实际上是一个局部变量,只能在定义它的位置生效,即只能在这个位置实例化。
      • 外部类需要访问内部类的成员时,需要通过上述流程,在外部类的方法中,将内部类实例化。
      • 外部其他类不能访问。
  4. 成员内部类:

    • 可以直接访问外部类的所有成员,包含私有的
      • 使用static修饰时,只能调用外部类声明为static的结构
      • 非静态内部类,内部不能声明静态成员
      • 前面省略了外部类.this.,不能使用this.
    • 可以使用四种权限修饰符修饰
    • 实例化内部类:
      • 静态内部类:外部类.内部类 变量名 = new 外部类.内部类();
      • 非静态内部类:外部类.内部类 变量名 = 外部类的引用.new 内部类();
        • 外部类.内部类 变量名 = 外部类的引用.new 外部类.内部类();
    • 内部类的成员与外部类的成员重名时,内部类调用遵循就近原则,需要调用外部成员时,需要通过外部类.this.成员名的方式,调用内部类自身的属性,this.成员名
  5. 匿名内部类:

    • 语法:new 父类构造器(实参列表)|实现接口(){ //匿名内部类的类体部分 };
      • 可以基于接口实现、也可基于父类实现
      • 分号不能少
    • 可以是成员内部类、也可以是局部内部类。
    /**
     * @author INFITNITY Juejin:https://juejin.cn/user/2788017217999896
     * @date 2022年5月24日下午9:25:26
     */
    package com.infinity.interfacetest;
    
    public class Cellphone{
        // 1.1 匿名内部类是成员内部类
            double b = new Calculator() {
                    double c = 9.0;
                    @Override
                    public double work() {
                            // TODO Auto-generated method stub
                            return 0;
                    }
            }.c;
        // 1.2 匿名内部类是成员内部类
            double a  = new Calculator() {
                    @Override
                    public double work() {
                            // TODO Auto-generated method stub
                            return 0;
                    }
            }.work();
            public double  testWork(Calculator c) {
                    double result;
                    result = c.work();
            // 2.1 匿名内部类是局部内部类
                    double b = new Calculator() {
                            double c = 9.0;
                            @Override
                            public double work() {
                                    return 0;
                            }
                    }.c;
            // 2.2 匿名内部类是局部内部类
                    double a  = new Calculator() {
                            @Override
                            public double work() {
                                    // TODO Auto-generated method stub
                                    return 0;
                            }
                    }.work();
                    return result;
            }
            public static void main(String[] args) {
                    Cellphone cp = new Cellphone();
            // 2.3 匿名内部类是局部内部类
                    double a = cp.testWork(new Calculator() {
                            @Override
                            public double work() {
                                    return 1 + 1;
                            }
                    });
                    System.out.println(a);
            }
    }
    
    • 匿名内部类本身也是一个对象,因此它也可以调用内部类内部的方法,执行时遵循多态的规则(匿名内部类重写了就执行内部类里的方法)
    • 其他有关问题参见2.4.8第6部分。
  6. 外部类不能被static修饰

2.2 jvm内存分析

2.2.1 内存结构

  1. 图结构:

image.png

  1. 引用类型的变量只能存储两类值:
  • null
  • 包含变量类型的地址值。

2.2.2 内存分配

  1. 栈:一般指虚拟机栈,存储局部变量。
  2. 堆:存放对象(含对象的属性)、数组。
  3. 方法区:常量池(常量、字符串)、静态变量、即时编译后的代码。

2.2.3 成员变量与局部变量

  1. 变量的分类方式二:属性(成员变量)与局部变量
    1. 相同点:
    • 定义变量的格式相同:数据类型 变量名 = 变量值
    • 先声明,后使用
    • 都有其对应的作用域以及生命周期
    1. 不同点:
    • 在类中声明的位置的不同
      • 属性:直接定义在类的一对{}
      • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
    • 作用域范围不同:
      • 属性:可以被本类使用、也可以被其他类使用(通过对象调用)
      • 局部变量:只能在本类中对应的方法使用。
    • 关于权限修饰符的不同
      • 属性:可以在声明属性时,指明其权限,使用权限修饰符。
      • 局部变量:不可以使用权限修饰符。
    • 默认初始化值的情况不同:
      • 属性:类的属性,根据其类型,都有默认初始化值。
      • 局部变量:没有默认初始化值。在调用局部变量之前,一定要显式赋值。形参在调用时赋值即可。
    • 在内存中加载的位置不同:
      • 属性:加载到堆空间中(非static),因为对象加载到了堆空间
      • 局部变量:加载到栈空间
    • 声明周期不同:
      • 属性:生命周期长,伴随对象的创建而创建,伴随对象的销毁而销毁。
      • 局部变量:生命周期短,伴随代码块的执行而创建,伴随代码块的执行结束而销毁。
    1. 属性和局部变量可以重名,访问时遵循就近原则。

  1. 属性赋值过程:
    • 默认初始化
    • 显式初始化 / 代码块
      • 取决于类中书写的顺序
    • 构造器中初始化
    • 通过“对象.属性“或“对象.方法”的方式赋值

2.2.4 可变个数形参

  1. 语法:
    • JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
      • public static void test(int a, String[] books);
    • JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
      • public static void test(int a, String… books);
  2. 注意点:
    • 调用方法时,可传入的参数个数可以是0个,1个或多个
    • 可变参数的方法可与其他方法构成重载,调用时,优先调用匹配度高的
      • ...[]作为方法参数的同名方法视为同一个类里视为重复定义(编译报错),父子类内视为重写。
    • 可变参数方法的使用与方法参数部分使用数组是一致的,方法体内使用for循环
    • 方法的参数部分有可变形参时,需要放在形参声明的最后
    • 一个方法最多只能声明一个可变个数形参

2.3 封装、继承和多态

2.3.1 封装与隐藏

  1. 含义:将类中的属性(数据)私有化,并提供外部可访问的类进行属性操作。

  2. 好处:

    • 对(变量)数据进行验证,保证数据安全
    • 隐藏实现细节
    • 便于修改,增强代码可维护性
  3. 体现:

    • 将属性使用private修饰,进行私有化,并提供公共的set和get方法获取和设置此属性的值
      • 提供public修饰的setter,用于判断并修改属性,无返回值
      • 提供public修饰的getter,用于判断并读取属性,有返回值
    • 不对外暴露私有方法
    • 单例模式(将构造器私有化)
    • 如果不希望类在包外调用,可以将类设置为缺省的
  4. 权限修饰符:

    修饰符类内部同一个包不同包的子类(其父类是protected)同一个工程
    private
    缺省
    protected
    public
    • 类的权限修饰符只能用public或缺省
      • 使用public时,本工程下使用
        • 需要导入全类名
      • 使用缺省时,只可以被同一个包内部的类访问
    • 局部变量(方法内、方法形参等)的修饰符只能缺省。
    • protected修饰的属性、方法需要在不同包内访问时,一是需要父子继承关系,且protected修饰的内容是父类,二是需要在子类中导入包

2.3.2 继承(inheritance

  1. 作用:
    • 减少代码的冗余,提高代码的复用性,提高开发效率
    • 有利于功能扩展
    • 使类与类之间产生联系,为多态提供了前提
  2. 格式:class A extends B{}
    • A:子类、派生类、subclass
    • B:父类、超类、superclas
  3. 体现:
    • 子类A继承父类B以后,子类A中就自动获取了父类B中声明的所有的属性和方法。
      • 直接父类、间接父类、自身都有同名属性的情况下,访问时执行就近原则。
        • 方法按重写规定执行。
      • 父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。需要通过setter和getter调用属性。
        • 通过super.属性的方法也访问不到。
    • 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
    • 子类必须调用父类的构造器,完成父类的初始化。
      • 实例化过程中会一直调用至Object对象。
    • **注意点:**子类A继承自父类B后,如果父类B中声明了带参的构造器,则必须在子类A中声明A的构造函数,且必须在首行通过super(形参列表)调用B的构造函数。否则会编译报错。
      • 原因为如果子类A中不写构造器,其默认构造器为无参构造器,无参构造器中默认会调用B的无参构造器super()。由于父类B中显式地声明了构造器,导致默认的无参构造器不可用,从而会报错。而如果不在首行写super(形参列表)语句,则表明调用的是super(),同样会报错。
        • 解决方案一:父类B中显式声明无参构造器。
        • 解决方案二:子类A中调用父类B中指定的构造器。声明A的构造器方法体内使用super(形参列表)
  4. 规定:
    • 一个父类可以被多个子类继承。
    • 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
    • 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
    • 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。
    • 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
    • 所有的java类具有java.lang.Object类声明的功能。
  5. 子类对象的实例化过程
    • 从结果上来看:(继承性)
      • 子类继承父类以后,就获取了父类中声明的属性或方法。
      • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
    • 从过程上来看:
      • 通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。
      • 正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
    • 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
    • 18446744072569838818556192156.png

2.3.3 多态性(Polymorphism

  1. 理解:方法和对象的多种形态。实现代码的通用性。
  2. 体现:
    • 方法多态:
      • 重载多态(参数不同,方法体现出多重形态)。
      • 重写多态。
    • 对象多态:
      • 对象的编译类型和运行类型可以不一致,编译类型在定义时确定,不能变化。
      • 对象的运行类型可以是变化的,可以通过getClass()查看运行类型。
    • 常将父类对象作为方法形参,执行方法时根据具体实例化的父/子类对象进行传入。
  3. 使用:虚拟方法调用
    • 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
    • 编译期只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。
    • 不能调用子类中特有的成员。
      • 需要调用子类特有成员时,需要使用向下转型。
    • 总结:编译,看左边;运行,看右边。
  4. 使用前提:
    • 类的继承关系
    • 方法的重写
  5. 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)。
    • 使用向下转型声明的父类对象,调用父子类同名的属性时,实际调用的是父类的属性。
      • 属性不具有多态性。
      • 由于父类的属性通常设置为private,所以需要getter方法才能获取到。
  6. instance of操作符:
    • x instanceof A:检验x的运行类是否为类A或其子类的对象,返回值为boolean型。
      • 如果x是类B的对象,而B是A的子类,则仍返回true。

         AA aa = new BB();
        System.out.println(aa instanceof AA);
        System.out.println(aa instanceof BB);
        //设BB继承自AA
        
        • 强制转型后的类型判断:这里均返回true,因为运行的是BB类
    • x instanceof Object:总是返回true。
    • 检验的A类必须与x的类有父子类关系,否则编译会报错。
  7. 造型:对java类型的强制类型转换
    • 子类到父类可以自动类型转换——多态、向上转型
    • 父类到子类必须通过造型()实现——向下转型。
    • 无继承关系的引用类型造型非法,运行报错:ClassCastException
    • 通常首先使用instanceof操作符进行判断后进行造型操作,避免报错。
  8. java的动态绑定机制:
    • 当调用对象的方法时,该方法会和对象的内存地址/运行类型绑定。
    • 当调用对象的属性时,没有动态绑定机制,哪里声明,哪里使用。

2.4 关键字

2.4.1 this

  1. 作用:
    • 方法内部使用时,代表方法所属对象的引用
    • 构造器内部使用时,代表该构造器正在初始化的对象
  2. 使用范围:属性、方法、构造器
  3. 时机:方法内需要调用该方法的对象时,可以使用this。
    • 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
    • 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量
    • 使用this访问属性和方法时,如果在本类中未找到,会从父类中查找
  4. this调用构造器
    • 可以在类的构造器中使用this(形参列表)的方式,调用本类中重载的其他的构造器!
    • 明确:构造器中不能通过this(形参列表)的方式调用自身构造器
    • 如果一个类中声明了n个构造器,则最多有n-1个构造器中使用了this(形参列表)
    • this(形参列表)必须声明在类的构造器的首行!
    • 在类的一个构造器中,最多只能声明一个this(形参列表)
  5. 有参的构造器如果没有显式地调用this(),则不会调用无参的构造器。因为如果没写this(),也没写super(0,默认调用的是super()。

2.4.2 package

  1. 作用:package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
  2. 格式:package 顶层包名.子包名
    • 一般:com.公司名.项目名.业务模块名
  3. 使用规范:
    • 包对应于文件系统的目录,package语句中,用.来指明包(目录)的层次
    • 包通常用小写单词标识。通常使用所在公司域名的倒置
  4. 作用:
    • 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
    • 包可以包含类和子包,划分项目层次,便于管理
    • 解决类命名冲突的问题
    • 控制访问权限
  5. 注意点:
    • 同一个包下,不能命名同名的接口、类。
    • 不同的包下,可以命名同名的接口、类。
  6. 常见包:
    • java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和 Thread,提供常用功能
    • java.net----包含执行与网络相关的操作的类和接口。
    • java.io ----包含能提供多种输入/输出功能的类。
    • java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
    • java.text----包含了一些java格式化相关的类
    • java.sql----包含了java进行JDBC数据库编程的相关类/接口
    • java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这 些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S

2.4.3 import

  1. 作用:为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。
  2. 语法格式:import 包名.类名;
  3. 使用细节:
    • 在源文件中使用import显式的导入指定包下的类或接口
    • 声明在包的声明和类的声明之间。
    • 如果需要导入多个类或接口,那么就并列显式多个import语句即可
    • 可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
    • 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
    • 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的 是哪个类。
    • 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
    • import static组合的使用:调用指定类或接口下的静态的属性或方法

2.4.4 super

  1. 用途:子类调用父类的属性、方法、构造器
    • 解决子父子类属性、方法冲突的问题。
    • 在合适的位置调用父类的属性、方法、构造器。
  2. 操作对象:属性、方法、构造器
  3. 调用属性和方法:
    • 子类的方法和构造器中,使用父类的属性和方法时,默认使用了super.的方式调用属性、方法。编程中习惯不写super.
      • super不能调用父类的私有属性。
    • 当子类和父类中定义了同名的属性时,使用super.属性调用的是父类的属性;如果属性没有重名,使用super.属性this.属性作用一样。
    • 在子类中重写的方法中调用父类被重写的方法时,必须使用super.方法的方式。
      • super不能调用父类的私有方法。
    • super调用父类时,不局限于直接父类,有多个上级类有重名方法、属性时,遵循就近原则。
  4. 调用构造器:
    • 可以在子类的构造器中,显式地使用super(形参列表)的方式调用父类的构造器。
    • super(形参列表)必须声明在首行。
    • 由于this(形参列表)也必须出现在首行,所以一个构造器中this(形参列表)super(形参列表)只能二选一,不能同时出现。
    • 在构造器的首行,没有显式的声明**this(形参列表)****super(形参列表)**,则默认调用的是父类中空参的构造器:**super()**
    • 在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表),调用父类中的构造器。
      • 因为所有的对象都是继承来的,都必然具有父类的特征。
  5. 默认的无参构造器默认调用的super()
    • 无论哪个构造器创建子类对象,需要先保证初始化父类,当子类继承父类以后,继承了父类中的属性和方法,因此子类有必要知道父类如何对对象初始化。

2.4.5 static

  1. 设计思想:
    • 属性:在各个对象间共享,不因对象的不同而改变。
    • 方法:方法的调用与对象无关,不需要创建对象就可调用方法。
  2. 修饰范围:
    • 可修饰:属性、方法、代码块、内部类
      • 被static修饰的属性叫:静态属性、静态变量、类变量。
      • 没有被static修饰的属性叫非静态属性、实例变量。
      • 被static修饰的方法叫:静态方法。
    • 不能修饰:局部变量、形参、构造器、外部类
      • 不能修饰构造器的原因:static随着类的加载而加载,根据构造器的加载时机区分static和非static,先于构造器加载的为static,后于构造器加载的为非static
  3. 特点:
    • 属性和方法随着类的加载而加载,与是否创建对象无关。
      • 由于类只会在JVM的内存里加载一次,所有属性会只有一份。
      • 存在于方法区的静态域中。
      • 无论创建多少次对象,使用多少次类,都只有一份
    • 属性和方法优先于对象而存在。
    • 被static修饰的成员,被所有对象所共享。
    • 访问权限允许时,可不创建对象,直接被类调用。
  4. 注意点:
    • 静态属性和静态方法可以通过类名.静态属性类名、静态方法名()的方式直接调用,也可以通过对象名.静态属性对象名、静态方法名()的方式调用。
    • 静态方法中,只能调用静态的方法或属性;
      • 同类中的静态方法调用静态方法,可以直接使用方法名();类名、静态方法名(),不能使用this
      • 实例化当前类后,可以通过对象名.静态属性对象名、静态方法名()的方式调用非静态的属性和方法。
    • 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
    • 在静态的方法内,不能使用this关键字、super关键字。
      • 也不能通过this.super.的方式调用静态属性、静态方法,只能通过类名.的方式调用。
    • static修饰的方法不能被重写,但可以被继承。
      • 不能被重写:父类、子类的同名、同参静态方法都会被加载。
      • 可以被继承:子类可以直接通过类名.方法名()的方式调用。
    • static修饰内部类,类中的属性、方法、代码块可以是非静态的
    • static可与四种权限修饰符搭配使用,控制可见范围。
      • 设置为private时,只能被类名.静态属性类名.静态方法名()调用,不能被对象的引用调用。
  5. 使用时机:
    • 属性:
      • 属性需要被多个对象共享
      • 常量
    • 方法:
      • 操作静态属性
      • 工具类中的方法。如Math、Collection、Arrays

2.4.6 main

  1. main()说明:
    • main方法的权限修饰符必须是public
    • main方法的修饰符必须是static,因为main方法也是一个内中的方法,需要加载对象的时候执行,而不是创建main方法所在类的对象时候执行。
    • 由于main方法使用static修饰了,所有不能直接访问本类中的非static属性和方法,必须通过实例化创建本类对象的方式进行调用。
    • main方法可以作为与控制台交互的方式
      • 在命令行中执行(运行时,先要生成字节码文件)java 文件名 参数
        • 参数可以带引号,也可以不带,多个参数使用空格分开
      • 在eclipse工具中执行:
        • image.png
        • image.png
    1. main方法中访问直接访问本类中的静态方法和静态静态属性可以不需要类名.的方式调用。

2.4.7 final

  1. 用途:修饰类、变量(成员变量及局部变量)、方法、内部类
    • 变量分为局部变量和成员变量
  2. final修饰类:不能被继承,提高安全性、可读性。
    • 通常final修饰类后,内部的方法没必要在用final修饰
    • String类、StringBuffer类、System类
  3. final修饰方法:不能被子类重写
    • Object的getClass()
    • 不能修饰构造器
  4. final修饰属性(成员变量):表示常量,且必须在定义时初始化。
    • 赋值的位置可以是类中显式初始化、代码块、构造器内。
    • 搭配static使用时,初始化位置只能是显示初始化和静态代码块内
  5. final修饰局部变量:。
    • final修饰形参时,表示给常量赋值,方法体内只能使用该常量,不能修改该形参。
    • 修饰方法体内局部变量时,称为局部常量。
  6. final可以和static搭配使用,
    • static final:只能用于修饰属性、普通方法,修饰属性时,表示全局常量。

2.4.8 abstract(抽象类与抽象方法)

  1. 修饰结构:类、方法、内部类
    • 不能修饰属性、私有(private)方法、静态(static)方法、final方法、final类
    • 通常用于处理父类方法不缺定时的需求。
    • 先有抽象方法,后有抽象类。
  2. 不可修饰结构:属性、构造器等。
  3. 修饰类:
    • 此类不能实例化
    • 开发中一般提供抽象类的子类,该子类使用多态的方式实例化父类。否则匿名化。
  4. 修饰方法:
    • 抽象方法只有方法名,没有方法体,用;结束
    • 包含抽象方法的类,一定是抽象类;但抽象类不一定包含抽象方法。
    • 抽象类不能被private修饰(因为要对外可见,需要被重写)
    • 若子类重写了父类所有的抽象方法,则该子类可实例化;
    • 若子类没有重写或部分重写了父类的所有抽象方法,则该子类也是一个抽象类,需要使用abstract修饰。
  5. 抽象类的应用:模板方法的设计模式。(见2.6.2)
  6. 匿名类(不一定为抽象类)与匿名对象:共有四种格式
    • 非匿名类非匿名对象:Worker worker = new Worker();
    • 非匿名类匿名对象:new Worker();
    • 匿名类非匿名对象:Person p = new Person(){重写Person的虚拟方法};
      • 注意分号不能省略。
      • Person是一个虚拟类,按理不能实例化,实际上也正是通过方法体中的重写,将Person类变成了其他类,正常来说应该单独定义一个非虚拟具名的类继承自Person,再将方法体写入其中,但正由于没有这样做,不知道这个子类叫什么名字,所以就是匿名类。
        • 在匿名类非匿名对象的方法体中,如果声明了属性a,通过对象引用.p.a的方式访问属性a的值,如果Person类中没有定义过a,则报错,如果Person中定义了a,则返回的是Person类中的a的值。即,匿名类非匿名对象中声明的自有属性访问不到。
        • 如果要能访问到,可采用int p = new Person(){int a = 1;重写Person的虚拟方法}.a;
          • int p = new Person(){int a = 1;重写Person的虚拟方法}.a;改变返回值类型、.a.方法名()可以获取.方法名()的返回值。但是仅限于有返回值类型的方法(含自有方法)。
    • 匿名类匿名对象:new Person(){重写Person的虚拟方法}
    • 注意分号不能省略。

2.5 面向对象补充知识

2.5.1 JavaBean

  1. JavaBean是一种Java语言编写的可重用组件。
  2. 特征:
    • 类是公共的
    • 有一个无参的公共构造器
    • 有属性
      • 属性一般定义为private,有对应的getter、setter
  3. 可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。

2.5.2 UML类图

  • Accout:类名
  • +表示public,-表示private,#表示protected
  • 属性:前表示属性名,后表示属性类型
  • 方法:方法的()外面有表示有返回值,后面为返回值类型
  • 方法有下划线表示构造器

2.5.3 MVC设计模式

  1. 内容:常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与数据模型层。
  2. 优点:这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。
  3. 模型层(model):主要处理数据
    • 数据对象封装:model.bean/domain
    • 数据库操作类:model.dao
    • 数据库:model.db
  4. 控制层(controller):处理业务逻辑
    • 应用界面相关:controller.activity
    • 存放fragment:controller.fragment
    • 显示列表的适配器:controller.adapter
    • 服务相关的:controller.service
    • 抽取的基类:controller.base
  5. 视图层(view ):显示数据
    • 相关工具类:view.utils
    • 自定义view:view.ui

2.5.4 Object类的使用

  1. Object类是所有Java类的根父类。
  2. 如果在类的声明中未使用extends关键字指明其父类,则默认直接父类为java.lang.Object类。
  3. Object类中的功能(属性、方法)就具有通用性。
    • 属性:无
    • 方法:
      • equals()
        • 只能比较引用数据类型,作用与==相同,比较是否指向同一对象。
        • File、String、Date及包装类由于重写了equals(),所以比较的时内容是否相同。
        • 重写equals()原则:
          • 对称性:如果x.equals(y)返回是true,那么y.equals(x)也应该返回是true。
          • 自反性:x.equals(x)必须返回是true。
          • 传递性:如果x.equals(y)返回是true,而且y.equals(z)返回是true,那么z.equals(x)也应该返回是true。
          • 一致性:如果x.equals(y)返回是true,只要x和y内容一直不变,不管重复x.equals(y)多少次,返回都是true。
          • 任何情况下,x.equals(null),永远返回是false,null。equals(x)会空指针异常。
          • x.equals(和x不同类型的对象)永远返回是false。
      • toString()
        • 返回值为String,返回类名和它的内存地址——全类名+@+哈希值的十六进制。
          • 全类名:包名+类名
        • File、String、Date及包装类由于重写了toString(),返回"实体内容"信息。
        • 使用String类型的数据与+进行连接操作时,自动调用toString()
      • getClass():获取当前对象所处的类
      • hashCode():返回该对象的哈希值,用于提高哈希表的性能。
        • 两个引用指向同一对象,哈希码值一定一样。
        • 两个引用指向不同对象,哈希码值一般不一样,(极少数情况一样)。
        • 哈希值主要根据地址值生成,但与地址值不同。
      • clone()
      • finalize()
        • 当对象被回收时,系统会自动调用该方法。
        • 当某个对象没有任何引用,则jvm认为该对象是一个垃圾对象。在销毁该对象前,会先调用该方法。
        • 可以通过System.gc()主动出发垃圾回收机制。
      • wait()
      • notify()
      • notifyAll()
  4. Object类只声明了一个空参的构造器
  5. final、finally、finalize的区别?

2.5.5 包装类

  1. java定义了8种数据类型对应的引用类型(包装类、封装类),使得可以调用类中的方法。
  2. 分类:

image.png

  1. 基本数据类型、包装类、String类型的相互转换:
    1. 基本数据类型转换为包装类:
    • 装箱:基本数据类型包装成包装类的实例。
    • 使用包装类的构造器:new Integer()
      • 可传入基本数据类型。
      • 可传入字符串。
    • 使用包装类的方法:Integer.valueOf();
    1. 包装类转换为基本数据类型:
    • 拆箱:获得包装类对象中包装的基本类型变量。
    • 调用包装类的.intValue()方法。
    1. 基本数据类型转换为字符串:
    • 调用字符串重载的valueOf()方法。
    • 使用+拼接""空字符串。
    1. 字符串转换为基本数据类型:
    • 使用包装类的构造器:new Integer(),传入字符串。
    • 使用包装类的parseInteger(String s)静态方法。
    1. 字符串转换为包装类:
    • 调用包装类的构造器:new Integer()
    1. 包装类转换为字符串:
    • 调用包装类的tostring()方法。

image.png

  1. JDK1.5之后支持自动装箱、自动拆箱,但类型必须匹配
    • Integer a = 10; 自动封箱
    • int b = a; 自动拆箱
  2. 基本数据类型和包装类型使用功能==号比较时,比较的是数值值。

2.5.6 JUnit单元测试

  1. 步骤:
    • 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
    • 创建Java类,进行单元测试。
    • 简便方式:在测试方法上一行写上**@Test**,然后Ctrl+1修复。
    1. 此时的Java类要求:① 此类是public的  ②此类提供公共的无参的构造器
    2. 此类中声明单元测试方法:方法的权限是public,没有返回值,没有形参。
    3. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
  2. 声明好单元测试方法以后,就可以在方法体内测试相关的代码。
  3. 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
  4. 说明:
    • 如果执行结果没有任何异常:绿条
    • 如果执行结果出现异常:红条

2.6 设计模式

  1. 概念:开发过程中经过大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

  2. 分类:

    • 创建型(5种):
    • 结构型(7种):
    • 行为型(11种):

2.6.1 单例模式(Singleton

  1. 定义:采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

  2. 实现原理:

    • 构造器私有化,防止new
    • 类的内部创建静态实例对象
    • 向外暴露一个静态的公共方法。
  3. 实现方式:

    • 饿汉式:
    class Singleton {
        // 1.私有化构造器
        private Singleton() {}
        // 2.内部提供一个当前类的实例
        // 4.此实例也必须静态化
        private static Singleton single = new Singleton();
        // 3.提供公共的静态的方法,返回当前类的对象
        public static Singleton getInstance() {
            return single; 
        }
    }
    
    • 懒汉式:
    class Singleton {
        // 1.私有化构造器
        private Singleton() {}
        // 2.内部提供一个当前类的实例
        // 4.此实例也必须静态化
        private static Singleton single;
        // 3.提供公共的静态的方法,返回当前类的对象
        public static Singleton getInstance() {
            if(single == null) {
                single = new Singleton();
            }
            return single; 
        }
    }
    
  4. 优缺点:

    • 饿汉式:
      • 优点:线程安全。
      • 缺点:对象加载时间过长。全生命周期。
    • 懒汉式:
      • 优点:延迟对象的创建。
      • 缺点线程不安全。
  5. 应用场景:

    • 网站计数器:
    • 应用程序的日志程序:
    • 数据库的连接池
    • 读取配置文件的类:
    • Application:
      • windows系统的任务管理器
      • windows系统的回收站

2.6.2 模板方法设计模式(TemplateMethod

  1. 体现:就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

    • 也是多态行的一种体现。
  2. 解决的问题:

    • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
    • 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。
  3. 应用:模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:

    • 数据库访问的封装
    • Junit单元测试
    • JavaWeb的Servlet中关于doGet/doPost方法调用
    • Hibernate中模板程序
    • Spring中JDBCTemlate、HibernateTemplate等
  4. 实现过程:

    • 定义抽象类
      • 抽象类中写明确定的流程,定义在一个方法(模板方法)内,模板方法调用各个流程
      • 将不确定的内容插入到方法中的合适位置调用
      • 将不确定的内容定义成抽象方法。
    • 子类继承抽象类,重写抽象方法,在方法中完成需要工作的代码。
    • 实例化子类,子类由于继承了父类的方法,通过子类对象的引用调用父类的模板方法的那个方法。

2.6.3 代理模式(Proxy)

  1. 体现:类A实现了接口,实际需要类A去调用(操作)接口时,表面上通过实例化类B去操作,而将A作为一个参数调用。
  2. 步骤:
    • 类A实现接口C
    • 类B实现接口C
    • 类B构造函数传入接口C
    • 在类B内定义检查、校验等方法,并在实现的接口C的方法内调用
    • 创建C 变量名 = new B(new A())
    • 变量名.C的虚拟方法
  3. 应用场景
    • 安全代理
    • 远程代理
    • 延迟加载
  4. 分类:
    • 静态代理(上述描述)
    • 动态代理(JDK自带的静态代理,需要反射实现)

2.6.4 工厂模式(略)

2.7 Interface(接口)

  1. 概述:接口是一组规则的集成,是抽象方法和常量值的集合。

  2. 修饰符:public、缺省。

    • public修饰的接口需要单独建一个文件
  3. 级别:接口是和类并列的一类结构

  4. 语法:

    interface 接口名{
        属性;
        抽象方法;
    }
    class 类名 extends 父类 implements 接口1, 接口2{
        类的自有属性;
        类的自有方法;
        必须实现的接口的抽象方法;
    }
    
    • 属性为全局常量,public static final修饰,不写时仍然为public static final,一般不写。
      • 可以省略任意多个修饰符。
      • 声明时必须初始化,因为为final修饰。
    • 接口中的抽象方法可以不用public abstract修饰,不写时仍然为public abstract,一般不写**(不写时注意不是缺省,override方法时注意权限修饰符范围,即实现类的重写方法权限修饰符必须为public,重写方法不能写abstract)**。
      • 可以省略任意多个修饰符。
    • 不能new
    • 接口名有没有abstract修饰不影响
    • JDK7之前:只能定义全局常量和抽象方法:没有方法体
    • JDK8及之后:还可以定义静态方法、默认方法:有方法体
      • 静态方法:static修饰
        • 可以通过接口直接调用并执行方法体。
      • 默认方法:default修饰
        • 可以通过类对象来调用
  5. 注意点:

    • 接口不能被实例化(不能声明构造器),匿名接口的方式可以new
    • 普通类实现接口,必须实现接口中的所有方法(重写接口中的所有抽象方法)
    • 抽象类实现接口,可以不用实现(重写)接口中的方法
    • 接口可以相互继承,子类接口含有了直接父类、间接父类的所有属性和方法,其实现接口的类必须实现所有方法
    • 接口中属性的访问形式:接口名.属性名或实现接口的类名.属性名对象名.属性名(同虚拟类访问静态属性)
      • 类名.属性名对象名.属性名的方式不能和继承的父类中的属性名冲突,否则会报错,原因为不明确
        • 继承+实现情况下属性名重复会报错
        • 实现+实现情况下属性名重复会报错
  6. 接口也具有多态性

    • 接口名 变量名 = new 实现接口的类名()
  7. 类优先原则:

    • class C extends A implements B中,如果A、B、C中均定义了同名的属性,实例化C后访问该属性时,返回C中的,如果C中没有,则会报错。
      • 如果A中实现了B中的抽象方法,而C中什么也没写,这时根据继承原则,默认C中有了A的方法,从而重写了B中的方法。
    • 两个接口同时定义了同名同参的默认方法:
      • 如果返回值不一致(权限修饰符均一致), 一个类都实现了接口时,会发生接口冲突(报错),无法通过重写两个同名同参不同返回值类型的方法,因为两个接口不知道谁是它的重写。
      • 如果返回值一致,则一个实现类可只写一个重写方法,表示对两个接口方法的重写。
    • 一个接口定义了默认方法,一个父类定义了同名同参数的非抽象方法,不会发生冲突,且类优先,接口中定义的同名同参方法会被忽略。
      • 调用指定接口的方法:接口名.super.默认方法名
  8. 简单总结

    内部类属性局部变量方法构造器代码块接口
    public
    protected
    缺省
    private
    • public修饰类和接口时,必须单独设置一个文件。 | | 类 | 内部类 | 属性 | 局部变量 | 方法 | 构造器 | 代码块 | 接口 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | static | 否 | 是 | 是 | 否 | 是 | 否 | 是 | 否 | | final | 是 | 是 | 是 | 是 | 是 | 否 | 否 | 否 | | abstract | 是 | 是 | 否 | 否 | 非private的方法 | 否 | 否 | 是 |

    • static、final、abstract要写在语句的开头,但与权限修饰符的先后顺序没关系。

    • final和abstract修饰符永远二选一(类、内部类、方法)。

    • final和static永远可搭配(内部类、属性、方法)

    • static和abstract可同时修饰内部类,不可同时修饰方法。

2.8 异常

  1. 概念:程序运行过程中发生的不正常情况。
  2. 异常的体系结构:
    • Error:Java虚拟机无法解决的严重问题。一般不做处理。
      • StackOverflowError:栈溢出
      • OutOfMemoryError(OOM):堆溢出
    • Exception:
      • 编译时异常:
      • 运行时异常:
java.lang.Throwable
  		|-----java.lang.Error:一般不编写针对性的代码进行处理。
 		|-----java.lang.Exception:可以进行异常的处理
  			|------编译时异常(checked)
 					|-----IOException
  						|-----FileNotFoundException
  					|-----ClassNotFoundException
  			|------运行时异常(unchecked,RuntimeException)
  					|-----NullPointerException:空指针异常
  					|-----ArrayIndexOutOfBoundsException:数组下标越界
 					|-----ClassCastException:类型不匹配
					|-----NumberFormatException:数字格式错误
 					|-----InputMismatchException:输入不匹配
					|-----ArithmeticException:运算错误
  1. 异常处理机制——抓抛模型:

    • "抛":程序执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并会将此对象抛出给调用它的方法,直到main方法,如果main方法没能处理,则程序运行终止,发生异常位置后面的代码也不会执行。
      • 异常产生有两种形式:一是系统自动产生,二是手动生成并抛出(throw)
    • "抓":即异常的处理方式:
      • try-catch-finally
      • throws
  2. try-catch-finally:try不能单独使用

    try{
    ...... //可能产生异常的代码
    }
    catch( ExceptionName1 e ){
    ...... //当产生ExceptionName1型异常时的处置措施
    }
    catch( ExceptionName2 e ){
    ...... //当产生ExceptionName2型异常时的处置措施
    }
    [ finally{
    ...... //无论是否发生异常,都无条件执行的语句
    } ]
    
    • IDE快捷键: 选中->ctrl + alt + t
    • 执行流程:
      • 如果异常没有发生,则顺序执行try的代码块,不会进入到catch。
      • 如果异常发生了,则异常发生后try块中的代码不会执行,直接进入到catch块。
      • 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用finally
        • 即使try、catch语句里有return(正常情况return结束方法),执行完之后仍然会执行finally中的语句,再返回结束方法。但当finally中有return语句,则不再执行try或catch的return。
      • 可以使用多个catch分别捕获不同的异常,要求子类异常写在前面,父类异常写在后面
        • try中有多个异常,且父类一致(如Exception),如果catch只匹配父类,则只捕获第一个异常,try中后面的代码(所有代码)不会被执行
        • try中有多个异常,catch匹配多个异常,但只捕获第一个异常,try块中后面的代码(所有代码)不会被执行,异常也不会捕获到
      • catch和finally是可选的。
        • try-finally:没有catch语句,相当于捕获到了异常但没有处置,会先输出finally中的语句,再报异常,程序也会中止。
        • try-catch-finally:先报异常,再输出finally语句,程序不会中止
      • 常用的异常对象处理的方式:
        • getMessage() :打印异常信息
        • printStackTrace(): 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
      • 在try结构中声明的变量,出了try结构以后,就不能再被调用
      • try-catch-finally结构可以嵌套
  3. throws + 异常类型

    • "throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。
      • 抛出多个异常使用,分隔
    • 一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。不满足则相当于异常没有处理。异常代码后续的代码,就不再执行!
    • throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉,方法调用者再抛出时,异常类型要不小于接收到的异常类型。
    • 运行异常的默认处理方式为throws,可不处理,编译异常没有默认处理方式,必须处理
    • 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
    • 在throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必throws
  4. try-catch-finally和throws的选择

    • 拥有继承关系的父子类,如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
    • 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。
  5. 手动抛出异常:

    • 在可能发生异常的位置生成异常类对象,然后通过throw语句实现抛出操作
    • 可以抛出的异常必须是Throwable或其子类的实例,否则会报错。
  6. 自定义异常类:

    • 继承于现有的异常结构:RuntimeException 、Exception
      • 继承exception时为编译异常
      • 一般使用RuntimeException,且需要方法再thows异常
    • 提供全局常量(static final修饰):serialVersionUID
    • 提供重载的构造器,传入String msg参数
    • 构造器内执行super(msg);

第3章 枚举和注解

3.1 枚举类型

3..1. 枚举的概念:

  1. enumeration、enum
  2. 一组常量类型的集合。
  3. 是一种特殊的类,只包含有限的特定的对象
  4. 特点:
    • 属性不允许被改动:属性使用private final修饰、且不设置setter
    • 为了能够初始化属性,使用构造器传参的方式,在构造器内赋值。
    • 在枚举类内创建实例。

3.1.2 枚举类型的实现

  1. 自定义类(JDK1.5之前):

    • 私有化属性,只配置getter,不配置seter:防止属性被修改
      • private修饰
    • 私有化构造器:防止new
      • private修饰
    • 在类内部,创建公开的枚举对象
      • 命名规范:属性名使用大写形式
      • 对象使用public static final修饰,进行底层优化
        • 三个可省略任意多个
  2. enum关键字(JDK1.5之后):

    enum Season{
        SPRING("春天", "温暖"), SUMMER("夏天", "炎热");
        private String name;
        private String desc;
        private Season(String name, String desc){
             this.name = name;
             this.desc = desc;
        }
        nam getter;
        desc getter;
    }
    
  3. 使用enum关键字的注意点:

    • 实例化的枚举类对象必须声明在枚举类的首航
    • 实例化的枚举类对象必须与构造器的参数对应
    • 枚举类前默认有final关键字,所以枚举类不能被继承
      • final的特性导致
      • 默认继承自java.lang.Enum类
    • 实例化的枚举对象前默认有public static final
    • 使用无参构造器实例化枚举类对象时,括号可以省略

3.1.3 常用方法

  1. enum声明的类会隐式继承Enum类,因此可以使用Enum中的相关方法:
    • toString():返回当前对象名,声明enum类时可重写,用于返回类中的属性内容
    • name():返回当前对象名,不能重写。
      • 有限使用toString()
    • ordinal():返回当前对象的位置号,从0开始
    • values():返回当前枚举类中的所有实例化对象,返回一个enum声明的枚举类型数组
    • valueOf():返回指定字符串的实例化对象,如果传入的字符串枚举类中没有,会报错
      • 传入的字符串名称要跟实例化对象名一致
    • compareTo():比较两个枚举常量的位置编号,返回常量值。
      • 调用的 - 参数的

3.1.4 enum实现接口

  1. 语法:enum 类名 implements 接口 1,接口 2{}

3.1.5 枚举配合switch-case

  1. switch()中传入一个枚举对象,case后是枚举对象的值

3.2 注解(Anotation)

3.2.1 概念

  1. 含义:又称为元数据(Meatdata),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息。
  2. 特点:和注释一样,不影响程序运行,但注解可以被编译或运行,相当于嵌入代码的补充信息。
  3. 功能:
  • javase中,注解主要用于标记过时功能、忽略警告等
  • Javaee中,可以用于配置应用程序的任何切面,代替旧版javaee中的繁冗代码和XML配置

3.2.2 常见类型及基本使用

  1. @Override:方法前使用,表示重写父类方法
  • 只能用在方法前。
  • 重写的方法前不写该注解不影响编译运行
  • 如果写了该注解,程序在编译阶段检查是否真正重写。
    • 非重写时会报错。
  1. @Deprecated:表示程序元素(类、方法等)已过时。
  • 可以用在类、方法(构造器)、变量(成员变量、局部变量)、包等
  • 用于新旧版本的兼容和过渡
  1. @SuppressWarnings():抑制编译器警告

    • 可以用在类、方法(构造器)、变量(成员变量、局部变量)、包等
      • 主要用在方法、类前
    • ():传入字符串值,表示抑制的警告类型
      • 多种类型时,可以传入数组字面量{}
    • 作用范围与书写位置有关

    @SuppressWarning 中的属性介绍以及属性说明

    • all,抑制所有警告
    • boxing,抑制与封装/拆装作业相关的警告
    • cast,抑制与强制转型作业相关的警告
    • dep-ann,抑制与淘汰注释相关的警告
    • deprecation,抑制与淘汰的相关警告
    • fallthrough,抑制与switch陈述式中遗漏break相关的警告
    • finally,抑制与未传回finally区块相关的警告
    • hiding,抑制与隐藏变数的区域变数相关的警告
    • incomplete-switch,抑制与switch陈述式(enum case)中遗漏项目相关的警告
    • javadoc,抑制与javadoc相关的警告
    • nls,抑制与非nls字串文字相关的警告
    • null,抑制与空值分析相关的警告
    • rawtypes,抑制与使用raw类型相关的警告
    • resource,抑制与使用Closeable类型的资源相关的警告
    • restriction,抑制与使用不建议或禁止参照相关的警告
    • serial,抑制与可序列化的类别遗漏serialVersionUID栏位相关的警告
    • static-access,抑制与静态存取不正确相关的警告
    • static-method,抑制与可能宣告为static的方法相关的警告
    • super,抑制与置换方法相关但不含super呼叫的警告
    • synthetic-access,抑制与内部类别的存取未最佳化相关的警告
    • sync-override,抑制因为置换同步方法而遗漏同步化的警告
    • unchecked,抑制与未检查的作业相关的警告
    • unqualified-field-access,抑制与栏位存取不合格相关的警告
    • unused,抑制与未用的程式码及停用的程式码相关的警告
  2. 源码中@interface xxx表示xxx是一个注解类。

  3. 元注解:修饰其他注解的注解

    • @Retention(RetentionPolicy.xxx):指定注解的作用时机
      • SOURCE:只在源码中显示,编译后丢弃这种注解策略
      • CLASS:在字节码文件中显示,运行时不会保留。是注解的默认值。
      • RUNTIME:运行时仍然保留,可以通过反射获取该注解
    • @Target(ElementType.xxx):指定注解用于修饰哪些元素。
      • 作用时机是RUNTIME
      • 只能用于修饰注解
    • @Documented:被修饰的注解会在生成文档注释时保留。
      • 作用时机时RUNTIME
    • @Inherited:被修饰的注解具有继承性,子类自动具有该注解
  4. 与文档相关的注解:

    • @author 标明开发该类模块的作者,多个作者之间使用,分割
    • @version 标明该类模块的版本
    • @see 参考转向,也就是相关主题
    • @since 从哪个版本开始增加的
    • @param 对方法中某参数的说明,如果没有参数就不能写
      • 标记方法
      • 可以并列多个
    • @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
      • 标记方法
      • 格式要求:@return 返回值类型 返回值说明
    • @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
      • 标记方法
      • 格式要求:@exception 异常类型 异常说明
      • 可以并列多个
  5. @WebServle()

  6. @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)

3.3.3 自定义注解