1. 关键字

A.理解: Java中事先定义的,含有特别意义的标识符

private 一种访问控制方式:私有模式
protected 一种访问控制方式:保护模式
public 一种访问控制方式:公有模式
abstract 表明类或者成员方法具有抽象属性
class
extends 表明一个类型是另一个类型的子类型,这里常见的类型有类和接口
final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变
implements 表明一个类实现了给定的接口
interface 接口
native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的
new 用来创建新实例对象
static 表明具有静态属性
strictfp 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE 754算术规范
synchronized 表明一段代码需要同步执行
transient 声明不用序列化的成员域
volatile 表明两个或者多个变量必须同步地发生变化
break 提前跳出一个块
continue 回到一个块的开始处
return 从成员方法中返回数据
do 用在do-while循环结构中
while 用在循环结构中
if 条件语句的引导词
else 用在条件语句中,表明当条件不成立时的分支
for 一种循环结构的引导词
instanceof 用来测试一个对象是否是指定类型的实例对象
switch 分支语句结构的引导词
case 用在switch语句之中,表示其中的一个分支
default 默认,例如,用在switch语句中,表明一个默认的分支
try 尝试一个可能抛出异常的程序块
catch 用在异常处理中,用来捕捉异常
throw 抛出一个异常
throws 声明在当前定义的成员方法中所有需要抛出的异常
import 表明要访问指定的类或包
package
boolean 基本数据类型之一,布尔类型
byte 基本数据类型之一,字节类型
char 基本数据类型之一,字符类型
double 基本数据类型之一,双精度浮点数类型
float 基本数据类型之一,单精度浮点数类型
int 基本数据类型之一,整数类型
long 基本数据类型之一,长整数类型
short 基本数据类型之一,短整数类型
null
true 正确
false 错误
super 表明当前对象的父类型的引用或者父类型的构造方法
this 指向当前实例对象的引用
void 声明当前成员方法没有返回值
goto 保留关键字,没有具体含义
const 保留关键字,没有具体含义

2. 保留字

A. 理解:Java中事先定义的以后的版本可能被使用的标识符

goto 和 const

3. 标识符

A. 理解:用来表示类名,变量名,方法名,类型名,数组名,文件名的有效字符序列称为标识符(通俗讲:自己起的名字)

组成:

  • 数字0-9
  • 字母A-Za-z
  • 下划线_
  • 美元符$

注意事项:

  • 不能以数字开头,阿里巴巴手册中规定不能以下划线或$开头
  • 命名遵守驼峰命名法
    • 大驼峰:类名
    • 小驼峰:方法名,变量名
  • Java中严格区分大小写
  • 结尾必须以分号结束
  • 写代码时注意缩进,等号前后加空格

4. 注释

A. 理解:用于解释说明程序的文字信息

B. 类别:

  • 单行注释://
  • 多行注释:/**/
  • 文档注释:/***/

5. 进制转换

A. 二进制

  • 由0,1组成。以0b开头
  • int num = 0b11011;

B. 八进制

  • 由0,1,...7组成。以0开头
  • int num = 01011;

C. 十六进制

  • 由0,1,...9,a,b,c,d,e,f(大小写均可)组成,以0x开头
  • int num = 0xac34b;

D. 十进制

  • 由0,1,...9组成,默认整数是十进制
  • int num = 110;

6. 变量

A. 理解:内存中的一块拥有名称可以存储数据的存储区域

  • 内存中的一个存储区域
  • 该区域有自己的名称(变量名)、类型(数据类型)、值
  • Java中每个变量必须先声明,再赋值,然后才能使用
  • 该区域的数据可以在同一类型范围内不断变化
  • Java 中的变量有四个基本属性:变量名,数据类型,存储单元和变量值

B. 变量的声明:数据类型 变量名 = 变量值

C. 变量必须初始化才能使用,初始化就是给一个初始值

7. 常量

A. 理解:在程序中固定不变的值,是不能改变的数据。

B. 字面常量:(值不会变化的量)

  • 整型常量(int)
  • 浮点数常量(float double) :2e3f 3.6d 0f 3.84d 5.022e+23f
  • 布尔常量(boolean):true false
  • 字符常量(char):'a' '1' '&' '\r' '\u0000'
  • 字符串常量(String):"HelloWorld" "123" "We come \n XXX"
  • null常量(null):null

C. 自定义常量:

8. 数据类型

A. 基本数据类型

类型 型别 字节 取值范围
byte 整型 1byte(8位) -27 ~ 27-1(-128~127,默认值0)1000 0000固定为-128
short 整型 2byte(16位) -215 ~ 215-1(-32768~32717,默认值0)1000 0000 0000 0000固定为-32768
int 整型 4byte(32位) -231 ~ 231-1(-2147483648~2147483647,默认值0)
long 整型 8byte(64位) -263 ~ 263-1(默认值0L)赋值时要以L结尾
float 浮点型 4byte(32位) +3.402823e+38 ~ 1.401298e-45(默认值0)赋值时要以F结尾
double 浮点型 8byte(64位) 1.797693e+308~ 4.9000000e-324(默认值0)以D结尾,java中浮点型默认为Double,所以不用加D
char 文本型 2byte(16位) 0 ~ 216-1(取值范围0~65535,默认值为空)
boolean 布尔型 1byte(8位) true/false

B. 引用数据类型

  • 字符串
  • 数组
  • 接口
  • Lambda

C. 注意:在数据做为参数传递的时候,基本数据类型是值传递,引用数据类型是引用传递(地址传递)

D. 数据类型转换

  • 默认转换(小转大,小的数据类型的值,赋给大的数据类型的变量)

    从低精度向高精度转换

    img
    img

    long->float的自动转型参考:https://blog.csdn.net/writing_happy/article/details/78880606

浮点数在内存中的存储形式参考:https://blog.csdn.net/u014470361/article/details/79820892

public class DataTypeDemo{
    public static void main(String [] args){
        System.out.println(1024); //这是一个整数,整数有byte、short、int、long四种,java中默认为int
        System.out.println(3.14); //这是一个浮点数,浮点数有float、double两种,java中默认为double
        long num1 = 1024L;   //加了L,为long类型
        System.out.println(num1);
        
        long num2 = 1024;	//未加L,默认为int类型,依然可以正常输出,中间发生了自动转换,int到long
        System.out.println(num2);
        
        double num3 = 2.5F;	//加了F,为float类型,发生自动转换,float到double
        System.out.println(num3);
        
        double num4 = 2.5;	//未加F,默认为double类型
        System.out.println(num4);
        
        float num5 = 30L;	//加了L,为long类型,依然可以正常输出,中间发生了自动转换,long到float
        System.out.println(num5);
        
        byte num6 = 25;
        int num7 = num6;
        System.out.println(num7); //可以正常输出,发生了自动类型的转换,byte到int
        
        byte num8 = 35;
        short num9 = num8;
        System.out.println(num9); //可以正常输出,发生了自动类型转换,byte到short
        
        short num10 = 100;
        int num11 = num10;
        System.out.println(num11); //可以正常输出,发生了自动类型转换,short到int
    }
}
  • 强制转换的格式:大数据类型 变量名称 = (大数据类型)(小数据类型)
  • 注意:强制转换的格式是在需要转型的数据前加上“( )”,然后在括号内加入需要转化的数据类型。数据经过转型运算后,精度会丢失
    • char不能转换为short和byte
public static void main(String[] args){
    int num = 127;
    byte by = 122;
    by = (byte)num;
}
  • byte/short/char 默认都可以发生数学运算,在运算的时候会被提升为int类型计算
public static void main(String [] args){
    char c = 'A'; 				// A的ASCII码为65
    System.out.println(c + 1); 	// 66
}
byte b1 = 20;
byte b2 = 22;
int b = b1 + b2;   	//byte被提升为int型并进行了数学运算,计算结果也是int

short s1 = 34;
short s2 = 32;
int s = s1 + s2;	//short被提升为int型并进行了数学运算,计算结果也是int
  • 布尔类型不能发生数据类型转换

在ASCII码表中,48表示字符0,65表示大写A,97表示小写a

JDK9新特性:“ 编译器常量优化 ”:当两个常量运算时,如果运算结果数值没有超过左侧变量类型的范围,则正常输出,右侧一旦出现变量,就无法参与这项优化

9. 字符集和字符编码

A. 为了实现计算机认识字母、符号、图形

B. 字符集就是字母数字指定的二进制编码

C. 字符集制成的表示字符编码表

  • ASCII

  • GBK

  • GB2312

  • w3c出面制成统一码表:unicode码表

    • 按照进制制成新的码表

    • utf-8

    • utf-16

    • utf-32

10. 运算符

A. 算数运算符

  • /
  • %
  • ++
    • 前++:先运算再赋值
    • 后++:先赋值再运算
  • --
    • 前--:先运算再赋值
    • 后--:先赋值再运算
public static void main(String [] args){
    int num = 999;
    int num1 = 300;
    System.out.println(num+num1);
    System.out.println(num-num1);
    System.out.println(num*num1);
    System.out.println(num/num1);
    System.out.println(num%num1);
    System.out.println(num1%num);//小的数取模于大的数,得到的是本身(小的数)
    int num2 = 3;
    num2++;
    num2--;
}

B. 赋值运算符

  • =
  • +=
  • -=
  • *=
  • /=
  • %=

补充:b+=4不等于b=b+4,扩展的赋值运算符都隐含了强制类型转换 ,等价于b=(b的数据类型)b+4;

instanceof:判断是否是类的对象 "String" instanceof String

public static void main(String [] args){
    String name = "zhiyuan";
    int sum = 666;
    sum = sum + 20;
    sum += 20; //上边的简写形式
}

C. 比较(关系)运算符

  • >

  • <

  • >=

  • <=

  • !=

  • ==

    • 基本数据类型,比较值是否相等
    • 引用数据类型,比较地址值是否相等
    • 和equals的区别
  • 运算结果为boolean值

    public static void main(String [] args){
        System.out.println(100 > 20);
        System.out.println(100 < 200);
        System.out.println(100 >= 200);
        System.out.println(100 <= 200);
        System.out.println(100 != 200);
        System.out.println(100 == 200);
    }
    

D. 逻辑运算符(左右两侧均为布尔表达式)

  • &:逻辑与

  • |:逻辑或

  • &&:左右两侧都成立,结果成立(全真为真,有假为假)(短路与)

  • || :左右两侧有一个成立,结果成立(有真为真,全假为假),前面成立,后面无需看结果(短路或)

  • !:取反

public static void main(String [] args){
    System.out.println(100 > 20 && 200 > 300);
    System.out.println(100 < 200 && 200 < 100);
    
    System.out.println(100 > 20 || 200 > 300);
    System.out.println(100 < 200 || 200 < 100);
    
    System.out.println(!(100 > 20));
}

E. 位运算符(位运算是最快的)

  • &:(二进制补码按位与)

  • | :(二进制补码按位或)

  • ^:(二进制补码按位异或)一个数对另外一个数按位异或两遍,还是它本身

  • <<:(二进制按位左移)次方运算直接左移(次方-1)位

    12 << 2 12*2^2 = 48

  • >>:(二进制按位右移)

    12 >> 2 12/2^2 = 3

  • ~

  • >>>:无符号右移规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义

public static void main(String [] args){
    System.out.println(8 & 3);
    System.out.println(8 | 3);
    System.out.println(8 ^ 3);
    System.out.println(8 << 3);
    System.out.println(8 >> 3);
}

F. 三元(三目)运算符

  • 表达式 ? 成立结果 : 不成立结果
public static void main(String [] args){
    System.out.println(8 > 3 ? true : false);   
}    

G. instanceof运算符

  • java中,instanceof运算符的前一个操作符是一个引用变量,后一个操作数通常是一个类(可以是接口),用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回true,否则返回false。

11. 转义字符

转义字符 描述
\r 回车符
\n 换行符
\t 制表符
\b 退格符
\' 单引号字符
\'' 双引号字符
\\ 反斜杠字符

12. 流程控制语句

A. 顺序语句:代码执行的时候是从上到下,从左到右的

B. 分支语句:

  • if语句

    public class ControlDemo {
        public static void main(String[] args) {
            int num = 10;
            if(num > 5){
                System.out.println(num + " > 5");
            }
        }
    }
    
  • if-else语句

    public class ControlDemo {
        public static void main(String[] args) {
            int num = 3;
            if(num > 5){
                System.out.println(num + " > 5");
            }else{
                System.out.println(num + " < 5");
            }
        }
    }
    
  • if-else-if-else语句

// if else if else语句
public class ControlDemo {
    public static void main(String[] args) {
        int age = 20;
        double height = 120.0;
        if(age > 10){
            System.out.println(age + " > 10");
        }else if(height < 120){
            System.out.println(height + " < 120");
        }else{
            System.out.println(age + " < 10" + height + " > 120");
        }
    }
}        
import java.util.Scanner;

public class ScannerDemo {
    public static void main(String[] args) {
        // 根据用户输入的成绩,判断是哪一个等级
        Scanner sc = new Scanner(System.in);//System.in系统的标准输入
        // String str = sc.next();
        //String str = sc.nextLine();
        int score = sc.nextInt();
        System.out.println(score);
        if (score > 90) {
            System.out.println("优");
        }else if(score > 80){
            System.out.println("甲");
        }else if(score > 70){
            System.out.println("乙");
        }else if(score > 60){
            System.out.println("丙");
        }else {
            System.out.println("丁");
        }
    }
}
  • switch语句(break不可省略)
import java.util.Scanner;

public class ControlDemo {
    public static void main(String[] args) {        
	//switch语句(int、char、byte、short、String【JDK1.7新增】)
        Scanner sc = new Scanner(System.in);
        int i = sc.nextInt();
        switch(i){
            case 1:
                System.out.println("星期一");break;
            case 2:
                System.out.println("星期二");break;
            case 3:
                System.out.println("星期三");break;
            case 4:
                System.out.println("星期四");break;
            case 5:
                System.out.println("星期五");break;
            case 6:
                System.out.println("星期六");break;
            case 7:
                System.out.println("星期日");break;
            default:
                System.out.println("没有");break;
        }
    }
}

C. 循环语句:

注意:如果出现死循环,程序不能及时停止,会出现值栈溢出Stack Overflow(内存溢出)

所以在使用while循环的时候,我们要在适当的时侯终止循环,结合if else使用break终止循环,直接退出循环最外层,不再执行循环

break是直接终止循环

continue是终止本次循环,进行下次循环,直到循环执行结束

  • while(布尔表达式){循环体}
public class WhileDemo {
    public static void main(String[] args) {
        //while循环 控制循环条件的变量值,固定循环次数
        int flag = 10;
        while(flag > 0){
            System.out.println("循环正常执行");
            flag --;
        }
    }
}    
  • do {循环体} while(布尔表达式)
