Java基础杂记

2021/4/17 22:28:38

本文主要是介绍Java基础杂记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Java基础知识点概要

写在前面:此篇文章并不是完整的从零开始学Java,而是作者自身根据所学知识的简要回顾,因此并不适合想要从零入门的同学,但对于已经学习过Java的可以进来看看,当作简要的知识回顾。文章中列举的知识点只是作者仅存脑海中或者查阅资料而书写的学习笔记,是对自身学习的写照。也因为Java如此庞大的知识体系,故难以叙述地面面俱到,如果你有任何更好的意见,欢迎随时留言,鄙人将吸取精华而改进。望大家不吝赐教!

Hello,this is Reflect. He always confirm there is no one is fool and just some lazy people.

SO , Open The Door . And Let’s Go!!!

Java面向对象的三大特征

封装:即访问控制权限。

  • 概述:成员变量隐藏在对象内部,外界无法直接操作。

  • 封装原则:私有属性用private修饰隐藏在类内部,不允许外界程序直接访问,提供getter和setter方法(二者方法必须为public)供外界访问。

  • 好处:通过方法操作成员变量,提高了代码的安全性;把代码用方法进行封装,提高了代码的复用性

继承:Java中只能单继承,但是支持多层继承(Grandpa->Father->Son)

  • 好处:子类继承父类,提高了代码的复用性、维护性。
  • 弊端:继承使类之间产生了关系,耦合性增加了。
  • 什么时候使用继承
    • 继承的体现关系:is a
    • 假设:两个类A、B;如果A是B的一种,或者B是A的一种,则说明它们之间存在继承关系,此时可以考虑使用继承。

多态:同一个对象,在不同时刻表现出来的不同形态

  • 多态的前提和体现
    • 有继承/实现关系
    • 有方法重写
    • 有父类引用指向子类对象
  • 好处:提高了程序的扩展性
    • 具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作
  • 弊端:不能使用子类的特有功能

基本数据类型

4类8种

  • 逻辑型:boolean(只有两种取值)
  • 文本型:char(默认是Unicode编码)
  • 整型:byte,short,int,long
  • 浮点型:float,double
类型长度(位)占用存储空间(字节)默认值取值范围
byte810-272^7^-1,即-128127
short1620-215~215-1,即-32 768~32 767
int3240-231~231-1,即-2 147 483 648~2 147 483 647
long6480L(不建议小写,容易与1混淆)-263~263-1
float3240.0f/F
double6480.0d
char162\u0000
boolean81falsetrue、false

null:空值,引用数据类型的默认值,表示不指向任何有效对象。

运算符

懂的都懂。辨析两种自增,理解位运算符、与或非、三元运算符

  • i++:先赋值再自加

  • ++i:先自加再赋值

  • 条件?a:b:条件成立执行a,不成立执行b

流程控制

循环语句

  • while、do while

    • while先判断后执行
    • do while先执行后判断,即执行执行一次
  • for

    • 执行次数可以提前确定

      for (int i=0;i<10;i++){
          System.out.println("执行");
      }
      // ①执行初始语句 int i=0
      // ②判断逻辑表达式 i<10
      // ③执行语句或语句块 print
      // ④执行迭代语句 i++
      // 第二次及其以后执行只需要②③④
      

分支语句:

  • if else
  • switch
    • swtich中整型表达式的值必须是int兼容的类型(byte,short,char,int),不允许浮点型(float、double)或long型
    • 每个case后一定要加break使得退出,否则将会一直判断
    • default子句是可选的,并且最后一个break语句可以省略

跳转语句:

  • break、continue、return(方法运行中碰到return即退出方法)
    • break:从与之最近的语句块跳出整个循环。
    • continue:结束本次循环,直接开始下一次循环。
    • 二者的区别:break是结束循环,即循环终止;continue是结束本次循环,开始下一次循环

增强for循环

为了简化数组和 Collection集合的遍历

  • 实现 Iterable接口的类允许其对象成为增强型for语句的目标

  • JDK5.0之后,引入了增强for循环,其内部原理是一个 Iterator迭代器。也叫做for each (loop)循环

注意:能被增强for迭代遍历的必须要是数组或集合类的派生子类。例如Map不能直接使用增强for

for (int i : arr) {
    System.out.println(i);
}

验证其内部原理是一个Iterator迭代器

会抛出并发修改异常,与Iterator进行add抛出异常相同

for (String s:list) {
    if(s.equals("world")) {
        list.add("reflect");
    }
}

数组

数组的定义

辨析

二者使用的时候没有区别,但是读的时候完全不同。推荐一般使用格式一

  • int[] arr:这种叫做定义了一个int类型的数组名叫arr

  • int arr[]:这种叫做定义了一个int类型的变量,变量名是arr数组

数组的创建

动态初始化:只需要在后面指定数组的长度,系统为之分配默认值(如int默认值为0)

int[] arr1 = new int[3];

静态初始化:需要给出指定的元素,但不需要指定数组的长度,系统会根据指定的元素个数自动推断长度

int[] arr2 = new int[]{1,2,3};
int[] arr3 = {1,2,3};	// 静态初始化的简化格式

数组的访问

使用索引访问数组,从0开始。数组的长度获取为arr.length

常见问题:

  • 索引越界(ArrayIndexOutOfBoundsException):即不能使用arr[arr.length]访问,数组的最后一位为arr[arr.length-1]

  • 空指针异常(NullPointerException):访问的数组已经不再执行堆内存的数据,造成空指针异常。简单示例如下:

    int[] arr = new int[3];
    System.out.println(arr[2]);// 输出0
    arr = null;
    System.out.println(arr[0]);// 空指针异常
    

多维数组

这个就。。二维数组类似于线性代数中的矩阵一样。多维数组也好理解,不过多解释了。

数组的复制

引用复制

数组变量之间的复制实际是引用赋值,如下

int[] a = new int[6];
int[] b;
b = a;
System.out.println(a == b);// 将输出true,因为二者指向同一个引用

数据复制

int[] a= {'a','b','c','d'};	// 此处实际存放的是'a','b,'c','d'的ASCII码
int[] c = new int[4];

System.arraycopy(a,0,c,0,4);
System.out.println(a == c);
for (int i : c) {
    System.out.println(i);	// 输出97-100
}

方法

方法定义和调用

public [static] [返回类型] MethodName([参数列表]) {
    // 方法体
    return 数据;
}
  • static决定是否为静态方法,定义为静态方法之后可直接使用MethodName调用方法;否则需要通过new了方法所在的类得到的对象**.**(点)MethodName调用

  • 参数列表中的参数格式:数据类型+变量名(多个参数用逗号分隔)

  • 返回类型:返回类型必须与return后面的返回值类型相同

  • 数据:方法操作完成之后返回的数据类型与返回类型要一致

方法必须先定义后调用,否则将会找不到方法。

有参数的方法调用的时候必须传递与参数类型相同的变量或常量。

形参和实参

  • 形参:方法定义中的参数
  • 实参:方法调用中传递给方法的参数

注意事项

  • 方法不能嵌套定义
  • void表示无返回值,此时可以省略return;也可以单独写return,后面不加东西

参数的传递

  • 对于基本数据类型的参数,形参的改变,不影响实参的值(如int类型作为实参,实际操作只是将实参的值传递给形参,即使形参进行值的修改也并不会影响实参所引用的值)

  • 对于引用类型的参数,形参的改变,影响实参的值(如数组作为实参,而形参使用arr[1]改变其中的值,实参中的arr[1]也发生改变)

类是对现实中一系列事物的抽象。

基本结构

  • 类声明
  • 变量(此处主要讨论的是成员变量)
  • 构造方法
  • 方法

示例:

package com.reflect.pojo;

/**
 * author: Administrator
 * date: 2020/12/30 - 10:08
 */
public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

类的声明

public class User // 此为示例,下面一行为语法格式。类名每个单词的首字母都大写,其余小写
[public][abstract][final] class ClassName [extends SuperClassName] [implements InterfaceNameList]

成员变量

[public | protected | private][static][final][transient][volatile] type variableName;
  • 参数细解
    • 第一个[ ]中为修饰符
    • static限制该成员变量为类变量,没有static修饰的成员变量为实例变量
    • final声明一个常量,使用了final限定的常量不能修改,且一般使用大写命名表示
    • transient声明一个暂时性变量,默认情况下,类中所有变量都是对象永久状态的一部分。而用transient限定的变量则指示Java虚拟机,该变量并不属于对象的永久状态,从而不能被永久存储
  • 存在于类中,但在方法外
  • 内存中的位置存在堆内存中
  • 随着对象的存在而存在,对象的消失而消失
  • 有默认的初始值

局部变量

  • 方法中的变量即为局部变量,即其存在方法内或方法声明中

  • 内存中的位置存在栈内存中。

  • 随着方法的调用而存在,方法的使用完毕而消失

  • 没有默认的初始值,必须先定义值才能使用

构造方法

寻思着也用不着写代码吧,毕竟应该都知道吧。。。直接上理论了!

分类

构造方法主要分为无参构造和有参构造。

  • 构造方法名称必须与类名相同,
  • 构方法不能有返回值(并不是指写成void(void实际上是返回空),而是不写)
  • 不能直接调用构造方法,必须通过new自动调用

注意事项

  • 默认自带无参构造,但是当写了有参构造之后,将不会再有默认的无参构造,因此建议无参和有全部参数的构造方法都写上。

构造方法可以重载,即有参构造可以不止一个。

比如上述的User类中,只写了一个全部参数的有参构造。但是如果需要的话,可以重载构造方法。其实是一种多态的体现。

方法重载与方法重写

方法重写是继承中的关系,但是与方法重载经常放在一起,因此放在一起做个分析。