public class DoWhileDemo {
    public static void main(String[] args) {
        public class DoWhileDemo {
    public static void main(String[] args) {
        //do while 循环 即使条件不成立,也会执行一次
        int num = 10;
        do{
            System.out.println("循环正常执行" + num);
            num --;
        }while(num > 0);
    }
}
  • for(初始化语句;循环条件;迭代体){循环体}
public class ForDemo {
    public static void main(String[] args) {
        //for循环 循环次数是初始变量值和循环条件计算得出的
        //i++和++i在for循环中没有区别
        for (int i = 0;i <= 10;i++){
            System.out.println(i);
        }
    }
} 
  • 死循环
for(;;){}

while(true){}

do{}while(true);

print 和 println的区别

print:直接输出 println:换行输出

D. 循环嵌套:

我们可以使用循环去做很多事情

打印九九乘法表

public class CombineForDemo {
  public static void main(String[] args) {
    //九九乘法表
    //外层循环控制行
      for(int i = 1;i <= 9;i++){
         //内层循环控制列
         for(int k = 1;k <= i;k++){
           System.out.print(k+"×"+i+"="+i*k+"\t");
         }
         System.out.println("");
      }
  }
}
1×1=1	
1×2=2	2×2=4	
1×3=3	2×3=6	3×3=9	
1×4=4	2×4=8	3×4=12	4×4=16	
1×5=5	2×5=10	3×5=15	4×5=20	5×5=25	
1×6=6	2×6=12	3×6=18	4×6=24	5×6=30	6×6=36	
1×7=7	2×7=14	3×7=21	4×7=28	5×7=35	6×7=42	7×7=49	
1×8=8	2×8=16	3×8=24	4×8=32	5×8=40	6×8=48	7×8=56	8×8=64	
1×9=9	2×9=18	3×9=27	4×9=36	5×9=45	6×9=54	7×9=63	8×9=72	9×9=81

输出菱形

public class DiamondDemo {
  public static void main(String[] args) {
    //菱形
    for (int i = 1;i <= 5;i++){
        for (int k = 5 - i;k >= 0;k--){
             System.out.print(" ");
         }
        for (int j = 1;j <= 2*i-1;j++){
              System.out.print("*");
          }
          System.out.println("");
      }
      for (int i = 4;i >= 1;i--){
          for (int k = 1; k <= 6-i;k++){
              System.out.print(" ");
          }
          for (int j = 1;j <= 2*i-1;j++){
              System.out.print("*");
          }
          System.out.println("");
      }
  }
}

输出等腰三角形

public class IsoscelesTriangleDemo {
  public static void main(String[] args) {
    //等腰三角形
    int num = 6;
    for(int i = 0;i <= num;i++){
       //打印空格占位
       //前半边占位的空格和行数的关系,是总的行数-1
        for(int k = num - i;k >= 0;k--){
              System.out.print(" ");
          }
          //打印后半部分星号
          for(int j = 1;j <= 2*i-1;j++){
              System.out.print("*");
          }
          //负责换行
          System.out.println("");
      }
  }
}

求1000以内的水仙花数

public class DaffodilsNumDemo {
  public static void main(String[] args) {
  //水仙花数
    for(int i = 100;i < 1000;i++){
        int onesplace = i % 10; //个位
         int hundredsplace = i / 100; //百位
         int tenplace = i / 10 % 10; //十位
         int newvalue = onesplace*onesplace*onesplace + tenplace*tenplace*tenplace + hundredsplace*hundredsplace*hundredsplace;
         if(newvalue == i){
              System.out.println("水仙花数:" + i);
          }
      }
  }
}

求1000以内的素数

public class PrimeNumberDemo {
  public static void main(String[] args) {
    for(int i = 2;i < 1000;i++){
       for(int j = 2;j < i;j++){
          if(i % j == 0){
               break;
           }else{
                System.out.println(i+"为素数");
                  break;
              }
          }
      }
  }
}

求100以内的7的倍数

public class SevenTimesDemo {
  public static void main(String[] args) {
    for(int i = 0;i < 100;i++){
      if(i % 7 == 0){
           System.out.println(i+"是7的倍数");
         }
    }
  }
}

今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?

public class ChickenRabbitCageDemo {
  public static void main(String[] args) {
    int count = 35;
    int foot = 94;
    int chickencount;
    int rabbitcount;
    for(chickencount = 1;chickencount <= 35;chickencount++){
       	rabbitcount = count - chickencount;
         if (chickencount*2+rabbitcount*4 == 94) {
           System.out.println("鸡有"+chickencount+"只");
           System.out.println("兔有"+rabbitcount+"只");
           break;
         }
      }
  }
}

有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问一年后的兔子总数为多少?(斐波那契数列)

1 1 2 3 5 8 13 21

int month = 12;
int first = 1;
int second = 1;
int count = 0;
for(int i = 3;i <= month;i++){
	count = first + second;
	first = second;
	second = count;
	System.out.println(i+"月份生的兔子的总数是:"+count*2); //288
}

3 3 3 3 3

3 2 2 2 3

3 2 1 2 3

3 2 2 2 3

3 3 3 3 3

for(int i = 0;i < 7;i++){
  if(i==0 && i == 13){
   System.out.println("7");
  }
}

E. Debug调试工具

image-20201021161002815

F. 指定跳转的位置(java的标签)

结合循环的break使用,指定程序中断跳出到循环外的位置

  • break
wc:for(int i = 0;i < 4;i++){
    nc:for(int j = 0;j < 5;j++){
        if(j == 3){
            //break;
            //break nc;
            break wc;
        }
        System.out.println("*");
    }
    System.out.println();
}

//输出结果
***
  • continue
wc:for(int i = 0;i < 4;i++){
    nc:for(int j = 0;j < 5;j++){
        if(j == 3){
            //continue;
            //continue nc;
            continue wc;
        }
        System.out.println("*");
    }
    System.out.println();
}

//输出结果
************
  • return也可以结束循环,不过本质是结束方法

13. 数组

A. 数组只能保存一种类型的数据

B. 数组只要声明了,那么数组的长度在程序运行过程中就是固定不可变化的,即使移除数组的值

C. 数组的声明

  • 静态声明:在声明数组的时候,给数组将值添加进去,然后由系统计算数组的长度
int [] arr = new int[]{0,1,2,3,4,5,6,7,8,9};
  • 动态声明:在声明数组的时候,固定数组的长度,然后由系统添加数组中的初始值
int [] arr = new int[10];
  • 基本类型各自初始化值
    • int类型,默认初始值为0
    • long类型,默认初始值为0
    • short类型,默认初始值为0
    • byte类型,默认初始值为0
    • char类型,默认初始值为\u0000(unicode字符,不可见)
    • float类型,默认初始值为0.0
    • double类型,默认初始值0.0
    • boolean类型,默认值false
    • 引用数据类型,默认初始值为null(空常量)
  • 数组是引用数据类型

D. 一维数组

  • 数组的声明
    • 静态声明:数据类型 [] 变量名称 = new 数据类型[] {值1,值2,值3}
    • 动态声明:数据类型 [] 变量名称 = new 数据类型[数组的长度];
    • 数组声明的简写形式:数据类型 [] 变量名称 = {值1,值2,值3}
  • length表示数组的长度

E. 数组的遍历

public class ArrayListDemo {
    public static void main(String[] args) { 
        int [] arr = new int[]{10,12,13,14,15,16};
        System.out.println(arr[0]);
        for (int i = 0; i < arr.length; i++) {
            System.out.println("arr[" + i + "]=" + arr[i]);
        }
        //增强for循环(foreach)
        //for(数组中元素的数据类型 i代表的是下表位的元素的值 :数组或者集合)
        for (int i : arr) {
            System.out.println(i);
        }
    }
}
//运行结果
10
arr[0]=10
arr[1]=12
arr[2]=13
arr[3]=14
arr[4]=15
arr[5]=16
10
12
13
14
15
16

练习:int [] arr = new int [] {21,3,41,45,67,2,78,6};中的最大值与最小值

public static void main(String [] args){
   for (int i = 0; i < arr.length ; i++) {
       for(int k = i+1; j <= arr.length;j++) {
           if(arr[i] > arr[j]){
               int temp = arr[i];
               arr[i] = arr[j];
               arr[j] = temp;
           }
       }
   }
   for(int k : arr){
       System.out.println(i);
   }
}
最大值为:78
最小值为:2
public class ArrayMaxMinDemo {
 public static void main(String[] args) {
     int [] arr = new int []{21,3,41,45,67,2,78,6};
     //从数组中拿任意一个数
     //进行数组中其他元素比较,按照比较的结果进行换位
     int num = arr[0];
     for (int i = 0; i < arr.length; i++) {
         if(num > arr[0]){
             num = arr[0];
         }
     }
     System.out.println("最小值为:"+num);
 }
}

F. 二维数组

  • 一维数组的下标位依然是一个一维数组,这种数组就是二维数组
  • 二维数组的声明
    • 数据类型 [] [] 变量名称 = new int [] []{};
    • 数据类型 [] [] 变量名称 = new int [3] [4];
public class SecondArrayDemo {
 public static void main(String[] args) {
     int [][] arr = new int[][]{{2,3,4},{5,6,7},{8,9,10}};
     int [][] arr1 = new int[3][4];
     //获取值
//        System.out.println(arr[0][0]);
//        System.out.println(arr[0][1]);
//        System.out.println(arr[0][2]);
//
//        System.out.println(arr[1][0]);
//        System.out.println(arr[1][1]);
//        System.out.println(arr[1][2]);
//
//        System.out.println(arr[2][0]);
//        System.out.println(arr[2][1]);
//        System.out.println(arr[2][2]);

     arr1[0][0] = 11;
     arr1[0][1] = 12;
     arr1[0][2] = 13;
     arr1[0][3] = 14;

     arr1[1][0] = 24;
     arr1[1][1] = 23;
     arr1[1][2] = 22;
     arr1[1][3] = 21;

     arr1[2][0] = 51;
     arr1[2][1] = 52;
     arr1[2][2] = 53;
     arr1[2][3] = 54;

//        System.out.println(arr1[0][0]);
//        System.out.println(arr1[0][1]);
//        System.out.println(arr1[0][2]);
//        System.out.println(arr1[0][3]);
//
//        System.out.println(arr1[1][0]);
//        System.out.println(arr1[1][1]);
//        System.out.println(arr1[1][2]);
//        System.out.println(arr1[1][3]);
//
//        System.out.println(arr1[2][0]);
//        System.out.println(arr1[2][1]);
//        System.out.println(arr1[2][2]);
//        System.out.println(arr1[2][3]);


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

G. 数组的排序

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 快速排序
  • 希尔排序
  • 归并排序
  • 折中排序
  • 桶排序

选择排序

public class ArraySelectorSortDemo {
 public static void main(String[] args) {
     int [] arr = new int []{21,3,41,45,67,2,78,6};
     //选择排序
     for (int i = 0; i < arr.length-1; i++) {
         for (int j = i+1; j < arr.length; j++) {
             if(arr[i] > arr[j]){
                 int temp = arr[i];
                 arr[i] = arr[j];
                 arr[j] = temp;
             }
         }
     }
     for (int i : arr) {
         System.out.println(i);
     }

 }
}
img

冒泡排序

public class ArrayBubbleSortDemo {
    public static void main(String[] args) {
        //冒泡排序:数组间两两之间进行比较,按照排序规则进行换位
        int [] arr = new int []{21,3,41,45,67,2,78,6};
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if(arr[j] > arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

选择排序

插入排序

public static void main(String[] args) {
        int [] arr = new int [] {21,3,41,45,67,2,78,6};

        for(int i = 0;i < arr.length-1;i++){
            //当前准备比较的值
            int temp  = arr[i+1];
             int num = i;
           //当前位置的值和前面所有的值比较进行换位
           while(num >= 0 && temp < arr[num] ){
               arr[num + 1] = arr[num ];
                num --;
           }
           arr[num + 1] = temp;
        }
        for (int k : arr) {
            System.out.println(k);
        }
    }

public class ArrayInsertSortDemo {
    public static void main(String[] args) {
        int [] arr = new int []{21,3,41,45,67,2,78,6};
        int temp;
        int i;
        int j;
        for(i = 1;i < arr.length;i++){
            for (j = i;j > 0;j--){
                if(arr[j] < arr[j-1]){
                    swap(arr,j,j-1);
                }
            }
        }
        for (int k : arr) {
            System.out.println(k);
        }
    }
    public static void swap(int [] arr,int i,int j){
        int temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;
    }
}

img
img

快速排序

img

H. 元素的查找

  • 二分查找(折半查找)

    • 要求数组本身有序,不能对数组排序后使用二分查找,因为会改变元素本身位置
    public static int getIndex(int [] array,int value){
        int left = 0;
        int right = array.length - 1;
        while(left <= right){
            int mid = (left + right) / 2;
            if (array[mid] >= key) {
                right = mid - 1;
            }else {
                left = mid + 1;
            }
        }
        if(left < arrat.length-1 && array[left] == key){
            return left;
        }
        return -1;
    }
    
  • 基本查找(数组元素无序)

14. java的内存模型

java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同,比如有些c/c++程序可能在windows平台运行正常,而在linux平台却运行有问题。

img
  • JDK8之前的JVM内存布局
img
  • JDK8以后的JVM内存布局
图摘自《码出高效》

栈内存是先进后出

栈内存只能保存基本数据类型的变量和值

栈内存保存对象的变量,引用到堆内存中的地址值

只要使用了new关键字就会在堆内存中开辟空间,new使用一次开辟一次

  • 本地方法区
  • 栈内存:int num = 10;基本数据类型声明变量
  • 堆内存:new 引用数据类型 栈内存对象的变量引用堆内存的十六进制地址值

15. 方法

A. 理解:对一段代码的封装

访问修饰符 修饰符 返回值类型 方法名(形参列表){

​ 方法体;

}

public static void main(String [] args){

​ System.out.println("Hello");

}

方法的分类:

  • 返回值
    • 有返回值:
    • 无返回值:
  • 参数
    • 有参数
    • 无参数
  • 访问权限修饰符:
    • 作用:用来修饰成员变量和成员方法,从而实现对方法和变量的使用权限控制
    • 分类:
      • public:公开的
      • private:私有的
      • protected:受保护的

16. 变量作用域

定义变量的位置不同

  • 全局变量:就是类中的所有方法都可以使用,当前类当前类不在使用的时候 由GC(垃圾回收器)回收
    • 全部变量一般定义在类 开始,属性定义的位置
  • 局部变量:只能在当前的方法中使用 方法执行完毕 变量从内存中释放

17. 代码块

  • 在java中,用{}括起来的代码就被称为代码块
    • 局部代码块
    • 构造代码块
    • 静态代码块
    • 同步代码块

1. 局部代码块

  • 在方法中出现,限定变量的作用范围(生命周期),让变量尽早释放,提高内存使用率

  • public class CodeBlockDemo{
        public static void main(String [] args){
            //局部代码块
            {  
                int a = 5;
                System.out.println(a);
            }   
            //System.out.println(a);//无法使用a变量
        }    
    }
    

2. 构造代码块

  • 在类中方法外出现,多个构造方法中相同的代码存放到一起,每次调用构造方法都会执行,而且在构造方法执行时一同执行,即每次创建对象都执行

  • public class CodeBlockDemo{
        private int a;
        private String s;
        //构造代码块
        {
            System.out.println("构造代码块");
        }
        //构造方法
        public CodeBlockDemo(){
            System.out.println("无参构造方法");
        }
        public CodeBlockDemo(int a,String s){
            this.a = a;
            this.s = s;
        }
    }
    

3. 静态代码块

  • 在类中方法外出现,被static修饰,用来给类进行初始化,在类被加载时执行,只执行一次

  • public class CodeBlockDemo{
        private int a;
        private String s;
        //静态代码块
        static{
            System.out.println("静态代码块");
        }
        //构造方法
        public CodeBlockDemo(){
            System.out.println("无参构造方法");
        }
        public CodeBlockDemo(int a,String s){
            this.a = a;
            this.s = s;
        }
    }
    

4. 同步代码块

  • Synchronize{}:解决线程安全问题

5. 静态代码块、构造代码块,构造方法的执行顺序和次数

  • 静态代码块 :在类被加载进内存时执行,只执行一次
  • 构造代码块:在构造方法被调用之前执行,每次调用都会被执行
  • 构造方法:对象被创建时执行,每次调用都会被执行

18. 方法的重载

方法的重载

  • 参数的数据类型不同
  • 参数的顺序不同
  • 参数的个数不同
  • 与方法的返回值无关

@OverLoad,早期方法重载声明,在jdk1.7以后更新不需要写了

public static int sum(int i , int j){
    return i+j;
}
public static int sum(String i,int j){
    return i+j;
}
@OverLoad
public static int sum(String i ,int j,int k){
    return i+j+k;
}
public static int sum(int j,int k,String i ){
    return i+j+k;
}

public static void main (String [] args){
    int c = num(1,1);
    System.out.println(c);
}
public static int num (int a,int b ){
    int c = a+b;
    return c;
}

19. 面向对象

面向对象是基于面向过程的

1. 封装(Encapsulation

  • 概述:封装(encapsulation)又叫隐藏实现(Hiding the implementation),就是只公开代码单元的对外接口,而隐藏其具体实现。

  • 详述:

    • 将类的某些信息隐藏在类的内部,不允许外部程序进行直接的访问调用。
    • 通过该类提供的方法来实现对隐藏信息的操作和访问。
    • 隐藏对象的信息。
    • 留出访问的对外接口。
  • 特点:

    • 提高了代码的安全性、复用性。
    • 对成员变量实行更准确的控制。
    • 封装可以隐藏内部程序实现的细节。
    • 良好的封装能够减少代码之间的耦合度。
    • 外部成员无法修改已封装好的程序代码。
    • 方便数据检查,有利于保护对象信息的完整性,同时也提高程序的安全性。
    • 便于修改,提高代码的可维护性。
  • 封装原则:

    • 把不需要对外提供的内容隐藏起来,提供对应的公共访问方式
  • 封装性在Java中的体现

    • 方法就是一种封装
    • 关键字private也是一种封装

2. 继承(Inheritance

  • 概述:继承,Inheritance,是一种看起来很神奇的代码重用方式。 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。当然,如果在父类中拥有私有属性(private修饰),则子类是不能被继承的。
  • 特点:
    • 提高代码复用性。
    • 提高了代码的维护性
    • 类与类之间产生联系(多态构成的前提)
    • 类的耦合度增加了
    • 父类的属性方法可以用于子类。
    • 可以轻松的定义子类。
    • 使设计应用程序变得简单
  • Java只能单继承,不能多继承,一个类只能有一个父类,不能有多个父类,但是可以多层继承
  • 注意事项:
    • 不要为了部分功能而用继承
    • 子类只能继承父类所有非私有的成员(成员变量和成员方法)
    • 子类不能继承父类的构造方法,但是可以通过super关键字访问父类构造方法
  • 什么时候用使用继承:
    • 继承体现的是一种关系:A is B(例如:img是水果,img是水果)

3. 多态(Polymorphism

  • 父类引用指向子类对象

开发原则:低耦合,高内聚

20. 类

一类相同事物的抽象

  • 类的组成

    • 成员变量:事物的属性(在类中方法外定义)
    • 成员方法:事物的功能
    • 构造方法:初始化对象(无参,有参,可被重载)
  • 类的定义

    • class Student{
          public int sid;
          public String sname;
          public Student(){}
          public Student(int id,String name){
              this.sid = id;
              this.name = sname;
          }
          public void study(){
              System.out.println("学习");
          }
          public void eat(){
              System.out.println("吃饭");
          }        
      }
      

21. 对象

某个事物的抽象

  • 创建对象的格式:

    类名   对象名   =   new   类名/构造方法(参数列表);
    Student stu = new Studnet();
    
  • 为成员变量赋值

    stu.sid = 174350;
    stu.sname = "zhiyuan";
    

注:堆内存中的变量(引用类型)会被初始化,栈内存中的变量(基本类型)未初始化不可使用

22. 成员变量和局部变量的区别

  • 在类中的位置不同
    • 成员变量:类中方法外
    • 局部变量:方法声明上或方法内
  • 在内存中的位置不同
    • 成员变量:在堆中
    • 局部变量:在栈中
  • 生命周期不同
    • 成员变量:随着对象的的存在而存在,随着对象的消失而消失
    • 局部变量:随着方法的调用而存在,随着方法的调用完成而消失
  • 初始化不同
    • 成员变量:有默认值
    • 局部变量:无默认值

注意事项:局部变量的名字可以和成员变量的名字一样,在方法中用的时候,采用的是就近原则

class Demo{
 public int num = 5;
 public static void show(){
     int num = 5;
     System.out.println(num);
 }
 public static void main(String [] args){
     System.out.println(num);
     show();
 }
}

23. 参数问题

  • 基本类型:形参的改变不影响实参
  • 引用类型:形参的改变直接影响实参

24. 匿名对象

  • 定义:没有名字的对象,是对象的一种简化表示方法
  • 应用:
    • 匿名调用方法(如果需要调用多次方法,不建议使用匿名对象调用,因为每调用一次都创建一个新对象)
    • 作为实际参数传递
  • 好处:匿名对象使用完成之后就是垃圾了,可以被回收。
new Student();
//匿名调用方法
new Student().study();
//匿名对象做参数
new StudentDemo().studyDemo(new Student());

25. equals()与 ==

  • ==

    • 基本类型:比较的是值是否相同
    • 引用类型:比较的是地址值是否相同
  • equals

    • 引用类型:Object默认比较的是地址值,子类重写后默认比较的是内容是否相同

26. this关键字

class Person{
  private String name;
	public void setName(String name){
    	name = name;
	}
}
  • 分析:
    • 这里实际上是把局部变量的值又赋给了局部变量
    • 应该是把这个值赋值给成员变量
    • 成员变量是随着对象的创建而存在
    • 之前 “对象名.成员变量名” 可以访问成员变量
    • 那么我们就需要找到一个可以表示 “当前对象” 的东西
    • 而java提供了一个关键字 “this” 就可以表示 “当前对象”

this -> 当前对象(所在类的对象的“引用”)

  • 当前方法被谁调用,this就表示谁
  • 修改上述例子
class Person{
    private String name;
	public void setName(String name){
    	this.name = name;
	}
}

27. 构造方法

  • 作用:初始化对象
public class Student{
    private int sid;
    private String sname;
    //无参构造方法
    public Student(){
        
    }
    //有参构造方法
    public Student(int sid,String sname){
        this.sid = sid;
        this.sname = sname;
    }
    //构造方法可以被重载
    public Student(int sid){
        this.sid = sid;
    }
    public Student(String sname){
        this.sname = sname;
    }
    //Setter方法
    public void setId(int sid){
        this.sid = sid;
    }
    public void setName(String sname){
        this.sname = sname;
    }
    //Getter方法
    public int getId(){
        return sid;
    }
    public String getName(){
        return sname;
    }
    //重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                '}';
    }
    
}

public class StudentTest{
    public static void main(String [] args){
        //创建对象
        Student stu1 = new Student();
        System.out.println(stu1);

        Student stu2 = new Student(18,"zhiyuan");
        System.out.println(stu2);

        Student stu3 = new Student(18);
        System.out.println(stu3);

        Student stu4 = new Student("zhiyuan");
        System.out.println(stu4); 
    }
}

/*
输出结果
Student{sid=0, sname='null'}
Student{sid=18, sname='zhiyuan'}
Student{sid=18, sname='null'}
Student{sid=0, sname='zhiyuan'}
*/
  • 说明:
    • 如果用户自己没写,系统会自动生成一个无参构造方法
    • 如果用户写了构造方法,无论有参无参,系统都不会再自动生成无参构造方法
    • 构造方法可以重载

28. 类的初始化过程

Student s = new Student();在内存中都干了哪些事?
//加载Student.class文件进内存
//在栈内存中给变量s分配内存空间
//在堆内存中给学生对象开辟空间
//对学生的成员变量进行隐式初始化(默认初始化)
//对学生的成员变量进行显式初始化(类内赋值初始化)    
//对学生的成员变量进行构造初始化(构造方法初始化) 

29. static关键字

  • 可以修饰成员变量和成员方法

  • 特点:

    • 被类内所有对象共享(也是我们判断要不要用static的标准)
    • 随着类的加载而加载
    • 优先于对象存在
    • 可以通过类名调用(本身可以通过对象调用,建议用类名调用,否则失去了用static修饰的意义 )
  • 静态方法只能访问静态的成员变量和静态成员方法(静态方法中不能用this)

    • 静态的成员变量和成员方法是对着类加载而加载的,this是随着对象的创建而存在的
    public class Demo{
        private int num;
        private static int num2 = 4;
        public void show(){
            System.out.println(num);
            System.out.println(num2);//无法通过非静态的成员方法访问静态的成员变量
            show2();
            show3();
        }
        public static void show1(){
            System.out.println(num);//报错,无法通过静态的成员方法访问非静态的成员变量
            System.out.println(num2);
            show2();  //静态的成员方法无法访问非静态的成员方法
            show3();
        }
        public void show2(){
            
        }
        public static void show3(){
            
        }
    }
    
  • 非静态方法既可以访问静态的成员变量和成员方法,又可以访问非静态的成员变量和成员方法

public static void main(String [] args){
    for(int i = 0;i < args.length;i++){
        System.out.println(args[i]);
    }
}
//String [] ars用于接收来自控制台交互的参数
  • 静态的成员变量和成员变量
    • 所属不同
      • 成员变量:属于对象
      • 静态成员变量:属于类
    • 内存区域不同
      • 成员变量:堆内存
      • 静态变量:方法区中的静态区
    • 出现时间不同
      • 成员变量:随着对象的创建而存在,随着对象的消失而消失
      • 静态变量:随着类的加载而加载,随着类的消失而消失
    • 调用方式不同
      • 成员变量:只能被对象调用
      • 静态变量:可以使用类名调用,也可以使用对象名调用

30. main方法

public static void main(String [] args){
    //public:权限修饰符,公共的
    //static:被jvm调用,不需要创建对象
    //void:返回值为空类型,即无返回值
    //main:程序的主入口名,固定默认为main
    //String [] args:早期用于接收键盘录入数据,1.5版本后使用Scanner
}
//控制台编译运行时传入参数
javac Demo.java
java Demo hello world java  

//尝试输出参数    
public static void main(String [] args){
    for(String s : args){
        System.out.println(s);
    }
}

31. 文档注释

  • 可以使用javadoc命令生成网页形式的说明书
  • 制作帮助文档(了解)
package com.demo.javase;
/**
 * @Author 絷缘
 * @Date 2020/11/2 10:02
 * @Version 1.0
 **/
public class ArrayTools {
    /**
     * 打印数组
     * @param arr 传入指定数组
     */
    public static void printArray(int [] arr){
        for (int a:arr) {
            System.out.println(a);
        }
    }

    /**
     * 获得数组中的最大值
     * @param arr 传入指定数组
     * @return int 返回数组中的最大值
     */
    public static int getMax(int [] arr){
        int max = arr[0];
        for(int i = 0;i < arr.length;i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }
        return max;
    }

    /**
     * 获取数组中的最小值
     * @param arr 传入指定数组
     * @return int 返回数组中的最小值
     */
    public static int getMin(int [] arr){
        int min = arr[0];
        for(int i = 0;i < arr.length;i++){
            if(arr[i] < min){
                min = arr[i];
            }
        }
        return min;
    }

    /**
     * 反转数组方法A
     * 逆序数组
     * @param arr 传入指定数组
     */
    public static void reverseA(int [] arr){
        for(int i = 0,j = arr.length - 1;i < j;i++,j--){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    /**
     * 反转数组方法B
     * 逆序数组
     * @param arr 传入指定数组
     */
    public static void reverseB(int [] arr){
        for(int i = 0;i < arr.length/2;i++){
            int temp = arr[i];
            arr[i] = arr[arr.length - 1 - i];
            arr[arr.length - 1] = arr[i];
        }
    }

    /**
     * 获取指定值第一次出现的索引值
     * @param arr  传入的指定数组
     * @param value 传入的指定值
     * @return int 返回的是第一次出现的索引值,如果不存在返回-1
     */
    public static int getIndex(int [] arr,int value){
        int index = -1;
        for(int i = 0;i < arr.length;i++){
            if(arr[i] == value){
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * 获取指定索引处的值
     * @param arr 传入指定数组
     * @param index 传入指定索引
     */
    public static void getValue(int [] arr,int index){
        if(index > arr.length -1 || index < 0){
            for(int i = 0;i < arr.length;i++){
                if(index == i){
                    System.out.println(arr[i]);
                    break;
                }
            }
        }else{
            System.out.println("index错误");
        }
    }
}
  • 使用javadoc命令解析

    • 格式:

      javadoc -d . -author -version ArrayTools.java 
      
    • 报错:找不到可以文档化的公共或受保护的类

      • 原因:权限不够
      • 解决方法:给类加上public
  • 看文档

    • 看类的结构
      • 看包名
      • 看类的说明
      • 看类的结构
        • 成员变量:字段摘要
        • 构造方法:构造方法摘要
        • 成员方法:方法摘要
          • 左边:返回值,是否为静态
          • 右边:方法名
  • 外部调用(参考JavaDoc和字节码文件使用功能)

    • 构造方法私有化

32. 继承(extends)

  • Person类
package com.demo.javase;

/**
 * @author 絷缘
 * @version 1.0
 * @date 2020/11/3 9:16
 **/
public class Person {
    protected String name;
    protected int age;
    protected String gender;

    /**
     * 有参构造方法
     * @param name 提供人的姓名
     * @param age  提供人的年龄
     * @param gender 提供人的性别
     */
    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    /**
     * 获取人的名字
     * @return String 返回人的姓名
     */
    public String getName() {
        return name;
    }

    /**
     * 获取人的年龄
     * @return int 返回人的年龄
     */
    public int getAge() {
        return age;
    }

    /**
     * 获取人的性别
     * @return String 返回人的性别
     */
    public String getGender() {
        return gender;
    }

    /**
     * 设置人的姓名
     * @param name 提供姓名参数
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 设置人的年龄
     * @param age 提供年龄参数
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 设置人的性别
     * @param gender 提供性别参数
     */
    public void setGender(String gender) {
        this.gender = gender;
    }

    public void eat(){
        System.out.println("吃饭");
    }
    public void sleep(){
        System.out.println("睡觉");
    }
    public void work(){
        System.out.println("工作");
    }
    public void study(){
        System.out.println("学习");
    }
    public void play(){
        System.out.println("玩耍");
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
  • Student类
package com.demo.javase;

/**
 * @author 絷缘
 * @version 1.0
 * @date 2020/11/3 9:26
 **/
public class Student extends Person{
    private int sid;
    public Student(int sid,String name,int age,String gender){
        super(name,age,gender);
        this.sid = sid;
    }

    /**
     * 获取学生ID
     * @return int 返回学生的ID
     */
    public int getSid() {
        return sid;
    }

    /**
     * 设置学生的ID
     * @param sid 提供学生ID参数
     */
    public void setSid(int sid) {
        this.sid = sid;
    }

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
  • 测试类
package com.demo.javase;

/**
 * @author 絷缘
 * @version 1.0
 * @date 2020/11/3 9:28
 **/
public class Test {
    public static void main(String[] args) {
        System.out.println("==========================================================");
        Student stu1 = new Student(174350,"zhiyuan",18,"male");
        System.out.println(stu1);
        System.out.println("==========================================================");
        System.out.println("sid="+stu1.getSid());
        System.out.println("name="+stu1.getName());
        System.out.println("age="+stu1.getAge());
        System.out.println("gender="+stu1.getGender());
        System.out.println("==========================================================");
        stu1.eat();
        stu1.sleep();
        stu1.study();
        stu1.play();
        System.out.println("==========================================================");
    }
}
  • 继承中的成员变量关系
    • 类的组成
      • 成员变量
      • 构造方法
      • 成员方法
    • 子类的成员变量名字和父类的成员变量名一样的话
      • 在子类的方法中找,有就用
      • 在子类的成员位置找,有就用
      • 在父类的成员位置找,有就用
      • 如果找不到,就去父类的父类成员位置找,有就用

33. super关键字

  • super的用法和this很像

    • this表示本类对象的调用
    • super表示的是父类对象的调用
  • 用法:

    • 访问成员变量
    this.成员变量
    super.成员变量    
    
    • 访问构造方法
    this()
    super()    
    
    • 访问成员方法
    this.成员方法()
    super.成员方法()    
    
  • super和this的区别

    • this表示本类对象的调用
    • super表示的是父类对象的调用
  • 注意:super调用构造方法或this调用构造方法时,必须是第一条语句

34. 继承中构造方法的关系

  • 子类中所有构造方法默认都会访问父类中的无参构造方法

    • 因为子类继承父类,会继承父类中的数据,还要使用这些数据,父类中的数据随着父类对象的存在才会存在,所以,子类初始化之前,一定会先初始化父类。

    • public class 子类 extends 父类{
      	public	子类构造(){
              //super父类构造();不写,默认会调用父类构造方法初始化对象
          }
          public 子类构造(参数列表){
              super(参数列表);
              this.子类成员变量 = 参数;
          }
      }
      

35. 继承中成员方法的关系

  • 子类中的成员方法与父类中的成员方法同名
    • 使用子类对象调用方法,先看子类有没有,有就用,没有就看父类有没有,有就用,没有就报错

36. 方法的重写

  • 在子父类中,子类出现了和父类中一模一样的方法声明,这种现象叫做方法重写(复写)
  • 方法重写应用
    • 当子类需要父类的功能,而功能的主体子类又有自己特有的内容时,可以重写父类继承过来的方法
    • 这样既继承了父类的功能,又定义了子类的特有功能内容
  • 注意事项:
    • 父类中私有的方法不能被重写(因为父类中的私有方法无法被继承,更不要说重写了)
    • 子类重写父类的方法,访问权限不能更低
    • 父类的静态方法,子类也必须使用静态方法重写(本质不算方法重写,现象确实如此,涉及多态)
    • 子类重写方法时,声明要一模一样

37. 方法重载和方法重写的区别

  • 方法重载:同一个类中,出现多个方法名相同,参数列表不同的方法,这种现象称为方法重载
  • 方法重写:在子类中,出现和父类中成员方法声明相同,功能主体不同的方法,这种现象称为方法重写

38. 访问权限修饰符

访问权限修饰符 描述 本类 同一个包的无关类 其他包的无关类 同一个包的子孙类 其他包的子孙类
public 访问权限公开 ✔️ ✔️ ✔️ ✔️ ✔️
protected 访问权限受保护 ✔️ ✔️ ✔️ ✔️
private 访问权限私有 ✔️
默认的缺省的 friendly(同一个包内友好) ✔️ ✔️ ✔️

1. public

public 公开的,是访问权限限制最宽的修饰符。被public修饰的类、属性、及方法不仅可以跨类访问,而且可以跨包访问

2. protected

protected 受保护的, 是介于public和private之间的一种访问修饰。被protected修饰的属性及方法只能被类本身的方法和子类访问。(子类在不同的包中也可以访问)

3. private

private 私有的,对访问权限限制最窄的修饰符。被private修饰的属性以及方法只能被该类的对象访问。它的子类也不可以访问,更不支持跨包访问

4. friendly

friendly 友好的,默认不加任何访问修饰符,只支持在同一个包中进行访问,对同一个包内的类友好

39. 其他修饰符

1. static

  • 修饰变量:静态变量
    • 独立于对象的变量,又称类变量,在类被加载进内存时加载,类无论被实例化几次,静态变量只有一份拷贝
  • 修饰方法:静态方法
    • 独立于对象的方法,又称类方法,在类被加载进内存时加载
      • 静态方法只能访问静态成员(变量、方法)

2. final

我们只希望子类能够继承父类的方法,不希望他改动,java就提供了final关键字

  • 修饰变量:变量一旦赋值后,不能被重新赋值。

    • 被 final 修饰的实例变量必须指定初始值(显式初始化,构造初始化,构造代码块)。

    • final 修饰符通常和 static 修饰符一起使用来创建类常量。

static final double PI = 3.14;
  • 修饰方法:父类方法可以被子类继承,但不可被重写
public class Person{
    private String name;
    public final void changeName(String name){
        this.name = name;
    }
}
  • 修饰类:表示最终类,该类不可被继承(但最终类可以继承别的类)
public final class Person{
    
}
  • final修饰局部变量
    • 基本类型:值不能发生改变
    • 引用类型:地址值不能发生改变,但是该对象在堆内存中的值是可以改变的
    • 修饰形参时,在方法内部不可对值进行改变
  • final修饰成员变量的初始化时机:在对象构造完成之前赋值即可,只能赋值一次

3. abstract

  • 修饰类:抽象类
  • 修饰方法:抽象方法

4. synchronized

5. transient

6. volatile

40. 多态

  • 同一事物在不同时刻表现出来不同的状态

  • 例:水在低温下是固态,常温下是液态,高温下是气态

  • 例:你在你爹面前是儿子,在你爷爷面前是孙子

1. 多态的前提和表现

前提:

  • 有继承关系
  • 有方法重写

表现:

  • 父类引用指向子类对象(向上转型)

2. 多态中成员的访问特点

class Father{
	int num = 111;
	public void show(){
		System.out.println("show father");
	}
	public static void function(){
		System.out.println("function father");
	}
}

class Son extends Father{
	int num = 222;
	int num2 = 333;
	public void show(){
		System.out.println("show son");
	}
	public static void function(){
		System.out.println("function son");
	}
	public void method(){
		System.out.println("method son");
	}
}

class Demo1{
	public static void main(String [] args){
		//多态
		Father f1 = new Son();
        System.out.println(f.num);
        System.out.println(f.num2);//找不到num2
	}
}
  • 访问成员变量
    • 编译看左边,运行看左边
  • 访问成员方法
    • 编译看左边,运行看右边(成员方法存在方法重写,所以运行看右边)
  • 访问静态方法
    • 编译看左边,运行看左边(静态和类相关,算不上重写,所以访问是父类)
  • 访问构造方法
    • 创建子类对象时,自动访问父类的构造方法,将父类数据进行初始化

3. 多态的好处和弊端

好处:

  • 提高了程序的复用性、可维护性(由继承保证)
  • 提高了程序的可扩展性(有多态的保证)

弊端:

  • 无法访问到子类独有的内容(变量以及方法)

难道真的不能访问子类的内容吗?

  • 向下转型

  • Father f = new Son();
    Son s = (Son)f;
    

4. 多态的作用

  • 当把不同的子类对象都当做父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。

41. 抽象类(abstract)

1. 抽象类的定义:

比如图形(Shape)类, 就是一个抽象的概念,因为我们无法计算这个**“图形”的面积,所以它的成员函数area()**是空的。

而继承它的子类**(矩形,椭圆形,三角形等)就可以去覆写area()**成员函数. 里面通过数学公式,计算出面积.

  • 抽象类,用来表示一个抽象概念,不是一个具体的事物。
  • 是一种只能定义类型,而不能产生对象的类,所以定义了抽象类则必须有子类的出现.
  • 抽象类的好处在于能够明确地定义子类需要覆写的方法
  • 抽象类需要使用abstract声明.
  • 定义格式:public abstract class 类名 { }

2. 抽象方法

  • 一个没有方法体的方法
  • 有抽象方法的类就必须定义为抽象类
  • 定义格式:public abstract 返回值类型 方法名();

3. 注意事项

  • 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  • 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  • 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
public abstract double computePay();
  • 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
  • 如果子类是具体类,子类必须给出抽象类中的抽象方法的具体实
  • 如果子类也是抽象类,子类可以不重写父类的抽象方法

4. 抽象类的构造方法的意义

  • 用于子类访问父类初始化数据
  • 可以通过多态的方式实现实例化

5. 抽象类中的成员特点

  • 成员变量
    • 可以是变量(成员变量,static 类变量)
    • 也可以是常量(final 成员常量,static final 类常量)
  • 成员方法
    • 可以有抽象方法(要求子类必须干的事情)
    • 也可以有非抽象方法(给子类继承,提高代码复用性)
  • 构造方法
    • 有构造,但是不能实例化
public abstract class Animal{
    private String name;
    private int age;
    private String color;
    
    public Animal(){}
    public Animal(String name,int age,String color){
        this.name = name;
        this.age = age;
        this.color = color;
    }
    
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
    public String getColor(){
        return color;
    }
    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public void setColor(String color){
        this.color = color;
    }
    public abstract void eat();
    public abstract void sleep();
    public abstract void play();
}
public class Dog extends Animal{
    public Dog(){}
    public Dog(String name,int age,String color){
        super(name,age,color);
    }
    public void eat(){
        System.out.println("狗吃狗");
    }
    public void sleep(){
        System.out.println("狗趴着睡");
    }
    public void play(){
        System.out.println("狗玩球");
    }
    public void lookDoor(){
        System.out.println("狗看门");
    }
}
public class Cat extends Animal{
    public Cat(){}
    public Cat(String name,int age,String color){
        super(name,age,color);
    }
        public void eat(){
        System.out.println("猫吃猫");
    }
    public void sleep(){
        System.out.println("猫蜷着睡");
    }
    public void play(){
        System.out.println("猫玩毛线");
    }
    public void catchMouse(){
        System.out.println("猫抓老鼠");
    }
}
public class Demo{
    public static void main(String [] args){
        Cat c1 = new Cat();
        c1.setName("咪咪");
        c1.setAge(1);
        c1.setColor("奶牛色");
        System.out.println(c1.getName()+c1.getAge()+c1.getColor());
        c1.eat();
        c1.sleep();
        c1.play();
        c1.catchMouse();
        
        Cat c2 = new Cat("橘猫",2,"橘黄色");
        System.out.println(c2.getName()+c2.getAge()+c2.getColor());
        c2.eat();
        c2.sleep();
        c2.play();
        c2.catchMouse();
        
        //多态
        Animal a1 = new Cat();
        a1.setName("狸花猫");
        a1.setAge(2);
        a12.setColor("花");
        System.out.println(a1.getName()+a1.getAge()+a1.getColor());
        a1.eat();
        a1.sleep();
        a1.play();
        Cat c3 = (Cat)a1;
        c3.catchMouse();
        
        
        Animal a2 = new Cat("蓝猫",1,"蓝色");
        System.out.println(a2.getName()+a2.getAge()+a2.getColor());
        a2.eat();
        a2.sleep();
        a2.play();
        Cat c4 = (Cat)a2;
        c4.catchMouse();
    }
}

6. 抽象类中的几个问题

  • 一个类如果没有抽象方法,能不能定义成抽象类?如果可以,有什么意义?
    • 可以,意义可以让类无法被实例化,即无法创建对象
  • abstract类和abstract方法不能被以下修饰:
    • private abstract void show()
    • final abstract void show()
    • static abstract void show()

42. 接口

1. 概述

  • 为了体现事物功能的扩展性,java就提供了接口来定义额外的功能,不给具体实现。

  • 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成。这样将功能的定义与实现分离,优化了程序设计。

    请记住:一切事物均有功能,即一切事物均有接口。

2. 接口的表示形式

  • 关键字:interface
  • 格式:interface 接口名 { }
  • 实现接口关键字:implements
  • 格式:class 类名 implements 接口名{ }

3. 特点

  • 接口不能被实例化
  • 可以依赖具体的实现类实现实例化(接口多态)

4. 接口的实现类

  • 可以是抽象类,意义不大
  • 可以是具体类,一定要重写接口中所有的抽象方法

5. 多态类型

  • 具体类多态(几乎不用)
  • 抽象类多态(常用)
  • 接口类多态(常用)
interface AnimalInter {
    //钻火圈的额外功能
    public abstract void 钻火圈();
    
}
class Tiger implements AnimalInter{
    public void 钻火圈(){
        System.out.pritln("老虎钻火圈");
    }
}
class Demo{
    public static void main(String [] args){
        Tiger t = new Tiger();
		t.钻火圈();
		AnimalInter a = new Tiger();
		a.钻火圈();
    }
}

6. 接口中的成员特点

  • 成员变量
    • 默认被 public static final 修饰,只能是类常量
    • 自己写上,否则会误认为是变量
  • 构造方法
    • 没有构造方法,也不能有构造方法
    • 因为接口是扩展功能,没有具体的实体对象
    • 那么接口如何实现多态?
      • java中所有的类都直接或者间接继承了一个类,这个类就是Object类
      • Object是java中所有类的基类,是类层次结构中的根类
  • 成员方法
    • 接口中所有的方法都被public abstract修饰,即接口中所有的方法都是抽象方法
    • (其实可以出现非抽象方法,但不要记忆default void show(){})
    • 默认被 public abstract 修饰
interface Inter{
    public static final int num = 5;
    public static final int num2 = 6;
    public static final int num3 = 7;
    public static final int num4 = 8;
    
    public abstract void show();
}
class InterImp implements Inter{
    
}
class Demo{
    public static void main(String [] args){
        
    }
}

7. 类和类、类和接口、接口和接口之间的关系

  • 类和类
    • 继承关系,只能单继承,可以多层继承
  • 类和接口
    • 实现关系,可以单实现,也可以多实现
    • 还可以在继承一个类的基础上同时实现多个接口
  • 接口和接口
    • 继承关系,可以单继承,也可以多继承

8. 抽象类和接口的区别

  • 成员区别:

    • 抽象类
      • 成员变量:可以是变量,可以是常量
      • 构造方法:有
      • 成员方法:可以有抽象方法,也可以有非抽象方法
    • 接口
      • 成员变量:都是静态常量
      • 构造方法:没有
      • 成员方法:只能是抽象方法
  • 关系区别:

    • 类和类:继承,单继承
    • 类和接口:实现,多实现
    • 接口和接口:继承,多继承
  • 设计区别:

    • 抽象类:存储继承体系中共性的内容,体现的是 is a 的关系
    • 接口:体现事物的扩展功能,体现的是 like a 的关系
  • 抽象类中可以有非抽象方法,接口中默认都为抽象方法(public abstract)

  • 抽象类中的成员变量可以随便定义,接口中的成员变量都是类常量(public static final)

9. 猫狗案例,加入一个钻火圈的额外功能

interface AnimalInter{
    //定义额外功能
    public abstract void 钻火圈(){
        
    }
}

public abstract class Animal{
    private String name;
    private int age;
    private String color;
    
    public Animal(){
        
    }
    public Animal(String name,int age,String color){
        this.name = name;
        this.age = age;
        this.color = color;
    }
    
    public String getName(){
        return name;
    }
    public int getAge(){
        return age;
    }
    public String getColor(){
        return color;
    }
    public void setName(String name){
        this.name = name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public void setColor(String color){
        this.color = color;
    }
    public abstract void eat();
    public abstract void sleep();
    public abstract void play();
    public void show(){
        System.out.println("Animal{"+ name + "," + age + "," + color + "}");
    }
}
//狗也是动物,所以继承动物
public class Dog extends Animal{
    public Dog(){}
    public Dog(String name,int age,String color){
        super(name,age,color);
    }
    public void eat(){
        System.out.println("狗吃骨头");
    }
    public void sleep(){
        System.out.println("狗趴着睡");
    }
    public void play(){
        System.out.println("狗玩球");
    }
    public void lookDoor(){
        System.out.println("狗会看门");
    }
}
//猫也是动物,所以继承动物
public class Cat extends Animal{
    public Cat(){}
    public Cat(String name,int age,String color){
        super(name,age,color);
    }
     public void eat(){
        System.out.println("猫吃鱼");
    }
    public void sleep(){
        System.out.println("猫蜷着睡");
    }
    public void play(){
        System.out.println("猫玩毛线");
    }
    public void catchMouse(){
        System.out.println("猫会抓老鼠");
    }
} 

//屌狗也是狗,所以继承狗
public class DiaoDog extends Dog implements AnimalInter{
    public DiaoDog(){}
    public DiaoDog(String name,int age,String color){
        super(name,age,color);
    }
    public void zhq(){
        System.out.println("屌狗钻火圈");
    }
}
//屌猫也是猫,所以继承猫
public class DiaoCat extends Cat implements AnimalInter{
    public DiaoCat(){}
    public DiaoCat(String name,int age,String color){
        super(name,age,color);
    }
    public void zhq(){
        System.out.println("屌猫钻火圈");
    }
}
//测试类
public class Demo{
    public static void main(String[]args){
        //测试普通狗
        Dog d1 = new Dog("哈巴狗",2,"黄色");
        d1.show();
        d1.eat();
        d1.sleep();
        d1.play();
        d1.lookDoor();
        
        //测试屌狗
        DiaoDog d2 = new DiaoDog("金毛",3,"金色");
        d2.show();
        d2.eat ();
        d2.sleep();
        d2.play();
        d2.lookDoor();
        d2.zhq();
        
        //测试普通猫
        Cat c1 = new Cat("橘猫",2,"橘黄色");
        c1.show();
        c1.eat();
        c1.sleep();
        c1.play();
        c1.catchMouse();
        
        //测试屌猫
        DiaoCat c2 = new DiaoCat("狸花猫",3,"狸花");
        c2.show();
        c2.eat ();
        c2.sleep();
        c2.play();
        c2.catchMouse();
        c2.zhq();
    }
}

43. 形参和返回值深入研究

1. 形参

  • 基本类型
  • 引用类型
    • 类:要的是该类的对象
    • 抽象类:要的是该抽象类的具体子类的对象
    • 接口:要的是该接口的具体实现类的对象

2. 返回值类型

  • 基本类型
  • 引用类型
    • 类:返回该类的具体对象
    • 抽象类:返回该抽象类的具体子类对象
    • 接口:返回该接口具体实现类对象

链式编程:new Student().study()

每次调用方法之后返回的是另一个对象,而你需要继续调用这个对象的方法,就可以直接.方法名

44. 包

1. 作用

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)

  • 区分同名文件,对类文件进行分类管理和存储

2. 包的划分

  • 基本划分
    • 按功能分
      • com.dnc.add
      • com.dnc.delete
      • com.dnc.update
      • com.dnc.select
    • 按模块分
      • com.dnc.teacher
      • com.dnc.student

3. 包的定义和注意事项

  • 格式:
    • package 包名;
    • 多级包用点隔开
  • 注意:
    • package语句必须为程序的第一条语句
    • 一个java文件中只能有一条package语句,且写在第一行

4. 带包的编译和运行

  • 手动写:编写一个带包的java文件,使用javac编译,手动创建包,将编译后的字节码文件放入包内,回到编写java文件的路径下,用java命令运行,要带包名
java com.dnc.demo.Demo
  • 自动:编写一个带包的java文件,javac编译时带上包名,回到编写java文件的路径下,用java命令运行
javac -d . HelloWorld.java
java com.dnc.demo.Demo    

5. 导包

  • 不同包下类之间的访问,每次使用的时候都要加包名,为了解决这个问题,java就提供了导包功能。
  • 格式:import 包名;
  • 注意:
    • 用谁导谁,直接导入具体的类,写在package下
    • 虽然包名后面可以写*,不建议
    • 一个java文件中可以有多条import语句

45. 类和类的组成可用的修饰符

  • 修饰符:
    • 权限修饰符:private 默认 protected public
    • 状态修饰符:static final
    • 抽象修饰符:abstract
  • 类:
    • 权限修饰符:默认 public
    • 状态修饰符:final
    • 抽象修饰符:abstract
  • 成员变量:
    • 权限修饰符:private 默认 protected public
    • 状态修饰符:static final
    • 抽象修饰符:没有
  • 成员方法:
    • 权限修饰符:private 默认 protected public
    • 状态修饰符:static final
    • 抽象修饰符:abstract
  • 构造方法:
    • 权限修饰符:public private protected 默认
    • 状态修饰符:没有
    • 抽象修饰符:没有
  • 除此之外的常用组合:
    • 成员变量:
      • public static final
    • 成员方法:
      • public static
      • public abstract
      • public final

46. 内部类

1. 把一个类定义在另一个类的内部,这个里面的类就是内部类

class A{
    class B{}				//B为成员内部类
    public void method(){
        class C{}			//C为局部内部类
    }
}

2. 分类

2.1 根据内部类在类中定义的位置不同

  • 成员内部类
  • 局部内部类

2.2 根据状态修饰符

  • 非静态内部类
  • 静态内部类(static)

静态内部类无法访问外部类的成员

2.3 根据访问权限修饰符

  • 私有内部类(private)

如果你不希望内部类被外部类访问可以使用 private 修饰符

  • 受保护的内部类(protected)
  • 默认的内部类(缺省)
  • 公开的内部类(public)

3. 成员内部类

3.1 格式

class Outer{
    class Inner{}
}

3.2 创建对象格式

Outer.Inner i = new Outer().new Inner();

3.3. 内部类的成员与外部类的成员的使用方式

  • 内部类可以直接访问外部类的东西
  • 外部类必须创建对象才能访问内部类的东西
  • 内部类直接访问外部类成员可以使用以下格式
Outer.this.成员变量/成员方法()
  • 例:
public class Outer{
    private int num = 10;
    public class Inner{
        int num = 20;
        public void methodInner(){
            int num = 30;
            System.out.println(num);			//局部变量
            System.out.println(this.num);		//内部类的成员变量
            System.out.println(Outer.this.num);	//外部类的成员变量
            this.show();						//内部类的成员方法
            Outer.this.show();					//外部类的成员方法
        }
        public void show(){
            System.out.println("Inner show()");
        }
    }
    public void show(){
        System.out.println("Outer show()");
    }
}

3.4 成员内部类常见用法和常用修饰符

  • private:为了保证安全

特殊情况下还需要让别人访问,就提供一个公有的方法,让外界来间接访问

public class Outer{
    private class Inner{
        public void show(){
            System.out.println("Inner show()");
        }
    }
    public void methos(){
        Inner i = new Inner();
        i.show();
    }
}
  • static:为了让数据访问更方便(可以看成外部类的静态成员)
  1. 静态内部类可以有静态成员,非静态内部类不能有静态成员。
  2. 静态内部类的创建不依赖于外部类,非静态内部类必须依赖于外部类的创建而创建。
public class Outer{
    private static int num = 2;
    public static class Inner{
        public static void show(){
            System.out.println(num);
        }
    }
}

创建对象格式

Outer.Inner i = new Outer.Inner();

4. 局部内部类

4.1 定义:

  • 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

4.2 格式:

class Outer{
    private int num = 5;
    public void show(){
        int num = 6;
        //局部内部类
        class Inner{
            public void function(){
                System.out.println(num);
                System.out.println(Outer.this.num);
            }
        }
    }
}

4.3 注意事项:

  • 局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
  • 局部内部类可以直接访问外部类成员,即使是私有的
  • 局部内部类中访问局部变量,局部变量会被变成final变量,JDK8之后,默认被final修饰,JDK7之前,需要自己添加final修饰符

方法执行结束出栈,局部变量不存在了,但是方法中的局部内部类实例化的对象在堆中依然存在,还会使用局部变量的值,而此时局部变量并不存在,被对象访问的只是一个字面值常量,所以会被final修饰

其实这是规定,无需理解,是延长变量生命周期的一种方式,因为类是加载到方法区中。而局部变量是随意方法的进栈而产生,随着方法的弹栈而消失,所以在局部内部类访问局部变量时,要给局部变量给final修饰使其变成常量,进入常量池,生命周期变长。

img
  • 在外部类中不能创建局部内部类的对象
  • 外部类不能直接访问局部内部类,只能在方法体中间接访问局部内部类,且访问必须在内部类定义之后

5. 匿名内部类

5.1 格式:

new 父类名或者接口名(){
    //方法重写
}

5.2 本质

  • 是一个继承了父类或者实现了接口的具体子类匿名对象

5.3 例

interface Inter{
    public abstract void show();
}
class Outer{
    public abstract void method(){
        //匿名内部类
        new Inter(){
            public void show(){
                System.out.println("匿名内部类");
            }
        }.show();
    }
}
public class Test{
    public static void main(String [] args){
        //接口多态
        Inter i = new Inter(){
            public void show(){
                System.out.println("匿名内部类");
            }
        };
        i.show();
    }
}

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护,以下是一段 Android 事件监听代码

scan_bt.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
         
    }
});
 
history_bt.setOnClickListener(new OnClickListener() {
     
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
         
    }
});

这段代码为两个按钮设置监听器,这里面就使用了匿名内部类

new OnClickListener() {
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
         
    }
}

5.4 注意事项

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为 Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

  • 匿名内部类可以访问外部所有成员,包括私有
  • 匿名内部类访问局部变量时,变量必须要用final修饰,JDK8以后即使没有用final修饰,会自动补上
  • 匿名内部类用于继承其他类或是实现接口,作为参数去给方法传值

5.5 例

public interface Inter{
    public abstract void show();
}
public class Outer{
    public static Inter method(){
        return new Inter(){
            public void show(){
                System.out.println("我爱**");
            }
        };
    }
}
public class Test{
    public static void main(String [] args){
        Outer.method().show();
    }
}

6. Lambda表达式

6.1 概述:用来实现一个接口,本质是一个匿名函数

6.2 条件:

  • 要求被实现接口中有且仅有一个需要被实现的方法(函数式接口)

6.3 函数式接口:

interface A{
    public abstract void show(); 
}
//标准的函数式接口定义,有且仅有一个需要被实现类重写的方法
interface B{
    public abstract void show();
    default void display(){}
}
//这里为什么说B也是一个函数式接口,是因为在JDK1.8版本中,接口中可以有default方法,default关键字修饰的方法就是初始化的抽象方法。或者说是一个已经实现了的抽象方法,不需要再在其他implement接口位置进行实现。比如定义了一个接口,有大量的类实现了这个接口,但是新需求来了,需要在原有的基础上添加一个方法,而使用default关键字的话就不用每个实现类都实现一次,只要在顶层接口中用default实现此方法,所有实现类就会全部拥有该方法,所以说display()已经被重写,所以类实现此接口时只需要重写show()方法,满足函数式接口的定义
interface C{
    public abstract void show();
    public static void display(){}
}
//这里为什么C也是一个函数式接口,是因为在JDK1.8版本中,接口中可以有静态方法,虽然静态方法可以不用实现但是必须有方法体,即{},在类实现此接口时,不会继承接口中的static的方法,也就重写不了,所以需要被重写的方法只有show()一个,满足函数式接口的定义
interface D{
    public abstract void show();
    public String toString();
}
//这里为什么说D也是一个函数式接口,是因为在类实现此接口时,由于默认预先继承Object类,而Object类重写了toString()方法也算重写过了toString()方法,因此此接口需要被实现的方法只有show()一个,满足函数式接口的定义

6.4 @FunctionalInterface

  • @FunctionalInterface 注解 表示接口是一个函数式接口
  • 可以用来验证某个接口是否为函数式接口

47. 常用类(API)

java.lang包下的类使用时无需导包,其他的都需要导包

1. Math类

  • 所在包:java.lang.Math

  • 常用方法:

    • public static double random():返回一个大于等于0.0,小于1.0的double型数字
    • public static double abs(double a):返回double类型的绝对值
    • public static float abs(float a):返回float类型的绝对值
    • public static int abs(int a):返回int类型的绝对值
    • public static long abs(long a):返回long类型的绝对值
    • public static double asin(double a):返回角度的反正弦值
    • public static double acos(double a):返回角度的反余弦值
    • public static double atan(double a):返回角度的反正切值
    • public static double sin(double a):返回角度的正弦值
    • public static double cos(double a):返回角度的余弦值
    • public static double tan(double a):返回角度的正切值
    • public static double log(double a):返回自然对数ln(以e为底)值
    • public static double pow(double a,double b):返回a的b次方值
    • public static long round(double a):四舍五入为long
    • public static int round(float a):四舍五入为int
    • public static double ceil(double a):向上取整为double
    • public static double floor(double a):向下取整double
    • public static double sqrt(double a):返回a的开平方根

2. Random类

  • 所在包:jav.util.Random

  • 构造方法:

    • public Random():创建一个新的随机数生成器
    • public Random(long seed):使用种子创建一个新的随机数生成器
  • 常用方法:

    • public int nextInt():获取一个随机 int 数字(范围是int类型的范围,有正负两种)
    import java.util.Random;
    ...
    Random r = new Random();    
    int num = r.nextInt();  //-2147483648~2147483647  
    System.out.println(num);
    
    • public int nextInt(int num):获取一个随机 int 数字,(参数表示范围,左闭右开区间)
    import java.util.Random;
    ...
    Random r = new Random();
    int num = r.nextInt(5);//[0,5)
    System.out.println(num);
    
    • public void setSeed(long seed):设置随机数种子(有规律可循的伪随机数)
    public static void main(String [] args){
    	System.out.println("未设置随机数种子,取到的随机数");
        for (int k = 0; k < 5; k++) {
    		Random ran = new Random();
    		for (int i = 0; i < 10; i++) {
    			System.out.print(ran.nextInt(100)+"\t");
    		}
            System.out.println("");
    	}
        System.out.println("设置随机数种子后,取到的随机数");
        for (int j = 0; j < 5; j++) {
    		Random ran2 = new Random();
    		ran2.setSeed(100);
    		for (int i = 0; i < 10; i++) {
    			System.out.print(ran2.nextInt(100)+"\t");
    		}
    		System.out.println("");
    	}    
    }
    
    //输出结果		
    //未设置随机数种子,取到的随机数
    58	76	62	95	90	61	53	30	31	20	
    87	22	11	77	87	61	9	76	43	81	
    17	52	16	94	10	14	11	11	73	83	
    11	93	34	66	82	87	30	43	65	81	
    12	31	77	91	80	54	58	16	86	46	
    //设置随机数种子后,取到的随机数
    15	50	74	88	91	66	36	88	23	13	
    15	50	74	88	91	66	36	88	23	13	
    15	50	74	88	91	66	36	88	23	13	
    15	50	74	88	91	66	36	88	23	13	
    15	50	74	88	91	66	36	88	23	13	
    

3. Object类

  • 所在包:java.lang.Object

  • 构造方法:

    • public Object()
    • 如果一个类没有继承其他类,则去使用Object的无参构造方法
  • 常用方法:

    • protected Object clone()

      • 创建一个副本对象
      • 自定义类实现Cloneable接口,是一个标记接口,实现了这个接口的类的对象可以实现自我克隆
      • 自定义类中重写Object中的clone方法
      @Override
      protected Object clone() throws CloneNotSupportedException {
      	return super.clone();
      }
      
    • boolean equals(Object obj)

      • 比较两个对象是否相等
      public boolean equals(Object obj) {
              return (this == obj);
      }
      
      • 默认的equals方法比较对象引用地址,没有意义,子类应该重写这个方法
      public boolean equals(Object anObject) {
              if (this == anObject) {
                  return true;
              }
              if (anObject instanceof String) {
                  String anotherString = (String)anObject;
                  int n = value.length;
                  if (n == anotherString.value.length) {
                      char v1[] = value;
                      char v2[] = anotherString.value;
                      int i = 0;
                      while (n-- != 0) {
                          if (v1[i] != v2[i])
                              return false;
                          i++;
                      }
                      return true;
                  }
              }
              return false;
      }
      
    • protected void finalize()

      • 当对象不再使用的时候,提醒垃圾回收器来回收对象
    • class<?> getClass()

      • 返回当前正在运行的类的字节码文件对象
    • int hashCode()

      • 返回对象的哈希Code值
      • 是通过对象地址值计算出来的,并不是对象的真正地址
    • String toString()

      • 返回对象的字符串表示形式
      //Object类中的toString方法
      public String toString() {
              return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
      
      • 默认的toString方法没有意义,子类应该去重写该方法
      	@Override
          public String toString() {
              return "Student{" +
                      "sname='" + sname + '\'' +
                      ", sage=" + sage +
                      ", sgender='" + sgender + '\'' +
                      '}';
          }
      

4. Scanner类

  • 接受键盘标准输入
  • 构造方法
    • Scanner(InputStream in)
      • InputStream in = System.in;
  • 成员方法
    • nextXxxx():查找并返回下一个键盘输入数据
      • next()
      • nextBoolean()
      • nextByte()
      • nextDouble()
      • nextFloat()
      • nextInt()
      • nextLine()
      • nextLong()
      • nextShort()
    • hasNextXxxx():判断并返回是否存在下一个键盘输入数据
      • hasNext()
      • hasNextBoolean()
      • hasNextByte()
      • hasNextDouble()
      • hasNextFloat()
      • hasNextInt()
      • hasNextLine()
      • hasNextLong()
      • hasNextShort()
    • close():关闭Scanner对象
  • 先获取一个数字,再获取一个字符串时,不让输入直接结束了,因为回车也算一个字符
    • 可以把两个数据都以字符串形式接受,然后在进行转换

5.Arrays类

  • 常用方法
    • sort方法:
      • public static void sort(数组):数组排序
    • toString方法:
      • public static String toString(数组):将数组转为字符串
    • binarySearch方法:
      • public static int binarySearch(被查找的数组,被查找的值):使用二分法在指定数组中查找指定值
    • public static List asList(T... a):把数组转集合
      • 数组转集合后,集合长度不可变

6. System类

  • 包:java.lang.System
  • 字段:
    • static printStream err:标准错误输出流
    • static InputStream in:标准输入流
    • static PrintStream out:标准输出流
  • 成员方法:
    • public static long currentTimeMillis():获取当前时间(单位:毫秒)
    • public static void exit(int status):终止当前运行的Java虚拟机
      • status为状态码,按照惯例,非零状态码表示异常终止
    • public static void gc():运行垃圾回收器
    • public static Properties getProperties():获取当前的系统属性
    • public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)
      • src:原数组
      • srcPos:原数组中的起始位置
      • dest:目标数组
      • destPos:目标数组的起始位置
      • length:要复制的数组元素的数量

7. Collections类

  • 包:java.util.Collections

  • 常用方法:

    • public static boolean addAll(Collection<? super T> c, T... elements):将多个元素加入指定集合中
    • public static void shuffle(List<?> list):打乱集合顺序
    • public static void sort(List list):将集合按照默认顺序(升序)排列
    • public static void sort(List list,Comparator<? super T>):将集合中元素按照指定比较器指定的排序规则排序

48. String类

  • 包:java.lang.String
  • 注意事项
    • 字符串是常量,一旦被创建出来,不可改变

    • 正是因为字符串是不可改变的,所以字符串可以共享使用

    • jdk1.8及以前String使用的是char数组,jdk1.9及以后使用的是byte数组。

      因为开发人员发现人们使用的字符串值是拉丁字符居多而之前使用的char数组每一个char占用两个字节而拉丁字符只需要一个字节就可以存储,剩下的一个字节就浪费了,造成内存的浪费,gc的更加频繁。因此在jdk9中将String底层的实现改为了byte数组。

1. 构造方法

  • public String():创建一个空白字符串,不含有任何内容
  • public String(char[ ] chars):根据字符数组的内容,来创建对应的字符串
  • public String(byte[ ] bytes):根据字节数组的内容,来创建对应的字符串
  • public String(byte[] bytes, Charset charset):按指定字符集解码并创建对应字符串
  • public String(byte[] bytes, int offset, int length):按照字节数组内容,从指定位置开始指定长度创建对应字符串
  • public String(byte[] bytes, int offset, int length, Charset charset):按照字节数组内容按指定编码从指定位置开始指定长度的字节数组内容创建对应字符串
  • public String(String original):初始化新创建的String对象
  • public String(StringBuffer buffer):将StringBuffer字符序列转为String对象
  • public String(StringBuilder builder):将StringBuilder字符序列转为String对象
String str1 = new String();

char [] c = {'a','b','c'};
String str2 = new String(c);

byte [] b = {65,66,67};//ASCII码:A,B,C
String str3 = new String(b);

//直接创建
String str4 = "String";
//直接写上双引号,即使没有new,也会创建对象

2. 字符串的常量池

  • JDK1.8之前,字符串常量池位于运行时常量池,运行时常量池存在于方法区中,JDK1.8之后,字符串常量池位于堆中
  • 对于引用类型来说,==比较的是地址值
  • 双引号直接写的字符串在常量池中,new的不在常量池中
  • Java6和6之前,常量池是存放在方法区(此时hotspot虚拟机对方法区的实现为永久代)中的。
  • Java7,将字符串常量池是单独存放到了堆中,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
  • Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中,只不过方法区的实现从永久代变成了元空间(堆外内存)

在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,所谓"Your father will always be your father",变动的只是方法区中内容的物理存放位置。正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放,逻辑上还是属于方法区的。

JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区,但是实际存放在堆内存中,因此既可以说两者存放在堆中,也可以说两则存在于方法区中,这就是造成误解的地方。

其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

引用自:https://www.cnblogs.com/cosmos-wong/p/12925299.html

img
img
  • 问题:String str1 = new String("abc");创建了多少个对象?
  1. 从字符串常量池中查找是否存在“abc”对象,如果存在,返回引用地址,如果不存在,则创建一个对象
  2. new String()在堆中开辟空间创建了一个对象

综上所述,创建了两个对象,一个引用

  • 问题:String str1 = new String("A"+"B") ; 会创建多少个对象?
  1. “A”如果没有在字符串常量池中被找到,就会被创建
  2. “B”如果没有在字符串常量池中被找到,就会被创建
  3. “AB”如果没有在字符串常量池中被找到,就会被创建
  4. new String()在堆中开辟空间创建了一个对象

综上所述,创建了四个对象,一个引用

  • 问题:String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?
  1. “ABC”如果没有在字符串常量池中被找到,就会被创建
  2. “ABC”已经被创建,在字符串常量池中找到,返回引用地址
  3. new String()在堆中创建了一个对象
  4. “ABCABC”被创建

综上所述,创建了三个对象,一个引用

3. 常用方法

  • public boolean equals(Object obj)

    • 参数可以是任何对象,只有参数为一个字符串并且内容相同才会返回true,否则返回false
    • 如果一个常量和一个变量比较
      • "abc".equals(str) ✔️推荐使用,当str为null时,返回false,结果依然正确
      • str.equals("abc")​ ​ ❌不推荐使用,当str为null时,会出现NullPointerException空指针异常
  • public boolean equalsIgnoreCase(String str)

    • 忽略大小写,直接比较内容
  • public char charAt(int index)

    • 返回指定索引处的字符
  • public boolean endsWith(String str)

    • 判断字符串是否以制定后缀结尾
  • public boolean startsWith(String str)

    • 判断字符串是否以指定前缀开头
  • public boolean contains(CharSequence s)

    • 当且仅当此字符串包含指定的char值序列时才返回true
    • CharSequence是一个接口,String,StringBuilder,StringBuffer均是此接口的实现
  • public byte[ ] getBytes()

    • 使用默认字符集编码将字符串编码为字节数组
  • public byte[ ] getBytes(Charset charset)

    • 使用给定编码将字符串编码为字节数组
  • public int indexOf(String str)

    • 返回指定字符串在字符串内第一次出现的索引
  • public int indexOf(String str,int fromIndex)

    • 返回指定字符串从指定索引处开始在字符串内第一次出现的索引
  • public int lastIndexOf(String str)

    • 返回指定字符串从在字符串中最后一次出现的位置
  • public int lastIndexOf(String str, int fromIndex)

    • 返回指定字符串从指定索引处开始在字符串内最后一次出现的索引
  • public boolean isEmpty()

    • 判断字符串是否为空
  • public String intern()

    • 返回字符串对象的规范表示
  • public int length()

    • 返回该字符串的长度
  • public String replace(char oldchar,char newchar)

    • 提供被替换的字符,和替换的新字符,返回替换后的字符串结果
  • public String replaceAll(String regex,String replacement)

    • 提供被替换字符串的正则表达式规则,和替换的字符串,返回替换后的结果
  • public String replaceFirst(String regex,String replacement)

    • 提供被替换字符串的正则表达式规则,和替换的字符串,只替换第一个满足条件的字符串并返回结果
  • public String [ ] split(String regex)

    • 提供字符串分割的正则表达式规则,将分割后的结果存入字符串数组并返回
  • public String [ ] split(String regex,int limit)

    • 提供字符串分割的正则表达式规则,limit控制分割的次数,次数为limit-1,将分割后的结果存入字符串数组并返回

    官方解释

    limit 参数控制模式应用的次数,因此影响所得数组的长度。如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。如果 n 为非正,那么模式将被应用尽可能多的次数,而且数组可以是任何长度。如果 n 为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。

  • public String substring(int beginIndex)

    • 提供切割字符串的起始位置,返回切割后的结果,包头不包尾
  • public String substring(int beginIndex,int endIndex)

    • 提供切割字符串的起始和结束位置,返回切割后的结果,包头不包尾
  • public char [ ] toCharArray()

    • 将字符串转为新的字符数组并返回字符数组
  • public String toString()

    • 将字符串转为字符串并返回

      为什么字符串依然有转为字符串的方法,因为java中所有的类都有一个默认的父类Object,toString方法就来自Object

  • public String toLowerCase()

  • 将字符串按照默认语言环境,转小写

  • public String toUpperCase()

    • 将字符串按照默认语言环境,转大写
  • public static String valueOf(数据类型 变量名)

    • 将任意数据类型转为字符串形式
  • public String concat(String str)

  • 将指定字符串连接到该字符串末尾

  • public int compareTo(String anotherString)

    • 按字典顺序(ASCII码表Unicode码)比较两个字符串
      • 一个一个的字符ASCII码依次比较,如果第一个大,直接返回第一个字符码值差值
      • 如果等于返回0

49. StringBuffer类

1. 概述

  • 我们对字符串进行拼接的时候,每次都会产生一个新的String对象,耗时又浪费时间,使用StringBuffer可以解决这个问题

  • 线程安全,可变的字符序列

  • 包:java.lang.StringBuffer

2. 构造方法

  • public StringBuffer()
    • 初始化空内容的字符串缓冲区
  • public StringBuffer(CharSequence seq)
    • 初始化指定字符序列的字符串缓冲区
  • public StringBuffer(int capacity)
    • 初始化指定容量的空内容的字符串(容量是理论值,长度是实际值,默认容量16)
  • public StringBuffer(String str)
    • 初始化指定字符串内容的字符串缓冲区

3. 常用方法

  • append方法

    • public StringBuffer append(boolean b)
    • public StringBuffer append(char c)
    • public StringBuffer append(char [ ] str)
    • public StringBuffer append(char [ ] str,int offset,int len)
    • public StringBuffer append(CharSequence s)
    • public StringBuffer append(CharSequence s,int start,int end)
    • public StringBuffer append(double d)
    • public StringBuffer append(float f)
    • public StringBuffer append(int i)
    • public StringBuffer append(long lng)
    • public StringBuffer append(Object obj)
    • public StringBuffer append(String str)
    • public StringBuffer append(StringBuffer sb)
  • insert方法

    • public StringBuffer insert(int offset,boolean b)
    • public StringBuffer insert(int offset,char c)
    • public StringBuffer insert(int offset。char [ ] str)
    • public StringBuffer insert(int index,char [ ] str,int offset,int len)
    • public StringBuffer insert(int dstOffset,CharSequence s)
    • public StringBuffer insert(int dstOffset, CharSequence s, int start, int end)
    • public StringBuffer insert(int offset, double d)
    • public StringBuffer insert(int offset, float f)
    • public StringBuffer insert(int offset, int i)
    • public StringBuffer insert(int offset, long l)
    • public StringBuffer insert(int offset, Object obj)
    • public StringBuffer insert(int offset, String str)
  • indexOf方法

    • public int indexOf(String str)
    • public int indexOf(String str,int fromIndex)
    • public int lastIndexOf(String str)
    • public int lastIndexOf(String str,int fromIndex)
  • 常用方法

    • public int length():返回当前长度

    • public int capacity():返回当前容量

    • public StringBuffer replace(int start,int end,String str):用一段字符串替换指定位置长度的字符串

    • public StringBuffer reverse():反转字符串

    • public String substring(int start):从指定位置截取字符串

    • public String substring(int start,int end):截取指定位置长度的字符串

    • public String toString():转为字符串

    • public void setCharAt(int index,char ch):将指定位置字符替换为ch

    • public void setLength(int newLength):设置长度

  • delete方法

    • public StringBuffer delete(int start,int end):删除指定位置开始到指定位置结束的字符串并返回结果
    • public StringBuffer deleteCharAt(int index):删除指定位置的字符并返回结果

50. StringBuilder

  • StringBuffer和StringBuilder的区别

    • 线程安全:
      • StringBuffer线程安全
        • 因为StringBuffer的所有公开方法都是synchronized修饰的
      • StringBuilder线程不安全
    • 缓冲区:
      • StringBuffer每次获取toString都会直接使用缓冲区的toStringCache值来构造字符串
      • StringBuilder每次都要复制一次字符数组来构造一个字符串
      • StringBuffer 对缓存区优化,不过 StringBuffer 的这个toString 方法仍然是同步的(synchronized)
    • 性能:
      • StringBuffer 是线程安全的,它的所有公开方法都是同步的
      • StringBuilder 是没有对方法加锁同步的,性能高于StringBuffer

    StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景,如果是单线程场合 StringBuilder 更适合

51. String作为参数

1. 形式参数

  • 基本类型:形式参数的改变不影响实参
  • 引用类型:形式参数的改变直接影响实际参数
  • String比较特殊,作为参数传递的时候,效果和基本类型一样
package com.demo.javase.demo;

/**
 * @author 絷缘
 * @version 1.0
 * @date 2020/11/13 13:58
 **/
public class StringBufferDemo1 {
    public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer("hello");
        StringBuffer sb2 = new StringBuffer("world");
        System.out.println("sb1="+sb1+",sb2="+sb2);
        change(sb1,sb2);
        System.out.println("sb1="+sb1+",sb2="+sb2);

    }
    public static void change(StringBuffer sb1,StringBuffer sb2){
        sb1 = sb2;
        sb2.append(sb1);
        System.out.println("sb1="+sb1+",sb2="+sb2);
    }
}
//输出结果为
//sb1=hello,sb2=world
//sb1=worldworld,sb2=worldworld
//sb1=hello,sb2=worldworld

52. Date类

  • 包:java.util.Date
  • 构造方法:
    • public Date() :创建一个Date对象,并初始化为当前时间
    • public Date(int year,int month,int date):创建一个Date对象,并初始化为指定时间
      • year:指定年份-1900
      • month:0-11
      • date:1-31
    • public Date(int year,int month,int date,int hrs,int min,int sec)
      • year:指定年份-1900
      • month:0-11
      • date:1-31
      • hrs:0-23
      • min:0-59
      • sec:0-59
    • public Date(long date):使用给定毫秒值创建Date对象
    • public Date(String s):使用字符串内容创建Date对象并初始化它
  • 常用方法:
    • public String toString():格式化日期为YYYY-MM--DD字符串
    • public static Date valueOf(String s):将JDBC日期字符串转为Date对象
    • public int getYear():获取年份
    • public int getMonth():获取月份
    • public int getDate():获取日期
    • public int getDay():获取星期几
    • public int getHours():获取时
    • public int getMinutes():获取分
    • public int getSeconds():获取秒
    • public long getTime():获取从1970年开始到现在的毫秒数
    • public void setYear(int year):设置年份
    • public void setMonth(int month):设置月份
    • public void setDate(int date):设置日期
    • public void setHours(int hours):设置时
    • public void setMinutes(int minutes):设置分
    • public void setSeconds(int seconds):设置秒
    • public void setTime(long time):设置从1970年开始到现在的毫秒数

53. DateFormat

  • 包:java.text.DateFormat
  • 子类:SimpleDateFormat
  • 构造方法:
    • protected DateFormat()
  • 成员方法:
    • public String format(Date date):将日期格式化为日期时间字符串
    • public Calendar getCalendar():将日期时间格式化为日历对象
    • public TimeZone getTimeZone():获取时区
    • public void setCalendar(Calendar newCalendar)
    • public void setTimeZone(TimeZone zone)
    • public Date parse(String source):将字符串格式化为Date类型
  • 子类
    • 构造方法
      • SimpleDateFormat()
      • SimpleDateFormat(String pattern):指定模式创建对象
      • SimpleDateFormat(String pattern,DateFormatSymbols formatSymbols)
      • SimpleDateFormat(String pattern,Locale locale)
    • 成员方法
      • public void applyLocalizedPattern(String pattern)
      • public void applyPattern(String pattern)
      • public boolean equals(Object obj)
      • public DateFormatSymbols getDateFormatSymbols()
      • public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
      • public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos)
      • public Date parse(String text, ParsePosition pos)
      • public String toLocalizedPattern()
      • public String toPattern()

54. Calendar类

  • 包:java.util.Calendar

  • 构造方法:

    • protected Calendar():创建日历对象
    • protected Calendar(TimeZone zone,Locale aLocale):创建指定时区和区域设置的日历对象
  • 成员方法:

    • public abstract void add(int field, int amount):为指定字段添加值

      • Calendar.YEAR
      • Calendar.MONTH
      • Calendar.DATE
      • Calendar.HOUR
      • Calendar.MINUTE
      • Calendar.SECOND
    • public boolean after(Object when):判断是否为参数时间之后的时间

    • public boolean before(Object when):判断是否为参数时间之前的时间

    • public boolean equals(Object obj):判断两个日期是否相同

    • public String getCalendarType():获取Calendar的的日历类型

    • public int get(int field):获取指定日历字段的值

      • Calendar.YEAR
      • Calendar.MONTH
      • Calendar.DATE
      • Calendar.HOUR
      • Calendar.MINUTE
      • Calendar.SECOND
    • public void set(int field, int value):为指定字段设置值

      • Calendar.YEAR
      • Calendar.MONTH
      • Calendar.DATE
      • Calendar.HOUR
      • Calendar.MINUTE
      • Calendar.SECOND
    • public static Calendar getInstance():使用默认时区获取日历对象

    • public Date getTime():获取Date对象

    • public long getTimeInMillis():获取当前时间毫秒值

    • public TimeZone getTimeZone():获取时区

    • public void set(int year, int month, int date, int hourOfDay, int minute, int second):设置字段值

    • public void setTimeZone(TimeZone value):设置指定时区

    • public void setTime(Date date):使用Date对象设置日历时间

    • public void setTimeInMillis(long millis):使用毫秒值设置日历时间

    • public Instant toInstant():将日历对象转为Instant对象

      • Instant:在时间线上的瞬间点。
      • 调用了Instant的toString方法,输出此瞬间使用ISO-8601表示形式的字符串表示形式。

55. 获取当前时间

  • 时间戳
//方法一:耗时最短
System.currentTimeMillis();
//方法二:耗时最长,效率最低
Calendar.getInstance().getTimeInMillis();
//方法三:耗时第二
new Date().getTime();
  • 时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳

56. 基本类型包装类

1. 概述

  • 把基本数据类型封装成类类型,然后我们就可以使用类中的方法来处理数据
  • 常用操作:用于基本数据类型和字符串之间的转换

2. 包装类

  • 基本类型:byte short int long float double char boolean
  • 包装类类型:Byte Short Integer Long Float Double Character Boolean

3. Integer

  • 包:java.lang.Integer

  • 字段

    • BYTES:int型字节数4
    • MAX_VALUE:int型最大值(2147483647)
    • MIN_VALUE:int型最小值(-2147483648)
    • SIZE:int型二进制位数32
    • TYPE:类的原始类型为int
  • 构造方法

    • Integer(int value):将int类型装箱为Integer类型
    • Integer(String s):将数字字符串装箱为Integer类型
  • 常用方法

    • public static int compare(int x,int y):比较两个int数字值
    • public int compareTo(Integer anotherInteger):比较两个Integer对象
    • public static int compareUnsigned(int x,int y) :忽略符号比较两个int数字值
    • public static Integer decode(String nm):将String解码为Integer
    • public boolean equals(Object obj):与指定对象进行比较
    • public int intValue():将Integer转为int
    • public static int parseInt(Stirng s):将字符串参数解析为(有符号)十进制整数
    • public static String toBinaryString(int i):将int型转换为二进制字符串
    • public static String toHexString(int i):将int型转换为十六进制字符串
    • public static String toOctalString(int i):将int型转换为八进制字符串
    • public String toString():转为字符串
    • public static String toString(int i):把int转为String
    • public static String toString(int i,int radix):把int转为指定进制字符串,radix为基数
    • public static Integer valueOf(int i):将int类型转为Integer对象
    • public static Integer valueOf(String s):将数字字符串转为Integer对象

最大进制为36进制:0-9,A-Z

4. 自动拆箱装箱

从JDK1.5+,支持自动装箱(基本数据类型 -> 包装类型),支持自动拆箱(包装类型 -> 基本数据类型)

Integer it = new Integer(555);
		||
Integer it = 555; 	//自动装箱:把基本数据类型自动装换为包装类类型

it = it + 666;		//自动拆箱:把包装类类型自动转换为基本数据类型

//如果it为null,会报NullPointerException异常
Integer it2 = null;
it2 += 10;

5. Character

  • 字段
  • 构造方法
    • public Character(char value):将char类型装箱成Character
  • 成员方法
    • public char charValue():返回Character的值
    • public static int compare(char x,char y):比较两个char值
    • public int compareTo(Charater anotherCharacter):比较两个Character对象
    • public boolean equals(Object obj):比较两个Character对象
    • public static boolean isDigit(char ch):确定字符是否是数字
    • public static boolean isLetter(char ch):确定字符是否为一个字母
    • public static boolean isLetterOrDigit(char ch):确定指定字符是字母还是数字
    • public static boolean isLowerCase(char ch):确定字符是否为小写字符
    • public static boolean isUpperCase(char ch):确定字符是否为大写字符
    • public static boolean isSpaceChar(char ch):确定字符是否为空格字符
    • public static boolean isWhitespace(char ch):确定字符是否为空格字符
    • public static char toLowerCase(char ch):字符转小写
    • public static char toUpperCase(cahr ch):字符转大写
    • public String toString()
    • public static Character valueOf(char c):将char装箱为Character对象

6. Boolean

  • 成员变量
    • FALSE
    • TRUE
    • TYPE
  • 构造方法
    • public Boolean(boolean value):只能是true和false
    • public Boolean(String s):只有字符串为true时,为true,其它都为false
  • 成员方法
    • public boolean booleanValue():将Boolean拆箱为boolean类型
    • public static int compareTo(Boolean b):比较两个Boolean类型
    • public boolean equals(Object obj):比较boolean的值是否相等
    • public static boolean parseBoolean(String s):将字符串转为boolean值
    • public static Boolean valueOf(boolean b):将boolean装箱为Boolean类型
    • public static Boolean valueOf(String s):将字符串转为Boolean类型

7. Double

  • 包:java.lang.Double
  • 字段
    • BYTES:用于表示double型字节数
    • MAX_VALUE:最大正有限值
    • MIN_VALUE:最小的非零有限值
    • NaN:Not a Number
    • SIZE:double所占位数
    • TYPE:返回类型
  • 构造方法
    • public Double(double value):将double类型变量装箱为Double类型对象
    • public Double(String s):通过字符串的值创建一个Double对象
  • 成员方法
    • public byte byteValue():将Double对象拆箱为byte类型
    • public int compareTo(Double anotherDouble):比较两个Double对象
    • public double doubleValue():将Double对象拆箱为double对象
    • public boolean equals(Object obj):比较两个对象的值
    • public float floatValue():将Double对象拆箱为float类型
    • public int intValue():返回Double对象的int值
    • public boolean isNaN():判断是否为非数字
    • public static double parseDouble(Stirng s):将String转为double型
    • public short shortValue():返回Double对象的short值
    • public static String toHexString(double d):返回double类型的十六进制字符串
    • public static Double valueOf(double d):将double 装箱为Double对象
    • public static Double valueOf(String s):将字符串转为Double对象

57. 正则表达式

1. 概述

  • 是一种用来描述匹配特定规则文本的字符串

2. 正则表达式的规则

  • 本处仅描述常用规则,其他详见 “正则表达式.md”
    • 普通字符
      • a,b,c.......
      • 0,1,2.......
    • 转义字符
      • \r:回车符
      • \n:换行符
      • \s:任意空字符
      • \t:制表符
      • \f:换页符
    • 预定义字符
      • 点:任意字符
      • \d:所有数字
      • \D:非数字
      • \b:单词边界
      • \B:非单词边界
      • \w:单词字符
      • \W:非单词字符
    • 字符类
      • [abc]:a或b或c
      • [^abc]:除了a,b,c
      • [a-zA-Z]:所有大小写字母
      • [0-9]:0到9所有数字
    • 边界匹配器
      • ^:行的开头
      • $:行的结尾
    • 数量词
      • ?:一次或零次
      • *:零次或多次
      • +:一次或多次
      • {n}:出现n次
      • {n,m}:出现n到m次
      • {n,}:至少出现n次

3. 正则表达式的应用

  • 判断
    • public boolean matches(String regex)
  • 分割
    • public String [ ] split(String regex)
  • 替换
    • public String replaceAll(String regex,String replacement)
  • 获取
    • Pattern类和Matcher类
//将正则表达式编译为模式对象
Pattern p = compile("\\d+");
//通过模式对象获取匹配器对象
Matcher m = p.matcher("zhiyuandnc666");
//调用匹配器的功能
boolean b = m.matches();
boolean b2 = m.lookingAt();
System.out.println(b);
System.out.println(b2);
System.out.println(b3); 
//返回结果
//false
//false
//true
String str = "momo is a da shuai ge,zhen de shi yi ge da shuai ge,da shuai ge,shuai ge.";
Pattern p2 = Pattern.compile("\\b\\w{3}\\b");
Matcher m2 = p2.matcher(str);
while(m2.find()){
	String group = m2.group();
	System.out.println(group);
}

58. BigInteger

  • 包:java.math.BigInteger

  • 字段

    • ONE:常数一
    • TEN:常数十
    • ZERO:常数零
  • 构造方法

    • public BigInteger(byte [ ] val)
    • public BigInteger(int signum,byte [ ] magnitude)
    • public BigInteger(String val)
  • 成员方法

    • public BigInteger add(BigInteger val):加法运算

    • public BigInteger subtract(BigInteger val):减法运算

    • public BigInteger divide(BigInteger val):除法运算

    • public BigInteger multiply(BigInteger val):乘法运算

    • public BigInteger remainder(BigInteger val):取余运算

    • public BigInteger pow(int exponent):次方运算

    • public BigInteger [ ] divideAndRemainder(BigInteger val):返回包含商和余数的BigInteger数组

    • public int intValue():转为int

59. BigDecimal

  • 浮点型数据操作容易损失精度

    System.out.println(0.01+0.09);
    System.out.println(0.08+0.02);
    System.out.println(1.0-0.33);
    System.out.println(1.015*100);
    System.out.println(1.211/100);
    //输出结果
    0.09999999999999999
    0.1
    0.6699999999999999
    101.49999999999999
    0.012110000000000001
    

    因为浮点型的存储形式参考:https://blog.csdn.net/u014470361/article/details/79820892,导致超过16位的浮点数的精度丢失,我们需要更精确的结果,所以出现了BigDecimal类型,用于对超过16位有效位浮点数进行精确运算

  • 包:java.math.BigDecimal

  • 字段:

  • 构造方法

    • BigDecimal(int)

      创建一个具有参数所指定整数值的对象

    • BigDecimal(double)

      创建一个具有参数所指定双精度值的对象

    • BigDecimal(long)

      创建一个具有参数所指定长整数值的对象

    • BigDecimal(String)

      创建一个具有参数所指定以字符串表示的数值的对象

  • 成员方法

    • add(BigDecimal)

      BigDecimal对象中的值相加,返回BigDecimal对象

    • subtract(BigDecimal)

      BigDecimal对象中的值相减,返回BigDecimal对象

    • multiply(BigDecimal)

      BigDecimal对象中的值相乘,返回BigDecimal对象

    • divide(BigDecimal)

      BigDecimal对象中的值相除,返回BigDecimal对象

    • toString()

      将BigDecimal对象中的值转换成字符串

    • doubleValue()

      将BigDecimal对象中的值转换成双精度数

    • floatValue()

      将BigDecimal对象中的值转换成单精度数

    • longValue()

      将BigDecimal对象中的值转换成长整数

    • intValue()

      将BigDecimal对象中的值转换成整数

60. StringTokenizer

  • 包:java.util.StringTokenizer
  • 构造方法:
    • public StringTokenizer(String str)
    • public StringTokenizer(String str,String delim)
      • str:字符串
      • delim:分隔符
    • public StringTokenizer(String str,String delim,boolean returDelims)
      • str:字符串
      • delim:分隔符
      • returnDelims:是否返回分隔符
  • 成员方法
    • public int countTokens():统计分隔符出现的次数
    • public boolean hasMoreElements():判断字符串中是否存在下一个元素
    • public Object nextElement():获取字符串中的下一个元素

61. 集合

  • 概述:集合就是java中用来存储多个不同引用类型的对象的容器

  • 数组和集合的区别

    • 数组长度固定,既可以存储普通类型,也可以存储引用类型,但是只能存一种数据类型
    • 集合长度可变,只能存储引用类型,但是可以存储不同引用类型
  • 集合特点

    • 智能存储对象,集合长度是可变的,集合可以存储不同类型的对象
  • 集合框架

    集合
Collection
Map

一. Collection集合(顶层接口)

  • 包:java.util.Collection

  • Collection集合层次结构中的顶层接口

  • 常用方法

    • public boolean add(E e):将指定元素加入集合
    • public boolean addAll (Collection<? extends E>):将指定集合所有元素加入集合
    • public void clear():删除集合中所有元素
    • public boolean contains(Object o):判断集合中是否存在指定元素
    • public boolean containsAll(Collection<?> c):判断集合中是否存在指定集合所有元素
    • public boolean equals(Object o):比较指定对象与集合是否相等
    • public int hashCode():返回此集合的哈希码值
    • public boolean isEmpty():判断集合是否为空
    • public Iterator iterator():返回集合的元素迭代器
    • public boolean remove(Object o):从集合中移除指定元素
    • public boolean removeAll(Collection<?> c):从集合中移除指定集合中所有元素
    • public boolean retainAll(Collection<?> c):仅保留集合中指定集合的所有元素,返回原集合是否被修改
      • 原集合如果没有指定集合中的元素,原集合置空
      • 如果两个集合元素相同,原集合保持不变
      • 如果原集合包含指定集合元素,则保留两者交集
    • public int size():返回集合的元素个数
    • public Object[ ] toArray():将集合转为数组
  • Collection集合框架

img

1. List接口(继承自Collection接口,JDK1.2)【列表】

  • 包:java.util.List

  • List接口继承自Collection接口

  • 特点:

    • 有序的集合(存储和取出元素顺序相同)
    • 允许存储重复的元素
    • 有索引,可以使用for循环直接遍历(这也是有序的一个表现)
  • 实现类:

    • ArrayList
      • 结构是数组,查询快,增删慢:线程不安全,效率高
    • LinkedList
      • 结构是链表,查询慢,增删快:线程不安全,效率高
    • Vector
      • 结构是数组,查询快,增删慢:线程安全,效率低
      • Stack
  • 常用方法:

    • public void add(int index,E element):将指定元素加入到集合指定索引处
    • public boolean addAll(int index,Collection<? extends E> c):将指定集合从指定索引处加入集合
    • public E get(int index):获取集合中指定索引处元素值
    • public int indexOf(Object o):指定元素在集合中第一次出现的索引值
    • public int lastIndexOf(Object o):指定元素在集合中最后一次出现的索引值
    • public ListIterator listIterator()
    • public ListIterator listIterator(int index)
    • public E remove(int index):移除集合中指定索引的元素
    • public boolean remove(Object o):移除集合中指定元素
    • public boolean removeAll(Collection<?> c):从集合中移除指定集合中的内容
    • public E set(int index,E element):设置集合中指定索引处元素值
    • public int size():获取集合元素个数
    • public Object[ ] toArray():将集合转为Object数组
    • public List subList(int fromIndex,int toIndex):从集合中取出一段集合

2. Set接口(继承自Collection接口,JDK1.2)【集合】

  • 包:java.util.Set
  • Set接口继承自Collection接口
  • 特点:
    • 无序的集合
    • 不允许有重复的元素
    • 没有索引,无法用for循环直接遍历,需要迭代器(这也是无序的一个表现)
  • 实现类:
    • TreeSet
    • HashSet
      • LinkedHashSet
  • 常用方法:
    • public boolean add(E e):若指定元素在集合中不存在,则加入集合
    • public boolean addAll(Collection <? extends E> c):若指定集合中的元素在集合中不存在,则加入集合
    • public void clear():删除集合中所有元素
    • public boolean contains(Object o):判断集合中是否包含指定元素
    • public boolean containsAll(Collection<? extends E> c):判断集合中是否包含制定集合中的所有元素
    • public boolean equals(Object o):比较两个集合是否相等
    • public boolean isEmpty():判断集合是否为空
    • public Iterator iterator():返回集合元素迭代器
    • public boolean remove(Object o):从集合中删除指定元素
    • public boolean removeAll(Collection<? extends E> c):从集合中删除指定集合中包含的所有元素
    • public boolean retainAll(Collection<? extends E> c):取交集
    • public int size():返回集合中元素的个数
    • public Object[ ] toArray():返回集合对象数组
    • public T[ ] toArray(T [ ] a):返回指定类型的集合对象数组

3. Queue接口(继承自Collection接口,JDK1.5)【队列】

  • Queue接口继承自Collection接口
  • 注意事项:
    • Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素,它们的优点是通过返回值可以判断是否成功,add()和remove()方法在失败的时候会抛出异常
    • LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用
  • 特点:
    • 先进先出,先添加的元素,最先被删除
    • 队列是一种特殊的线性表,他只允许在表的前端删除元素,在表的后端插入元素
  • 实现类:
    • ArrayDeque
    • PriorityQueue
  • Queue集合框架
img

(1). Deque接口(继承自Queue接口)【双端队列】

  • Deque接口继承自Queue接口,同时具有队列和栈的功能
  • 实现类
    • LinkedList:使用双向链表
    • ArrayDeque:使用循环数组实现双向队列

(2). AbstractQueue类(实现自Queue接口)

  • 子类
    • PriorityQueue:优先队列,使用数组实现堆的结构,线程不安全
    • PriorityBlockingQueue:优先阻塞队列,使用ReentrantLock锁来保持同步,线程安全

二. Map集合(顶层接口)

  • 包:java.util.Map
  • Map集合层次接口中的顶层接口
  • 常用方法:
    • public void clear():删除Map集合中所有元素
    • public boolean containsKey(Object key):判断集合中是否包含指定键
    • public boolean containsValue(Object value):判断集合中是否包含指定值
    • public boolean equals(Object o):判断集合是否相同
    • public boolean isEmpty():判断集合是否为空
    • public V get(Object key):获取指定键对应的值
    • public Set keySet():获取Map集合中键的Set集合
    • public Set<Map.Entry<K,V>> entrySet():获取Map集合映射关系的Set集合
    • public V put(K key,V value):将指定键值对加入Map集合
    • public void putAll(Map<? extends K,? extends V> m):将指定集合中的所有映射加入到当前集合
    • public int size():返回Map集合中映射数量
    • public Collection values():获取Map集合中值的Collection集合
  • Map集合框架
img

三. Iterator迭代器

  • 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
  • 包:java.util.Iterator
  • 获取迭代器对象的方式
    • iterator()
  • 常用方法:
    • next():获取集合中的下一个元素
    • hasNext():判断集合中是否存在下一个元素
    • remove():删除迭代器已迭代元素
 public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("String");
        c.add(123);
        c.add(true);
        c.add('a');
        c.add(12.0);
        System.out.println(c);
        Iterator it = c.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        for(;it.hasNext();){
            System.out.println(it.next());
        }
    }

四. Spliterator接口(JDK1.8)

  • 包:java.util.Spliterator
    • Spliterator是Java 8中加入的一个新接口;这个名字代表“可拆分迭代器”(splitable iterator)。
    • 和Iterator一样,Spliterator也用于遍历数据源中的元素,但它是为了并行执行而设计的。
    • Java 8已经为集合框架中包含的所有数据结构提供了一个默认的Spliterator实现。
    • 集合实现了Spliterator接口,接口提供了一个spliterator方法。
  • 获取迭代器对象的方法:
    • spliterator()

五. ListIterator接口(JDK1.2)

  • 包:java.util.ListIterator
  • 常用方法:
    • public void add(E e)
    • public boolean hasNext()
    • public boolean hasPrevious()
    • public E next()
    • public int nextIndex()
    • public E previous()
    • public int previousIndex()
    • public void remove()
    • public void set(E e)

62. ArrayList可变数组

  • 数组的长度不可以发生改变
  • ArrayList的长度可以发生改变
  • 包:java.util.ArrayList
  • 构造方法:
    • ArrayList():构造一个初始容量为10的空列表
    • ArrayList(Collection<? extends E> c):构造一个包含指定集合的元素列表
    • ArrayList(int initialCapacity):构造具有初始容量的空列表

ArrayList

代表泛型

泛型:也就是装在集合中的所有元素属于什么类型,注意泛型只能是引用类型,不能是基本类型

1. 格式:

ArrayList<String> list = new ArrayList<String>();

ArrayList list = new ArrayList();

从JDK1.8开始,右侧尖括号内部可以不写内容,默认与左边相同,JDK1.8之前需要写明内容,但是建议写上

  • 注意事项:
    • 对于ArrayList集合来说,直接打印得到的不是地址值,而是内容,如果内容为空,得到的是空的中括号

2. 向集合中添加一些数据,需要用到add方法

list.add("赵丽颖");
list.add("古力娜扎");
list.add("迪丽热巴");
list.add("胡歌");
System.out.println(list);
  • 注意事项:
    • 添加的数据类型必须为创建ArrayList集合时使用的类型,否则为错误写法

3. 常用方法

  • public boolean add(E e):向列表中添加元素,参数类型和创建列表时的泛型一致,返回值为添加是否成功
  • public E get(int index):从列表中获取元素,参数是索引编号,返回值就是对应位置的元素
  • public E set(int index,E element):为列表中指定位置的元素设置值
  • public E remove(int index):从列表当中删除元素,参数是索引编号,返回值就是被删除的元素
  • public int size():获取列表的尺寸,返回值是集合中元素的个数
  • public void clear():从列表中删除所有元素
  • public boolean addAll(Collection c):将指定集合的内容(按迭代器返回的顺序)追加到列表的末尾
  • public boolean contains(Object o):如果列表中包含指定元素,则返回true
  • public int indexOf(Object o):返回此列表指定元素第一次出现的位置,如果不存在,返回-1
  • public int lastIndexOf(Object o):返回此列表指定元素最后一次出现的位置,如果不存在,则返回-1
  • public boolean isEmpty():判断列表是否为空
  • public Object [ ] toArray():将列表转为数组
  • public void trimToSize():将列表的容量修改为列表当前大小

4. 遍历ArrayList集合

for(int i = 0;i < list.size();i++){
    System.out.println(list.get(i));
}

5. ArrayList存储基本数据类型

  • 如果希望ArrayList当中存储基本数据类型,就必须使用基本数据类型的 “包装类”

包装类:是引用类型,包装类都位于java.lang包下

基本数据类型 包装类

byte Byte

short Short

int Integer

long Long

float Float

double Double

char Character

boolean Boolean

从JDK1.5+,支持自动装箱(基本数据类型 -> 包装类型),支持自动拆箱(包装类型 -> 基本数据类型)

  • 定义格式
ArrayList<Integer> list = new ArrayList<Integer>();

6. ArrayList的遍历(三种)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListDemo {
    public static void main(String args[]){
        List<String> list = new ArrayList<String>();
        list.add("鞠婧祎");
        list.add("佟丽娅");
        list.add("迪丽热巴");
        //方法一:Iterator(迭代器)遍历
        Iterator it1 = list.iterator();
        while(it1.hasNext()){
            System.out.println(it1.next());
        }
        for(Iterator it2;it2.hasNext();){
            System.out.println(it2.next());
        }
        //方法二:foreach遍历
        for(String li:list){
            System.out.println(li);
        }
        //方法三:for循环遍历
        for(int i = 0;i < list.size();i++){
            System.out.println(list.get(i));
        }
    }    

63. LinkedList

  • 包:java.util.LinkedList
  • 构造方法:
    • LinkedList():构造一个空链表
    • LinkedList(Collection<? extends E> c):构造一个包含指定集合的空链表

1. 格式:

LinkedList<Student> list = new LinkedList<Student>();

2. 常用方法:

  • public boolean add(E e):将指定元素追加到集合末尾
  • public void add(int index,E element):在集合中指定位置插入指定元素
  • public boolean addAll(Collection<? extends E> c):将指定集合内容加入到集合末尾
  • public boolean addAll(int index,Collection<? extends E> c):将指定集合内容从指定位置加入到集合
  • public void addFirst(E e):在集合首部加入指定元素
  • public void addLast(E e):在集合末尾加入指定元素
  • public void clear():删除集合中所有元素
  • public boolean contains(Object o):判断集合中是否包含指定元素
  • public E element():
  • public E get(int index):获取集合中指定位置的元素
  • public E getFirst():返回集合中第一个元素
  • public E getLast():返回集合中最后一个元素
  • public int indexOf(Object o):返回指定元素在集合中第一次出现的位置
  • public int lastIndexOf(Object o):返回指定元素在集合中最后一个出现的位置
  • public ListIterator listIterator(int index):获取迭代器
  • public boolean offer(E e):将指定元素添加到集合的尾部
  • public boolean offerFirst(E e):将指定元素添加到集合的首部
  • public boolean offerLast(E e):将指定元素添加到集合的尾部
  • public E element():检索但不删除链表的头部(第一个元素)
  • public E peek():检索但不删除链表的头部(第一个元素)
  • public E peekFirst():检索但不删除链表的第一个元素,如果链表为空,返回null
  • public E peekLast():检索但不删除链表的最后一个元素,如果链表为空,返回null
  • public E poll():检索并删除链表的头(第一个元素)
  • public E pollFirst():检索并删除链表的第一个元素,如果链表为空,返回null
  • public E pollLast():检索并删除链表的最后一个元素,如果链表为空,返回null
  • public E pop():
  • public void push(E e):
  • public E remove():检索并删除链表的头(第一个元素)
  • public E remove(int index):删除链表中指定位置元素
  • public E removeFirst()
  • public E removeLast()
  • public E set(int index,E element)
  • public int size()
  • public Spliterator spliterator()
  • public Object[ ] toArray()
  • public T[ ] toArray(T[ ] a)

64. Vector

  • 包:java.util.Vectory
  • 构造方法:
    • Vector():构造一个初始容量为10的空集合
    • Vector(Collection<? extends E> c):构造一个包含指定集合的集合
    • Vector(int initialCapacity):构造一个指定容量的集合
    • Vector(int initialCapacity,int capacityIncrement):构造一个指定容量指定增量的集合

1. 格式:

Vector v = new Vector(16,2);

2. 常用方法:

  • public boolean add(E e):将指定元素追加到集合末尾
  • public void add(int index,E element):将指定元素插入到集合指定位置
  • public boolean addAll(Collection<? extends E> c):将指定集合中所有内容追加到当前集合末尾
  • public boolean addAll(int index,Collection<? extends E> c):将指定集合中所有内容插入到集合指定位置
  • public void addElement(E obj):将指定组件添加到集合的末尾
  • public int capacity():获取集合容量
  • public void clear():删除集合中所有元素
  • public boolean contains(Object o):判断集合中是否包含指定元素
  • public boolean containsAll(Collection<?> c):判断集合中是否包含指定集合的所有元素
  • public void copyInto(Object[ ] anArray):将集合复制到指定数组中
  • public E elementAt(int index):获取指定索引处的元素
  • public Enumeration elements():获取集合的元素枚举
  • public boolean equals(Object o):将集合与指定对象比较
  • public E firstElement():返回集合的第一个元素
  • public void forEach(Consumer<? super E> action):
  • public E get(int index):获取指定位置的元素
  • public int indexOf(Object o):返回集合中指定元素第一次出现的索引
  • public int indexOf(Object o,int index):返回集合中指定元素从指定位置开始第一次出现的索引
  • public boolean isEmpty():检查集合是否为空
  • public Iterator iterator():迭代器
  • public E lastElement():返回集合中最后一个元素
  • public int lastIndexOf(Object o):返回集合中指定元素最后一次出现的索引
  • public int lastIndexOf(Object o,int index):返回集合中指定元素从指定位置开始最后一次出现的索引
  • public ListIterator listIterator():获取迭代器
  • public E remove(int index):删除集合中指定元素
  • public boolean removeAll(Collection<?> c):从集合中删除指定集合的所有元素
  • public void replaceAll()
  • public boolean retainAll(Collection<?> c):保留两集合的交集
  • public E set(int index,E element):设置集合中指定位置的元素值
  • public void setSize(int newSize):设置集合容量
  • public int size():获取集合元素个数
  • public List subList(int fromIndex,int toIndex):从集合中截取一段集合
  • public Object [ ] toArray():转Object数组
  • public T [ ] toArray(T [ ] a):转指定类型数组
  • public void trimToSize():修改容量

65. Enumeration:java.util中的接口,现已被Iterator取代,与enum没有关系

66. 泛型

  • 概述:限定统一存储数据类型的一种特殊类型
    • 泛型的实现是靠类型擦除技术 类型擦除是在编译期完成的 也就是在编译期 编译器会将泛型的类型参数都擦除成它的限定类型,如果没有则擦除为object类型之后在获取的时候再强制类型转换为对应的类型。 在运行期间并没有泛型的任何信息,因此也没有优化。
  • 格式:<数据类型>
    • 数据类型必须是引用类型
  • 好处:
    • 类型参数化,让应用类型像参数一样传递
    • 使运行时异常变为编译时异常,减少了编译时的类型检查
    • 泛型方法
    • 减少类型转换,提高代码的安全性
  • 用法:
    • 泛型接口
    • 泛型抽象类
    • 泛型类
    • 方法参数类型约束
    • 方法返回类型约束

1. 泛型类

public class 类名<泛型类型>{}

2. 泛型方法

public <泛型返回类型> 返回类型 方法名(泛型类型形参){}

3. 泛型接口

interface 接口名<泛型类型>{}

4. 泛型抽象类

public abstract class 类名<泛型类型>{}

5. 泛型通配符

<?>任意类型,如果不明确,那就是Object以及任意java类
<? extends E>向下限定,E及其子类
<? super E>向上限定,E及其父类

67. 增强for循环

for(元素数据类型 临时变量:多元素容器){
    System.out.println(临时变量);
}
  • 实现原理
for (Integer i : list) {
   System.out.println(i);
}

反编译后

Integer i;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){
   i = (Integer)iterator.next();        
}

68. 静态导入

  • 直接导入到方法级别(方法必须是静态的)

    • import static packageName.className.methodName;
  • 注意事项:

    • 方法必须为静态方法
    • 如果有多个静态方法,就不知道用哪个,就需要加方法的绝对包路径进行调用
  • 例:

import static java.lang.Math.abs;
import static java.lang.Math.pow;
import static java.lang.Math.max;
public class Demo{
    public static void main(String [] args){
        System.out.println(abs(111));
        System.out.println(pow(3,2));
        System.out.println(max(3,2));
    }
}

69. 可变参数

  • 概述:
    • 不知道方法将来需要多少个参数
  • 格式:
    • 修饰符 返回值类型 方法名(数据类型... 变量名){ }
  • 注意:
    • 这里的变量其实是一个数组
    • 如果一个方法既有可变参,也有其他参数,那么,可变参必须是最后一个
    • 可变参只能有一个,且放在参数列表最后
  • 例:
public static int sum(int... a){
    return a;
}
public static String method(Object... o){
    return o.toString();
}

70. TreeSet

  • 包:java.util.TreeSet
  • 父类:java.util.AbstractSet
  • 实现了SortedSet接口
  • 构造方法:
    • public TreeSet():构造一个空的集合
    • public TreeSet(Collection<? extends E> c):构造一个包含指定集合元素的新集合,根据元素自然排序进行排序
    • public TreeSet(Comparator<? super E> comparator):按照指定比较器的顺序构造一个空的集合
    • public TreeSet(SortedSet s):构造一个包含指定集合元素的新集合,按照默认排序
  • 常用方法:
    • public boolean add(E e):将指定元素添加到集合中
    • public boolean addAll(Collection<? extends E> c):将指定集合中所有元素加入到集合中
    • public E ceiling(E e):查找集合中大于等于指定元素的最小元素,如果没有,返回null
    • public Object clone():返回集合的浅拷贝
    • public void clear():删除集合中所有元素
    • public boolean contains(Object o):判断集合中是否包含指定元素
    • public E first():返回集合中第一个元素
    • public E floor(E e):查找集合中小于等于指定元素的最大元素,如果没有,返回null
    • public boolean isEmpty():判断集合是否为空
    • public Iterator iterator():以升序返回集合元素迭代器
    • public Iterator descendingIterator():以降序返回集合元素迭代器
    • public NavigableSet descendingSet():返回集合元素反向排序集合
    • public E last():返回集合中最后一个元素
    • public E lower(E e):查找集合中小于指定元素的最大元素,如果没有,返回null
    • public E higher(E e):查找集合中大于指定元素的最小元素,如果没有,返回null
    • public E pollFirst():检索并删除集合中第一个元素
    • public E pollLast():检索并删除集合中最后一个元素
    • public boolean remove(Object o):从集合中删除指定元素
    • public int size():返回集合中元素个数
    • public Spliterator spliterator()
    • public NavigableSet subSet(E fromElement,boolean fromInclusive,E toElement,boolean toInclusive):截取一段集合
    • public SortedSet subSet(E fromElement,E toElement):截取一段集合
    • public SortedSet tailSet(E fromElement):返回集合中大于等于指定元素的部分集合
    • public NavigableSet tailSet(E fromElement,boolean inclusive)
  • 实现原理
img

比较器

  • 包:java.lang.Comparable
  • 红黑树完成的自然排序实际上是依赖于Comparable接口中的compareTo()方法,所以想要按照自己的方式进行数据排序,必须实现此接口并重写该方法
  • 需要比较的对象的类去实现Comparable接口并重写compareTo()方法
  • 需要比较的对象的类也可以去实现Comparator接口并重写compare()方法

Comparable和Comparator的区别

  • img

71. HashSet

  • 底层数据结构是哈希表(元素是链表的数组)

  • 包:java.util.HashSet

  • 父类:java.util.AbstractSet

  • 构造方法:(初始容量16,负载因子0.75)

    • public HashSet():构造一个空集合
    • public HashSet(Collection<? extends E> c):构造一个包含指定集合中所有元素的集合
    • public HashSet(int initialCapacity):构造一个之指定容量的空集合
    • public HashSet(int initialCapacity,float loadFactor):构造一个指定容量,指定负载因子的空集合
  • 常用方法:

    • public boolean add(E e):将指定元素加入集合

    • public void clear():删除集合中所有元素

    • public Object clone():返回HashSet的浅拷贝

    • public boolean isEmpty():判断集合是否为空

    • Iterator iterator():返回集合元素迭代器

    • public boolean remove(Object o):移除集合中指定元素

    • public int size():返回集合中元素个数

72. LinkedHashSet

  • 底层数据结构是哈希表(元素是双向链表的数组)
  • 元素存取有序
  • 包:java.util.LinkedHashSet
  • 父类:java.util.HashSet
  • 构造方法:
    • public LinkedHashSet():构造一个默认容量16,负载因子0.75的空集合
    • public LinkedHashSet(Collection<? extends E> c):构造包含指定集合的集合
    • public LinkedHashSet(int initialCapacity):构造指定初始容量的空集合
    • public LinkedHashSet(int initialCapacity,float loadFactor):构造指定初始容量和指定负载因子的空集合
  • 常用方法:继承自父类(HashSet的方法)
    • public boolean add(E e):将指定元素加入集合
    • public void clear():删除集合中所有元素
    • public Object clone():返回HashSet的浅拷贝
    • public boolean isEmpty():判断集合是否为空
    • Iterator iterator():返回集合元素迭代器
    • public boolean remove(Object o):移除集合中指定元素
    • public int size():返回集合中元素个数

73. HashMap

  • 包:java.util.HashMap
  • 构造方法:
    • public HashMap():构造一个空的HashMap集合,初始容量为16,负载因子为0.75
    • public HashMap(int initialCapacity):构造一个指定容量的HashMap集合
    • public HashMap(int initialCapacity,int loadFactor):构造一个具有指定容量和指定负载因子的HashMap集合
    • public HashMap(Map<? extends K,? extends V> m):构造一个包含指定Map集合所有映射关系的HashMap集合
  • 常用方法:
    • public void clear():删除集合中所有映射关系
    • public Object clone():获得集合的浅拷贝
    • public boolean containsKey(Object key):判断集合中是否包含指定键
    • public boolean containsValue(Object value):判断集合中是否包含指定值
    • public boolean isEmpty():判断集合是否为空
    • public V get(Object key):获取指定键对应的值
    • public Set keySet():获取Map集合中键的Set集合
    • public Set<Map.Entry<K,V>> entrySet():获取Map集合映射关系的Set集合
    • public V put(K key,V value):将指定键值对加入Map集合(若指定键存在,则将指定值与之关联,说白了就是修改映射关系)
    • public void putAll(Map<? extends K,? extends V> m):将指定集合中的所有映射加入到当前集合
    • public int size():返回Map集合中映射数量
    • public Collection values():获取Map集合中值的Collection集合
    • public V remove(Object key):删除指定键对应的键值对
    • public boolean remove(Object key,Object value):删除唯一对应关系的键值对

74.LinkedHashMap

  • 包:java.util.LinkedHashMap
  • 父类:java.util.HashMap
  • 构造方法:
    • public LinkedHashMap():构造一个空的LinkedHashMap集合,初始容量为16,负载因子为0.75
    • public LinkedHashMap(int initialCapacity):构造一个指定容量的LinkedHashMap集合
    • public LinkedHashMap(int initialCapacity,int loadFactor):构造一个具有指定容量和指定负载因子的LinkedHashMap集合
    • public LinkedHashMap(Map<? extends K,? extends V> m):构造一个包含指定Map集合所有映射关系的LinkedHashMap集合
    • public LinkedHashMap(int initialCapacity,int loadFactor,boolean accessOrder):构造一个具有指定初始容量和指定负载因子以及制定订购模式的LinkedHashMap集合
  • 常用方法:
    • public void clear():删除集合中所有映射关系
    • public boolean containsValue(Object value):判断集合中是否存在指定值
    • public Set<Map.Entry<K,V>> entrySet():获取集合中所有映射关系组成的Set集合
    • public V get(Object key):获取指定键对应的值
    • public Set keySet():获取集合中所有键组成的Set集合
    • public Collection values():获取集合中所有键组成的Collection集合

75. TreeMap

  • 包:java.util.TreeMap

  • 父类:java.util.AbstractMap

  • 构造方法:

    • public TreeMap():构造一个(默认使用键自然排序)空TreeMap集合
    • public TreeMap(Comparator<? super K> comparator):构造一个(按照指定比较器规则排序)空TreeMap集合
    • public TreeMap(Map<? extends K,? extends V> m):构造一个包含指定Map集合所有映射关系的(按照指定Map集合中key的自然排序)TreeMap集合
    • public TreeMap(SortedMap<K,? extends V> m):构造一个包含指定SortedMap集合中所有映射关系的并按照SortedMap中的排序规则排序的TreeMap集合
  • 常用方法:

    • public void clear():删除集合中所有映射关系
  • public Object clone():获取集合的浅拷贝

    • public Comparator<? super K> comparator():返回集合的比较器
  • public boolean containsKey(Object key):判断集合中是否包含指定键

    • public boolean containsValue(Object value):判断集合中是否包含指定值
  • public NavigableSet descendingKeySet():返回相反顺序的键的Set集合

    • public NavigableMap<K,V> descendingMap():返回相反顺序的映射关系的Map集合
  • public Set<Map.Entry<K,V>> entrySet():获取集合中所有映射关系组成的Set集合

    • public Map.Entry<K,V> firstEntry():获取集合中第一个键对应的映射关系
  • public K firstKey():获取集合中第一个键的值

  • public Map.Entry<K,V> floorEntry():获取集合中最后一个键对应的映射关系

  • public K floorKey():获取集合中最后一个键的值

  • public V get(Object key):获取指定键对应的值

  • public Set keySet():获取集合中所有键构成的Set集合

  • public Collection values():获取集合中所有值构成的Collection集合

  • public V put(K key,V value):将指定映射关系加入集合中,若指定键已存在,则修改对应值

  • public void putAll(Map<? extends K,? extends V> m):将指定Map集合中所有映射关系加入集合中

  • public V remove(Object key):删除指定键对应的映射关系

  • public int size():返回集合中映射数量

76. 异常

1. 分类

  • 运行时异常:所有RuntimeException类及子类都属于运行时异常
    • 运行时异常一般是代码问题,提高代码健壮性
  • 编译时异常:除运行时异常外都属于编译时异常
    • 编译时异常必须显示处理,否则无法编译通过
  • 自定义异常

2. 处理异常

  • try...catch...finally

    • 变形格式:try...catch

      try{
          //try里的代码越少越好,因为每捕获一次异常,JVM就会启动分配一次资源
      }catch(异常名 变量名){
          //catch里面必须有内容,哪怕只给一个简单的提示
      }
      
    • 一个异常的处理,写一个try{}catch{}

    • 多个异常的处理,写一个try{},写多个catch(){},但是一个一场被捕获到就直接走catch语句了,无法捕获另外的异常

    • 注意事项:

      • 能明确的异常尽量明确,实在不明确异常再使用Exception
      • 如果有明确的异常,同级的异常前后无所谓,不同级的异常,明确的异常在前,Exception必须放置于最后,因为Exception可以匹配所有异常,是明确异常的父类的父类
    • JDK1.7出现了一个新的异常处理方案:

      try{
          
      }catch(Exception1 | Exception2 | Exception3 | ... variableName){
          
      }
      
      • 可以通过或将多个异常连接在一起,使得catch可以同时处理多个异常,但是**注意:这种方式异常中不可以有Exception**
  • 异常中需要了解的方法:

    • public String getMessage():返回异常的详细信息
    • public String toString():返回异常的简单介绍
      • 这个对象的类的name(全路径名)
      • “:”(一个冒号和一个空格)
      • 调用这个对象的**getLocalizedMessage()方法的结果,默认返回getMessage()**
    • public void printStackTrace():将toString的内容以及异常出现的位置输出到控制台
    • public void printStackTrace(PrintStream s):将异常内容保存到日志文件中,方便查看
  • throws:有些异常我们无法处理或者权限不够时,就需要将异常抛出

    • throws必须跟在方法的括号后面
    • 编译期异常被抛出,将来的调用者必须处理
    • 运行时异常被抛出,将来的调用者可以不处理
    • throws后可以跟多个异常,中间用逗号隔开
  • throw:如果出现了异常情况,我们需要抛出该异常的对象,使用throw new 异常名();

    • 使用throw一定抛出了异常
    • 使用throws可能抛出异常,并不一定会发生异常
  • throw和throws的区别

    • 放在方法声明后面,跟的是异常类名

    • 可以跟多个异常类名,用逗号隔开

    • 表示抛出异常,由该方法的调用者来处理

    • throws表示出现异常的可能性,并不一定会抛出异常


    • 放在方法体内,跟的是对象名

    • 只能抛出一个异常对象

    • 表示抛出异常,有方法体内语句处理

    • throw表示抛出了异常,执行throw一定会抛出异常

  • finally

    • 格式:try{}catch(){}finally{}
    • finally 里的语句一定会被执行
    • 用于释放资源,在IO流和数据库操作中常见
  • 如果catch中有return语句,finally中的语句依然会被执行,且在return之前

  • try...catch的几种变形

    • try...catch...finally
    • try...catch
    • try...catch...catch
    • try...catch...catch...finally
    • try...finally(主要为了释放资源)

3. 自定义异常

  • 继承自Exception

    • public class MyException extends Exception{
          public MyException(){}
          public MyException(String message){
              super(message);
          }
      }
      
      public class Teacher{
          public void checkScore(int score) throws MyException{
              if(score > 0 && score < 100){
                  System.out.println("分数正常");
              }else{
                  throw new MyException("分数不正常");
              }
          }
      }
      public class Demo{
          public static void main(String [] args){
              Teacher t = new Teacher();
              t.checkScore(50);
              t.checkScore(120);
          }
      }
      

77. I/O流

1. File类(路径名的表示形式,文件是否存在还不一定)

  • 包:java.io.File
  • 构造方法:
    • public File(File parent,String child):创建在父路径对象(File parent)下的指定子文件(String child)的File对象
    • public File(String pathname):创建一个绝对路径文件的File对象
    • public File(String parent,String child):创建在父路径(String parent)下的指定子文件(String child)的File对象
    • public File(URI uri):创建一个统一资源标识符表示的文件的File对象
  • 常用方法:
    • public boolean canExecute():测试文件是否具有可执行权限

    • public boolean canRead():测试文件是否具有可读权限

    • public boolean canWrite():测试文件是否具有可写权限

    • public int comparaTo(File pathname):

    • public boolean createNewFile():当且仅当前文件不存在时创建新文件

    • public boolean delete():删除File对象对应的文件或目录

    • public void deleteOnExit():在虚拟机终止时删除File对象对应的文件或目录

    • public boolean equals(Object obj):比较是否相等

    • public boolean exists():测试该文件是否存在

    • public File getAbsoluteFile():返回File对象的绝对路径的File对象

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

    • public String getCanonicalPath():返回File对象的规范化绝对路径,(带.和..的路径格式)

    • public File getCanoncialFile():返回File对象的规范化表示File对象

    • public String getName():获取File对象表示的文件名或目录名

    • public String getParent():获取File对象父目录的字符串

    • public File getParentFile():获取File对象父目录的File对象

    • public String getPath():将定义File对象时的路径返回

    • public long getTotalSpace():获取File对象表示的文件所在的磁盘分区的总容量(单位:Byte)

    • public long getFreeSpace():获取File对象表示的文件所在的磁盘分区的剩余容量(单位:Byte)

    • public boolean isAbsolute():判断File对象的路径名是否为绝对路径

    • public boolean isDirectory():判断File对象所表示的文件是否为目录

    • public boolean isFile():判断File对象所表示的文件是否为文件

    • public boolean isHidden():判断File对象所表示的文件是否为隐藏文件

    • public long lastModified():获取File对象所表示的文件最后一次修改时间

    • public long length():获取File对象多表示的文件的大小(单位:Byte)

    • public String[ ] list():将File对象表示的目录中所有文件名加入到字符串数组中,如果File对象不表示目录,返回null

    • public String[ ] list(FilenameFilter filter):将File对象表示的目录中所有文件名满足过滤器规则的加入到字符串数组中,如果File对象不表示目录,返回null

      • FilenameFilter文件名过滤器

        • 包:java.io.FilenameFilter(函数式接口)

        • 方法:public boolean accept(File dir,String name)

        • //匿名内部类写法
          File file = new File("E:\\壁纸");
          String [] list = file.list(new FilenameFilter(){
             @Override
              public boolean accept(File dir,String name){
                  return name.toLowerCase().endsWith(".png");
              }
          });
          //Lambda表达式写法
          File file = new File("E:\\壁纸");
          String [] list = file.list((File dir,String name) -> {
          	return name.toLowerCase().endsWith(".png");
          });        
          
    • public File[ ] listFiles():将File对象表示的目录中所有文件加入到文件对象数组中,如果File对象不表示目录,返回null

    • pubic File[ ] listFiles(FileFilter filter):将File对象表示的目录中所有满足过滤器规则的文件加入到文件对象数组中,如果File对象不表示目录,返回null

    • public File[ ] listFiles(FilenameFilter filter):将File对象表示的目录中所有满足过滤器规则的文件加入到文件对象数组中,如果File对象不表示目录,返回null

    • public static File[ ] listRoots():获取所有文件系统根目录,即盘符

      • File[] files = File.listRoots();
        for (int i = 0; i < files.length; i++) {
        	System.out.println(files[i]);
        }
        //输出结果
        C:\
        D:\
        E:\
        F:\
        
    • public boolean mkdir():创建File对象对应的目录

    • public boolean mkdirs():创建File对象对应的目录,包括任何必须但不存在的父目录

    • public boolean renameTo(File dest):重命名File对象所表示的文件

    • public Path toPath():将File对象转为Path对象(java.nio.file.Path)

    • public String toString():将File对象转为字符串

    • public String toURI():将File对象转为URI对象

    • public URL toURL():将File对象转为URL对象,已弃用,建议使用URI.toURL()方法

2. 递归

  • 概述:方法定义中调用方法本身的现象

  • 注意

    • 递归一定要有出口,否则就是死递归
    • 递归次数不能太多,否则栈内存溢出【递归次数(即方法调用次数)×方法栈帧 > 栈大小 导致 栈溢出】
    • 构造方法不能递归使用
    • //递归求阶乘
      public static void main(String [] args){
          factorical(5);
      }
      public static void factorial(int number){
          if(number==1){
              return 1;
          }else{
              return number*factorical(number-1);
          }
      }
      
    • //递归求不死神兔问题(斐波那契数列)1,1,2,3,5,8...
      public static void mian(String [] args){
          fib(10);
      }
      public static int fib(int number){
          if(number==1 || number==2){
              return 1;
          }else{
              return fib(number-1)+fib(number-2);
          }
      }
      
    • //递归查找某路径下所有以指定后缀.mp4结尾的文件
      public static void main(String [] args){
          
      }
      public static void getAllFilePathMp4(File srcFolder){
         	File[] files = srcFolder.listFiles();
          for(File f:files){
              if(f.isDirectory()){
                  getAllFilePathMp4(f);
              }else{
                  if(f.getName().endsWith(".mp4")){
                      System.out.println("Name:"+f.getName()+"["+"Path:"+f.getAbsolutePath()+"]");
                  }
              }
          }
      } 
      
    • //递归删除指定目录下所有文件
      public static void deleteAllFile(File srcFolder){
          File[] files = srcFolder.listFiles();
          if(files.length==0){
              srcFolder.delete();
          }else{
              for(File f:files){
                  if(f.isDirectory()){
                      deleteAllFile(f);
                  }else{
                      System.out.println("Name:"+f.getName()+"\tDeleteState:"+f.delete());
                  }
              }
              System.out.println("Name:"+ srcFolder.getName()+"\tDeleteState:"+srcFolder.delete());
          }
      }
      

3. 字节流(万能的流)

3.1 字节输入流(java.io.InputStream抽象类)

  • FileInputStream
    • 包:java.io.FileInputStream
    • 构造方法
      • public FileInputStream(File file)
      • public FileInputStream(FileDescriptor fdObj)
      • public FileInputStream(String name)
    • 常用方法
      • public int available()
      • public void close():关闭输入流并释放资源
      • protected void finalize()
      • public FileChannel getChannel()
      • public FileDescriptor getFD()
      • public int read()
      • public int read(byte[ ] b):从输入流中读取b.length字节的数据,返回读入b缓冲区的字节数,如果读取完毕返回-1,(一次读取多个字节数据,提高读写速度)
      • public int read(byte[ ] b,int off,int len)
      • public long skip(long n)

3.2 字节输出流(java.io.OutputStream抽象类)

  • FileOutputStream
    • 包:java.io.FileOutputStream
    • 构造方法
      • public FileOutputStream(File file)

      • public FileOutputStream(File file,boolean append)

      • public FileOutputStream(FileDescriptor fdObj)

      • public FileOutStream(String name)

      • public FileOutputStream(String name,boolean append)

      • 如果文件不存在,会自动创建,但如果目录不存在,不会自动创建,抛异常

    • 常用方法:
      • public void close():关闭输出流,并释放资源
      • protected void finalize()
      • public FileChannel getChannel()
      • public FileDescriptor getFD()
      • public void write(byte[ ] b)
      • public void write(byte[ ] b,int off,int len)
      • public void write(int b)

字符串是utf-8编码,一个汉字三个字节,一个字母一个字节。
字符串是gbk编码时,一个汉字两个字节,一个字母一个字节。

计算机如何判断将三个字节拼成一个汉字?

文件的编码格式,以及单字节的格式

不同系统的换行符识别不同

  • \r(Mac系统)
  • \n(Linux系统)
  • \r\n(Windows系统)

3.3 字节缓冲流(提高FileInputStream的读写效率)装饰设计模式

  • BufferedInputStream
    • 包:java.io.BufferedInputStream
    • 构造方法:
      • public BufferedInputStream(InputStream in)
      • public BufferedInputStream(InputStream in,int size):可以指定缓冲区byte数组大小
    • 常用方法:
      • public int read()
      • public int read(byte[ ] b,int off,int len):
      • public void reset()
      • public long skip(long n)
  • BufferedOutputStream
    • 包:java.io.BufferedOutputStream
    • 构造方法:
      • public BufferedOutputStream(OutputStream out)
      • public BufferedOutputStream(OutputStream out,int size)
    • 常用方法:
      • public void flush():刷新缓冲输出流
      • public void write(byte [ ] b,int off,int len):从指定字节数组写入len个字节
      • public void write(int b):将指定字节写入缓冲输出流

读写文件效率比较

public class Demo5 {
    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
//        methodA("E:\\视频教程\\java\\day02\\day01\\视频\\1_2020-09-09.mp4","F:\\copy1.mp4");//半个小时,我吐了
//        methodB("E:\\视频教程\\java\\day02\\day01\\视频\\1_2020-09-09.mp4","F:\\copy2.mp4");//共耗时:5246毫秒
//        methodC("E:\\视频教程\\java\\day02\\day01\\视频\\1_2020-09-09.mp4","F:\\copy3.mp4");//共耗时:13230毫秒
//        methodD("E:\\视频教程\\java\\day02\\day01\\视频\\1_2020-09-09.mp4","F:\\copy4.mp4");//共耗时:1389毫秒
        long endTime = System.currentTimeMillis();
        System.out.println("共耗时:"+ (endTime-startTime) +"毫秒");
    }
    public static void methodA(String srcFolder,String destFolder) throws IOException {
        FileInputStream fis = new FileInputStream(srcFolder);
        FileOutputStream fos = new FileOutputStream(destFolder);
        int flag = 0;
        while((flag=fis.read())!=-1){
            fos.write(flag);
        }
        fos.close();
        fis.close();
    }
    public static void methodB(String srcFolder,String destFolder) throws IOException {
        FileInputStream fis = new FileInputStream(srcFolder);
        FileOutputStream fos = new FileOutputStream(destFolder);
        int flag = 0;
        byte [] b = new byte[1024];
        while((flag=fis.read(b))!=-1){
            fos.write(b);
        }
        fos.close();
        fis.close();
    }
    public static void methodC(String srcFolder,String destFolder) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFolder));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFolder));
        int flag = 0;
        while((flag=bis.read())!=-1){
            bos.write(flag);
        }
        bos.close();
        bis.close();
    }
    public static void methodD(String srcFolder,String destFolder) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFolder));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFolder));
        int flag = 0;
        byte [] b = new byte[1024];
        while((flag=bis.read(b))!=-1){
            bos.write(b);
        }
        bos.close();
        bis.close();
    }
}

4. 字符流(为了操作文本数据,Java提供了字符流,将字节流转换为字符流)

4.1 字符输入流(java.io.Reader)

  • InputStreamReader
    • 构造方法

      • public InputStreamReader(InputStream in):使用默认字符集将字节输入流转为字符输入流
      • public InputStreamReader(InputStream in,Charset cs):创建指定字符集将字节输入流转为字符输入流
      • public InputStreamReader(InputStream in,CharsetDecoder dec):创建指定字符集解码器输入流
      • public InputStreamReader(InputStream in,String charsetName):创建指定字符集输入流
    • 常用方法

      String(byte [ ] bytes,String charsetName):通过指定字符集编码字节数组

      byte[ ] getBytes(String charsetName):使用指定字符集编码把字符串编码为字节数组

      • public void close():关闭输入流释放资源
      • public String getEncoding():返回字符集编码名称
      • public int read():读一个字符
      • public int read(char[ ] cbuf,int offset,int length):将字符输入流读入数组的一部分
      • public boolean ready():告诉这个流是否准备好被读取
      • public void mark(int readAheadLimit):标记流中当前位置
      • public boolean markSupport():告诉这个流是否支持mark操作
      • public int read(char[ ] cbuf):将字符输入流读入数组
      • public int read(CharBuffer target):将字符输入流读入指定字符缓冲区
      • public void reset():重置流
      • public long skip(long n):跳过指定长度字符