方法重载的条件

  • 方法名称必须相同
  • 方法的参数列表必须不同,参数的类型、个数不同,甚至是参数的位置改变顺序都可以
  • 方法的返回类型可以相同也可以不相同,因此返回类型不同不足以构成重载
    // 方法重载示例
    public User(int id) {
        this.id = id;
    }

    public User(String name) {
        this.name = name;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

方法重写的条件

  • 子类中重写的方法必须与父类保持一致,包括返回值类型,方法名,参数列表也都一样
  • 子类中重写方法的访问权限不能缩小(访问权限就在下面介绍)
  • 子类中重写方法不能抛出新的异常
  • 一般都可以使用@Override注解来标识重写的方法,可以帮助检测是否符合重写规范。

this和super

this和super都是Java中的关键字。super是继承中使用的关键字,但是在此,放在一起做个区分。

this

this代表当前对象,可以调用方法、调用属性和指向对象本身。

一般有三种指向:

  • 指向当前对象

        /**
         * author: Administrator
         * date: 2021/1/3 - 13:54
         */
        public class Test1 {
            public static class Apple {
                int i = 0;
                Apple eatApple(){
                    i++;
                    return this;
                }
                public static void main(String[] args) {
                    Apple apple = new Apple();
                    // 链式编程
                    apple.eatApple().eatApple();
                }
            }
        }
    

    这段代码的精妙之处就在链式编程,竟然可以多次调用eatApple方法,实际上就是this关键字的效果,返回的一直都是Apple类自身,所以可以一直调用。

  • this修饰属性,最常见就是在构造方法中使用。几乎每个有参构造中都有用到。

    • 这里最重要的就是:当成员变量和局部变量重名时,默认在方法中优先使用局部变量,如果想要使用成员变量就要在前面加上this关键字
  • 与构造函数一起使用,充当全局关键字的效果

        public class Apple {
            private int num;
            private String color;
            public Apple(int num){
                this(num,"ᕁᜋ");
            }
    
            public Apple(String color){
                this(1,color);
            }
            public Apple(int num, String color) {
                this.num = num;
                this.color = color;
            }
        }
    

    上面这段代码使用的不是this,而是this(参数)。相当于调用了其它构造方法,然后传递参数进去。

    需要注意的是:this()必须放在构造方法的第一行,否则编译不通过

super

如果把this理解为指向自身的一个引用,那么super就是执行父类的一个引用。

  • 使用方法也可以和this一样,super.对象来引用父类的成员
  • 也可以使用super(参数)来调用父类的构造函数,也必须放在第一行。
关键字调用方式调用位置调用次数
this调用本类中的属性、构造函数、方法构造函数第一行一个构造函数只能调用一次
super调用父类中的属性、构造函数、方法构造函数第一行一个构造函数只能调用一次

修饰符

权限修饰符

4种不同的访问权限

privatedefaultprotectedpublic
同一个类
同一个包
子类
其他包中的类

状态修饰符

final(最终态)

final关键字是最终的意思,可以修饰成员方法、成员变量、类

final修饰的特点

  • 修饰方法:表明该方法是最终方法,不能被重写
  • 修饰变量:表明该变量是常量,不能再次被赋值,因此建议变量名全部字母都大写
  • 修饰类:表明该类是最终类,不能被继承
  • 修饰局部变量
    • 变量是基本类型:final修饰指的是基本类型的数据值不能发生改变
    • 变量是引用类型:final修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的

static(静态)

static关键字是静态的意思,可以修饰成员方法,成员变量

static修饰的特点

  • 被类的所有对象共享:这也是我们判断是否使用静态关键字的条件
  • 可以通过类名调用,也可以通过对象名调用,推荐使用类名调用

静态代码块

  • 不属于任何方法体并且以static关键字修饰的语句块,称为静态语句块。常用来进行类变量的初始化。

    static {
        ...
    }
    

static访问特点

一句话:静态成员方法只能访问静态成员(不能访问非静态的;而非静态成员方法可以访问非静态或者静态)

其实对应就是文件夹,作用对类进行分类管理

定义格式

package 包名(多级包使用*.*分开,包名统一使用小写)

使用包

使用其它包下的类时,需要书写类的全路径,写起来较麻烦,为了简化此操作,Java提供了导包功能(import)

对象的生命周期

初始化

我们都知道Car car = new Car();就可以new出一个对象car。但是需要知道在new出car中进行了类的初始化。

类的初始化

上述的new中括号没有参数,实际上就是使用了无参构造。

成员初始化

Java会尽量(难免出现异常)保证每个变量在使用前都会获得初始化。初始化涉及两种成员初始化。

  • 编译器默认指定的字段初始化——基本数据类型的初始化
    • 前面所讲述的4类8种基本类型的默认赋值,在表格中都有说到默认值
  • 其他对象类型的初始化。
    • 如String也是一种对象,初始值都是null,也包括基本类型的包装类,都是为null
  • 指定数值的初始化,int a = 11

构造器初始化

通过构造器可以初始化其中的参数值

初始化顺序

  • ①静态属性、静态代码块:static开头的属性、static{}包围代码块;二者谁在前就先执行
  • ②普通属性、普通代码块:非static开头的属性、{}包围的代码块,二者谁在前就先执行
  • ③构造函数:类名相同的方法

使用

  • 引用对象的变量:如car.name;
  • 调用对象的方法:如car.run();

销毁

  • 垃圾收集器: 垃圾收集器可以周期性释放不再引用的对象所占的内存。也可以显式调用System.gc()方法执行垃圾回收
  • 对象的最终化(finalization)处理:一个对象在被收集前,垃圾收集器将调用对象的finalization()方法,以使对象自己能够做最后的清理,释放占有资源,这个过程称为对象的最终化

对象作用域

作用域决定了其内部定义的变量名的可见性和生命周期。通常由{}来决定。

String

内存结构

1.8中String的两个重要属性(JDK1.8及其之前的底层原理字符串方法char[])

  • value:引用或内容
  • hash:内容所对应的哈希码

1.9中String的三个重要属性(JDK1.9及其之后的底层原理字符串方法byte[])

目的为了节约内存,但是也只有当内容全为拉丁字符(英文字母)时才能节省。

  • value:引用或内容
  • coder:区分byte[]是否全是英文拉丁字符为0,有Unicode字符时为1。当二者都有时,统一当成Unicode处理
  • hash:内容所对应的哈希码

拼接方式

1.8及其以前默认拼接方式为StringBuilder

1.9及其以后默认拼接方式为invokedynamic,若还行继续使用StringBuilder为拼接方式,可在javac时加参数

String的内容是不可变的,字符串拼接时容易浪费空间

字符串的创建

package com.reflect.base.string;

/**
 * author: Administrator
 * date: 2021/1/3 - 8:42
 */

/**
 * 字符串的六种基本的创建方式
 * 使用char[]数组配合new来创建,JDK1.8及其之前的底层原理字符串方法
 * 1.8及其之前版本,不管任何类型创建之后会转换为char[],为了统一字符集编码为Unicode
 * 使用byte[]数组配合new来创建,JDK1.9及其之后的底层原理字符串方法,目的是为了更节约内存。并且增加了invokedynamic指令扩展字符串的拼接实现方式
 * 使用int[]数组配合new来创建
 * 使用已有字符串配合new来创建
 * 使用字面量创建(不使用new),最熟悉的创建方式。三点:
 * 非对象:字面量在代码运行到它所在语句之前,它还不是字符串对象
 * 懒加载:当第一次用到"abc"字面量时,才会创建对应的字符串的对象
 * 不重复:同一个类中,值相同的字面量只有一份。再次创建相同的值时,其实是引用之前的地址
 * 合二为一,使用+运算符来拼接创建
 */
public class StringEstablishTest {
    public static void main(String[] args) {
//        字符串的六种基本的创建方式
//        使用char[]数组配合new来创建,JDK1.8及其之前的底层原理字符串方法
        String s1 = new String(new char[]{'a','b','c'});
//        使用byte[]数组配合new来创建,JDK1.9及其之后的底层原理字符串方法,以及网络传递(http)和I/O读取的数据
        String s2 = new String(new byte[]{97, 98, 99});
//        使用int[]数组配合new来创建,unicode中的emoji表情就是如此存储的
        String s3 = new String(new int[]{0x1F602}, 0, 1);
//        使用已有字符串配合new来创建,这种方式最为简单,但是s4与s1引用的是同一个char[]对象
        String s4 = new String(s1);
//        使用字面量创建(不使用new)
        String s5 = "abc";
//        合二为一,使用+运算符来拼接创建
        // 真正没有[拼接]操作发生,从源码编译为字节码时,javac就已经把'a'和'b'串在一起,这是一种编译期的优化处理
        String s6 = "a" +"b";

        // 编译器发现x为final已经不可变,相当于x直接可以被'b'替换,最后如上所示编译
        final String x = "b";
        String s7 = "a" + x;

        // x为变量,不能在编译期间确定值,运行期间可能发生改变,
        // 所以要真正进行拼接操作——创建StringBuilder进行拼接(append()之后再toString返回成字符串)
        String y = "b";
        String s8 = "a" + y;

        // 1为int类型,也需要使用StringBuilder进行拼接操作
        String s9 = "a" + 1;
    }
}

字符串的比较

"=="比较

  • 基本类型比较的是数据值是否相同;

  • 引用类型比较的是地址值是否相同。

String不属于基本数据类型,所以上述比较的引用地址,如果想要比较数据值是否相同,则使用equals()比较内容

equals()比较

public boolean equals(Object anObject):将此字符串与指定对象进行比较。

字符串的遍历

public char charAt(int index)获取字符串中的每一个字符

字符串的拼接

使用s+=“abc”

字符串的反转

倒序charAt遍历,然后再使用+拼接

StringBuilder

可变的字符串类(即StringBuilder中的内容是可变的),可以看成是一个容器。

String与StringBuilder的转换

    StringBuilder sb = new StringBuilder();
    sb.append("hello");
    // String s =sb;	// 错误的做法
    String s = sb.toString();
    System.out.println(s);

    String s = "hello";
    // StringBuilder sb = s;	// 错误的做法
    StringBuilder sb = new StringBuilder(s);
    System.out.println(sb);

字符串的拼接

采用sb.append()方法。

字符串的反转

new StringBuilder(s).reverse().toString()

StringTable

管理字符串

只有通过字面量创建的String才会进入StringTable管理,好处就是其中的值不会重复,减少内存开销。

StringTable本质其实是哈希表,字符串对象就是hash表中的key,key的不重复性,是hash表的基本特性。

    @Test
    public void test1() {
        String s1 = "abc";  // StringTable管理
        String s2 = "abc";  // 同上
        String s3 = new String(new char[]{'a','b','c'});    // 不受StringTable管理
        String s4 = "a" + "bc"; // 由于前后都是定值,所以在编译期间会直接创建"abc",受到StringTable管理
        String x = "a"; // x为变量
        String s5 = x + "bc";   // 由于x为变量,所以编译期间不会直接创建"abc",因此不收到StringTable管理

        System.out.println(s1 == s2);   // true
        System.out.println(s1 == s3);   // false
        System.out.println(s1 == s4);   // true
        System.out.println(s1 == s5);   // false
}

收留字符串

字符串提供了intern方法实现去重,让其它字符串有机会受到StringTable管理。

    public native String intern();

此方法会尝试将调用者放入StringTable

如果StringTable中存在
    String x = ...;
    String s = x.intern();

如果已经存在,则会返回其中已经存在的String对象

@Test
public void test2() {
    String x = new String(new char[]{'a','b','c'}); // 不受管理
    String y = "abc";   // 受管理
    String z = x.intern();  // 已经存在,返回StringTable中的"abc",即y

    System.out.println(z == x); // false
    System.out.println(z == y); // true
}
如果StringTable中不存在(1.7及其以后JDK的做法)

如果StringTable中不存在,则将x的引用对象加入StringTable,返回StringTable中的对象

如果StringTable中不存在(1.6及其以前JDK的做法)

如果StringTable中不存在,则将x的引用对象复制一份,将新复制的那份加入StringTable,返回StringTable中的对象

StringTable的位置

1.6版本时

StringTable属于方法区,方法区的实现叫作永久代(PerGen)

1.8版本时

方法区变成了元空间(Metaspace),1.7开始StringTable不属于方法区,而是从方法区移到了堆(heap)中。

原因
  • 永久代的回收一般是Full GC,当进行Full GC时,说明此时内存紧张,此时再进行回收StringTable就没什么用处。
  • Minor GC回收速度比Full GC快很多

intern去重原理

需要手动调用才能实现去重。

G1去重

使用JDK8u220,可使用JVM参数开启G1垃圾回收器,并开启字符串去重功能

    -XX:+UseG1GC -XX:+UseStringDeduplication

原理是让多个字符串对象引用同一个char[]来达到节省内存的目的

  • 与intern相比,G1去重好处在于自动,但缺点是char[]即使不重复,但字符串对象本身还要占用一定内存;intern去重是字符串对象只存一份,更省内存。

StringTable的大小

StringTable足够大,才能发挥性能优势。意味着String在hash表中冲突减少,链表短,性能高。

JDK8中默认大小为60013,StringTable底层的hash表在JVM启动后大小就固定不变了。

字符串消亡

当字符串没有引用时,会收垃圾回收掉!

继承(extends)

继承中访问特点:说白了可以这样理解,就是自己力所能及的事情就不用就麻烦长辈,否则就需要向长辈请教,如果长辈也不懂,那么就报错。

继承中变量的访问特点

在子类中访问一个变量的顺序

  • 先到子类中所在方法的局部范围找
  • 再到子类中的成员范围找
  • 再到父类成员范围找
  • 否则报错

super

在与this关键字的时候已经说过,不过多叙述,知道这也是一个重点即可。

继承中构造方法的访问特点

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

  • 因为子类继承自父类的数据,可能还会用到父类中的数据,所以子类初始化之前,一定要先完成父类数据的初始化
  • 每个子类的构造方法的第一条语句默认都是super(),这就是即使调用子类的有参构造创建时,父类初始化还是使用无参构造的原因

2.如果父类中没有无参构造,只有带参构造,如何进行初始化?

  • 通过在子类中的super关键字显式调用父类中的带参构造
  • 在父类中提供无参构造(推荐一般无参构造都写)

继承中成员方法的访问特点

通过子类对象访问一个方法

  • 先子类成员范围找
  • 再父类成员范围找
  • 否则就报错

方法重写

上面已经与方法重载做了区分条件,此处只讲解在继承中的注意事项。

  • 父类中的private修饰的方法,子类不能重写
  • 子类重写时方法访问权限不能更低

多态

举例

我们可以说猫是猫,也可以说猫是动物。

这里猫在不同的时刻表现出不同的形态,这就是多态。

    Cat cat = new Cat();
    Animal animal = new Cat();

多态中成员访问特点

  • 成员变量:编译看左边,执行看左边
  • 成员方法:编译看左边,执行看右边

为什么成员变量和成员方法的访问不一样?

  • 因为成员方法有重写,而成员变量没有

转型

instaceof

转型之前为了防止出错,一般要先测试确定对象的类型,然后再执行转换。

    // 判断a是否是Cat类型
    Animal a = new Cat();
    if(a instanceof Cat) {
        // dosomething
    }

向上转型(上溯造型upcasting)

从子到父,父类引用指向子类对象

    Animal a = new Cat();	// 向上转型
    a.eat();	// Animal中和猫都有此方法
    // a.playGame();	// Animal中没有此方法,猫中特有

向下转型(向下造型downcasting)

从父到子,父类引用转为子类对象。即所谓的强制转型

向下转型,解决了不能访问子类中特有功能的弊端。

    Cat c = (Cat) a;	// 将上面的Animal对象a向下转型为Cat对象c
    c.eat();	// 猫中方法
    c.playGame();	// 猫中方法

抽象

Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类

就是一个方法没有{}这个方法体,就要变成抽象方法,而一个类中只要有抽象方法就要成为抽象类,但是其中也可以包含非抽象方法。递推的过程应该容易理解吧。

    没有{} ---> 此方法为抽象方法 ---> 此类为抽象类
    但是  抽象类 --×->类中方法都为抽象方法

抽象类的特点

  • 抽象类和抽象方法必须使用abstract关键字修饰

    • public abstract class 类名{}
    • public abstract void eat();
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

  • 抽象类不能实例化

    • 如果要实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态

          Animal a = new Cat();	// 抽象类Animal,Cat为其子类,通过此种方式实例化
      
  • 抽象类的子类

    • 要么重写抽象类中的所有抽象方法
    • 要么自身定义为抽象类

抽象类的成员特点

  • 成员变量
    • 可以为变量,也可以为常量,与正常类基本无差别
  • 构造方法
    • 有构造方法,但是不能实例化
    • 构造方法的作用???用于子类访问父类数据的初始化
  • 成员方法
    • 可以有抽象方法:限定子类必须完成某些动作
    • 也可以有非抽象方法:提高代码复用性

接口(interface)

Java中的接口更多的体现在对行为的抽象

继承类,但是要实现(implements)接口

接口的特点

  • 接口用关键字interface修饰
    • public interface 接口名 {}
  • 类实现接口用implements表示
    • public class 类名 implements 接口名 {}
  • 接口不能实例化
    • 接口如何实例化?参照多态的方式,通过实现类对象实例化,这叫接口多态
    • 多态的形式:具体类多态,抽象类多态,接口多态
    • 多态的前提:有继承或者实现关系;有方法重写;有父(类/接口)引用指向(字/实现)类对象
  • 接口的实现类
    • 要么重写接口中的所有抽象方法
    • 要么是抽象类

接口的成员特点

  • 成员变量
    • 只能是常量
    • 默认修饰符:public static final
  • 构造方法
    • 接口没有构造方法,因为接口主要是对行为进行抽象的,是没有具体存在
    • 一个类如果没有父类,默认继承自Object类
  • 成员方法
    • 只能是抽象方法
    • 默认修饰符:public abstract

类和接口的关系

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

抽象类和接口的区别

  • 成员区别
    • 抽象类:变量,常量;有构造方法;有抽象方法,也有非抽象方法
    • 接口:常量;抽象方法
  • 关系区别
    • 类与类:继承,只能单继承
    • 类和接口:实现,可以单实现或多实现
    • 接口和接口:继承,可以单继承或多继承
  • 设计理念区别
    • 抽象类:对类抽象,包括属性、行为
    • 接口:对行为抽象,主要是行为

抽象类名作为形参和返回值

  • 方法的形参是抽象名,其实需要的是该抽象类的子类对象
  • 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象

接口作为形参和返回值

  • 方法的形参是接口名,其实需要的是该接口的实现类
  • 方法的返回值是接口名,其实返回的是该接口的实现类

内部类

在一个类中定义一个类,也称为嵌套类。

    public class Outer{
        修饰符 class Inner{

        }
    }

内部类的访问特点

  • 内部类可以直接访问外部类的成员,包括私有的属性或方法
  • 外部类要访问内部类的成员,必须创建对象

根据内部类在类中定义的位置不同,分为两种形式

  • 在类的成员位置:成员内部类
  • 在类的局部位置:局部内部类

成员内部类

public class Outer{
    public class Inner{
        ...
    }
}
// 当内部类的修饰符为public时,可使用如下格式进行创建内部类对象
Outer.Inner oi = new Outer().new Inner();

// 但是内部类的定义就是为了使得内部类隐藏起来,不能被外界调用。
// 所以,内部类的修饰符一般为private,此时将不能再通过如上方式创建内部类对象使用
public class Outer{
    private class Inner{
        ...
    }
    public void method() {
        Inner i = new Inner();
    }
}
// Outer.Inner oi = new Outer().new Inner();	// 失效
// 此时需要使用内部类时可通过创建Outer对象,使用其method方法对内部类进行操控即可

局部内部类

    public class Outer{
        public void method() {
            class Inner{
                ...
            }
            // 若method方法中存在局部变量,Inner类可以直接访问method的变量
            // Inner i = new Inner();	// 需要使用局部内部类时创建出此对象
        }
    }
    // 直接通过Outer的对象调用method的方法访问不到局部内部类,因为在里面没有任何关于内部类的创建对象
    Outer o = new Outer();
    o.method();	// 如果上述的局部内部类一行不存在,将无法对Inner进行操作。写上之后才可进行操作

匿名内部类

匿名内部类是局部内部类的特殊形式。

  • 前提:存在一个类或接口,这里的类可以是具体类或抽象类

  • 格式:

        new Inter() {	
            // 此处的Inter为类名或接口名,中间show为重写的方法
            public void show() {
                ...
            }
        };	// 此处不要忘记分号
    
  • 本质:是一个继承了该类或该接口的子类匿名对象

    即上述代码可以在前面用Inter i =去承接这个对象,之后直接使用i.show()即可

静态内部类

    public class Outer{
        public static class Inner{
            ...
        }
    }

常用API

Scanner

Random

Math

  • Math包含执行基本数字运算的方法

  • 没有构造方法,如何使用类中的成员?

    • 看类的成员是否都是静态的,如果是,通过类名就可以直接调用

常用方法

方法名说明
public static int abs(int a)返回参数的绝对值
public static double ceil(double a)返回大于或等于参数的最小double值,等于一个整数
public static double floor(double a)返回小于或等于参数的最大double值,等于一个整数
public static int round(float a)按照四舍五入返回最接近参数的int
public static int max(int a,int b)返回两个int值中的较大值
public static int min(int a,int b)返回两个int值中的较小值
public static double pow(double a,double b)返回a的b次幂的值
public staticdouble random()返回值为double的正值,[0.0,1.0)

System

方法名说明
public static void exit(int status)终止当前运行的Java虚拟机,非零表示异常终止
public static long currentTimeMills()返回当前时间(以毫秒为单位)

Object

Object是Java中类层次数的根。每个类都是Object的直接或间接子类。正是由于这种特殊地位,此类中定义了所有对象都需要的状态和行为。

在Object子类中可以重写以下Object类的方法。

其中,clone是复制,hashCode是根据哈希算法计算此对象的哈希码,equals比较内容是否相等,finalize为对象终结的方法,toString对象的字符串表示,以及最后三个是与线程相关的方法。

  • clone()
    • 调用clone方法的对象,必须实现类java.lang.Cloneable接口,否则将抛出CloneNotSupportedException异常。因为Object类本身没有实现这个接口,所以提供复制能力的类必须自己实现Cloneable接口。
    • clone方法是shallow copy(浅复制)而不是deep copy(深复制)
      • shallow copy(浅复制):新建一个对象,将其引用指向克隆的对象即可,即比较克隆之后的对象与被克隆对象的内容相同,引用也是相同的
      • deep copy(深复制):完全从被克隆对象复制一份,交给克隆对象,即二者的内容相同,但引用不同
  • equals() / hashCode()
    • public boolean equals (object obj)equals比较当前对象的引用是否与参数obj执行同一个对象,如果指向同一个对象返回true。但String、Date、File类和所有包装类(Integer、Long等)都重写此方法,改为比较所指向对象的内容。
    • "=="对于引用型变量,比较的是这两个变量所指对象的地址。
    • 要比较两个字符串内容是否相同应该使用str1.equals(str2),而不是使用"=="
  • finalize():对象的最终态。前面已经做过叙述,不过多叙述。
  • toString():返回对象的字符串表示形式,表达的内容因具体对象而异。一般在自己类中都重写
  • notify()
  • notifyAll()
  • wait()

Arrays

Arrays类包含用于操作数组的各种方法

方法名说明
public static String toString(int[] a)返回指定数组的内容的字符串表示形式
public static void sort(int[] a)按照数字顺序排列指定的数组

工具类的设计思想

  • 构造方法用private修饰
  • 成员用public static修饰

基本类型包装类

将基本数据类型封装成对象的好处在于可以在对应中定义更多的功能方法操作该数据

常用操作之一:用于基本数据类型和字符串之间的转换

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Integer

构造方法
方法名说明
public Integer(int value)根据int值创建Integer对象(过时)
public Integer(String s)根据String值创建Integer对象(过时)
public static Integer valueOf(int i)返回一个标识指定的int值的Integer实例
public static Integer valueOf(String s)返回一个保存指定值的Integer对象String
int和String的相互转换
  • int转换为String
    • public static valueOf(int i):返回int参数的字符串表示形式。该方法是String类中的方法
  • String转换为int
    • public static int parseInt(String s):将字符串解析为int类型,该方法是Integer类中的方法

自动装箱和拆箱

  • 装箱:把基本数据类型转换为对应的包装类类型
  • 拆箱:把包装类类型转换为对应的基本数据类型
package com.reflect.base.box;

/**
 * author: Administrator
 * date: 2021/1/4 - 15:16
 */
public class AutoBox {
    public static void main(String[] args) {
        // 装箱:把基本数据类型转换为对应的包装类类型
        Integer i1 = Integer.valueOf(100);   // 装箱操作
        Integer i2 = 100;   // 自动装箱,JDK1.5做出的改进,底层也使用了Integer.valueOf()

        // 拆箱:把包装类类型转换为对应的基本数据类型
        i1 = i1.intValue() + 200;   // 先进行拆箱成int类型,再与int类型的200相加
        i2 = i2 + 200; //自动拆箱,JDK1.5做出的改进,底层也使用了i2.intValue()拆箱,再进行自动装箱

        Integer i = null;
//        i += 300;   // NullPointerException
        // 鉴于以上操作,在对包装类型进行使用时都先进行判断是否为空操作
        if(i != null) {
            i += 300;
        }
    }
}

注意:在使用包装类类型的时候,如果做操作,最好先判断是否为null

推荐的是,只要是对象,在使用前就必须进行不为null的判断

日期类

Date

常用方法
方法名说明
public long getTime()获取的日期对象从1970年1月1日00:00:00到现在的毫秒值
public void setTime(long time)设置时间,给的是毫秒值

SimpleDateFormat

SimpleDateFormat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。重点掌握日期格式化和解析

日期和时间格式由日期和时间模式字符串指定,在提起和时间模式中,从’A’到’Z’以及从’a’到’z’引导的字母被解释为表示日期和时间字符串的组件的模式字母

常用模式和字母对应关系

  • y 年
  • M 月
  • d 日
  • H 时
  • m 分
  • s 秒
SimpleDateFormat构造方法
方法名说明
public SimpleDateFormat()构造一个SimpleDateFormat,使用默认模式和日期格式
public SimpleDateFormat(String pattern)构造一个SimpleDateFormat使用给定的模式和默认的日期格式
SimpleDateFormat格式化和解析日期
  • 格式化(从Date到String)
    • public final format(Date date):将日期格式化成日期/时间字符串
  • 解析(从String到Date)
    • public Date parse(String source):从给定字符串的开始解析文本以生成日期

Calendar

Calendar为某一时刻和一组日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法

Calendar提供了一个类方法getInstance用于获取Calendar对象,其日历字段已使用当前日期和时间初始化

Calendat rightNow = Calendar.getInstance();

package com.reflect.base.calendar;

import java.util.Calendar;

/**
 * author: Administrator
 * date: 2021/1/4 - 15:55
 */
public class CalendarTest {
    public static void main(String[] args) {
        // 获取日历类对象
        Calendar instance = Calendar.getInstance();

        // 返回给定日历字段的值
        int year = instance.get(Calendar.YEAR);
        int month = instance.get(Calendar.MONTH) + 1;   // 此处的日历从0开始,因此要加1
        int date = instance.get(Calendar.DATE);
        System.out.println(year +"年"+ month +"月"+ date +"日");
    }
}
常用方法
方法名说明
public int get(int field)返回给定日历字段的值
public abstract void add(int field,int amount)根据日历的规则,将指定的时间量添加或减去给定的日历字段
public final void set(int year,int month,int date)设置当前日历的年月日

异常

异常概述

  • Error:严重问题,不需要处理
  • Exception:称为异常类,表示程序本身可以处理的问题
    • RuntimeException:在编译期是不检查的,出现问题后,需要回来修改代码
    • 非RuntimeException:编译期就必须处理的,否则程序不能通过编译,更不能正常运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJaQqpC2-1618663004183)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210104162526813.png)]