4.2 字符输出流(java.io.Writer)

  • OutputStreamWriter
    • 构造方法:
      • public OutputStreamWriter(OutputStream out):使用默认字符集将字节输出流转为字符输出流
      • public outputStreamWriter(OutputStream out,Charset cs):使用指定字符集解码将字节输出流转为字符输出流
      • public OutputStreamWriter(OutputStream out,CharsetEncoder enc):创建指定字符集解码的字符输出流
      • public OutputStreamWriter(OutputStream out,Stirng charsetName):创建指定名字编码解码的字符输出流
    • 常用方法:
      • public void close():关闭流,释放资源(会先刷新流,即调用flush()方法)
      • public void flush():刷新流
      • public String getEncoding():返回该字符输出流的编码格式
      • public void write(char[ ] cbuf,int off,int len):写入字符数组的一部分
      • public void write(int c):写入一个字符
      • public void write(String str,int off,int len):写入一个字符串的一部分
      • public Writer append(char c):追加指定字符
      • public Writer append(CharSequence csq):追加指定字符序列
      • public Writer append(CharSequence csq,int start,int end):追加指定字符序列子序列
      • public abstract void close():关闭流
      • public abstract void flush():刷新流
      • public void write(char[ ] cbuf):写入一个字符数组
      • public void write(String str):写入字符串