JVM的默认处理方案

如果程序出现了问题,我们不做任何处理,最终JVM会做默认处理

  • 把异常的名称、异常原因及异常的位置等信息输出在控制台
  • 程序从出现异常的那一行停止执行

异常处理

try…catch

  • 格式

        try {
            可能出现异常的代码;
        } catch(异常类名 变量名) {
            异常的处理代码;
        }
    
  • 执行流程

    • 程序从try中的代码开始执行
    • 出现异常,自动生成一个异常类对象,提交给Java运行时系统
    • 当Java运行时系统接收到异常对象时,会到catch中去找匹配的异常类,找到后进行异常的处理
    • 执行完毕之后,程序可以继续往下执行

Throwable的成员方法

方法名说明
public String getMessage()返回此throwable的详细信息字符串
public String toString()返回此可抛出的简短描述
public void printStackTrace()把异常的错误信息输出在控制台

printStackTrace输出的信息最全面,一般使用此方法

编译时异常和运行时异常的区别

Java中的异常分为两大类:编译时异常运行时异常,也被成为受检异常非受检异常

所有的RuntimeException类及其子类被称为运行时异常,其它的异常都是编译时异常。

  • 编译时异常:必须显式处理,否则程序就会发生错误,无法通过编译
  • 运行时异常:无须显式处理,也可以和编译时异常一样处理

throws

虽然try…catch…可以对异常进行处理,但并不是所有情况都有权限进行异常的处理。

当有些异常处理不了,Java提供了throws的处理方案

throws实际上只是声明异常,throw实际上是直接抛出异常

throws 异常类名;	// 注意:这个格式要跟在方法的括号后
  • 编译时异常必须要进行处理
    • 两种处理方案:try…catch…或者throws;采用throws只是声明异常,将来谁调用还得谁处理
  • 运行时异常可以不处理,出现问题后,回来修改代码即可,

throws和throw的区别

  • throws:实际上只是声明异常,并不处理

    • 用在方法声明后,跟的是异常类名
    • 表示抛出异常,由该方法的调用者来处理
    • 表示出现异常的一种可能性,并不一定会发生这些异常
  • throw:实际上的抛出异常

    • 用在方法体内,跟的是异常对象名
    • 表示抛出异常,由方法体内的语句处理
    • 执行throw一定抛出了某种异常
    // throw抛出异常示例
    if (条件) {
        throw new RuntimeException("输出结果");
    }
    

自定义异常

public class 异常类名 extends Exception {
    无参构造
    有参构造
}

泛型

泛型概述

泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型

它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传弟实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型

这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口

泛型定义格式

  • <类型>:指定一种类型的格式。这里的类型可以看成是形参
  • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
  • 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型

泛型的好处

  • 把运行时期的问题提前到了编译期间

  • 避免了强制类型转换

泛型类

泛型类的定义格式

  • 格式:修饰符 class类名 <类型> {}
  • 范例:public class Generic<T> {}
    • 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型

泛型方法

泛型方法的定义格式:

  • 格式:修饰符<类型>返回值类型方法名(类型变量名){
  • 示例:public<T> void show(Tt)

泛型接口

泛型接口的定义格式:
格式:修饰符 interface接口名<类型>{}
范例:public interface Generic(}

类型通配符

为了表示各种泛型List的父类,可以使用类型通配符

  • 类型通配符:<?>
  • List<?>:表示元素类型末知的List,它的元素可以匹配任何的类型
  • 这种带通配符的List仅表它是各种泛型List的父类,并不能把元素添加到其中

如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型Lis的父类,可以使用类型通配符的上限

  • 类型通配符上限:<? extends类型>
  • List<? extends Number>:它表示的类型是Number或者其子类型

除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限

  • 类型通配符下限:<? super类型>
  • List<? super Number>:它表示的类型是Number或者其父类型

可变参数

可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了

  • 格式:修饰符返回值类型 方法名(数据类型…变量名) { }
  • 范例:public static int sum(int…a) { }

注意事项

  • 这里的变量其实是一个数组
  • 如果一个方法有多个参数,包含可变参数,可变参数要放在最后

可变参数的使用

Arrays工具类中有一个静态方法

  • public static <T> List<T> asList(T…a):返回由指定数组支持的固定大小的列表
    • 返回的集合不支持add、remove,因为会修改列表大小;支持set方法修改元素

List接口中有一个静态方法

  • public static <E> List<E> of (E… elements):返回包含任意数量元素的不可变列表
    • 返回的集合不支持add、remove和set方法,因为它返回的是一个不可变列表

Set接口中有一个静态方法:

  • public static <E> Set<E> of (E… elements):返回一个包含任意数量元素的不可变集合
    • 在给定元素时,不能给重复元素,否则抛出参数异常错误
    • 返回的集合不支持add、remove方法,没有修改方法

集合

集合合集

Collection

Collection集合概述

  • 是单例集合的顶层接口,它表示组对象,这些对象也称为 Collection的元素
  • JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Se和List)实现