4.3 转换流简化写法

  • FileWriter = FileOutputStream + 字符编码
  • FileReader = FileInputStream + 字符编码

4.4 字符缓冲流

  • BufferedReader
  • BufferedWriter

复制文本文件

复制图片

把ArrayList中的字符串数据存储到文本文件

从文本文件读取一行数据保存到集合中,并遍历

复制单级文件夹

复制多级文件夹

键盘输入几个学生信息,按照年龄排序,存入文本文件

加入a.txt文件中一串字符串“asdlfjsdaldjasf”

请编写代码实现读取内容,然后排序后写入b.txt

登录注册io版

5. 打印流(只有写数据的没有读数据的)

5.1 字节打印流(java.io.PrintStream)

5.2 字符打印流(java.io.PrintWriter)

5.3 特点:

  • 只有写数据没有读取数据

  • 可以操作任意类型的数据

  • 如果启动了自动刷新,无需手动刷新

    • PrintWriter pw = new PrintWriter(new FileWriter("E:\\a.txt"),true)
      
  • 可以直接操作文本文件

6. 序列化流

序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

为什么要把Java对象序列化呢?

因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。 有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象

6.1 ObjectOutputStream(序列化)

  • 父类:OutputStream

  • 包:java.io.ObjectOutputStream

  • 构造方法:

    • public ObjectOutputStream(OutputStream out)
  • 常用方法:

    • public void writeObject(Object obj):写出对象
    //类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态序列化或反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段,仅用于标识可串行化的语义。 
    
    • public void close():关闭输出流

6.2 ObjectInputStream(反序列化)

  • 父类:InputStream

  • 包:java.io.ObjectInputStream

  • 构造方法:

    • public ObjectInputStream(InputStream in)
  • 常用方法:

    • public Object readObject():从ObjectOutputStream中读取对象
    • public void close():关闭输入流
package com.demo.javase;

import java.io.Serializable;
/**
 * @author 絷缘
 * @version 1.0
 * @date 2020/12/9 8:56
 **/
public class Student implements Serializable {
    private String name;
    private int age;
    public Student(){}
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\b.txt"));
    oos.writeObject(new Student("zhiyuan",22));
    oos.close();
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\b.txt"));
    Object o = ois.readObject();
    if(o instanceof Student){
		System.out.println(o);
	}        
}

java.io.InvalidClassException:

Exception in thread "main" java.io.InvalidClassException: com.demo.javase.Student; 
local class incompatible: stream classdesc serialVersionUID = -2157397684930231136, 
local class serialVersionUID = 1206998284855657234
  • 若在序列化对象时,类未被修改,序列化后,手动修改了类文件,那么进行反序列化时会有异常

    • 可序列化的类的版本号serialVersionUID不同,抛出InvalidClassException
  • 若在序列化对象时,类没有无参构造方法,序列化后,进行反序列化时会有异常

  • 若在序列化对象时,类中有未知的数据类型,序列恶化后,进行反序列化时会有异常

serialVersionUID和transient

  • serialVersionUID(Serializable的底层比较方式)
    • 官方手册:强烈建议声明可序列化类的版本号
    • private static final long serialVersionUID = 25L;

声明此版本号后,手动修改类文件后,反序列化未发生错误

  • transient(类中成员不想被序列化和反序列化)
    • 被transient修饰的成员,不参与序列化与反序列化,被反序列化后依据成员本身类型给默认值

7. 特殊操作流

7.1 Properties

  • 父类:java.util.Hashtable<Object,Object>

  • 包:java.util.Properties

  • Properties是一个Map体系的集合类,可以直接保存到流中或从流中加载

  • 构造方法:

    • public Properties()
    • public Properties(Properties defaults)
  • 常用方法:

    • public String getProperty(String key):根据指定key,获取value
    • public String getProperty(String key,String defaultValue)
    • public void list(PrintStream out):把属性列表打印到指定输出流
    • public void list(PrintWriter out):把属性列表打印到输出流
    • public void load(InputStream in):从输入字节流读取属性列表
    • public void load(Reader reader):
    • public void loadFromXML(InputStream in):从指定XML文件输入流中读取所有属性加入properties中
    • public Enumeration<?> propertiesNames():返回属性表的所有key的枚举
    • public Object setProperty(String key,String value):调用HashTable的put()方法,向属性集合中加入数据
    • public void store(OutputStream out,String comments)
    • public void store(Writer writer,String comments)
    • public void storeToXML(OutputStream os,String comment)
    • public void storeToXML(OutputStream os,Stirng comment,Stirng encoding)
    • public Set stringPropertyNames():键的集合
    public static void main(String[] args) throws IOException {
            Properties pt = new Properties();
            pt.setProperty("username","root");
            pt.setProperty("password","123456");
            Set<String> keys = pt.stringPropertyNames();
            Iterator<String> it = keys.iterator();
            for(;it.hasNext();){
                System.out.println(it.next());
            }
            pt.store(new BufferedWriter(new FileWriter("F:\\c.txt")),null);
            Properties pt2 = new Properties();
            pt2.load(new BufferedReader(new FileReader("F:\\c.txt")));
            for (String key:pt2.stringPropertyNames()) {
                System.out.println(key+"="+pt2.getProperty(key));
            }
        }
    

78. 多线程

1. 进程:系统资源分配的最小单位

2. 线程:是进程的执行单元,是CPU调度的最小单位

  • 单线程
  • 多线程

3. 多线程实现方式

3.1 继承Thread类(java.lang.Thread)

  • 继承Thread类并重写run方法,使用start方法启动线程
public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.start();
        mt2.start();
    }

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.print(i+"\t");
        }
    }
}
  • 设置和获取线程名称
//第一种,使用setName设置线程名称
public static void main(String[] args) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.setName("线程A");
        mt2.setName("线程B");
        mt1.start();
        mt2.start();
    }

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.print(getName()+":"+i+"\t");
        }
    }
}
//第二种,使用父类带参构造设置线程名称
public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程A");
        MyThread mt2 = new MyThread("线程B");
        mt1.start();
        mt2.start();
    }

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.print(getName()+":"+i+"\t");
        }
    }
    public MyThread(String name){
        super(name);
    }
}
  • 获取启动线程的线程名
String name = Thread.currentThread().getName();
System.out.println(name);
  • 线程调度

    • 分时调度模型
    • 抢占式调度模型
  public final int getPriority():返回此线程的优先级
  public final void setPriority():更改线程优先级    
  Thread.MAX_PRIORITY=10
  Thread.MIN_PRIORITY=1
  Thread.NORM_PRIORITY=5    
      public static void main(String[] args) {
          ThreadPriority tp1 = new ThreadPriority("线程A");
          ThreadPriority tp2 = new ThreadPriority("线程B");
          tp1.setPriority(5);
          tp2.setPriority(6);
          tp1.start();
          tp2.start();
      }
  
  public class ThreadPriority extends Thread{
      @Override
      public void run() {
          for (int i = 0; i < 100; i++) {
              System.out.println(getName()+":"+i);
          }
      }
      public ThreadPriority(String name){
          super(name);
      }
  }
  • 线程控制

    • public static sleep(long millis):使当前正在执行的线程停留指定毫秒数
    • public void join():等待这个线程死亡
    • public void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
    • public static void yield():使当前占用资源的线程主动让出资源
//sleep()
public static void main(String[] args) {
        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ts1.start();
        ts2.start();
    }

public class ThreadSleep extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//join()
public static void main(String[] args) {
        ThreadJoin tj1 = new ThreadJoin("A");
        ThreadJoin tj2 = new ThreadJoin("B");
        tj1.start();
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
    }

public class ThreadJoin extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.print(getName()+":"+i+"\t");
        }
    }
    public ThreadJoin(String name){
        super(name);
    }
}
//setDaemon()
public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon("A");
        ThreadDaemon td2 = new ThreadDaemon("B");

        Thread.currentThread().setName("C");
        td1.setDaemon(true);
        td2.setDaemon(true);
        td1.start();
        td2.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }

public class ThreadDaemon extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
    public ThreadDaemon(String name){
        super(name);
    }
}
  • 线程生命周期
    • 创建线程对象
    • 线程就绪等待分配资源
    • 分配到资源开始运行
    • 线程结束