创建 Collection集合的对象

  • 多态的方式
    • 例如:具体的实现类 ArrayList

Collection集合常用方法

  • boolean add(E e):添加元素

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

  • void clear():清空集合中的元素

  • boolean contains(Object o):判断集合中是否存在指定的元素

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

  • int size():集合的长度,也就是集合中元素的个数

Collection集合的遍历

Iterator:迭代器,集合的专用遍历方式

  • Iterator iterator():返回此集合中元素的迭代器,通过集合的 iterator()方法得到
  • 迭代器是通过集合的 iterator()方法得到的,所以我们说它是依赖于集合而存在的

Iterator中的常用方法

  • E next():返回迭代中的下一个元素
  • boolean hasNext():如果迭代具有更多元素,则返回true

Collection使用步骤

  • 步骤1:创建集合对象
  • 步骤2:添加元素
    • 步骤2.1:创建元素
    • 步骤2.2:添加元素到集合
    • 合并2.1与2.2:添加元素到集合
  • 步骤3:遍历集合步骤
    • 3.1:通过集合对象获取迭代器对象
    • 步骤3.2:通过迭代器对象的 hasNext()方法判断是否还有元素
    • 步骤3.3:通过迭代器对象的next()方法获取下一个元素

数据结构常见类型

栈是一种数据先进后出的模型。

一端开口(栈顶),一段封闭(栈底)

  • 数据进入栈的过程称为:压/进栈
  • 数据离开栈的过程称为:弹/出栈

队列

队列是一种数据先进先出的模型。

一端开口(队尾),一端开口(队首)

  • 数据从后端进入队列模型的过程称为:入队列
  • 数据从前端离开队列模型的过程称为:出队列

数组

数组是一种查询快,增删慢的模型

  • 查询数据通过索引定位,查询任意数据耗时相同,查询效率高
  • 删除数据时,要将原始数据删除,同时后面毎个数据前移,删除效率低
  • 添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低

链表

链表是一种增删快,查询慢的模型(对比数组而言)

哈希表

  • JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
  • JDK8以后,在长度比较长的时候,底层实现了优化

List

List集合概述

  • 有序集合(也称为序列,用户可以精确控制列表中毎个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
  • 与Set集合不同,列表通常允许重复的元素

List集合特点

  • 有序:存储和取出的元素顺序一致

  • 可重复:存储的元素可以重复

List集合特有方法

  • void add(int index, E element):在此集合中的指定位置插入指定的元素

  • E remove(int index):删除指定索引处的元素,返回被删除的元素

  • E set(int index, E element):修改指定索引处的元素,返回被修改的元素

  • E get(int index):返回指定索引处的元素

并发修改异常

  • 并发修改异常:ConcurrentModificationException
  • 产生原因:迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
  • 解决方案:用for循环遍历,然后用集合对象做对应的操作即可

ListIterator:列表迭代器

  • 通过List集合的 listiterator()方法得到,所以说它是list集合特有的迭代器
  • 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期可修改列表,并获取列表中迭代器的当前位置

ListIterator中的常用方法

  • E next():返回迭代中的下一个元素
  • boolean hasNext():如果迭代具有更多元素,则返回true
  • E previous():返回列表中的上一个元素
  • boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
  • void add(E e):将指定的元素插入列表(使用listIterator进行add操作将不会出现并发修改异常,因为其里面进行完添加操作之后,将实际修改操作次数modCount赋值给了预期修改次数expectModCount)

List集合常用子类

  • ArrayList:底层数据结构是数组,查询快,增删慢

  • LinkedList:底层数据结构是链表,查询慢,增删快

ArrayList

ArrayList构造方法和添加方法
  • public ArrayList():创建一个空的集合对象

  • public boolean add(Ee):将指定的元素追加到此集合的末尾

  • public void add(int index,E element):在此集合中的指定位置插入指定的元素

ArrayList集合常用方法
  • public boolean remove(Object o):
    删除指定的元素,返回删除是否成功
  • public E remove(int index):删除指定索引处的元素,返回被删除的元素
  • public E set(int index, E element):修改指定索引处的元素,返回被修改的元素
  • public E get(int index):返回指定索引处的元素
  • public int size():返回集合中的元素的个数

LinkedList

Linkedlist集合的特有功能
  • public void addFirst(Ee):在该列表开头插入指定的元素

  • void addLast(Ee):将指定的元素追加到此列表的末尾

  • public E getFirst():返回此列表中的第一个元素

  • public E getLast():返回此列表中的最后个元素

  • public E removeFirst():从此列表中删除并返回第一个元素

  • public E removeLast():从此列表中删除并返回最后一个元素

Set

Set集合特点

  • 不可重复性:不包含重复元素的集合
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • HashSet:对集合的迭代顺序不作任何保证

哈希值

哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

Object类中有一个方法可以获取对象的哈希值

  • public int hashCode():返回对象的哈希码值对象的哈希值特点同一个对象多次调用 hashCode0方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的。而重写 hash Code0方法,可以实现让不同对象的哈希值相同
特例
public static void main(String[] args) {
    // 默认情况,hashCode是不相同的。这是因为String中重写了hashCode方法
    System.out.println("重地".hashCode());    // 1179395
    System.out.println("通话".hashCode());    // 1179395
}

HashSet

HashSet集合特点
  • 底层数据结构是哈希表
  • 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以是不包含重复元素的集合
HashSet保证元素唯一性

HashSet保证元素唯一性

LinkedHashSet

Linked Set集合特点
  • 哈希表和链表实现的Set接口,具有可预测的迭代次序
  • 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
  • 由哈希表保证元素唯一,也就是说没有重复的元素

TreeSet

TreeSet集合特点
  • 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
    • TreeSet():根据其元素的自然排序进行排序(自然排序:数字从小到大以及a-z、A-Z排序)
    • TreeSet(Comparator comparator):根据指定的比较器进行排序
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素的集合

Comparable自然排序

在比较的对象类中实现此接口

  • 用 TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
  • 自然排序,就是让元素所属的类实现 Comparable接口,重写 compareTo(T o)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

Comparator比较器排序

在TreeSet构造方法中传递一个比较器实现此接口

  • 用 TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
  • 比较器排序,就是让集合构造方法接收 Comparator的实现类对象,重写 compare(T o1,T o2)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

Map

Map集合概述

  • interface Map<K, V> K:键的类型;V:值的类型
  • 将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值

创建Map集合的对象

  • 多态的方式
    • 具体的实现类 HashMap

Map集合的基本功能

  • V put(K key, V value):添加元素
  • V remove(Object key):根据键删除键值对元素
  • void clear():移除所有的键值对元素
  • boolean containsKey(Object key):
    判断集合是否包含指定的键
  • boolean containsValue(Object value):判断集合是否包含指定的值
  • boolean isEmpty():判断集合是否为空int sized集合的长度,也就是集合中键值对的个数
  • int size():集合的长度,也就是集合中键值对的个数

Map集合的获取功能

  • V get(Object key):根据键获取值
  • Set keyset():获取所有键的集合
  • Collection values():获取所有值的集合
  • Set<Map Entry<K, V>> entrySet():获取所有键值对对象的集合

Map集合的遍历

方式一

转换为Map集合中的操作:

  • 获取所有键的集合。用 keyset()方法实现
  • 遍历键的集合,获取到每一个键。用增强for实现
  • 根据键去找值。用 get(Object key)方法实现
    Map<String, String> map = new HashMap<>();
    map.put("1","10");
    map.put("2","20");
    map.put("3","30");

    // 获取所有键的集合,用keySet()方法实现
    Set<String> keySet = map.keySet();
    for (String key : keySet) {
        String value = map.get(key);
        System.out.println(key + "," + value);
    }
方式二

转换为Map集合中的操作

  • 获取所有键值对对象的集合
    • Set<Map.Enty<K,V>> entrySet():获取所有键值对对象的集合
  • 遍历键值对对象的集合,得到海每一个键值对对象
    • 用增强for实现,得到每一个Map.Entry
  • 根据键值对对象获取键和值
    • 用 getKey()得到键
    • 用 getvalue()得到值
// 获取所有键值对对象的集合,用entrySet()方法实现
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + "," +value);
}

Collections

Collections类概述

  • 是针对集合操作的工具类