image-20201209223406529

3.2 实现Runnable接口(java.lang.Runnable)

  • 定义一个类实现Runnable接口
  • 重写run方法
  • 创建该类对象,并将对象作为参数构造Thread对象
  • 启动线程
public class RunnableDemo1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public RunnableDemo1(String s){
        Thread.currentThread().setName(s);
    }
}
public class Demo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new RunnableDemo1());
        Thread t2 = new Thread(new RunnableDemo1());
        Thread t3 = new Thread(new RunnableDemo1(), "线程C");
        t1.start();
        t2.start();
        t3.start();
    }
}

推荐使用实现Runnable接口的方式实现多线程

原因:

  • Java中只能单继承,所以放我们继承Thread类实现多线程后,想要继续继承其他类就不可能了
  • 实现Runnable接口的多继承可以实现资源共享

3.3 实现Callable接口(java.util.concurrent.Callable)

  • 定义一个类实现Callable接口
  • 重写call方法
  • 创建该类对象,启动线程
public class MyCallableDemo implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
        return null;
    }
}

public class Demo7 {
    public static void main(String[] args) {
        MyCallableDemo myCallableDemo = new MyCallableDemo();
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.submit(myCallableDemo);

    }
}

3.4 匿名内部类实现多线程

    public void ThreadDemo(){
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"-"+i);
                }
            }
        };
        t1.start();
    }
    public void RunnableDemo(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"-"+i);
                }
            }
        };
        new Thread(r1,"线程A").start();
        new Thread(r1,"线程B").start();
    }

4. 线程同步

//卖票问题
public class SellTicket implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while(true){
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
                tickets--;
            }else{
                break;
            }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

4.1 出现了问题(多线程数据安全问题)

  • 相同的票出现了多次
    • 资源被其他线程抢占,其他线程资源又被其他线程抢占
  • 出现了负数票数
    • 当某个线程抢到CPU执行权,而且正在出售第1张票,票数减一变为0,而此时其他线程抢到了执行权,票数减一变为-1

4.2 解决办法

  • 同步代码块:锁定多条语句操作共享数据的代码
    • 优点:解决了多线程的数据安全问题
    • 缺点:当线程较多的时候,每个线程都回去判断同步上的锁,耗费资源,降低运行效率
synchronized(任意对象){
    多条语句操作共享数据的代码
}
synchronized(任意对象):相当于给代码加锁了,任意对象就可以看成是一把锁
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object object = new Object();

    @Override
    public void run() {
        while(true){
            synchronized(object){
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
                    tickets--;
                }else{
                    break;
                }
            }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

  • 同步方法:把synchronized关键字加到方法上
    • 同步方法的锁对象:this
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object object = new Object();
    private int i = 0;

    @Override
    public void run() {
        while(true){
            if(i%2==0){
                synchronized(this){
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
                        tickets--;
                    }else{
                        break;
                    }
                }
                i++;
            }else{
                sellTicket();
            }

        }
    }

    public synchronized void sellTicket(){
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
            tickets--;
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
  • 同步静态方法:在static后加上synchronized关键字
    • 同步静态方法的锁对象:类名.class(反射)
public class SellTicket implements Runnable {
    private static int tickets = 100;
    private Object object = new Object();
    private int i = 0;

    @Override
    public void run() {
        while(true){
            if(i%2==0){
                synchronized(SellTicket.class){
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
                        tickets--;
                    }else{
                        break;
                    }
                }
                i++;
            }else{
                sellTicket();
            }

        }
    }

    public static synchronized void sellTicket(){
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"票");
            tickets--;
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

5. 线程安全的类

5.1 StringBuffer(在多线程中会被使用到)

5.2 Vector(不常被使用)

5.3 Hashtable(不常被使用)

常使用Collections.synchronizedList(new ArrayList())将线程不安全的集合类变成线程安全的集合类

6. 锁Lock(JDK1.5)

  • Lock实现同步比使用synchronized方法和语句可以获得更广泛的锁定操作

  • 常用方法:

    • public void lock():获得锁
    • public void unlock():释放锁
  • 实现类:ReentrantLock

    • 构造方法:
      • public ReentrantLock()
    • 实现
    public class SellTicket implements Runnable {
        private static int tickets = 100;
        private Object object = new Object();
        private int i = 0;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try{
                    lock.lock();
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "票");
                        tickets--;
                    } else {
                        break;
                    }
                }finally{
                    lock.unlock();
                }
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st = new SellTicket();
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

7. 死锁问题

image-20201216100627936

7.1 同步弊端

  • 效率低
  • 如果出现了同步嵌套,容易出现死锁

7.2 死锁的产生

  • 概述:死锁或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

  • 产生:我们在解决多线程共享资源的线程同步问题时,会使用synchronized关键字修饰方法或者通过Lock加锁方式修饰方法、代码块,防止多个线程访问统一资源产生的线程安全问题。但是当线程X持有锁A,等待锁B,而线程Y此时持有锁B,等待锁A时,就会出现X,Y两个线程互相等待的情况,这种情况就是死锁。

  • 产生条件

    • 互斥条件:一个资源每次只能被一个进程使用。
    • 保持和请求条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
    • 不可剥夺条件:进程已获得资源,在未使用完成前,不能被剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

7.3 死锁的解决

  • 只要破坏其中任意一个条件,就可以避免死锁,其中最简单的就是破环循环等待条件。按同一顺序访问对象,加载锁,释放锁。

8. 线程间的通信(等待唤醒机制)

8.1 概述

  • 多个线程在处理同一个资源,但是处理的任务不相同

8.2 等待唤醒机制

  • 多个线程间的⼀种协作机制。谈到线程我们经常想到的是线程间的竞争( race ),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司你你和你的同事,你们可能存在在晋升时的竞争,但更多时候你们更多是⼀起合作以完成某些任务。

    就是在⼀个线程进行了规定操作后,就进⼊等待状态( wait()),等待其他线程执行完他们的指定代码过后再将其唤醒( notify() );在有多个线程进⾏等待时,如果需要,可以使⽤ notifyAll()来唤醒所有的等待线程。

    wait/notify 就是线程间的⼀种协作机制。

  • 常用方法:

    • wait():线程不再活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁,此时线程状态为WAITING,等待其他线程完成指定任务,使用notify通知当前线程从wait set中释放出来,重新进入调度队列
    • notify():通知处在wait set中的线程释放进入调度队列
    • notifyAll():通知所有处在wait set中的线程释放进入调度队列

    注意:
    哪怕只通知了⼀个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,
    所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调⽤ wait 方法之后的地方恢复执行。
    总结如下:

    • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
    • 否则,从 wait set 出来,又进⼊ entry set,线程就从WAITING状态⼜变成BLOCKED 状态。
  • 注意事项

    • wait方法与notify方法必须要由同⼀个锁对象调用。因为:对应的锁对象可以通过notify唤
      醒使用同⼀个锁对象调用的wait方法后的线程。
    • wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对
      象的所属类都是继承了Object类的。
    • wait方法与notify方法必须要在同步代码块或者是同步函数中使⽤。因为:必须要通过锁对
      象调用这2个方法。

8.3 生产者消费者问题

image-20201216110342885
image-20201216110904509
  • 代码实现
public class BaoZi {
    private String pi;
    private String xian;
    private boolean flag = false;

    public String getPi() {
        return pi;
    }

    public String getXian() {
        return xian;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setPi(String pi) {
        this.pi = pi;
    }

    public void setXian(String xian) {
        this.xian = xian;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

public class BaoZiPu implements Runnable{
    private BaoZi bz;

    public BaoZiPu(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        while(true){
            synchronized (bz){
                if(bz.isFlag()){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    if(count%2==0){
                        bz.setPi("薄皮");
                        bz.setXian("猪肉大葱");
                    }else{
                        bz.setPi("冰皮");
                        bz.setXian("韭菜鸡蛋");
                    }
                    count++;
                    System.out.println("包子铺正在做:"+bz.getPi()+bz.getXian()+"包子");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    bz.setFlag(true);
                    bz.notify();
                    System.out.println("包子已经做好了:"+bz.getPi()+bz.getXian()+"包子,大家可以来买了");
                }
            }
        }

    }
}

public class ChiHuo implements Runnable{
    private BaoZi bz;

    public ChiHuo(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(!bz.isFlag()){
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    System.out.println("吃货正在吃:"+bz.getPi()+bz.getXian()+"的包子");
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    bz.setFlag(false);
                    bz.notify();
                    System.out.println("吃货已经把:"+bz.getPi()+bz.getXian()+"的包子吃完了");
                    System.out.println("===================================================");
                }
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        BaoZi bz = new BaoZi();
        new Thread(new BaoZiPu(bz)).start();
        new Thread(new ChiHuo(bz)).start();
    }
}

9. 线程池

  • 概述:我们使用线程时如果并发的线程数量很多,并且每个线程都是执行一个很短的任务就结束了,这样频繁地创建线程就会大大降低系统的效率
  • 线程池:就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,以避免消耗过多的资源
  • 原理:集合容器,队列思想
    • image-20201216114457792
    • image-20201216114530635
  • 代码实现:
    • 包:java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
    • 静态方法:
      • public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的线程数的线程池
        • 参数:int nThread(创建线程池中包含的线程数量)
        • 返回值:ExecutorService接口,返回ExecutorService接口的实现类对象
          • java.util.concurrent.ExecutorService:线程池接口
            • submit(Runnable task):提交一个Runnable任务用于执行
            • void shutdown() :关闭销毁线程池的方法
  • 线程池使用步骤
    1. 使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定数量的线程池
    2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3. 调用ExecutorService中的静态方法submit,传递线程任务(实现类),开启线程,执行run方法
    4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
public class ThreadPoolDemo{
    public static void main(String [] args){
        //1.使用线程池的工厂类Executors里提供的静态方法newFixedThreadPool生成一个指定数量的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
        //3.调用ExecutorService中的静态方法submit,传递线程任务(实现类),开启线程,执行run方法
        es.submit(new MyThread()));
        es.submit(new MyThread()));
        es.submit(new MyThread()));
        es.shutdown();
        es.submit(new MyThread()));//抛异常,线程池已经被销毁了,不能再获取线程了
    }
}
public class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"创建了一个新线程");
    }
}

10. 线程组

img
  • 可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式
  • 线程组的作用是:可以批量管理线程或线程组对象,有效地对线程或线程组对象进行组织
  • 包:java.lang.ThreadGroup
  • 构造方法
    • ThreadGroup(String name)
    • ThreadGroup(ThreadGroup parent,String name)
  • 代码实现
public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class ThreadGroupDemo {
    public static void main(String[] args) {
        ThreadGroup g1 = new ThreadGroup("线程组1");
        Thread t1 = new Thread(g1, new MyThread(), "a");
        Thread t2 = new Thread(g1, new MyThread(), "b");
        System.out.println(t1.getThreadGroup());
        System.out.println(t2.getThreadGroup());

    }
}
  • 多线程实现文件上传

  • 多线程实现文件下载

11. 定时器

  • 线程工具,用来调度多个定时任务,以后台线程的方式执行

11.1 类:java.util.Timer 和 java.util.TimerTask

  • java.util.Timer
  • 线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。
    • 方法
      • void schedule(TimeTasj task,Data time)
      • void cancle()
  • java.util.TimerTask
    • 方法
      • boolean cancle()
      • abstract void run()

12. 小结

  1. 多线程的实现方式有几种
  2. 同步有几种方式
  3. 启动线程的方式
  4. sleep和wait方法的区别
  5. 为什么wait和notify方法在Object中
  6. 线程的生命周期
  7. 练习:多线程网络编程群聊

79. 网络编程

1. 计算机网络

  • 计算机之间为了实现资源共享和信息传递,通过通信线路连接起来的若干计算机组成的网络
    • 局域网
    • 城域网
    • 广域网

2. 网络模型(OSI)

  • OSI(Open System Interconnection开放式系统互联)参考模型(理论模型,但实际上没有人用这个标准)
    • 物理层:负责为设备之间的数据通信提供传输信号和物理介质
    • 数据链路层:负责通过协议保证传输数据的正确性MAC
    • 网络层:负责定义能够表示所有网络节点的逻辑地址IP地址
    • 传输层:负责是否选择差错恢复协议、数据流重用、错误顺序重排
    • 会话层:负责使应用建立和维持会话,使通信在失效时继续恢复通信
    • 表示层:负责定义转换数据格式及加密
    • 应用层:负责文件访问和管理、可靠传输服务、远程操作服务(HTTP、FTP、SMTP)
  • TCP/IP模型
    • 网络接口层
    • 网络层
    • 传输层
    • 应用层

3. 通信协议

  • TCP协议(Transmission Control Protocol传输控制协议):三次握手,四次挥手
  • UDP协议(User Datagram Protocol用户数据报协议):无连接的传输协议,不可靠的信息传输服务,每个包最大64KB
  • IP协议(Internet Protocol互联网协议/网际协议):负责数据从一台机器发送到另一台机器,给互联网每台设备分配一个唯一的标识IP地址
    • IPv4(4字节32位二进制标识地址):4段8位二进制数
    • IPv6(16字节128位二进制标识地址):8段十六进制数

4. IP地址与端口号

  • IP地址:用于在网络中唯一标识一台主机的一段地址(主机)
  • 端口号:用于在通信实体上进行网络通讯应用程序的标识(进程)
    • 常用端口号:
      • MySQL:3306
      • Oracle:1521
      • Tomcat:8080
      • HTTP:80
      • FTP:21
      • SMTP:25
    • 用户端口(1024以后的端口)

5. Java网络编程

5.1 InetAddress

  • 包:java.net.InetAddress
  • 子类:
    • Inet4Address
    • Inet6Address
  • 无构造方法
  • 常用方法:
    • public static InetAddress[ ] getAllByName(String host):根据指定主机名称的配置返回InetAddress对象数组
    • public static InetAddress getByAddress(byte[ ] addr):根据指定IP地址字节数组返回InetAddress对象
    • public static InetAddress getByAddress(String host,byte[ ] addr):根据指定主机名称和IP地址字节数组返回InetAddress对象
    • public boolean equals(Object obj):比较两个对象
    • public byte[ ] getAddress():返回InetAddress对象的原始IP地址
    • public static InetAddress getLocalHost():获取本机主机InetAddress对象
    • public String getHostAddress():获取IP地址字符串
    • public String getHostName():获取IP地址对应主机名
    • public boolean isReachable(int timeout):测试改地址是否可达,参数单位毫秒
    • public String toString():将IP地址转为String

5.2 Socket编程

  • socket(套接字)是网络中的一个通信节点:网络中通信端点的抽象

    • 客户端Socket
    • 服务器ServerSocket
  • 通信要求:IP地址+端口号

  • 开发步骤

    • 服务器端:

      • 创建ServerSocket,指定端口号
      • 调用accept等待客户端接入
      • 使用输入流,接收请求数据到服务器
      • 使用输出流,发送响应数据给客户端
      • 释放资源
    • 客户端:

      • 创建Socket,指定服务器IP地址+端口号
      • 使用输出流,发送请求数据给服务器
      • 使用输入流,接受相应数据到客户端
      • 释放资源
  • ServerSocket类

    • 包:java.net.ServerSocket
    • 子类:java.net.SSLServerSocket
    • 构造方法:
      • public ServerSocket():创建未绑定的服务器套接字
      • public ServerSocket(int port):创建绑定到指定端口号的服务器套接字
      • public ServerSocket(int port,int backlog):创建服务器套接字并绑定到指定本地端口号和backlog
      • public ServerSocket(int port,int backlog,InetAddress bindAddr):创建绑定指定端口号的套接字,backlog和本地IP地址绑定
    • 常用方法:
      • public Socket accept():监听客户端数据,并返回客户端的Socket
      • public void bind(SocketAddress endpoint):将ServerSocket绑定到特定IP地址和端口号
      • public void bind(SocketAddress endpoint,int backlog)
      • public void close():关闭套接字
      • public ServerSocketChannel getChannel()
      • public InetAddress getInetAddress():返回ServerSocket的本地地址
      • public int getLocalPort():返回ServerSocket正在使用的端口号
      • public SocketAddress getLocalSocketAddress()

案例1:TCP编程实现客户端发送数据给服务器端

package com.demo.javase;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 基于TCp协议的服务端开发
 * ①.创建ServerSocket,并绑定端口号
 * ②.调用accept()接收客户端请求
 * ③.获取输入流,读取客户端发送数据
 * ④.获取输出流,发送数据给客户端
 * @author 絷缘
 * @version 1.0
 * @date 2020/12/8 21:46
 **/
public class TCPServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器已启动......");
        //①.创建ServerSocket,并绑定端口号
        ServerSocket listener = new ServerSocket(8890);
        //②.调用accept()接收客户端请求,阻塞方法,如果没有客户端请求,则阻塞
        Socket socket = listener.accept();
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String data = br.readLine();
        System.out.println("客户端IP:" + socket.getInetAddress() + "\t发送数据:" + data + "\t时间:" + new SimpleDateFormat("yyyy-MM-dd kk:mm:ss").format(new Date(System.currentTimeMillis())));
        br.close();
        socket.close();
        listener.close();
    }
}
package com.demo.javase;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * 基于TCP的客户端开发
 * ①.创建客户端套接字,并指定服务器的IP地址和端口号
 * ②.获取输出流,发送数据给服务器
 * ③.获取输入流,接收服务器返回数据
 * ④.关闭,释放资源
 * @author 絷缘
 * @version 1.0
 * @date 2020/12/8 22:04
 **/
public class TCPClient {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端已启动......");
        //①.创建客户端套接字,并指定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8890);
        //②.获取输出流,发送数据给服务器
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        bw.write("Are you OK ?");
        bw.close();
        socket.close();
    }
}

5.3 UDP协议发送接收数据

  • 步骤

    • 创建发送端的Socket对象
    • 创建数据,把数据打包
    • 调用Socket对象的方法发送数据
    • 释放资源
    • 创建接收端的Socket对象
    • 创建接收数据包容器
    • 接收数据并解析数据
    • 释放资源
    public class UDPSend {
        public static void main(String[] args) throws IOException {
            DatagramSocket ds = new DatagramSocket();
            byte[] bytes = "你好!".getBytes();
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8090);
            ds.send(dp);
        }
    }
    
    public class UDPReceive {
        public static void main(String[] args) throws IOException {
            //创建接收端Socket
            DatagramSocket ds = new DatagramSocket(8090);
            //创建一个数据包,作为接收容器
            byte [] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys,bys.length);
            ds.receive(dp);
            //接收数据,解析数据并打印到控制台
            byte[] data = dp.getData();
            String str = new String(data,0,data.length);
            System.out.println(new StringBuffer().append("发送方IP:").append(dp.getAddress()).append("\t发送内容:").append(str).toString());
            //关闭资源
        }
    }
    

5.4 TCP协议发送接收数据

  • 创建客户端的Socket对象(Socket)
  • 获取输出流对象,写数据
  • 释放资源
  • 创建服务器端的Socket对象(ServerSocket)
  • 获取输入流对象,读数据
  • 释放资源
public class TCPClient {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端已启动......");
        //①.创建客户端套接字,并指定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8890);
        //②.获取输出流,发送数据给服务器
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        bw.write("Are you OK ?");
        bw.close();
        socket.close();
    }
}
public class TCPServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器已启动......");
        //①.创建ServerSocket,并绑定端口号
        ServerSocket listener = new ServerSocket(8890);
        //②.调用accept()接收客户端请求,阻塞方法,如果没有客户端请求,则阻塞
        Socket socket = listener.accept();
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String data = br.readLine();
        System.out.println("客户端IP:" + socket.getInetAddress() + "\t发送数据:" + data + "\t时间:" + new SimpleDateFormat("yyyy-MM-dd kk:mm:ss").format(new Date(System.currentTimeMillis())));
        br.close();
        socket.close();
        listener.close();
    }
}
  • 服务器可以反馈
public class TCPServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器已启动......");
        //①.创建ServerSocket,并绑定端口号
        ServerSocket listener = new ServerSocket(8890);
        //②.调用accept()接收客户端请求,阻塞方法,如果没有客户端请求,则阻塞
        Socket socket = listener.accept();
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String data = br.readLine();
        System.out.println("客户端IP:" + socket.getInetAddress() + "\t发送数据:" + data + "\t时间:" + new SimpleDateFormat("yyyy-MM-dd kk:mm:ss").format(new Date(System.currentTimeMillis())));
        //给反馈
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream,StandardCharsets.UTF_8));
        bw.write("Hello!Thank you!Thank you very much!");
        bw.newLine();
        bw.flush();

        socket.close();
        listener.close();
    }
}


public class TCPClient {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端已启动......");
        //①.创建客户端套接字,并指定服务器的IP地址和端口号
        Socket socket = new Socket("127.0.0.1",8890);
        //②.获取输出流,发送数据给服务器
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        bw.write("Are you OK ?");
        bw.newLine();
        bw.flush();
        //接受反馈
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String str = br.readLine();
        System.out.println("服务器:" + str);

        bw.close();
        br.close();
        socket.close();
    }
}
//在此处我犯了一个严重的错误,如果使用BufferedWriter 不加newLine()和flush()的话,服务端BufferedReader的readline()方法就读取不到msg,会一直阻塞下去。

6. 网络编程模型

6.1 BIO网络编程模型(同步阻塞的BIO)【Blocking I/O】

  • JDK1.4之前,我们使用的都是BIO,我们想要实现ServerSocket和Socket之间的通信,需要使用多线程来处理大量请求
  • 同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解
image-20201210143814190

6.2 NIO网络编程模型(同步非阻塞NIO)【Non-blocking I/O】

  • NIO是基于事件驱动思想来完成的
  • 同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持
image-20201210144443494

6.3 AIO网络编程模型(异步非阻塞AIO)

  • 异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。

7. NIO

image-20201210140343354

参考文档:https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html

7.1 NIO与传统IO的区别

  • NIO简介
    • Java NIO(New IO/Non-Blocking IO)是从Java1.4版本开始引入的一个新的IO,为了替代传统的阻塞式IO,NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作,NIO将以更加高效的方式进行文件的读写操作。
  • 区别
    • 传统的IO是面向流的,NIO是面向缓冲区的
    • 传统的IO是阻塞式IO,NIO是非阻塞式的
    • 传统的IO没有选择器,NIO有选择器
image-20201213100451064

7.2 通道(Channel)和缓冲区(Buffer)

  • 概述:

    • Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer),通道用于建立起到IO设备的连接(例如:文件、套接字),所以如果需要使用NIO时,首先就要获取一个连接到IO设备的通道,然后通过与缓冲区结合,对数据进行读写处理

    简而言之:Channel负责传输,Buffer负责存储

7.3 缓冲区(Buffer)

  • java.nio.Buffer:抽象类

  • 概述:负责数据存取,缓存区底层就是数组

  • 根据数据类型的不同,提供了相应的缓冲区(Boolean除外)

    • ByteBuffer
    • ShortBuffer
    • IntBuffer
    • LongBuffer
    • FloatBuffer
    • DoubleBuffer
    • CharBuffer
  • 四个重要属性:

    • Capacity:容量
    • Position:位置
    • Limit:上限
    • Mark:标记
  • 常用方法:

    • public int capacity():返回缓冲区容量
    • public Buffer clear():清除此缓冲区
    • public Buffer flip():反转这个缓冲区,由写变读,由读变写
    • public boolean hasRemaining():当前位置position到上限limit是否存在元素
    • public int limit():返回缓冲区的上限
    • public Buffer limit(int newLimit):设置此缓冲区的上限
    • public Buffer mark():设置此缓冲区的标记
    • public int position():返回此缓冲区的位置
    • public Buffer position(int newPosition):设置此缓冲区的位置
    • public int remaining():返回当前位置和上限之间的元素
    • public Buffer reset():将此缓冲区的位置重置为上一次标记的位置
    • public Buffer rewind():倒带缓冲区四大属性
  • 非直接缓冲区(使用allocate方法获取缓冲区对象,创建在JVM内存中)

    • allocate():获取非直接缓冲区对象
    • put():存入数据到缓冲区中
    • get():从缓冲区中读取数据
    • limit():获取缓冲区上限
    • position():获取缓冲区位置
    • capacity():获取缓冲区容量
    • mark():设置缓冲区标记
    • reset():重置position到上一次mark处
    • clear():清除缓冲区,position为0,limit为capacity
    //使用allocate分配一个指定大小的缓冲区
    System.out.println("=================allocate()方法创建缓冲区后===================");
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    System.out.println("===================put()方法加入数据后=================");
    byteBuffer.put("HelloWorld!".getBytes());
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    System.out.println("===================flip()方法切换读数据模式后================");
    byteBuffer.flip();
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    System.out.println("===================get()方法读取数据后================");
    byte [] dst = new byte[byteBuffer.limit()];
    byteBuffer.get(dst);
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    System.out.println("====================rewind()方法执行后===================");
    byteBuffer.rewind();
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    System.out.println("==================clear()方法执行后=======================");
    byteBuffer.clear();
    System.out.println("position:" + byteBuffer.position());
    System.out.println("limit:" + byteBuffer.limit());
    System.out.println("capacity:" + byteBuffer.capacity());
    /*
    clear()方法并非删除了数据,而是将position变为0,limit变为capacity的大小,数据依然存在
    */
    System.out.println((char)byteBuffer.get(1));
    byte [] bys = new byte[1024];
    System.out.println((char)byteBuffer.get(1));
    byteBuffer.get(bys,0,2);
    System.out.println(new String(bys,0,bys.length));
    byteBuffer.mark();
    byteBuffer.get(bys,2,4);
    System.out.println(new String(bys,0,bys.length));
    byteBuffer.reset();
    byteBuffer.get(bys,2,4);
    System.out.println(new String(bys,0,bys.length));
    
    =================allocate()方法创建缓冲区后===================
    position:0
    limit:1024
    capacity:1024
    ===================put()方法加入数据后=================
    position:11
    limit:1024
    capacity:1024
    ===================flip()方法切换读数据模式后================
    position:0
    limit:11
    capacity:1024
    ===================get()方法读取数据后================
    position:11
    limit:11
    capacity:1024
    ====================rewind()方法执行后===================
    position:0
    limit:11
    capacity:1024
    ==================clear()方法执行后=======================
    position:0
    limit:1024
    capacity:1024
    e
    He                                                                            
    HelloW                                                                                 
    HelloW                                                                                 
    
  • 直接缓冲区(使用allocateDirect方法获取缓冲区对象,创建在物理内存中)

    • allocateDirect():获取直接缓冲区对象
    • put():存入数据到缓冲区中
    • get():从缓冲区中读取数据
    • limit():获取缓冲区上限
    • position():获取缓冲区位置
    • capacity():获取缓冲区容量
    • mark():设置缓冲区标记
    • reset():重置position到上一次mark处
    • clear():清除缓冲区,position为0,limit为capacity
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    byteBuffer.put("zhiyuan".getBytes());
    System.out.println(byteBuffer.isDirect());
    
  • 直接缓冲区和非直接缓冲区的区别