Collections类的常用方法

  • public static< T extends Comparable<? super T>> void sort(List list):将指定的列表按升序排序

  • public static void reverse(List<?>list):反转指定列表中元素的顺序

  • public static void shuffle(List<?>list:使用默认的随机源机排列指定的列表

I/O流

File

File类概述

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

  • 文件和目录是可以通过Fie封装成对象的
  • 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的将来是要通过具体的操作把这个路径的内容转换为具体存在的

File类构造方法

  • File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的File实例

  • File(String parent, String child):从父路径名字符串和子路径名字符串创建新的File实例

  • File(File parent, String child):从父抽象路径名和子路径名字符串创建新的File实例

File类创建功能

  • public boolean createNewFile():当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件
  • public boolean mkdir():创建由此抽象路径名命名的目录
  • public boolean mkdirs():创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录

File类判断和获取功能

  • public boolean isDirectory():测试此抽象路径名表示的File是否为目录
  • public boolean isFile():测试此抽象路径名表示的File是否为文件
  • public boolean exists():测试此抽象路径名表示的File是否存在
  • public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串
  • public String getPath():将此抽象路径名转换为路径名字符串
  • public String getName():返回由此抽象路径名表示的文件或目录的名称
  • public String[] list():返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
  • public File[] listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组

File类删除功能

  • public boolean delete():删除由此抽象路径名表示的文件或目录

绝对路径和相对路径的区别

  • 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如:F:\reflex\java.txt
  • 相对路径:必须使用取自其他路径名的信息进行解释。例如:.\java.txt

删除目录时的注意事项:

  • 如果一个目录中有内容目录,文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录

递归

递归概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象

递归解决问题的思路:

  • 把一个复杂的问题层层转化为个与原问题相似的模较小的问题来求解

  • 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算

递归解决问题要找到两个内容:

  • 递归出口:否则会出现内存溢出
  • 递归规则:与原问题相似的规模较小的问题

字节流

IO流概述和分类

IO流概述
  • IO:输入/输出(Input/Output)
  • 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
  • IO流就是用来处理设备间数据传输问题的常见的应用:文件复制;文件上传;文件下载
IO流分类
  • 按照数据的流向
    • 输入流:读数据
    • 输出流:写数据
  • 按照数据类型来分
    • 字节流
      • 字节输入流
      • 字节输出流
    • 字符流
      • 字符输入流
      • 字符输出流

一般来说,我们说IO流的分类是按照数据类型来分的

那么这两种流都在什么情况下使用呢?

  • 如果数据通过 Window自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。
  • 如果不知道该使用哪种类型的流,就使用字节流
字节流抽象基类
  • InputStream:这个抽象类是表示字节输入流的所有类的超类
  • OutputStream:这个抽象类是表示字节输出流的所有类的超类
  • 子类名特点:子类名称都是以其父类名作为子类名的后缀

字节流写数据(FileOutputStream)

FileOutputStream:文件输出流用于将数据写入File

  • FileOutputStream(String name):创建文件输出流以指定的名称写入文件

使用字节输出流写数据的步骤:

  • 创建字节输岀流对象(调用系统功能创建了文件,创建字节输岀流对象,让字节输岀流对象指向文件)
  • 调用字节输出流对象的写数据方法
  • 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源
字节流写数据的3种方式
  • void write(int b):将指定的字节写入此文件输次写一个字节数据
  • void write(byte[ ] b):将 b.length字节从指定的字节数组写入此件输出流一次写一个字节数组数据
  • void write(byte[ ] b, int off, int len) :将 len字节从指定的字节数组开始,从偏移量of开始写入此文件输出流一次写—个字节数组的部分数据
字节流写数据的两个小问题
  • 字节流写数据如何实现换行呢?
    写完数据后,加换行符
    • windows:\r\n
    • linux:\n
    • mac:\r
  • 字节流写数据如何实现追加写入呢
    • public FileOutputStream(String name, boolean append):创建文件输出流以指定的名称写入文件。如果第二个参数为true,则字节将写入文件的未尾而不是开头
字节流写数据加异常处理

完整示例

    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream("Z:\\reflect\\java.txt");
        fos.write("hello".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 如果此处不进行判断,当上述路径错误时,此处将会发生空指针异常
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

字节流读数据(FileInputStream)

FileInputStream:从文件系统中的文件获取输入字节

  • FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名

使用字节输入流读数据的步骤:

  • 创建字节输入流对象
  • 调用字节输入流对象的读数据方法
  • 释放资源
一次读取一个字节
		FileInputStream fis = new FileInputStream(".\\fos.txt");
		int by;
        while ((by = fis.read()) != -1) {
            System.out.print((char)by);
        }
		fis.close();
一次读取一个字节数组
		FileInputStream fis = new FileInputStream(".\\fos.txt");
		byte[] bytes = new byte[1024];  // 一般都是1024及其整数倍
        int len;
        while ((len = fis.read(bytes)) != -1) {
            System.out.print(new String(bytes,0,len));
        }
		fis.close();

字节缓冲流

  • BufferOutputStream:该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
  • BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节

构造方法

  • 字节缓冲输出流:BufferedOutputStream(OutputStream out)
  • 字节缓冲输入流:BufferedInputStream(InputStream in)

为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?

  • 字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作

字符流

为什么会出现字符流

由于字节流操作中文不是特别的方便,所以Java就提供字符流

  • 字符流=字节流+编码表

用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢

  • 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

编码表

基础知识

  • 计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
  • 按照某种规则,将字符存储到计算杋中,称为编码。反之,将存储在计算杋中的进制数按照某种规则解析显示出来,称为解码。这里强调—下:按照八A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象字符编码:就是一套自然语言的字符与二进制数之间的对应规则(A–>65)
字符集
  • 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
  • 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有套字符编码。
  • 常见字符集有ASCII字符集、GBXX字符集、 Unicode字符集等
ASCII字符集
  • ASCII( American Standard Code for Information Interchange,美国信息交换标准代码):是基于拉丁字母的套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
  • 基本的ASCII字符集,使用7位表示个字符,共128字符。ASCII的扩展字符集使用8位表示个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
GBXXX字符集
  • GB2312:简体中文码表。一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名等都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫”半角"字符了
  • GBK:最常用的中文码表。是在GB2312标准住基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
  • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国內少数民族的文字,同时支持繁体汉字以及日韩汉字等

Unicode字符集

  • 为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF8、UTF-16和UTF32.最为常用的UTF-8编码
  • UTF-8编码:可以用来表示 Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
  • 编码规则
    • 128个US-ASCII字符,只需一个字节编码
    • 拉丁文等字符,需要二个字节编码
    • 大部分常用字(含中文),使用三个字节编码
    • 其他极少使用的Unicode辅助字符,使用四字节编码

小结:采用何种规则编码,就要采用对应规则解码,否则就会出现乱码

字符串中的编码解码问题

编码

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

解码

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

字符流中的编码解码问题

字符流抽象基类

  • Reader:字符输入流的抽象类
  • Writer:字符输出流的抽象类

字符流中和编码解码问题相关的两个类:

  • InputStreamReader
  • OutputStreamWriter

字符流写数据的5种方式

  • void write(int c):写一个字符
  • void write(char[ ] cbuf):写入一个字符数组
  • void write(char[ ] cbuf, int off, int len):写入字符数组的部分
  • void write(String str):写一个字符串
  • void write(String str, int off, int len:写一个字符串的部分

flush():刷新流,还可以继续写数据

close():关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

字符流读数据的2种方式

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

  • int read(char[ ] cbuf):一次读一个字符数组数据

字符缓冲流

  • BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途

  • BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途

构造方法:

  • BufferedWriter (Writer out )
  • BufferedReader(Reader in)
字符缓冲流特有功能

Bufferedwriter

  • void newline():写一行行分隔符,行分隔符字符串由系统属性定义

Bufferedreader

  • public String readLine():读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null

特殊操作流

  • 特殊记录
// 读取输入
BufferedReader br = new BufferedReader(new InputStream(System.in));


// 输出
// PrintStream类的字节流方式即为System.out
// PrintWriter可以使得程序更加国际化
PrintWriter pw = new PrintWriter(System.out, true);

标准输入输出流

Systen类中有两个静态的成员变量

  • public static final InputStream in:标准输入流。通常该流对应于键盘输λ或由主机环境或用户指定的另一个输入源
  • public static final PrintStream out:标准输出流。通常该流刈应于显示输岀或由主机环境或用宀指定的另一个输出目标
标准输入流

自己实现键盘录入数据:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

写起来太麻烦,Java就提供了一个类实现键盘录入

Scanner sc = new Scanner(Sytem.in);
标准输出流

输出语句的本质:是一个标准的输出流

PrintStream ps = new System.out;

PrintStream类有的方法,System.out都可以使用

打印流

打印流分类

  • 字节打印流:PrintStream
  • 字符打印流:PrintWriter

打印流的特点:

  • 只负责输出数据,不负责读取数据
  • 有自己的特有方法
字节打印流
  • PrintStream(String fileName):使用指定的文件名创建新的打印流

  • 使用继承父类的方法写数据,查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出

字符打印流

PrintWriter的构造方法:

  • PrintWriter(String file Name):使用指定的文件名创建一个新的 PrintWriter,而不需要自动执行
  • PrintWriter(Writer out, boolean auto Flush):创建一个新的 PrintWriter
    • out:字符输出流
    • autoFlush:一个布尔值,如果为真,则 printIn,printf,或format方法将刷新输出缓冲区

对象序列化流

对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象

这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息

字节序列写到文件之后,相当于文件中持久保存了一个对象的信息

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化

要实现序列化和反序列化就要使用对象序列化流和对象反序列化流

  • 对象序列化流:ObjectOutputStream
  • 对象反序列化流:ObjectInputStream
对象序列化流:ObjectOutputStream
  • 将Java对象的原始数据类型和图形写入 OutputStream。可以使用 ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象

构造方法

  • ObjectOutputStream( OutputStream out):创建一个写入指定的 OutputStream的 ObjectOutputStream
  • 序列化对象的方法void writeObject(Object obj):将指定的对象写入 ObjectOutputStream

序列化对象的方法

  • void writeObject( Object obj):将指定的对象写入ObjectOutputStream

注意

  • 一个对象要想被序列化,该对象所属的类必须必须实现Serializable接囗

  • Serializable是—个标记接口,实现该接口,不需要重写任何方法

对象反序列化流:ObjectInputStream

  • ObjectInputStream序列化先前使用 ObjectoutputStream编写的原始数据和对象

构造方法

  • ObjectInputStream(InputStream in):创建从指定的 InputStream读取的 ObjectInputStream

反序列化对象的方法

  • Object readObject():从 ObjectInputStream读取一个对象
序列化中的问题

用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?

  • 会出问题,抛出InvalidClassException异常

如果出问题了,如何解决呢?

  • 给对象所属的类加一个serialVersionUID

    private static final long serialVersionUID= 42L;
    

如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

  • 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

Properties

Properties概述
  • 是一个Map体系的集合类,但创建不能使用泛型
  • Properties可以保存到流中或从流中加载
Properties作为集合的特有方法
  • Object setProperty(String key, String value):设置集合的键和值,都是 String类型,底层调用 Hashtable方法put
  • String getProperty(String key):使用此属性列表中指定的键搜索属性
  • Set< String> stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
Properties和IO流结合的方法
  • void load(InputStream inStream):从输入字节流读取属性列表(键和元素对)
  • void load(Reader reader):从输入字符流读取属性列表(键和元素对)
  • void store(OutputStream out, String comments):将此属性列表(键和元素对)写入此 Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流
  • void store(Writer writer, String comments):将此属性列表(键和元素对)写入此 Properties表中,以适合使用load( Reader)方法的格式写入输出字符流

线程

实现多线程

进程和线程

进程:是正在运行的程序

  • 是系统进行资源分配和调用的独立单位
  • 每一个进程都有它自己的内存空间和系统资源

线程:是进程中的单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
  • 多线程:一个进程如果有多条执行路径,则称为多线程程序

多线程的实现方式

方式1:继承 Thread类

  • 定义一个类 MyThread继承Thread类
  • 在 My Thread类中重写run()方法
  • 创建 My Thread类的对象
  • 启动线程

方式2:实现 Runnable接口

  • 定义一个类 MyRunnable实现 Runnable接口
  • 在 MyRunnable类中重写run()方法
  • 创建 MyRunnable类的对象
  • 创建 Thread类的对象,把 MyRunnable对象作为构造方法的参数
  • 启动线程

多线程的实现方案有两种

  • 继承Thread类
  • 实现 Runnable接口

相比继承 Thread类,实现 Runnable接口的好处

  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好孖的体现了面向对象的设计思想

两个小问题

  • 为什么要重写run()方法?
    • 因为run()是用来封装被线程执行的代码
  • run()方法和 start()方法的区别?
    • run():封装线程执行的代码,直接调用,相当于普通方法的调用
    • start():启动线程;然后由JVM调用此线程的run()方法

设置和获取线程名称

Thread类中设置和获取线程名称的方法

  • void setName( String name):将此线程的名称更改为等于参数name
  • String getName():返回此线程的名称
  • 通过构造方法也可以设置线程名称

如何获取main()方法所在的线程名称?

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

线程调度

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型:优先让优先級高的线程使用CPU,如果线程的优先級相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些

Java使用的是抢占式调度模型

假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为抢到CPU的使用权是不一定的

Thread类中设置和获取线程优先级的方法

  • public final int getPriority():返回此线程的优先级
  • public final void setPriority (int newPriority):更改此线程的优先级
    • 线程默认优先级是5,优先级范围是1-10
    • 线程优先级高仅仅表示线程获取CPU时间片的几率高,并不代表一定先执行。

线程控制

  • static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的亳秒数
  • void join():等待这个线程死亡才启动其它线程
  • void setDaemon(boolean on):将此线程标记为守护线程。当运行的线程都是守护线程时,Java虚拟机将退出

线程生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPKR4AEg-1618663004188)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210114000705783.png)]

线程同步

同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

  • 格式

    synchronized(任意对象){

    ​ 多条语句操作共享数据的代码

    }

  • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

同步方法

同步方法:就是把 synchronized关键字加到方法上

  • 格式

    修饰符 synchronized 返回值类型 方法名(方法参数){ }

同步方法的锁对象是什么呢?

  • this

同步静态方法:就是把 synchronized关键字加到静态方法上

  • 格式

    修饰符 static synchronized 返回值类型 方法名(方法参数){ }

同步静态方法的锁对象是什么呢?

  • 类名 class

线程安全的类

StringBuffer

  • 线程安全,可变的字符序列
  • 从版本JDK5开始,被StringBuilder替代。通常应该使用 StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java2平台v1.2开始,该类改进了List接口,使其成为 Java Collections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用 Arraylist代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
  • 从Java2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。
    与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable

StringBuffer可能会用到,但是后两个基本用不到,被如下代替了。

List<String> list = Collections.synchronizedList(new ArrayList<String>());	// 返回线程安全的List
Set<String> set = Collections.synchronizedSet(new HashSet<String>());	// 返回线程安全的Set
Map<String> map = Collections.synchronizedMap(new HashMap<String>());	// 返回线程安全的Map

Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的对象Lock

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

Lock中提供了获得锁和释放锁的方法

  • void lock():获得锁
  • void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类 ReentrantLock来实例化

ReentrantLock的构造方法

  • ReentrantLock():创建一个 Reentrantlock的实例

生产者和消费者

生产者消费者模式概述

为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在 Object类中

Object类的等待和唤醒方法

  • void wait():导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
  • void notify():唤醒正在等待对象监视器的单个线程
  • void notifyAll():唤醒正在等待对象监视器的所有线程

网络编程

网络编程三要素

IP地址

  • 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识

端口

  • 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯标识设备中的应用程序了。也就是应用程序的标识

协议

  • 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式传输竦率、传输步骤等做了统规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

InetAddress的使用

为了方便我们对P地址的获取和操作,Java提供了一个类InetAddress供我们使用

InetAddress:此类表示Internet协议(IP)地址

  • static InetAddress getByName(String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是|P地址
  • String getHostName():获取此IP地址的主机名
  • String getHostAddress():返回文本显示中的IP地址字符串

端口

  • 端口:设备上应用程序的唯标识
  • 端口号:用两个字节表示的整数,它的取值范围是0-65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外个服务或应用所占用,会导致当前程序启动失败

协议

  • 协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议

  • 用户数据报协议(User Datagram Protocol)

  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

  • 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输

    • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响,但是在使用UDP协以传送数据时,由于UD的面向无连接性,不能保教拥的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议

  • 传输控制协议(Transmission Control Protocol)

  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据它提供了两台计算机之间可靠无差错的数据传输。在ICP娑中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手“

  • 三次握手:TCP协议中,在发送数据的准佳备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

    • 第一次握手,客户端向服务器端发岀连接请求,等待服务器确认。
    • 第二次握手,服务器端向户端回送个响应,通知客户端收到了连接请求。
    • 第三次握手,客户端再次向服务器端发送确认信息,确认连接。
  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性, TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

UDP通信程序

UDP通信原理

  • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
  • Java提供了DatagramSocket类作为基于UDP协议的Socket

UDP发送数据

  • 发送数据的步骤
    • ①创建发送端的Socket对象(Datagram Socket)
      • DatagramSocket()
    • ②创建数据,并把数据打包
      • DatagramPacket(byte[] buf, int length, InetAddress address, int port)
    • ③调用DatagramSocket对象的方法发送数据
      • void send(DatagramPacket p)
    • ④关闭发送端
      • void close()

UDP接收数据

  • 接收数据的步骤
    • ①创建接收端的 Socket对象(Datagram Socket)
      • DatagramSocket(int port)
    • ②创建一个数据包,用于接收数据
      • DatagramPacket(byte[] buf, int length)
    • ③调用DatagramSocket对象的方法接收数据
      • void receive(DatagramPacket p)
    • ④解析数据包,并把数据在控制台显示
      • byte[] getData()
      • int getLength()
    • ⑤关闭接收端
      • void close()

TCP通信程序

TCP通信原理

  • TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个 Socket对象,从而在通信的两端形成网络虚拟链路一旦建立了虛拟的网络链路,两端的程序就可以通过虛拟链路进行通信

  • Java对基于TCP协议的的网络提供了良好的封装,使用 Socket象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信

  • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

TCP发送数据

  • 发送数据的步骤
    • ①创建客户端的Socket对象(Socket)
      • Socket(String host, int port)
    • ②获取输出流,写数据
      • OutputStream getOutputStream()
    • ③释放资源
      • void close()

TCP接收数据

  • 接收数据的步骤
    • ①创建服务器端的Socket对象(ServerSocket)
      • ServerSocket(int port)
    • ②监听客户端连接,返回一个Socket对象
      • Socket accept()
    • ③获取输入流,读数据,并把数据显示在控制台
      • InputStream getInputStream()
    • ④释放资源
      • void close()

Lambda表达式

函数式编程思想概述

在数学中,函数就是有输入量输出量的套计算方案,也就是“拿数据做操作

  • 面向对象思想强调“必须通过对象的形式来做事情
  • 函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做
  • 而我们要学习的 Lambda表达式就是函数式思想的体现

体验Lambda表达式

需求:启动一个线程,输出一句话。

// 匿名内部类的方式
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程启动了!!!");
    }
}).start();

// Lambda表达式方式改进
new Thread( () -> {
    System.out.println("线程启动了!!!");
}).start();

Lambda表达式格式

组成Lambda表达式的三要素:形式参数箭头代码块

  • 格式:(形式参数)-> 代码块
  • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
  • ->:由英文中画线和大于符号组成,固定写法。代表指向动作
  • 代码块:是具体要做的事情,也就是以前写的方法体内容

Lambda表达式的使用

使用前提:

  • 有一个接口
  • 接口中有且仅有一个抽象方法

Lambda表达式的省略模式

省略规则:

  • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,甚至是 return

Lambda表达式的注意事项

注意事项

  • 使用 Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
  • 必须有上下文环境,才能推导出 Lambda对应的接口
    • 根据局部变量的赋值得知 Lambda对应的接口:Runnable r = ()-> System.out.printIn(" Lambda表达式");
    • 根据调用方法的参数得知 Lambda对应的接口:new Thread(()-> System.out.println(“Lambda表达式”)).start();

Lambda表达式和匿名内部类的区别

所需类型不同

  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
  • Lambda表达式:只能是接囗

使用限制不同

  • 如果接囗中有且仅有一个抽象方法,可以使用 Lambda表达式,也可以使用匿名内部类
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda表达式

实现原理不同

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

接口组成更新

接口组成更新概述

接口的组成

  • 常量:public static final

  • 抽象方法:public abstract

  • 默认方法(Java8)

  • 静态方法(Java8)

  • 私有方法(Java9)

接口中默认方法

  • 格式:public default返回值类型方法名(参数列表){}
  • 范例:public default void show()(}

接口中默认方法的注意事项

  • 默认方法不是抽象方法,所以不强制被重写。但是可以重写,重写的时候去掉 default关键字
  • public可以省略, default不能省略

接口中静态方法

  • 格式:public static返回值类型方法名(参数列表){}
  • 范例:public static void show(){}

接口中静态方法的注意事项:

  • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
  • public可以省略, static不能省略

接口中私有方法

Java9中新増了带方法体的私有方法,这其实在Java8中就埋下了伏笔:Java8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java9增加私有方法的必然性

接口中私有方法的定义格式

  • 格式1:private返回值类型方法名(参数列表){}
  • 范例1:private void show() {}
  • 格式2:private static返回值类型方法名(参数列表){}
  • 范例2:private static void method() {}

接口中私有方法的注意事项

  • 默认方法可以调用私有的静态方法和非静态方法
  • 静态方法只能调用私有的静态方法

方法引用

方法引用符

方法引用符

  • 该符号为引用运算符,而它所在的表达式被称为方法引用

回顾一下体验方法引用中的代码

  • Lambda表达式:usePrintable(s -> System. out. printIn(s));
    • 分析:拿到参数S之后通过Lambda表达式,传递给 System.out.println方法去处理
  • 方法引用:usePrintable(System.out::printin);
    • 分析:直接使用 System. out中的 printIn方法来取代 Lambda,代码更加的简洁

推导与省略

  • 如果使用 Lambda,那么根据“可推导就是可省略“的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
  • 如果使用方法引用,也是同样可以根据上下文进行推导
  • 方法引用是 Lambda的孪生兄弟

Lambda表达式支持的方法引用

常见的引用方式

  • 引用类方法
  • 引用对象的实例方法
  • 引用类的实例方法
  • 引用构造器

引用类方法

引用类方法,其实就是引用类的静态方法

  • 格式:类名::静态方法
  • 示例:Integer::parseInt
    • Integer类的方法:public static int parseInt(String s)将此 String转换为int类型数据

Lambda表达式被类方法代替时,它的形参将全部传递给静态方法作参数

引用对象的实例方法

引用对象的实例方法,其实就引用对象中的成员方法

  • 格式:对象::成员方法
  • 示例:"Hello World"::toUpperCase
    • String类中的方法:public String toUpperCase()将此 String所有字符转换为大写

Lambda表达式被对象的实例方法代替时,它的形参将全部传递给静态方法作参数

引用类的实例方法