非直接缓冲区

直接缓冲区

7.4 通道(Channel)

  • 用于建立起与IO设备的连接,结合缓冲区实现读写数据

  • 特性

    • 双向性:类似于流,但不同于InputStream和OutputStream,流具有单向性和独占性,通道则偏向于数据的流通性,一个Channel支持双向传输(输入、输出)
    • 非阻塞式
    • 操作唯一性
  • 实现

    • 文件类:FileChannel
    • UDP类:DatagramChannel
    • TCP类:ServerSocketChannel / SocketChannel
  • 获取通道

    1. 通过支持通道的类提供的getChannel()获取
      • FileInputStream/FileOutputStream
      • RandomAccessFile
      • ServerSocket/Socket
      • DatagramSocket
    2. JDK1.7以后NIO2针对各个类提供了静态方法open()获取
    3. JDK1.7以后NIO2的Files工具类提供了newByteChannel()获取
  • 利用通道完成文件的复制

//使用非直接缓冲区完成文件的复制
public static void test1() throws IOException {
        FileInputStream fis = new FileInputStream("E:\\壁纸\\壁纸\\212.jpg");
        FileOutputStream fos = new FileOutputStream("copy1.jpg");
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(inChannel.read(buffer)!=-1){
            buffer.flip();
            outChannel.write(buffer);
            buffer.clear();
        }
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
    }
//使用直接缓冲区通道写数据
public static void test2() throws IOException{
        FileInputStream fis = new FileInputStream("E:\\壁纸\\壁纸\\212.jpg");
        FileOutputStream fos = new FileOutputStream("copy2.jpg");
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        while(inChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            outChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
    }
//使用直接缓冲区完成文件的复制(使用内存映射文件的方式)
public static void test3() throws IOException, URISyntaxException {
        FileChannel inChannel = FileChannel.open(Paths.get("E:\\壁纸\\壁纸\\212.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("copy3.jpg"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        //内存映射文件
        MappedByteBuffer inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
        //直接对缓冲区进行数据的读写操作
        byte [] dst = new byte[inMapBuffer.limit()];
        inMapBuffer.get(dst);
        outMapBuffer.put(dst);
        inChannel.close();
        outChannel.close();
    }
//使用直接缓冲区完成文件复制(使用通道数据交换的方式)
    public static void test4() throws IOException{
        FileChannel inChannel = FileChannel.open(Paths.get("E:\\壁纸\\壁纸\\212.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("copy4.jpg"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        inChannel.transferTo(0, inChannel.size(), outChannel);
//        outChannel.transferFrom(inChannel,0,inChannel.size());
        inChannel.close();
        outChannel.close();
    }
//耗时对比
    public static void main(String[] args) throws IOException, URISyntaxException {
        long startTime = System.currentTimeMillis();
//        test1();//52毫秒(非直接缓冲区使用Buffer复制)
//        test2();//41毫秒(直接缓冲区使用Buffer复制)
//        test3();//24毫秒(直接缓冲区使用文件映射复制)
//        test4();//17毫秒(直接缓冲区使用通道数据交换复制)
        long endTime = System.currentTimeMillis();
        long useTime = endTime - startTime;
        System.out.println("耗时:" + useTime + "毫秒");
    }
  • 分散(Scatter)读取和聚集(Gather)写入

    • 分散读取:将通道中数据分散到多个缓冲区中
    • 聚集写入:将多个缓冲区的数据聚集到通道中
    //首先创建一个随机访问文件
    RandomAccessFile raf= new RandomAccessFile("test.txt", "rw");
    //根据随机获取通道
    FileChannel channel = raf.getChannel();
    //分配指定大小指定缓冲区
    ByteBuffer buf1=ByteBuffer.allocateDirect(200);
    ByteBuffer buf2=ByteBuffer.allocateDirect(1024);
    // 分散读取
    ByteBuffer[] bufs={buf1,buf2};
    channel.read(bufs);//采用通道分散读取数据
    for (ByteBuffer byteBuffer : bufs) {
        // 切换成读模式
        byteBuffer.flip();
    }
    System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
    System.out.println("-------------------------------------------------");
    System.out.println(new String(bufs[1].array(),1,bufs[1].limit()));
    System.out.println("------聚集写入---------");
            //创建一个随机写入文件
    RandomAccessFile raf2= new RandomAccessFile("test2.txt", "rw");
    //获取写入通道
    FileChannel channel2 = raf2.getChannel();
    channel2.write(bufs);//采用通道写入数据
    raf2.close();//关闭
    raf.close();//关闭
    

7.5 字符集(Charset)

  • 编码:字符串 -> 字节数组
  • 解码:字节数组 -> 字符串
public static void main(String[] args) {
        SortedMap<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> set = map.entrySet();
        for (Map.Entry<String, Charset> s : set) {
            System.out.println(s.getKey() + "\t" + s.getValue() + "\t" + s.getClass());
        }
   		 //创建编码格式
        Charset gbk = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder charsetEncoder = gbk.newEncoder();
        //获取解码器
        CharsetDecoder charsetDecoder = gbk.newDecoder();
        //编码
        CharBuffer cb = CharBuffer.allocate(1024);
        cb.put("HelloWord!");
        cb.flip();
        ByteBuffer encode = charsetEncoder.encode(cb);
        System.out.print("[");
        for (int i = 0; i < encode.limit(); i++) {
            System.out.print(encode.get(i)+",");
        }
        System.out.print("]");
        //解码
        encode.rewind();
        CharBuffer decode = charsetDecoder.decode(encode);
        System.out.print("\n[");
        for (int i = 0; i < decode.limit(); i++) {
            System.out.print(decode.get(i)+",");
        }
        System.out.print("]");
}

7.6 NIO的非阻塞简述

image-20201210140343354
  • 在客户端与服务器之间,建立一个选择器(Selector),将每一个用于传输数据的通道(Channel)注册到该选择器(Selector)上,选择器(Selector)来监控各个通道的IO(WRITE、READ、ACCEPT、CONNECT)状态,当客户端IO状态准备就绪时,选择器(Selector)通知服务器开始处理各种IO

7.7 NIO阻塞式编程

  • 三个核心

    • 第一步:创建通道(Channel),负责建立IO设备连接

      • java.nio.channels.Channel接口
        • SelectableChannel
          • ServerSocketChannel
          • SocketChannel
          • DatagramChannel
          • Pipe.SinkChannel
          • Pipe.SourceChannel

      FileChannel不能切换成非阻塞模式

    • 第二步:创建缓冲区(Buffer),负责数据的存取

    • 第三步:创建选择器(Selector),是SelectableChannel的多路复用器,负责监控SelectableChannel的IO状态

public class TCPDemo {
    @Test
    public void client() throws IOException {
        SocketChannel socketChannel = null;
        FileChannel inChannel = null;

        //1.建立IO通道
        try {
            socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8090));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2.建立缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
        //3.读取本地图片发送到服务器
        try {
            inChannel = FileChannel.open(Paths.get("E:\\壁纸\\壁纸\\212.jpg"), StandardOpenOption.READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(inChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        inChannel.close();
        socketChannel.close();
    }

    @Test
    public void server() throws IOException {
        //1.建立IO通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8090));
        //3.获取客户端连接的通道
        SocketChannel socketChannel = serverSocketChannel.accept();
        //4.接收客户端的数据,并保存到本地
        FileChannel outChannel = FileChannel.open(Paths.get("client1.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        while(socketChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            outChannel.write(byteBuffer);
            byteBuffer.clear();
        }
        //5.关闭资源
        socketChannel.close();
        serverSocketChannel.close();
        outChannel.close();
    }
}

7.8 NIO非阻塞式编程

  • TCP

  • UDP

7.9 管道(Pipe)

//1. 获取Pipe管道
Pipe pipe = Pipe.open();
//2. 将缓冲区中的数据写入管道
ByteBuffer buffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
buffer.put("Pipe".getBytes());
buffer.flip();
sinkChannel.write(buffer);
//3. 读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
buffer.flip();
int length = sourceChannel.read(buffer);
System.out.println(new String(buffer.array(),0,length));
sourceChannel.close();
sinkChannel.close();

7.2 Selector:选择器/多路复用器

  • 作用:I/O就绪状态选择

  • 地位:NIO网络编程的基础

  • 事件

    • SelectionKey.OP_CONNECT(连接就绪)
    • SelectionKey.OP_ACCEPT(接受就绪)
    • SelectionKey.OP_READ(读就绪)
    • SelectionKey.OP_WRITE(写就绪)
  • 实现

    //创建Selector
    Selector selector = Selector.open();
    //将Channel注册到Selector上,监听读就绪事件
    SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_READ);
    //阻塞等待Channel有就绪事件发生
    int selectNum = selector.select();
    //获取发生就绪事件的Channel集合
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    

7.4 NIO编程实现步骤

  • 第一步:创建Selector
  • 第二步:创建ServerSocketChannel,并绑定监听端口
  • 第三步:将Channel设置为非阻塞模式
  • 第四步:将Channel注册到Selector上,监听连接事件
  • 第五步:循环调用Selector的select方法,检查就绪情况
  • 第六步:调用selectedKeys方法获取就绪Channel集合
  • 第七步:判断就绪事件种类,调用业务处理方法
  • 第八步:根据业务需要,决定是否再次注册监听事件,重复执行第三步操作

7.5 NIO网络编程实战


7.6 分散读取Scatter和聚集写入Gather

  • 分散读取:从Channel中读取数据分散到多个Buffer中
  • 聚集写入:把多个Buffer的数据聚集写入Channel中
public static void main(String[] args){
    RandomAccessFile raf = new RandomAccessFile("a.txt","rw");
    
    FileChannel channel = raf.getChannel();
    
    ByteBuffer bb1 = ByteBuffer.allocate(10*3);
    ByteBuffer bb2 = ByteBuffer.allocate(100*3);
    
    Byter[] bbs = {bb1,bb2};
    channel.read(bbs);
    
    for(ByteBuffer bb : bbs){
        bb.flip();
        System.out.println(new String(bb.array(),0,bb.limit));
    }
    System.out.println("-----------------------------------");
    
    RandomAccessFile raf2 = new RandomAccessFile("b.txt","rw");
    FileChannel channel2 = raf2.getChannel();
    channel2.write(bb2);
}

7.7 编解码

  • Charset

80. 反射和注解

1. 反射

1.1 类加载

  • 概述:当程序要使用某个类时,如果该类还未被加载进内存中,则系统会通过**类的加载类的连接类的初始化**这三个步骤来对类进行初始化

  • 类加载

    • 将class文件读入内存,并为其创建一个java.lang.Class对象
    • 任何类被使用时,系统都会为其建立一个java.lang.Class对象
  • 类的连接

    • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
    • 准备阶段:负责为类的类变量分配内存,并设置默认初始值
    • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
  • 类的初始化

    • 在该阶段,主要就是对类变量进行初始化
  • 类的初始化步骤

    1. 假如类还未被加载和连接,则程序先加载并连接该类

    2. 假如该类的直接父类还未被初始化,则先初始化其直接父类(初始化直接父类也遵循1-3)

    3. 假如类中有初始化语句,则系统依次执行这些初始化语句

  • 类的初始化时机

    1. 创建类的实例
    2. 调用类的类方法
    3. 访问类或者接口的类变量,或者为类变量赋值
    4. 使用反射方式来强制创建某个类或者接口对应的java.langClass对象
    5. 初始化某个类的子类
    6. 直接使用java.exe命令来运行某个主类

1.2 类加载器

  • 作用:负责将class文件加载进内存中,并为之生成对应的java.lang.Class对象

  • JVM的类加载机制

    • 全盘负责:就是当一个类加载器负责加载某个class时,该class所依赖的和引用的其他class也将由类加载器负责载入,除非显式使用另外一个类加载器来载入
    • 父类委托:就是当一个类加载器负责加载某个class时,先让父类加载器试图加载该class,只有在父类加载器无法加载该类时才尝试从自己的了一路径中加载该类
    • 缓存机制:保证所有加载过的class都会被缓存,当程序需要使用某个class对象时,类加载器先从缓存区中搜索该class,只有当缓存区中不存在该class对象时,系统才会读取该类对应的二进制数据,并将其转换成class对象,存储到缓存区
  • 包:java.lang.ClassLoader(抽象类)

  • 常用方法:

    • public ClassLoader getParent():返回父类加载器
    • public static ClassLoader getSystemClassLoader():返回系统类加载器
  • Java运行时内置类加载器

    • BootstrapClassLoader:虚拟机内置类加载器,通常为null
    • ExtensionClassLoader:平台类加载器(JDK1.9变为PlatFormClassLoader)

    The extension class loader has been renamed; it is now the platform class loader. All classes in the Java SE Platform are guaranteed to be visible through the platform class loader. In addition, the classes in modules that are standardized under the Java Community Process but not part of the Java SE Platform are guaranteed to be visible through the platform class loader.

    Just because a class is visible through the platform class loader does not mean the class is actually defined by the platform class loader. Some classes in the Java SE Platform are defined by the platform class loader while others are defined by the bootstrap class loader. Applications should not depend on which class loader defines which platform class.

    • SystemClassLoader:应用程序类加载器

    类加载器的继承关系:System的父加载器为Extension,而Extension的父加载器为Bootstrap

img
public class Demo {
    public static void main(String[] args) {
        ClassLoader c1 = ClassLoader.getSystemClassLoader();
        System.out.println(c1);//AppClassLoader
        ClassLoader c2 = c1.getParent();
        System.out.println(c2);//ExtClassLoader
        ClassLoader c3 = c2.getParent();
        System.out.println(c3);//null
    }
}
//sun.misc.Launcher$AppClassLoader@18b4aac2
//sun.misc.Launcher$ExtClassLoader@1b6d3586
//null

1.3 反射

image-20201211115906252
  • 概述:是指在运行时获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制,由于这种动态性,可以极大地增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展

  • 获取Class类的对象

    • 使用class属性,获取所属类对应的对象
    • 使用getClass()方法,获取所属类对应的对象(Object类中的方法)
    • 使用Class类中的静态方法forName(String className),字符串参数为某个类的全路径
    public class Demo2 {
        public static void main(String[] args) {
            Class<Student> c1 = Student.class;
            Student stu = new Student();
            Class<? extends Student> c2 = stu.getClass();
            Class<?> c3 = null;
            try {
                c3 = Class.forName("com.demo.javase.Student");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            System.out.println("c1:"+c1+"\tc2:"+c2+"\tc3:"+c3);
        }
    }
    //c1:class com.demo.javase.Student	c2:class com.demo.javase.Student	c3:class com.demo.javase.Student
    
  • 反射获取构造方法并使用

    • public Constructor<?>[ ] getConstructors():获取所有public的构造方法
    • public Constructor<?>[ ] getDeclaredConstructors():获取所有构造方法
    • public Constructor getConstructor(Class<?> ... parameterTypes):获取指定public构造方法
    • public Constructor getDeclaredConstructor(Class<?> ... parameterTypes):获取指定构造方法
    • public T newInstance(Object... initargs):根据指定构造方法创建对象
//获取class对象
Class<?> c = Class.forName("com.demo.javase.Student");
Constructor<?>[] cons = c.getConstructors();
for(Constructor con : cons){
    System.out.println(con);
}
//public com.demo.javase.Student(java.lang.String,int)
//public com.demo.javase.Student()

Constructor<?>[] allCons = c.getDeclaredConstructors();
for (Constructor<?> allCon : allCons) {
	System.out.println(allCon);
}
//public com.demo.javase.Student(java.lang.String,int)
//com.demo.javase.Student(int)
//private com.demo.javase.Student(java.lang.String)
//public com.demo.javase.Student()

//基本类型和引用类型都可以通过class属性来获取对应的类对象
Constructor<?> con = c.getConstructor(String.class,int.class);
Object o = con.newInstance("zhiyuan",23);
System.out.println(o);
//学生{name='zhiyuan', age=23}

Constructor<?> con2 = c.getDeclaredConstructor(int.class);
System.out.println(con2.newInstance(22));
//学生{name='null', age=22}
//私有构造方法可以获取到但无法使用私有构造方法创建对象
Constructor<?> con3 = c.getDeclaredConstructor(String.class);
//System.out.println(con3.newInstance("zhiyuan"));

  • 暴力反射(使用私有构造方法创建对象)
//私有构造方法可以获取到但无法使用私有构造方法创建对象,若要使用,可以用暴力反射
Constructor<?> con3 = c.getDeclaredConstructor(String.class);
con3.setAccessible(true);
System.out.println(con3.newInstance("zhiyuan"));
//学生{name='zhiyuan', age=0}
  • 反射获取成员变量并使用
    • public Field[ ] getFields():获取所有公共成员变量和类变量
    • public Field getField(String name):获取指定公共成员变量和类变量
    • public Field[ ] getDeclaredFields():获取所有成员变量和类变量
    • public Field getDeclaredField(String name):获取指定成员变量和类变量
      • Field类中用于给变量赋值的方法
        • public void set(Object obj,Object value):给obj对象的成员变量赋值为value
Class<?> c = Class.forName("com.demo.javase.Student");
Field[] fields = c.getFields();
for (Field field : fields) {
	System.out.println(field);
}
//public java.lang.String com.demo.javase.Student.gender

Field[] declaredFields = c.getDeclaredFields();
for (Field declaredField : declaredFields) {
	System.out.println(declaredField);
}
//private java.lang.String com.demo.javase.Student.name
//private int com.demo.javase.Student.age
//int com.demo.javase.Student.count
//public java.lang.String com.demo.javase.Student.gender
//static int com.demo.javase.Student.num

Field genderField = c.getField("gender");
Constructor<?> con = c.getConstructor(String.class,int.class);
Object o = con.newInstance("zhiyuan",25);
genderField.set(o,"male");
System.out.println(o);
//Student{name='zhiyuan', age=25, count=0, gender='male'}
  • 暴力反射(给私有成员变量赋值)
Field nameField = c.getDeclaredField("name");
Object zhiyuan = con.newInstance("zhiyuan", 26);
nameField.setAccessible(true);
nameField.set(zhiyuan,"zhiyuan002");
System.out.println(zhiyuan);
//Student{name='zhiyuan002', age=26, count=0, gender='null'}
  • 反射获取成员方法并使用
    • public Method [ ] getMethods():获取所有公共的方法(包含继承过来的方法)
    • public Method [ ] getDeclaredMethods():获取所有的方法(不包含继承过来的方法)
    • public Method getMethod():获取指定的公共方法
    • public Method getDeclaredMethod():获取指定的方法
      • Method类中用于使用方法的方法
        • public Object invoke(Object obj,Object... args):obj为调用方法的对象,args是方法需要的参数
Class<?> c = Class.forName("com.demo.javase.Student");
Method[] methods = c.getMethods();
for (Method method : methods) {
	System.out.println(method);
}
System.out.println("=================================================================");
Method[] declaredMethods = c.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
	System.out.println(declaredMethod);
}
/*
public java.lang.String com.demo.javase.Student.toString()
public java.lang.String com.demo.javase.Student.getName()
public void com.demo.javase.Student.setName(java.lang.String)
public int com.demo.javase.Student.getAge()
public void com.demo.javase.Student.setAge(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
=================================================================
public java.lang.String com.demo.javase.Student.toString()
public java.lang.String com.demo.javase.Student.getName()
public void com.demo.javase.Student.setName(java.lang.String)
public int com.demo.javase.Student.getAge()
private void com.demo.javase.Student.show()
static void com.demo.javase.Student.why()
public void com.demo.javase.Student.setAge(int)
*/

Method setName = c.getMethod("getName");
Constructor<?> con = c.getConstructor(String.class, int.class);
Object zhiyuan = con.newInstance("zhiyuan", 22);
Object o = setName.invoke(zhiyuan);
System.out.println(o);
//zhiyuan

Method setName = c.getMethod("setName",String.class);
Constructor<?> con = c.getConstructor(String.class, int.class);
Object zhiyuan = con.newInstance("zhiyuan", 22);
Object o = setName.invoke(zhiyuan,"Hulk");
System.out.println(o);
System.out.println(zhiyuan);
//null
//Student{name='Hulk', age=22, count=0, gender='null'}
  • 暴力反射(调用私有方法)
Method show = c.getDeclaredMethod("show");
Constructor<?> con2 = c.getConstructor(String.class, int.class);
Object hulk = con2.newInstance("Hulk", 50);
show.setAccessible(true);
show.invoke(hulk);

1.4 反射练习

  • 向**ArrayList<Integer>**集合中添加一个字符串数据
ArrayList<Integer> integers = new ArrayList<>();
Class<?> c = integers.getClass();
Method add = c.getDeclaredMethod("add", Object.class);
add.invoke(integers,"我吐");
add.invoke(integers,"反射真神奇");
System.out.println(integers);
//[我吐, 反射真神奇]

2. 注解

2.1 概述:

  • 注解(Annotation),一种代码级别的说明,它是JDK1.5以后版本引入的一个特性,与类、接口、枚举在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等前面,用来对这些元素进行说明,注释。
  • 作用:
    1. 编写文档:通过代码里的标识的注解生成文档
    2. 代码分析:通过代码里的标识的注解对代码进行分析(反射)
    3. 编译检查:通过代码里的标识的注解让编译器能够实现基本的编译检查(@Override)

2.2 JDK预定义注解

  • @Override:检测被该注解标注的方法是否继承自父类
  • @Deprecate:该注解标注的内容,表示已过时
  • @SuppressWarnings:压制警告
    • @SuppressWarnings("all"):压制所有警告

2.3 自定义注解

  • 格式:
public @interface 注解名称{}
  • 本质:注解本质上就是一个接口,继承自java.lang.annotation.Annotation
public interface MyAnno extends java.lang.annotation.Annotation{}
  • 属性:接口中可以定义的抽象方法

    • 要求:
      1. 属性的返回值类型
        • 基本数据类型
        • String
        • 枚举
        • 注解
        • 以上类型的数组
      2. 定义了属性,需要给属性赋值
        • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不进行属性的赋值
        • 如果只有一个属性需要赋值,并且属性名是value,则value可以忽略不写,直接定义值即可
        • 数组赋值时,使用大括号{}包裹,如果数组中只有一个值,则大括号可以省略
  • 元注解:用于描述注解的注解

    • @Target:描述注解能够作用的范围
      • ElementType取值
        • TYPE:可以作用于类上,接口上,注解接口上,枚举类上
        • METHOD:可以作用于方法上
        • FIELD:可以作用于成员变量上,枚举常量上
    • @Retention:描述注解被保留的阶段
      • RetentionPolicy取值
        • SOURCE:注解保留到源码中,编译为字节码class文件中就不存在了
        • CLASS:注解保留在字节码class文件中,不必被JVM读取
        • RUNTIME:注解保留到字节码class文件中,并被JVM读取(可以通过反射方式读取)
    • @Documented:描述注解是否被抽取到api文档中
    • @Inherited:描述注解是否被子类继承
  • 在程序中使用注解:获取注解中定义的属性值

    • 获取注解定义的位置的对象
    • 获取指定的注解
      • getAnnotation(Class)
    • 调用注解中的抽象方法获取配置的属性值

小结:

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用:
    1. 编译器
    2. 解析程序
  3. 注解不是程序的一部分

2.4 代码实现

//简单的实现:解析注解属性值,调用方法
//材料:一个自定义注解,一个测试类,一个解析注解类

//1.自定义注解
@Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    public abstract String className();
    public abstract String methodName();
}
//2.测试类
public class AnnotationDemo {
    public void show(){
        System.out.println("show()方法");
    }
}
//3.注解解析类
@MyAnnotation(className = "com.demo.javase.AnnotationDemo",methodName = "show")
public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //获取当前解析类的对象
        Class<Demo1> c = Demo1.class;
        //获取注解对象(本质上就是在内存中实现了一个注解的类的对象)
        MyAnnotation annotation = c.getAnnotation(MyAnnotation.class);
        /*
        * public MyAnnotionImp implements MyAnnotation{
        *   public String className(){
        *       return "com.demo.javase.AnnotationDemo";
        *   }
        *   public String methodName(){
        *       return "show";
        *   }
        * }
        * */
        //获取注解对象的属性值
        String className = annotation.className();
        String methodName = annotation.methodName();
        System.out.println(className);
        System.out.println(methodName);
        //获取测试类的对象
        Class<?> c2 = Class.forName(className);
        //创建测试类的实例化对象
        Object o = c2.newInstance();
        //获取对应名称的方法
        Method method = c2.getMethod(methodName);
        //执行方法
        method.invoke(o);
    }
}
//利用注解实现程序检错
//材料:一个自定义注解,一个测试类,一个解析注解检错类

//1.自定义注解
@Target(value={ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface Check {
}
//2.测试类
public class Demo2 {
    @Check
    public void add(){
        System.out.println("1+0="+(1+0));
    }
    @Check
    public void subtract(){
        System.out.println("1-0="+(1-0));
    }
    @Check
    public void multiply(){
        System.out.println("1*0="+(1*0));
    }
    @Check
    public void divide(){
        System.out.println("1/0="+(1/0));
    }
}
//3.解析注解检错类
public class CheckDemo {
    public static void main(String[] args) throws IOException {
        Demo2 demo2 = new Demo2();
        Class<? extends Demo2> aClass = demo2.getClass();
        Method[] methods = aClass.getMethods();
        int number = 0;
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt",true));
        for (Method method : methods) {
            if(method.isAnnotationPresent(Check.class)){
                try {
                    method.invoke(demo2);
                } catch (Exception e){
                    number++;
                    bw.write("问题:"+method.getName()+"()方法出现异常");
                    bw.newLine();
                    bw.write("异常:"+e.toString());
                    bw.newLine();
                }
            }
        }
        if(number > 0){
            bw.write("类名:"+ demo2.getClass().getName());
            bw.newLine();
            bw.write("本次总共检测到"+number+"个错误");
            bw.newLine();
            bw.write("检测时间:"+new SimpleDateFormat("yyyy年MM月dd日 kk时mm分ss秒").format(new Date(System.currentTimeMillis())));
            bw.newLine();
        }else{
            bw.write("恭喜你,本次检测此程序没有错误");
        }
        bw.write("---------------------------------------------");
        bw.newLine();
        bw.flush();
        bw.close();
        System.out.println(number);
    }
}
//bug.txt的结果
/*
问题:divide()方法出现异常
异常:java.lang.reflect.InvocationTargetException
类名:com.demo.javase.demo.Demo2
本次总共检测到1个错误
检测时间:2020年12月15日 12时39分35秒
*/

原文作者:絷缘
作者邮箱:zhiyuanworkemail@163.com
原文地址:https://zhiyuandnc.github.io/3pOEFSCc4/
版权声明:本文为博主原创文章,转载请注明原文链接作者信息