引用类的实例方法,其实就是引用类中的成员方法

  • 格式:类名::成员方法
  • 示例:String::substring
    • String类中的方法:public String substring(int beginIndex, int endIndex)
    • 从 beginIndex开始到 endIndex结束,截取字符串。返回一个子串,子串的长度为 endIndex - beginlndex

Lambda表达式被类的实例方法代替时,第一个参数作为调用者,后面的参数全部传递给该方法作为参数

引用构造器

引用构造器,其实就是引用构造方法

  • 格式:类名::new

  • 范例:Student::new

Lambda表达式被构造器代替时,它的形参将全部传递给静态方法作参数

函数式接口

函数式接口概述

  • 函数式接口:有且仅有一个抽象方法的接口

  • Java中的函数式编程体现就是 Lambda表达式,所以函数式接口就是可以适用于 Lambda使用的接口

  • 只有确保接口中有且仅有一个抽象方法,Java中的 Lambda才能利地进行推导

如何检测一个接口是不是函数式接口呢?

  • @Functionallnterface

  • 放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败

注意

  • 我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解。

函数式接口作为方法的参数

如果方法的参数是一个函数式接口,我们可以使用 Lambda表达式作为参数传递

  • startThread() -> System.out.println(ThreadcurrentThread().getName()+“线程启动了”);

函数式接口作为方法的返回值

如果方法的返回值是一个函数式接口,我们可以使用 Lambda表达式作为结果返回

  • private static Comparator<String> getComparator {

    ​ return(s1, s2) -> s1.length() - s2.length();

    }

常用的函数式接口

Java8在 java.util.function包下预定义了大量的数式接口供使用

重点来学习下面的4个接囗

  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接囗

Supplier接口

Supplier:包含一个无参的方法

  • T get():获得结果
  • 该方法不需要参数,它会按照某种实现逻辑(由 Lambda表达式实现)返回一个数据
  • Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用

Consumer接口

Consumer:包含两个方法

  • void accept(T t):对给定的参数执行此操作
  • default Consumer andThen(Consumer after):返回一个组合的 Consumer,依次执行此操作,然后执行after操作
  • Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定

Predicate接口

Predicate:常用的四个方法

  • boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
  • default Predicate negate():返回一个逻辑的否定,对应逻辑非
  • default Predicateand(Predicate other):返回一个组合判断,对应短路与
  • default Predicateor(Predicate other):返回一个组合判断,对应短路或
  • Predicate接口通常用于判断参数是否满足指定的条件

Function接口

Function<T,R>:常用的两个方法

  • R apply(T t):将此函数应用于给定的参数
  • default Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
  • Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由 Lambda表达式实现),然后返回一个新的值

Stream流

Stream流的使用

  • 生成流
    • 通过数据源(集合数组等)生成流
    • 如:list.stream()
  • 中间操作
    • 一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
    • 如:filter()
  • 终结操作
    • 一个流只能有一个终结操作,当这个操作执行后,流就被用“光”了,无法再被操作。所以这必定是流的最后一个操作
    • 如:foreach()

Strean流的生成方式

Stream流的常见生成方式

  • Collection体系的集合可以使用默认方法 stream()生成流
    • default Stream stream()
  • Map体系的集合间接的生成流
  • 数组可以通过 Stream接口的静态方法of(T… values)生成流
    // Collection体系的集合可以使用默认方法 stream()生成流
    // List集合生成流
    List<String> list = new ArrayList<>();
    Stream<String> listStream = list.stream();
    // Set集合生成流
    Set<String> set = new HashSet<>();
    Stream<String> setStream = set.stream();

    // Map体系的集合间接的生成流
    Map<String,Integer> map = new HashMap<>();
    // Key的流生成
    Stream<String> keyStream = map.keySet().stream();
    // Value的流生成
    Stream<Integer> valueStream = map.values().stream();
    // Key-Value对的流生成
    Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

    // 数组可以通过 Stream接口的静态方法of(T… values)生成流
    String[] strArray = {"hello","world","java"};
    Stream<String> strArrayStream1 = Stream.of(strArray);
    Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
    Stream<Integer> intStream = Stream.of(10, 20, 30);

Strean流的常见中间操作方法

  • Stream filter(Predicate predicate):用于对流中的数据进行过滤
    • Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值
  • Stream< T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
  • Stream< T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
  • static Stream concat(Stream a, Stream b):合并a和b两个流为一个流
  • Stream distinct():返回由该流的不同元素(根据 Object.equals(Object))组成的流
  • Stream sorted():返回由此流的元素组成的流,根据自然顺序排序
  • Stream sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的 Comparator进行排序
  • Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
    • Function接口中的方法 R apply(T t)
  • InStream mapToInt(TolntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
    • IntStream:表示原始int流
    • ToIntFunction接口中的方法 int applyAsInt(T value)

Strean流的常见终结操作方法

  • void forEach(Consumer action):对此流的每个元素执行操作
    • Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
  • long count():返回此流中的元素数

Strean流的收集操作

对数据使用 Strean流的方式操作完毕后,想把流中的数据收集到集合中,该怎么办呢?
Stream流的收集方法

  • R collect(Collector collector)
    • 但是这个收集方法的参数是一个 Collector接口

工具类 Collectors提供了具体的收集方式

  • public static Collector toList0:把元素收集到Lis集合中
  • public static Collector toSet0:把元素收集到set集合中
  • public static Collector toMap(Function keyMapper, Function valueMapper):把元素收集到Map集合中

反射

类加载器

类加载

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出意外的情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化

类的加载
  • 将class文件读入内存,并为之创建一个java.lang.Class对象
  • 任何类被使用时,系统都会为之建立一个java.lang.Class对象
类的连接
  • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其它类协调一致
  • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
  • 解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化
  • 在该阶段,主要就是对类变量进行初始化
类的初始化步骤
  • 假如类还未被加载和连接,则程序先加载并连接该类
  • 假如该类的直接父类还未被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统一次执行这些初始化语句

注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3

类的初始化时机
  • 创建类的实例
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令运行某个主类

类加载器

类加载器的作用
  • 负责将class文件加载到内存中,并为之生成对应的 java.lang.Class 对象
  • 虽然我们不用过分关心类加载机制,但是了解这个机制我们能更好的理解程序的运行
JVM的类加载机制
  • 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外—个类加载器来载入
  • 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该 Class,只有在父类加载器无法加载类时才尝试从自己的类路径中加载该类
  • 缓存机制:保证所有加载过的 Class都会被缓存,当程序需要使用某个Class象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该 Class对象时,系统才会读取该类对应的二进制数据,并将其转换成 Class对象,存储到缓存区
ClassLoader:是负责加载类的对象
Java运行时具有以下内置类加载器
  • Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null,并且没有父null

  • Platform class loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的 Java Se平台AP其实现类和JDK特定的运行时类

  • System class loader:它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径,模块路径和JK特定工具上的类

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

Classloader中的两个方法
  • static Classloader getSystemClassLoader():返回用于委派的系统类加载器
  • Classloader getParent():返回父类加载器进行委派

反射

概述

Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的种机制。由于这种动态性,可以极大的増强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展

获取 Class类的对象

我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象

这里提供三种方式获取Class类型的对象

  • 使用类的class属性来获取该类对应的class对象。举例:Student.class将会返回 Student类对应的class对象
  • 调用对象的 getClass()方法,返回该对象所属类对应的Class对象
    • 该方法是Object类中的方法,所有的Java对象都可以调用该方法
  • 使用Class类中的静态方法forName(String className)该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
package com.reflect.base.reflect;

/**
 * author: Administrator
 * date: 2021/1/4 - 17:46
 */

import com.reflect.base.test.Test1;

/**
 * - 使用类的class属性来获取该类对应的class对象。举例:Student.class将会返回 Student类对应的class对象
 * - 调用对象的 getClass()方法,返回该对象所属类对应的Class对象
 * - 使用Class类中的静态方法forName(String className)该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
 */
public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException {
        // 使用类的class属性来获取该类对应的class对象
        Class<Test1> c1 = Test1.class;
        System.out.println(c1);
        
        Class<Test1> c2 = Test1.class;
        System.out.println(c2);
        System.out.println(c1 == c2);

        // 调用对象的 getClass()方法,返回该对象所属类对应的Class对象
        Test1 test1 = new Test1();
        Class<? extends Test1> c3 = test1.getClass();
        System.out.println(c1 == c3);
        System.out.println("==========");

        // 使用Class类中的静态方法forName(String className)该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
        Class<?> c4 = Class.forName("com.reflect.base.test.Test1");
        System.out.println(c1 == c4);
    }
}

反射获取构造方法

Class类中用于获取构造方法的方法

  • Constructor<?> getConstructors():返回所有公共构造方法对象的数组
  • Constructor<?> getDeclaredConstructors():返回所有构造方法对象的数组
  • Constructor<T> getConstructor(Class<?>… parameterTypes):返回单个公共构造方法对象
  • Constructor<T> getDeclaredConstructor(class<?> parameter Types):返回单个构造方法对象

Constructor类中用于创建对象的方法

  • T newInstance(Object… initargs):根据指定的构造方法创建对象

值得一提

  • 基本数据类型也可以通过.class得到对应的Class类型
  • public void setAccessible(boolean flag):值为true,取消访问检查

反射获取成员变量

Class类中用于获取成员变量的方法

  • Field[] getFields():返回所有公共成员变量对象的数组
  • Field[] getDeclaredFields():返回所有成员变量对象的数组
  • Field getField(String name):返回单个公共成员变量对象
  • Field getDeclaredField(String name):返回单个成员变量对象

Field类中用于给成员变量赋值的方法

  • void set(Object obj, Object value):给obj对象的成员变量赋值为 value

反射获取成员方法并使用

Class类中用于获取成员方法的方法

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

Method类中用于调用成员方法的方法

  • Object invoke(Object obj, Object…args):调用obj对象的成员方法,参数是args,返回值是 Object类型

反射越过泛型检查

package com.reflect.base.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * author: Administrator
 * date: 2021/1/11 - 0:22
 */
public class ReflectDemo3 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 创建集合
        ArrayList<Integer> arrayList = new ArrayList<>();
//        // 正常添加元素
//        arrayList.add(1);
//        arrayList.add(2);
//        arrayList.add(3);
//        // 添加字符串将会报错,泛型检查
//        arrayList.add("reflect");

        Class<? extends ArrayList> aClass = arrayList.getClass();
        Method m = aClass.getMethod("add", Object.class);
        m.invoke(arrayList,"hello");
        m.invoke(arrayList,"world");
        m.invoke(arrayList,"reflect");
        System.out.println(arrayList);
    }
}


这篇关于Java基础杂记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程