基础知识回顾

1.数组 数组工具类 二维数组

1.1概念

数组,标志是[ ],用于存储多个相同类型数据的集合,获取数组中的元素通过脚标(下标)来获取,数组下标是从0开始的

1.2创建数组

创建数组的方式分为两种

  1. 动态初始化
int[] a = new int[5] //初始化一个长度为5的数组
  1. 静态初始化
int b = new int[]{1,2,3,4,5}
//或者
int c = {1,2,3,4,5} 

1.3 创建数组的底层实现原理

  1. 在内存中开辟连续的空间,用来存放数据,长度是5
  2. 给数组完成初始化过程,给每个元素赋予默认值,int类型默认是0
  3. 数组完成初始化会分配一个唯一的地址值
  4. 把唯一的地址值交给引用类型的变量a去保存
    数组名是一个引用类型的变量,保存的是数组的地址,不是数组中的数据
    数组一旦创建,长度不可变,长度允许为0

1.4练习

1.向数组中存入数据hello

package com.common.test.base;

public class TestCreateArray {
    public static void main(String[] args) {
//        创建数组
//        创建静态数组
        char[] a1 = new char[]{'h', 'e', 'l', 'l', 'o'};
        char[] a2 = {'h', 'e', 'l', 'l', 'o'};
        //创建动态数组
        char[] a3 = new char[5];
        a3[0] = 'h';
        a3[1] = 'e';
        a3[2] = 'l';
        a3[3] = 'l';
        a3[4] = 'o';
        System.out.println(a1);
        System.out.println(a2);
        System.out.println(a3);

    }
}
  • char类型的数组底层中做了处理,可以直接打印具体元素
  • 除了char类型以外的数组想要查看数组中的具体元素,需要使用数组的工具类Arrays
  • arrays.toString(数组名)
package com.common.test.base;

public class TestCreateArray {
    public static void main(String[] args) {
 /**
         * char类型的数组底层中做了处理,可以直接打印具体元素
         * 除了char类型以外的数组想要查看数组中的具体元素,需要使用数组的工具类Arrays
         * arrays.toString(数组名)
         */
        String[] s1 = new String[3];
        s1[0] = "第一";
        s1[1] = "第二";
        s1[2] = "第三";
        String[] s2 = new String[]{"a", "b", "c"};
        String[] s3 = {"a", "b", "c"};
        System.out.println(s1); //打印出的是地址值[Ljava.lang.String;@6996db8
        System.out.println(Arrays.toString(s1)); //[第一, 第二, 第三]
}

2.输出每个月的天数

package com.common.test.base;

public class TestArrayExec {
    public static void main(String[] args) {
        int[] day = {31,28,31,30,31,30,30,31,30,31,30,31};
        for (int i = 0; i < day.length; i++) {
            System.out.println(i+1+"月有"+day[i]+"天");
        }
    }
}

3.遍历数组存入1-10

package com.common.test.base;

import java.util.Arrays;

public class TestArrayExec {
    public static void main(String[] args) {
        m2();
    }

    public static void m2() {

        int[] a = new int[10];
        for (int i = 0; i < a.length; i++) {
            a[i] = i + 1;
        }
        System.out.println(Arrays.toString(a));//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    }
}


3.创建随机数组

package com.common.test.base;

import java.util.Arrays;

public class TestArrayExec {
    public static void main(String[] args) {
        m3();
    }

    public static void m3(){
        int[] a  = new int[10];
        for (int i = 0; i < a.length; i++) {
            a[i] = new Random().nextInt(100);
        }
        System.out.println(Arrays.toString(a));//[67, 90, 99, 89, 24, 52, 73, 39, 99, 24]
    }
}


1.5数组工具类 Arrays

1. Arrays.toString(数组)

把数组里的数据用逗号连接成一个字符串[值1,值2]

2. Arrays.sort(数组)

对数组从小到大排序,对与基本类型的数组使用的是优化会后的快速排序算法,效率高
对于引用类型数组,使用的是优化后的合并排序算法

3.Arrays.copyOf(数组,新的长度)

把数组赋值成一个指定长度的新数组
新数组的长度 大于 原数组,相当于复制,并增加位置
新数组的长度 小于 原数组,相当于截取一部分数据

练习

1.练习Arrays.sort(数组)
package com.common.test.base;

import java.util.Arrays;

public class TestArraySort {
    public static void main(String[] args) {
        int[] a = {21,96,75,23,25};

        Arrays.sort(a);//直接排序
        System.out.println(Arrays.toString(a));//[21, 23, 25, 75, 96]
    }
}

2.练习Arrays. copyOf(数组,新的长度)
package com.common.test.base;

import java.util.Arrays;

public class TestArraysCopyOf {
    public static void main(String[] args) {
        //1.创建数组
        int[] from = {1,2,3,4,5};//数组一旦创建,长度不可改变

        //2.1 数组的普通复制
        /**copyOf()用于完成数组的复制,两个参数:
         * 参数1:要复制哪个数组
         * 参数2:新数组的长度*/
        int[] to = Arrays.copyOf(from, 5);
        System.out.println(Arrays.toString(to));

        //2.2数组的扩容
        /**扩容:给数组扩大容量,新数组的长度>原数组的长度
         * 扩容思路:先创建对应长度的新数组,每个位置上都是默认值0
         * 然后从原数组中将元素复制到新数组,没有被覆盖的元素还是默认值0*/
        int[] to2 = Arrays.copyOf(from, 10);
        System.out.println(Arrays.toString(to2));

        //2.3数组的缩容
        /**缩容:缩小数组的容量,新数组的长度<原数组的长度
         * 缩容思路:先创建对应长度的新数组,每个位置上都是默认值0
         * 然后从原数组中复制指定个数的元素到新数组中,类似于截取*/
        int[] to3 = Arrays.copyOf(from, 3);
        System.out.println(Arrays.toString(to3));

        //2.4指定首尾截取原数组中的元素
        /**copyOfRange()用于完成数组的截取,3个参数:
         * 参数1:要截取哪个数组【原数组】
         * 参数2:从原数组的哪个下标开始
         * 参数3:到原数组的哪个下标结束
         * 注意:截取的元素包含开始下标处的元素,不包含结束下标处的元素*/
        int[] to4 = Arrays.copyOfRange(from, 2, 4);
        System.out.println(Arrays.toString(to4));
    }

}


1.5二维数组

创建二维数组

int[][] a1 = new int[3][3];
int[][] a2 = {{3,5},{7,9},{1,2}};

练习

package com.common.test.base;


public class TestArray2 {
    public static void main(String[] args) {
        String[][] a = new String[5][5];
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                a[i][j] = " * ";
            }
        }

        for (int i = 0; i < a.length; i++) {
            for (int j = 0; j < a[i].length; j++) {
                //a[i][j]--根据外部数组的下标和内部数组的下标定位具体的元素
                System.out.print(a[i][j]);
            }
            System.out.println();
        }
    }
}

在这里插入图片描述

2.冒泡排序

package com.common.test.base;

import java.util.Arrays;

public class TestBubbleSort {
    public static void main(String[] args) {
        //1.创建一个无序的数组
        int[] a = {27, 96, 73, 25, 21};
        //2.调用method()完成排序
        int[] newA = method(a);
        System.out.println("排序完毕:" + Arrays.toString(newA));
    }

    public static int[] method(int[] a) {
        //1.外层循环,控制比较的轮数,假设有n个数,最多比较n-1次
        //开始值:1 结束值:<= a.length-1 变化:++
        //控制的是循环执行的次数,比如5个数,最多比较4轮,<= a.length-1,最多取到4,也就是[1,4]4次
        for (int i = 1; i <= a.length - 1; i++) {
            System.out.println("第" + i + "轮:");
            //2.内层循环:相邻比较+互换位置
            for (int j = 0; j < a.length - i; j++) {
                //相邻比较,a[j]代表的就是前一个元素,a[j+1]代表的就是后一个元素
                if (a[j] > a[j + 1]) {
                    //交换数据
                    int t = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = t;
                    //System.out.println("第"+(j+1)+"次比较交换后:"+Arrays.toString(a));
                }
            }
            System.out.println("第" + i + "轮的结果:" + Arrays.toString(a));
        }
        return a;//把排序好的数组a返回

    }
}

优化后

 public static int[] bubbleSort(int[] a){
        for (int i = 1; i < a.length-1; i++) {
            boolean flag = false;
            System.out.println("第"+i+"轮:");
            for (int j = 0; j < a.length-i; j++) {
                if (a[j]>a[j+1]){
                    int t = a[j];
                    a[j] = a[j+1];
                    a[j+1] = t;
                    flag = true;
                }

            }
            System.out.println("第" + i + "轮的结果:" + Arrays.toString(a));
            if (flag == false) {
                return a;
            }

        }
        return a;//把排序好的数组a返回
    }

3.面向对象 类 对象 封账

3.1面向对象的概念

3.2面向对象的三大特征

封装

封装是隐藏对象的属性和实现细节,仅对外提供公共的访问方式
优点:
1.提高安全性
2.提高重用性

使用private关键字来封装成员变量与方法
setXxx – 对外提供公共的设置值方式
getXxx – 对外提供公共的获取值方式
关于成员方法:
把私有方法放在公共方法里供外界调用即可

练习封装的必要性
package com.common.test.base;

public class TestPrivate {
    public static void main(String[] args) {
        User user = new User();
        user.name = "陈小吨";
        user.setIdCard(123456);
        Integer idCard = user.getIdCard();
        System.out.println(idCard);
        System.out.println(user.name);

    }
}

class User{
    String name;
    private Integer idCard;
    public Integer getIdCard(){
        return idCard;

    }
    public void setIdCard(Integer idCard){
        this.idCard = idCard;
    }
}

继承

继承是从已有的类中派生出新的类,新类能吸收已有类的数据属性和行为,并扩展最新的功能

  1. 通过extend子类继承父类 构成子类与父类的继承关系
  2. 子类继承父类相当于子类复制了一份父类的功能
  3. 子类无法继承父类的私有方法,无法继承构造方法
  4. 支持单继承,具有传递性,耦合性非常强
  5. 子类可以修改父类的功能,也可扩展自己的功能
    继承的方法 :继承方法重写规则(两同两小一大)
    两同:方法名相同,参数列表相同
    两小:子类的返回值类型要小于父类的返回值类型,子类 抛出的异常类型 要小于父类的抛出的异常类型
    一大:子类的修饰符要大于或等于父类的权限修饰符
    子类创建对象时默认会先调用父类的构造方法

重写与重载
1.重载是指一个类中有多个同名方法,但参数列表不同;重写的前提是继承,子类修改父类的功能便是重写
2.重载的意义是方便外界的调用;重写的意义是在不修源码的情况下对功能进行扩展

super
可以把super 看做是父类的对象

  1. 当父类的成员变量与子类的变量同名时,使用super指定父类的成员变量
  2. 使用super在子类构造方法的第一行调用父类构造方法的功能
    super()调用的是父类的无参构造
    super(参数)调用的是父类对应的含参构造
    在构造方法里,super出现的调用位置必须是第一行
  • 1.子类在创建对象时,默认会先调用父类的构造方法
  • 2.原因是子类构造函数中的第一行默认存在super();–表示调用父类的无参构造
  • 3.当父类没有无参构造时,可以通过super(参数)调用父类的其他含参构造
  • 子类必须调用一个父类的构造函数,不管是无参还是含参,选一个即可
  • 4.构造方法不可以被继承!因为语法的原因:要求构造方法的名字必须是本类类名
  • 不能在子类中出现一个父类名字的构造方法

this 与super的区别
*

案例
  1. 继承入门案例
package com.common.test.base;

public class TestExtend {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Cat cat = new Cat();
        Miao miao = new Miao();
        animal.eat();
        cat.eat();
        miao.eat();
    }

}

class Animal {

    public void eat() {
        System.out.println("动物吃东西!");

    }
}

class Cat extends Animal {
    int a = 10;
    private int b = 100;//私有属性


}

class Miao extends Cat {

    public void study(){
        System.out.println("正在学习");
        System.out.println(a);
    }
}

  1. super之继承中成员变量使用
package com.common.test.base;

public class ExtendDemo {
    public static void main(String[] args) {
        Son son = new Son();
        son.study();

    }
}

class Father {

    int sum = 1;
    int count = 2;

}

class Son extends Father {
    int sum = 10;

    public void study() {
        System.out.println("好好学习");
        int sum= 100;//局部变量
        System.out.println(sum);

        System.out.println(this.sum);//成员变量

        System.out.println("打印父类的成员变量"+ super.sum);

        System.out.println(count);

    }
}
  1. super之继承中构造方法的使用
package com.common.test.base;


/*本类用于测试继承中构造方法的使用
 * 1.子类在创建对象时,默认会先调用父类的构造方法
 * 2.原因是子类构造函数中的第一行默认存在super();--表示调用父类的无参构造
 * 3.当父类没有无参构造时,可以通过super(参数)调用父类的其他含参构造
 * 子类必须调用一个父类的构造函数,不管是无参还是含参,选一个即可
 * 4.构造方法不可以被继承!因为语法的原因:要求构造方法的名字必须是本类类名
 * 不能在子类中出现一个父类名字的构造方法
 * */
public class ExtendDemo2 {
    public static void main(String[] args) {
        Son2 son2 = new Son2();

    }
}
class Father2{

    public Father2(String name){
        System.out.println("父类的含参构造" +name);
    }
}

class Son2 extends Father2{
    public Son2(){
        super("子类调用");
        System.out.println("子类的无参构造");
    }
}

  1. 练习:继承中成员方法的使用
package com.common.test.base;



public class ExtendDemo3 {
    public static void main(String[] args) {
        Father3 father3 = new Father3();
        Son3 son3 = new Son3();
        son3.eat();
        father3.eat();

    }
}
class Father3{

    public void eat(){
        System.out.println("爸爸爱吃蔬菜");
    }
}

class Son3 extends Father3{

    @Override
    public void eat() {
        System.out.println("儿子爱吃肉");
    }
}

多态

多态是一个重要特征,指

  1. 多态的前提是继承+重写 同一个实体具有多种形态
  2. 声明看父类,实现看子类
  3. 多态对象使用的成员变量是父类的
  4. 若使用的方法重写了则使用的就是子类的
  5. 静态资源谁调用就是谁的
  6. 父类引用指向子类对象

3.3类和对象

  1. java语言最基本的单位
  2. 一类事务抽取共同属性与功能形成的

对象

每个对象有三个特点:对象的属性,对象的功能和对象的标识

  1. 对象的属性用来描述对象的基本特征
  2. 对象的功能用来描述对象可以完成的操作
  3. 对象的标识是指每个对象在内存中都有一个唯一的地址值用于与其他对象进行区分,类似于身份证号

类和对象的关系

  1. 先创建类,再通过类创建对象
  2. 通过一个类创建多个对象
  3. 类时抽象的,对象是具体的

对象在内存中的存储 **

对象的创建过程分析:
Person person = new Person();

  1. 在栈内存中开辟一片空间,存放引用类型Person类型的变量 person
  2. 在堆内存中开辟一块空间,存放Person 类型的对象
  3. 给对象进行初始化
  4. 当对象准备好后,生成一个唯一的地址值,然后将地址值交给引用类型的变量person来保存

访问修饰符

在这里插入图片描述

4.static final 静态/构造/局部代码块之间的关系

4.1 static

4.1.1 概念

java中的关键字,用于修饰成员变量和成员方法

4.1.2 特点

1.static可以修饰成员变量和方法
2.被static修饰的资源称为静态资源
3.静态资源随着类的加载而加载,最先加载,优先于对象进行加载
4.静态资源可以通过类名直接调用,也被称作类资源
5.静态被全局所有对象共享,值只有一份
6.静态资源只能调用静态资源
7.静态区域内不允许使用this与super关键字

4.1.3 练习

  1. static入门案例
package com.common.test.base;

public class TestStatic1 {
    public static void main(String[] args) {
        Fruit.kind= "苹果"; //静态成员变量可以通过类名.属性名 调用
        Fruit.clean(); //静态成员方法可以直接类名.方法 调用

        Fruit fruit = new Fruit();
        fruit.weight = 20D;
        fruit.grow();

    }
}

class Fruit {

    static String kind;
    double weight;


    public static void clean() {
        System.out.println("洗水果");

    }

    public void grow(){
        System.out.println("种一颗果树");
    }
}

  1. static静态调用关系
package com.common.test.base;

public class TestStatic2 {

}

class Teacher {
    //定义普通属性与普通方法
    String name;

    public void teach() {
        System.out.println("正在教课");
        //非静态资源可以调用静态资源
        System.out.println(age);
        ready();

    }

    static int age;

    public static void ready() {
        System.out.println("正在备课");
        //静态资源不可以调用普通资源
//        System.out.println(name);
//        teach();

    }

    public static void eat() {
        System.out.println("正在吃饭中");
        //静态资源可以调用静态资源
        System.out.println(age);
        ready();

    }
}

4.2静态代码块、构造代码块、局部代码块

4.2.1 静态代码块格式

static{}

静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
特点: 被static修饰,位置在类里方法外

4.2.2 三种代码块的比较

  • 静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
  • 构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
  • 局部代码块:方法里的代码块,限制局部变量的范围
    **执行顺序:**静态代码块 --> 构造代码块 --> 构造方法 --> 局部代码块

4.2.3 练习

  1. 几种代码块的关系
package com.common.test.base;

public class TestStaticBlock {
    public static void main(String[] args) {

        Person person = new Person();
        person.play();
    }
}

class Person {
    /**
     * 静态代码块
     * 类里方法外
     * 随着类的加载而加载,优先于对象加载,并且只会加载一次
     */
    static {
        System.out.println("我是静态代码块");

    }

    /**
     * 构造代码块
     * 类里方法外
     * 每次创建对象时触发,优先于构造方法执行
     */
    {
        System.out.println("我是构造代码块");
    }

    /**
     * 构造方法
     *
     */
    public Person(){
        System.out.println("我是构造方法");

    }

    /**
     * 普通方法
     */
    public void play(){
        {
            System.out.println("我是局部代码块");
        }
        System.out.println("我是普通方法");
        

    }}

4.3 final

4.3.1 概念

final 关键字可以修饰类,方法,字段(属性)
java出现继承后,子类可以继承父类的功能,当父类功能不允许子类改变时,可以利用final关键字修饰父类

4.3.2 特点

1.被final修饰的变量是常量,必须赋值且不能被修改
在这里插入图片描述
在这里插入图片描述

2.被final修饰的方法不能被重写
在这里插入图片描述

3.被final修饰的类不能被继承
在这里插入图片描述

5.异常

5.1 概述

异常是一些用来封装错误信息的对象
由异常的类型、提示信息、报错的行号提示三部分组成

5.2异常的继承结构

Throwable:顶级父类
	Error:错误,程序无法处理
		内存移除
	Exception:编码可以修复的错误
			运行时异常
			IO异常
	编译错误(checked异常);
	运行时错误(unchecked异常);
	逻辑错误;

	编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
	运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
	逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。

5.3异常的处理方式

通常有两种处理方式:捕获或者向上抛出
在这里插入图片描述

5.4throws 与 throw 的区别

throws
用在方法声明处,其后跟着的是异常类的名字
表示此方法会抛出异常,需要由本方法的调用者来处理这些异常
但是注意:这只是一种可能性,异常不一定会发生

throw
用在方法的内部,其后跟着的是异常对象的名字
表示此处抛出异常,由方法体内的语句处理
注意:执行throw一定抛出了某种异常

5.4 练习

package com.common.test.base;

import java.util.InputMismatchException;
import java.util.Scanner;

/*本类用于异常的入门案例*/
public class ExceptionDemo {
    //public static void main(String[] args) throws Exception {//问题实际未处理,还报错
    public static void main(String[] args) {
        //method1();//调用暴露异常的方法
        //method2();//调用解决异常的方法--异常解决方案1--捕获处理--自己解决
        /*main()不直接调用会抛出异常的method3()
         * 而是调用f(),f()解决了method3()可能会抛出的异常*/
        f();
        //method3();//调用解决异常的方法--异常解决方案2--向上抛出--交给调用者来解决
    }
    //相当于在main()调用method3()之前解决了method3()可能会抛出的异常
    private static void f() {
        try {
            method3();
        }catch (Exception e){
            System.out.println("您输入的数据不对~请重新输入!");
        }
    }

    /*如果一个方法抛出了异常,那么谁来调用这个方法,谁就需要处理这个异常
     * 这里的处理也有两种方案:捕获解决 或者 继续向上抛出
     * 但注意:我们一般会在main()调用之前将异常解决掉
     * 而不是将问题抛给main(),因为没人解决了,该报错还报错*/
    /*异常抛出的格式:在方法的小括号与大括号之间,写:throws 异常类型
     * 如果有多个异常,使用逗号分隔即可*/
    //0.定义一个解决异常的方法-方案2
    //private static void method3() throws ArithmeticException,InputMismatchException{
    private static void method3() throws Exception{
        //1.复写一下刚刚的代码
        System.out.println("请您输入要计算的第一个整数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请您输入要计算的第二个整数:");
        int b = new Scanner(System.in).nextInt();
        System.out.println(a/b);
    }

    /*异常捕获处理的格式:
     * try{
     *    可能会抛出异常的代码
     * }catch(异常的类型 异常的名字){
     *    万一捕获到了异常,进行处理的解决方案
     * }
     * try-catch结构可以嵌套,如果有多种异常类型需要特殊处理的话
     * */
    //0.定义一个解决异常的方法-方案1
    private static void method2() {
        //1.按照捕获处理的格式完成结构
        try{
            //2.复写一下刚刚的代码
            System.out.println("请您输入要计算的第一个整数:");
            int a = new Scanner(System.in).nextInt();
            System.out.println("请您输入要计算的第二个整数:");
            int b = new Scanner(System.in).nextInt();
            System.out.println(a/b);
        }catch(ArithmeticException e){//异常类型 异常名
            System.out.println("除数不能为0!");
        }catch (InputMismatchException e){
            System.out.println("请输入规定的整数类型!");
            /*使用多态的思想,不论是什么子异常,统一看作父类型Exception
             * 做出更加通用的解决方案,甚至可以只写这一个,上面2个不写了*/
        }catch (Exception e){
            System.out.println("您输入的数据不对~请重新输入!");
        }
    }

    //0.定义一个用来暴露异常的方法
    private static void method1() {
        //1.提示并接收用户输入的两个整数
        System.out.println("请您输入要计算的第一个整数:");
        int a = new Scanner(System.in).nextInt();
        System.out.println("请您输入要计算的第二个整数:");
        int b = new Scanner(System.in).nextInt();
        //2.输出两个数除法的结果
        //输入11和0,报错:ArithmeticException--算术异常,除数不能为0,数学规定
        //输入11和3.4,报错:InputMismatchException--输入不匹配异常
        System.out.println(a/b);
        /*1.不要害怕BUG,真正的勇士敢于直面自己写的BUG*/
        /*2.学会看报错的信息提示,确定自己错误的方法*/
        /*3.学会看报错的行号提示,确定自己报错的位置,哪里不对点哪里
         * 注意:源码不会错,要看的是自己写的代码*/
    }
}

6.抽象

6.1 概念

Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法.
Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类

如果一个类含有抽象方法,那么它一定是抽象类
抽象类中的方法实现交给子类来完成

6.2 抽象方法的格式

6.3 特点

  1. abstract 可以修饰方法或者类
  2. 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
  3. 抽象类中可以没有抽象方法;
  4. 如果类中有抽象方法,那么该类必须定义为一个抽象类
  5. 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
  6. 多用于多态中
  7. 抽象类不可以被实例化

6.4abstract注意事项

抽象方法要求子类继承后必须重写。
那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
1.private:被私有化后,子类无法重写,与abstract相违背。
2.static:静态优先于对象存在,存在加载顺序问题。
3.final:被final修饰后,无法重写,与abstract相违背。

6.5案例

package com.common.test.base;

public class AbstractDemo3 {

}

abstract class Fruit1{
    /*抽象类中可以定义成员变量*/
    int sum = 100;
    /*抽象类中可以定义成员常量*/

    final String name = "葡萄";

    /*抽象类中可以定义普通方法,也可以都是普通方法*/
    public void clean(){
        System.out.println("洗水果");

    }
    /* 抽象类中可以定义抽象方法,这个类必须是抽象类*/
    //抽象类中可以没有抽象方法,但是抽象方法必须在抽象类中
    public abstract void grow();
    public abstract void clean2();



}
//2.创建子类Banana
/*如果一个子类继承了抽象父类,有两种处理方案:
 * 方案一:继续抽象,也就是作为抽象子类,无需实现抽象方法-"躺平"
 * 方案二:不再抽象,实现继承自父类中的所有未实现的抽象方法-"父债子偿"*/
abstract class Apple extends Fruit1{

}

class Banana extends Fruit1{

    @Override
    public void grow() {
        
    }

    @Override
    public void clean2() {

    }
}


7.接口

7.1概念

7.2格式

interface 接口名 {代码}

7.3 特点

  1. 通过interface关键字来定义接口
  2. 通过implements让子类来实现接口
  3. 接口中的方法全部都是抽象方法(JAVA8)
  4. 可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
  5. 类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
  6. 接口突破了java单继承的局限性
  7. 接口和类之间可以多实现,接口与接口之间可以多继承
  8. 接口是对外暴露的规则,是一套开发规范
  9. 接口提高了程序的功能拓展,降低了耦合性

通过关键字implement 关键字来建立实现关系后
1.可以选择不实现接口中的抽象方法,把自己变成一个抽象类
2.可以选择实现接口中的所有抽象方法,把自己变成一个普通类
3.接口不可以实例化,通过实例化接口实现类对象
4.接口中的成员变量是静态常量格式
在这里插入图片描述

5.接口中有抽象方法

在这里插入图片描述

7.4练习

1.接口之成员方法

package com.common.test.base;

public class TestUserInter {
    public static void main(String[] args) {
        UserInterImpl u = new UserInterImpl();
        u.eat();
        u.play();
    }
}
//1.创建接口
interface UserInter{
    //7.测试接口中有抽象方法吗?
    /*5.接口中抽象方法的定义可以简写,会自动给方法拼接public abstract*/
    public abstract void eat();
    void play();
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
    @Override
    public void eat() {
        System.out.println("实现接口中的抽象方法1");
    }
    @Override
    public void play() {
        System.out.println("实现接口中的抽象方法2");
    }
}


总结:接口里的方法,默认都是抽象的,方法上会默认拼接public abstract。例如:public abstract void save();

2.接口的多继承多实现

package com.common.test.base;

/*本类用于测试接口与类之间的复杂关系*/
public class TestRelation {
    public static void main(String[] args) {
        //创建对象进行功能测试
        Inter3Impl i = new Inter3Impl();
        i.save();
        i.delete();
        i.update();
        i.find();
    }
}

//1.创建接口1
interface Inter1{
    void save();//保存功能
    void delete();//删除功能
}
//2.创建接口22
interface Inter22{
    void update();//更新功能
    void find();//查询功能
}
//3.创建接口1的实现类
class Inter1Impl implements Inter1{
    @Override
    public void save() { }
    @Override
    public void delete() { }
}

//4.创建接口3,同时继承两个接口
/*1.接口可以继承接口,并且可以多继承,多个接口之间用逗号隔开*/
interface Inter3 extends Inter1,Inter22{ }

//5.创建接口3的实现类
/*2.接口与实现类是实现的关系,并且可以多实现,多个接口之间用逗号隔开
 * 对于Java中的类而言,遵循:单继承 多实现
 * 一个类只能有一个父类,但是一个类可以实现多个接口*/
//class Inter3Impl implements Inter3{//写法1
class Inter3Impl implements Inter1,Inter22{//写法2
    @Override
    public void save() {
        System.out.println("稍等...正在努力保存中...");
    }
    @Override
    public void delete() {
        System.out.println("删除成功!");
    }
    @Override
    public void update() {
        System.out.println("小二正在马不停蹄的更新~");
    }
    @Override
    public void find() {
        System.out.println("客官,马上就查询好啦,稍等一丢丢~");
    }
}

7.5总结

类与类的关系

继承关系,只支持单继承
比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法)
子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)

类和接口的关系

实现关系.可以单实现,也可以多实现
class A implements B,C{}
其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类

接口与接口的关系

是继承关系,可以单继承,也可以多继承
interface A extends B,C{}
其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法)
class X implements A{}
X实现类需要重写ABC接口的所有方法,否则就是抽象类
class A extends B implements C,D{}
其中A是实现类,也是B的子类,同时拥有CD接口的所有功能
这时A需要重写CD接口里的所有抽象方法

接口与抽象类的区别

接口是一种用interface定义的类型
抽象类是一种用class定义的类型
接口中的方法都是抽象方法,还有默认方法与静态方法
抽象类中的方法不做限制
接口中的都是静态常量
抽象类中可以写普通的成员变量
接口没有构造方法,不可实例化
抽象类有构造方法,但是也不可以实例化
接口是先天设计的结果,抽象是后天重构的结果
接口可以多继承
抽象类只能单继承

8内部类

8.1概念

如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
在这里插入图片描述

8.2特点

1) 内部类可以直接访问外部类中的成员,包括私有成员
2) 外部类要访问内部类的成员,必须要建立内部类的对象
3) 在成员位置的内部类是成员内部类
4) 在局部位置的内部类是局部内部类

8.3 案例

package com.common.test.base;

public class TestInner1 {
}

class Outer {
    //    外部类的成员变量
    String name;
    private int age;

    //创建外部类的成员方法
    public void find() {
        System.out.println("外部类方法");
        //6.测试外部类如何使用内部类的资源
        //System.out.println(sum);--不能直接使用内部类的属性
        //delete();--不能直接调用内部类的方法
        /*外部类如果想要使用内部类的资源,必须先创建内部类对象
         * 通过内部类对象来调用内部类的资源*/
        Inner in = new Inner();
        System.out.println(in.sum);
        in.delete();
    }

    //    创建内部类
    class Inner {
        int sum;

        public void delete() {
//5.测试内部类是否可以使用外部类的资源
            /*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
            System.out.println(name);
            System.out.println(age);
            /*注意:此处测试完毕需要注释掉,否则来回调用
             * 会抛出异常StackOverFlowException栈溢出异常*/
            //find();


        }
    }
}

8.4成员内部类

**
成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源
**

package com.common.test.base;

public class TestInner2 {
    public static void main(String[] args) {
        /**怎么使用内部类Inner2的资源?*/
        //4.创建内部类Inner2对象进行访问
        //Outer2.Inner2 oi = new Outer2().new Inner2();
        //oi.eat();

        /**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
        //7.创建外部类对象,间接访问私有内部类资源
        new Outer2().getInner2Eat();
    }
}
//1.创建外部类Outer2
class Outer2{
    //6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
    public void getInner2Eat() {
        Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
        in.eat();
    }
    //2.1创建成员内部类Inner2
    /**成员内部类的位置:类里方法外*/
    //5.成员内部类,被private修饰私有化,无法被外界访问
    private class Inner2{
        //3.创建内部类的普通成员方法
        public void eat() {
            System.out.println("我是Inner2的eat()");
        }
    }
}

**
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问
**

package cn.tedu.innerclass;
/**本类用来测试成员内部类被static修饰*/
public class TestInner3 {
	public static void main(String[] args) {
		/**如何访问内部类的show()?*/
		//4.创建内部类对象访问show()
		//方式一:按照之前的方式,创建内部类对象调用show()
		//Outer3.Inner3 oi = new Outer3().new Inner3();
		//oi.show();
		//方式二:创建匿名内部类对象访问show()
		//new Outer3().new Inner3().show();
		
		/**现象:当内部类被static修饰以后,new Outer3()报错*/
		//6.用static修饰内部类以后,上面的创建语句报错,注释掉
		//通过外部类的类名创建内部类对象
		Outer3.Inner3 oi = new Outer3.Inner3();
		oi.show();
		
		//7.匿名的内部类对象调用show()
		new Outer3.Inner3().show();
		
		//9.访问静态内部类中的静态资源--链式加载
		Outer3.Inner3.show2();
	}
}

//1.创建外部类Outer3
class Outer3{
	//2.创建成员内部类Inner3
	//5.内部类被static修饰—并不常用!浪费内存!
	static class Inner3{
		//3.定义成员内部类中普通的成员方法
		public void show() {
			System.out.println("我是Inner3类的show()");
		}
		//8.定义成员内部类的静态成员方法
		static public void show2() {
			System.out.println("我是Inner3的show2()");
		}
	}
}

8.5局部内部类

**
如何使用内部类的资源呢?
注意:直接调用外部类的show()是无法触发内部类功能的
需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
**

package com.common.test.base;

/**
 * 本类用来测试局部内部类
 */
public class TestInner4 {
    public static void main(String[] args) {
        /**如何使用内部类的资源呢?
         * 注意:直接调用外部类的show()是无法触发内部类功能的
         * 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
         * */
        //5.创建外部类对象调用show()
        //7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
        new Outer4().show();
    }
}

//1.创建外部类Outer4
class Outer4 {
    //2.创建外部类的成员方法
    public void show() {
        //3.创建局部内部类Inner4—不太常用!!!
        /**位置:局部内部类的位置在方法里*/
        class Inner4 {
            //4.创建局部内部类的普通属性与方法
            String name;
            int age;

            public void eat() {
                System.out.println("我是Inner4的eat()");
            }
        }
        /**如何使用局部内部类的资源?*/
        //6.在show()里创建内部类对象
        Inner4 in = new Inner4();
        in.eat();
        System.out.println(in.name);
        System.out.println(in.age);
    }
}

8.6匿名内部类对象

匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用

package com.common.test.base;

public class TestInner5 {
    public static void main(String[] args) {

        new Inter1() {
            @Override
            public void save() {

            }

            @Override
            public void delete() {

            }
        }.save();


        new Inter2(){

            @Override
            public void drink() {

            }
        }.drink();
    }
}

interface inter1 {
    void save();

    void get();
}

abstract class Inter2 {
    public void play() {
        System.out.println("抽象类中的普通方法");

    }

    abstract public void drink();
}

class inter3 {
    public void study() {
        System.out.println("普通类方法");

    }
}

9 JAVA API Object String StringBuffer/StringBuilder

9.1Object

概念

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”
它存在于java.lang.Object,这个包不需要我们手动导包
需要注意的是:每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法.
在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类.

常用方法

toString()
用于返回对应对象的字符串表示
hashCode()
返回对象的哈希码值
equals()
用于指示某个对象是否与当前对象相等

9.2String

特点

String 是一个封装char[]数组的对象,字符串不可变

创建String对象的方式

方式一
String(char[] value) 分配一个新string ,使其表示字符数组参数中当前包含的字符序列

方式二
String str = “ABC”

如果是第一次使用字符串,java会在字符串堆中常量池创建一个对象。
再次使用相同的内容时,会直接访问堆中常量池中存在的对象。

String API总结
int hashCode() //返回此字符串的哈希码。
boolean equals(Object anObject) //将此字符串与指定的对象比较,比较的是重写后的串的具体内容
String toString() //返回此对象本身(它已经是一个字符串!)。

int length() //返回此字符串的长度。
String toUpperCase() //所有字符都转换为大写。
String toLowerCase() //所有字符都转换为小写
boolean startsWith(String prefix) //测试此字符串是否以指定的元素开头。
boolean endsWith(String suffix) //测试此字符串是否以指定的字符串结束。

char charAt(int index) //返回指定索引/下标处的 char 值/字符
int indexOf(String str) //返回指定字符在此字符串中第一次出现处的索引。
int lastIndexOf(String str) //返回指定字符在此字符串中最后一次出现处的索引。
String concat(String str) //将指定字符串连接/拼接到此字符串的结尾,注意:不会改变原串
String[] split(String regex) //根据给定元素来分隔此字符串。

String trim() //返回去除首尾空格的字符串
byte[] getBytes() //把字符串存储到一个新的 byte 数组中
String substring(int beginIndex) //返回一个新子串,从指定下标处开始,包含指定下标
String substring(int beginIndex, int endIndex) //返回一个新子串,从执定下标开始,到结束下标为止,但不包含结束下标
static String valueOf(int i) //把int转成String

9.3StringBuilder/StringBuffer

特点

  1. 封装了char[]数组
  2. 是可变的字符序列
  3. 提供了一组可以对字符内容修改的方法
  4. 常用append()来代替字符串做字符串连接”+”
  5. 内部字符数组默认初始容量是16:super(str.length() + 16);
  6. 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity =value.length* 2 + 2;
  7. StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全

常见方法

append()

StringBuilder和StringBuffer的区别

1.在线程安全上 :
–StringBuffer是旧版本就提供的,线程安全的。
–StringBuilder是jdk1.5后产生,线程不安全的
2. 在执行效率上,StringBuilder > StringBuffer > String
3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。

10正则表达式 包装类 自动装箱/自动拆箱 BigDecimal

10.1正则表达式Regex

10.1.1 概述

正确的字符串格式规则。
常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。

10.1.2 常见语法

在这里插入图片描述

10.1.3String提供了支持正则表达式的方法

Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串

10.2 2.2 Number

数字包装类的抽象父类。
提供了各种获取值的方式。
在这里插入图片描述

在这里插入图片描述

10.3 Integer

创建对象
方式一: new Integer(5);
方式二: Integer.valueOf(5);
Integer类中包含256个Integer缓存对象,范围是 -128~127
使用valueOf()时,如果指定范围内的值,直接访问缓存对象不新建;如果指定范围外的值,直接新建对象。

常见方法
static int parseInt(String s) 将字符串参数作为有符号的十进制整数进行解析

10.4 Double

创建对象
new Double(3.14)
Double.valueOf(3.14)//和 new 没有区别
常用方法
Double.parseDouble();

10.5 自动装箱和自动拆箱

自动装箱:把 基本类型 包装成对应的 包装类型 的过程
Integer a = 5;//a是引用类型,引用了包装对象的地址。
编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);

自动拆箱:从包装类型的值,自动变成 基本类型的值
int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。
编译器会完成自动拆箱:int i = a.intValue();

10.6 BigDecimal

BigDecimal:常用来解决精确的浮点数运算不精确的问题

10.6.1创建对象

方式一 :
BigDecimal(double val)
将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑!
方式二 :
BigDecimal(String val)
将String类型字符串的形式转换为BigDecimal

10.6.2 常用方法

Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

舍入方式解析

ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样

11 IO流 File 字节流 字符流

在这里插入图片描述

11.1IO流的继承

IO流根据处理的数据单位不同可以分为字节流和字符流
字节流:针对二进制文件
字符流:针对文本文件
根据对应类型的输入和输出方向
输入流
输出流

File
字节流:针对二进制文件
InputStream
FileInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
BufferedOutputStream
ObjectOutputStream
字符流:针对文本文件
Reader
FileReader
BufferedReader
InputStreamReader
Writer
FileWriter
BufferedWriter
OutputStreamWriter
PrintWriter一行行写出

11.2File文件类

概述

封装一个磁盘路径字符串,对这个路径可以执行一次操作
可以封装文件路径、文件夹路径、不存在的路径

3.2创建对象
File(String pathname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例
new File(“d:/abc/a.txt”);
new File(“d:/abc”,”a.txt”);

3.3常用方法
在这里插入图片描述

package cn.tedu.file;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/*本类用于测试文件类File*/
public class TestFile {
    public static void main(String[] args) throws IOException {
        //1.创建File类对象
        /*1.ready目录与1.txt需要自己手动创建
        * 2.File需要导包:import java.io.File;
        * 3.路径是String类型,必须写正确,不然找不到文件
        * 4.完整的文件名包含两部分:文件名+后缀名*/
        File file = new File("E:\\ready\\1.txt");

        //2.1测试File中的常用方法
        System.out.println(file.length());//3,获取文件的字节量
        System.out.println(file.exists());//true,判断文件是否存在
        System.out.println(file.isFile());//true,判断是否为文件
        System.out.println(file.isDirectory());//false,判断是否为文件夹
        System.out.println(file.getName());//1.txt获取文件名
        System.out.println(file.getParent());//E:\ready 获取父级路径
        System.out.println(file.getAbsolutePath());//E:\ready\1.txt 获取带盘符的完整路径:绝对路径

        //2.2 测试创建与删除
        /*new 只会帮你在内存中创建一个File类型的对象
        * 并不会帮你在磁盘中创建一个真实存在的2.txt文件*/
        file = new File("E:\\ready\\2.txt");

        //创建一个之前不存在的文件2.txt,如果创建成功,会返回true
        /*如果指定创建文件的路径不对,会抛出异常:java.io.Exception
        * 所以需要提前处理这个问题,我们暂时选择在main()上抛出
        * 这个IO异常是目前我们遇到的强制要求必须预先处理的异常
        * 如果不处理,方法的调用会报错,通不过编译*/
        System.out.println(file.createNewFile());//创建之前不存在的文件

        file = new File("E:\\ready\\m");
        System.out.println(file.mkdir());//创建之前不存在的单层文件夹

        file = new File("E:\\ready\\a\\b\\c");
        System.out.println(file.mkdirs());//创建之前不存在的多层文件夹

        System.out.println(file.delete());//c被删除,删除空文件夹或者文件

        file = new File("E:\\ready\\a");
        System.out.println(file.delete());//false,a文件夹不是空的,里面有内容

        file = new File("E:\\ready\\2.txt");
        System.out.println(file.delete());//2.txt被删除,可以删除文件

        //2.3测试展示文件列表
        file = new File("E:\\ready");
        String[] list = file.list();/*不常用*/
        System.out.println(Arrays.toString(list));
        //这句话会报错,因为这是一个String[],所以数组中每个元素都是String类型的
        //那么只能用String类中的方法,而isDirectory()是File类中的方法
        //System.out.println(list[0].isDirectory());

        File[] fs = file.listFiles();/*常用*/
        System.out.println(Arrays.toString(fs));
        System.out.println(fs[0].isDirectory());
    }
}


序列化与反序列化

12.1 概述

序列化是指将对象的状态信息转换为可以存储或传输形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象.

序列化:利用ObjectOutputStream,把对象的信息,按照固定的格式转成一串字节值输出并持久保存到磁盘
反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象
在这里插入图片描述

12.2 特点/应用场景

需要序列化的文件必须实现Serializable接口,用来启用序列化功能
不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
常用使用套接字流在主机之间传递对象
不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存

12.3 涉及到的流对象

序列化:ObjectOutputStream
ObjectOutputStream 将 Java 对象的基本数据类型写入 OutputStream,通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。

构造方法:
ObjectOutputStream(OutputStream out)
创建写入指定 OutputStreamObjectOutputStream
普通方法:
writeObject(Object obj)
将指定的对象写入 ObjectOutputStream

反序列化:ObjectInputStream
ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化重构对象。

构造方法:
ObjectInputStream(InputStream in) //创建从指定 InputStream 读取的 ObjectInputStream
普通方法:
readObject() //从 ObjectInputStream 读取对象

13泛型 Collection List Set

13.1Collection接口

在这里插入图片描述

13.2 集合概念

集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法.Collection接口层次结构 中的根接口,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父接口公共方法.
由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合是通用的.

13.3 集合的继承结构

Collection接口
List 接口【数据有下标,有序,可重复】
ArrayList子类
LinkedList子类
Set 接口【数据无下标,无序,不可重复】
HashSet子类
Map 接口【键值对的方式存数据】
HashMap子类

13.4 Collection方法速查表

在这里插入图片描述

13.2 泛型

在这里插入图片描述

13.3List接口

13.3.1概念

有序的colletion(也称为序列).此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素.

13.3.2特点

  1. 元素都有下标
  2. 数据是有序的
  3. 允许存放重复的元素

13.3.3List方法速查表

在这里插入图片描述

 List<String> list = new ArrayList<String>();
list.contains("元素")//判断集合是否包含指定元素
list.isEmpty();//判断集合是否为空
list.remove("元素");//移除集合中指定的元素
list.size();//获取集合中元素的个数
list.toArray();//将集合转成数组

集合的迭代方式:
* 1.for循环
* 2.高效for循环
* 3.iterator
* 4.listIterator

package com.common.test.base;

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

public class TestList2 {
    public static void main(String[] args) {
        //1.创建List接口的多态对象
        List<String> list = new ArrayList<>();
        //2.向创建好list集合添加元素
        list.add("喜羊羊");
        list.add("美羊羊");
        list.add("懒羊羊");
        list.add("沸羊羊");
        list.add("小肥羊");
        list.add("肥羊卷");
        System.out.println(list);

        //3.测试集合的迭代
        /**集合的迭代方式:
         * 1.for循环
         * 2.高效for循环
         * 3.iterator
         * 4.listIterator
         * */
        //方式一:因为List集合是有序的,元素有下标的,所以可以根据下标进行遍历
        //从哪开始:0  到哪结束:list.size()-1  如何变化++
        for (int i = 0; i < list.size(); i++) {
            //根据本轮循环遍历到的索引值获取对应的集合元素
            System.out.println(list.get(i));
        }
        System.out.println("************方式一*************");

        //方式二:因为普通for循环遍历效率低,语法复杂,所以使用高效for来遍历
        //格式for(本轮遍历到的元素类型 元素名 :要遍历的内容名){循环体}
        for (String s : list) {
            System.out.println(s);
        }
        System.out.println("************方式二*************");

        //方式三:从父接口中继承过来的迭代器iterator
        //1.获取对应的迭代器对象
        Iterator<String> it = list.iterator();
        //2.通过刚刚获取到的迭代器循环迭代集合中的所有元素
        while (it.hasNext()) {//判断是否仍有下一个元素可以迭代
            System.out.println(it.next());//打印当前获取到的元素
        }
        System.out.println("************方式三*************");
        /**方式四:listIterator属于List接口特有的迭代器
         * Iterator<E>--父接口--hasNext() next()
         * ListIterator<E>--子接口--除了父接口的功能以外
         * 还有自己的特有功能,比如逆序遍历,添加元素等等,但是不常用
         * public interface ListIterator<E>extends Iterator<E>
         * */
        ListIterator<String> it2 = list.listIterator();
        while (it2.hasNext()) {
            System.out.println(it2.next());
        }
        System.out.println(list);
        System.out.println("listIterator的逆序遍历:");
        ListIterator<String> it3 = list.listIterator();
        while (it3.hasNext()) {//判断是否有下一个元素可迭代
            System.out.println(it3.next());//打印当前迭代到的元素
            if (!it3.hasNext()) {//直到迭代器没有下一个元素可迭代--到最后了
                System.out.println("开始逆序迭代:");
                while (it3.hasPrevious()) {//判断是否有上一个元素可迭代
                    System.out.println(it3.previous());//打印获取到的上一个元素
                }
                break;//终止循环,不然会一直从头到尾,再从尾到头迭代
            }
        }
    }
}

13.4 ArrayList

13.4.1概述

  1. 存在java.util包中
  2. 内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
  3. 内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
  4. 查询快,增删数据效率会低

13.4.2 创建对象

在这里插入图片描述

13.5 LinkedList

13.5.1 概述

链表,两端效率高,底层就是链表实现的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14 Set HashSet Map HashMap

14.1 Map接口

14.1.1概述

Java.util接口Map<K,V>
类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值
也叫做哈希表、散列表. 常用于键值对结构的数据.其中键不能重复,值可以重复

14.1.2 特点

  1. Map可以根据键来提取对应的值
  2. Map的键不允许重复,如果重复,对应的值会被覆盖
  3. Map存放的都是无序的数据
  4. Map的初始容量是16,默认的加载因子是0.75

14.1.3 继承结构

在这里插入图片描述

14.1.4 常用方法

void clear() //从此映射中移除所有映射关系(可选操作) 
boolean containsKey(Object key) //如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) //如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() //返回此映射中包含的映射关系的 Set 视图
boolean equals(Object o) //比较指定的对象与此映射是否相等
V get(Object key) //返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() //返回此映射的哈希码值
boolean isEmpty() //如果此映射未包含键-值映射关系,则返回 true
Set keySet() //返回此映射中包含的键的 Set 视图
V put(K key, V value) //将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m)//从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key) //如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size() //返回此映射中的键-值映射关系数
Collection values() //返回此映射中包含的值的 Collection 视图

14.1.5 map 的迭代方式

  1. 将map转换成set集合再去遍历
		//4.map集合的迭代方式一
        /**方式一:
         * 遍历map中的数据,但是map本身没有迭代器,所以需要先转换成set集合
         * Set<Key>:把map中的所有key值存入到set集合当中--keySet()*/
        //4.1将map集合中的key值取出存入set集合中,集合的泛型就是key的类型Integer
        Set<Integer> keySet = map.keySet();
        //4.2想要遍历集合就需要获取集合的迭代器
        Iterator<Integer> it = keySet.iterator();
        //4.3循环迭代集合中的所有元素
        while(it.hasNext()){//判断是否有下一个元素可以迭代
            Integer key = it.next();//拿到本轮循环中获取到的map的key
            String value = map.get(key);
            System.out.println("{"+key+","+value+"}");
        }

        /**方式二:
         * 遍历map集合,需要把map集合先转成set集合
         * 是把map中的一对键值对key&value作为一个Entry<K,V>整体放入set
         * 一对K,V就是一个Entry*/
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        //获取迭代器
        Iterator<Map.Entry<Integer, String>> it2 = entrySet.iterator();
        while(it2.hasNext()){//判断是否有下一个元素可迭代
            //本轮遍历到的一个Entry对象
            Map.Entry<Integer, String> entry = it2.next();
            Integer key = entry.getKey();//获取Entry中的key
            String value = entry.getValue();//获取Entry中的value
            System.out.println("{"+key+","+value+"}");
        }

14.2HashMap

14.2.1前言

HashMap的键要同时重写hashCode()和equlas()
hashCode()用来判定二者的hash值是否相同,重写后根据属性生成
equlas()用来判断属性的值是否相同,重写后,根据属性判断
–equlas()判断数据如果相等,hashCode()必须相同
–equlas()判断数据如果不等,hashCode()尽量不同

14.2.2HashMap的存储过程:

  1. HashMap的结构是数组+链表 或者 数组+红黑树 的形式
  2. HashMap底层的Entry[ ]数组,初始容量为16,加载因子是0.75f,扩容按约为2倍扩容
  3. 当存放数据时,会根据hash(key)%n算法来计算数据的存放位置,n就是数组的长度,其实也就是集合的容量
  4. 当计算到的位置之前没有存过数据的时候,会直接存放数据
  5. 当计算的位置,有数据时,会发生hash冲突/hash碰撞
  6. 解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素也就是说数组中的元素都是最早加入的节点
  7. 如果链表的长度>8且数组长度>64时,链表会转为红黑树,当链表的长度<6时,红黑树会重新恢复成链表
    在这里插入图片描述

15.3 set接口

在这里插入图片描述

15.3.1 概述

Set是一个不包含重复数据的Collection
Set集合中的数据是无序的(因为Set集合没有下标)
Set集合中的元素不可以重复 – 常用来给数据去重

15.3.2 Set集合的特点

数据无序且数据不允许重复
HashSet : 底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复。
TreeSet : 底层是TreeMap,也是红黑树的形式,便于查找数据

16 进程与线程

16.1 进程

16.1.1 进程的概念

进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。

16.1.2 进程的特点

  • 独立性
    进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
  • 动态性
    进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
  • 并发性
    多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.

16.2 线程

16.2.1 线程的概念

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。

16.2.2 进程与线程的关系

一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)
在这里插入图片描述
每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间.
所以想使用线程技术,得先有进程,进程的创建是OS操作系统来创建的,一般都是C或者C++完成
在这里插入图片描述

16.3多线程的特性

16.3.1 随机性

我们宏观上觉得多个进程是同时运行的,但实际的微观层面上,一个CPU【单核】只能执行一个进程中的一个线程。
那为什么看起来像是多个进程同时执行呢?
是因为CPU以纳秒级别甚至是更快的速度高效切换着,超过了人的反应速度,这使得各个进程从看起来是同时进行的,也就是说,宏观层面上,所有的进程看似并行【同时运行】,但是微观层面上是串行的【同一时刻,一个CPU只能处理一件事】。
在这里插入图片描述

串行与并行
串行是指同一时刻一个CPU只能处理一件事,类似于单车道
并行是指同一时刻多个CPU可以处理多件事,类似于多车道

在这里插入图片描述
在这里插入图片描述

13.3.2 CPU分时调度

时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己规则,如:

  1. FCFS(First Come First Service 先来先服务算法)
  2. SJS(Short Job Service短服务算法)
    在这里插入图片描述

13.3.3 线程的状态

由于线程状态比较复杂,我们由易到难,先学习线程的三种基础状态及其转换,简称”三态模型” :

  • 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
  • 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
  • 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
    在这里插入图片描述

就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正在访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞变为就绪状态,等待分配CPU再次执行

我们可以再添加两种状态:

  • 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
  • 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统
    在这里插入图片描述

PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程

13.3.4 线程状态与代码对照

在这里插入图片描述
线程生命周期,主要有五种状态:

  1. 新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
  2. 就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
    处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
  3. 运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
    就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
  4. 阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
    根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
    等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
    同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
    其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
  5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

13.4 多线程代码创建

13.4.1方式1:继承Thread

概述

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例
启动线程的唯一方法就是通过Thread类的start()实例方法
start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()
这种方式实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法
模拟开启多个线程,每个线程调用run()方法.

常用方法

构造方法

Thread() 分配新的Thread对象
Thread(String name) 分配新的Thread对象
Thread(Runnable target) 分配新的Thread对象
Thread(Runnable target,String name) 分配新的Thread对象

普通方法

static Thread currentThread( )
返回对当前正在执行的线程对象的引用
long getId()
返回该线程的标识
String getName()
返回该线程的名称
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()
使该线程开始执行:Java虚拟机调用该线程的run()

package com.common.test.base;


/*本类用于多线程编程实现方案一:继承Thread类来完成*/
public class TestThread1 {
    public static void main(String[] args) {
        //4.创建线程对象进行测试
        /*4.new对应的是线程的新建状态
         * 5.要想模拟多线程,至少得启动2个线程,如果只启动1个,是单线程程序*/
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        /*6.这个run()如果直接这样调用,是没有多线程抢占执行的效果的
         * 只是把这两句话看作普通方法的调用,谁先写,就先执行谁*/
        //t1.run();
        //t2.run();
        /*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中
         * 至于什么时候执行,就是多线程执行的效果,需要等待OS选中分配CPU
         * 8.执行的时候start()底层会自动调用我们重写的run()种的业务
         * 9.线程的执行具有随机性,也就是说t1-t4具体怎么执行
         * 取决于CPU的调度时间片的分配,我们是决定不了的*/
        t1.start();//以多线程的方式启动线程1,将当前线程变为就绪状态
        t2.start();//以多线程的方式启动线程2,将当前线程变为就绪状态
        t3.start();//以多线程的方式启动线程3,将当前线程变为就绪状态
        t4.start();//以多线程的方式启动线程4,将当前线程变为就绪状态
    }
}

//1.自定义一个多线程类,然后让这个类继承Thread
class MyThread extends Thread{
    /*1.多线程编程实现的方案1:通过继承Thread类并重写run()来完成的 */
    //2.重写run(),run()里是我们自己的业务
    @Override
    public void run() {
        /*2.super.run()表示的是调用父类的业务,我们现在要用自己的业务,所以注释掉*/
        //super.run();
        //3.完成业务:打印10次当前正在执行的线程的名称
        for (int i = 0; i < 10; i++) {
            /*3.getName()表示可以获取当前正在执行的线程名称
             * 由于本类继承了Thread类,所以可以直接使用这个方法*/
            System.out.println(i+"="+getName());
        }
    }
}

13.4.2创建方式2:实现Runnable接口

概述

如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口

常用方法

void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法

两种实现方式的比较

  • 继承Thread类
    优点: 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
    缺点: 自定义的线程类已继承了Thread类,所以后续无法再继承其他的类
  • 实现Runnable接口
    优点: 自定义的线程类只是实现了Runnable接口或Callable接口,后续还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想
    缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread()方法
package com.common.test.base;


/*本类用于多线程编程实现方案二:实现Runnable接口来完成*/
public class TestThread2 {
    public static void main(String[] args) {
        //5.创建自定义类的对象--目标业务类对象
        MyRunnable target = new MyRunnable();
        //6.如何启动线程?自己没有,需要与Thread建立关系
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义多线程类
class MyRunnable implements Runnable{
    //2.添加父接口中的抽象方法run(),里面是自己的业务
    @Override
    public void run() {
        //3.写业务,打印10次当前正在执行的线程名称
        for (int i = 0; i < 10; i++) {
            /*问题:自定义类与父接口Runnable中都没有获取名字的方法
             * 所以还需要从Thread中找:
             * currentThread():静态方法,获取当前正在执行的线程对象
             * getName():获取当前线程的名称*/
            System.out.println(i+"="+Thread.currentThread().getName());
        }
    }
}

14 同步锁-线程安全问题解决方案

14.1同步锁

14.1.1前言

我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件:

在多线程程序中 + 有共享数据 + 多条语句操作共享数据

14.1.2 同步与异步

那怎么"把有可能出现问题的代码都包裹起来"呢?我们可以使用synchronized关键字来实现同步效果
也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的

接下来介绍下同步与异步的概念:
同步:体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。
坏处就是效率会降低,不过保证了安全。
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
坏处就是有安全隐患,效率要高一些。

14.1.3 synchronized同步关键字

14.1.3.1 写法
synchronized (锁对象){
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}
14.1.3.2 前提

同步效果的使用有两个前提:

  • 前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
  • 前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
14.1.3.3 特点
  1. synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!
  2. synchronized同步关键字可以用来修饰方法,称为同步方法
  3. 同步的缺点是会降低程序的执行效率,
  4. 但我们为了保证线程的安全,有些性能是必须要牺牲的但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了

为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
因为同步代码块可以保证同一个时刻只有一个线程进入
但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步

14.2 线程创建的其他方式

14.2.1 ExecutorService/Executors

ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

execute(Runnable任务对象) 把任务丢到线程池

Executors 辅助创建线程池的工具类

newFixedThreadPool(int nThreads) 最多n个线程的线程池
newCachedThreadPool() 足够多的线程,使任务不必等待
newSingleThreadExecutor() 只有一个线程的线程池

14.3线程锁

14.3.1 悲观锁和乐观锁

悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

14.3.2 两种常见的锁

synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

15 设计模式之单例设计模式

在这里插入图片描述

15.1 概念

单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个。
简单来说,保证一个类在内存中的对象就一个。
RunTime就是典型的单例设计,我们通过对RunTime类的分析,一窥究竟。

 /**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */
public class Runtime {
	//2.创建静态的全局唯一的对象
	private static Runtime currentRuntime = new Runtime();

	//1.私有化构造方法,不让外部来调用
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    
	//3.通过自定义的静态方法获取实例
    public static Runtime getRuntime() {
        return currentRuntime;
}
}

通过分析,底层的实现思路一共分为了3个步骤:

对本类构造方法私有化,防止外部调用构造方法创建对象
创建全局唯一的对象,也做私有化处理
通过自定义的公共方法将创建好的对象返回(类似封装属性后的getXxx() )

15.2练习

饿汉式实现方式

package com.common.test.base;

/*本类用于实现单例设计模式实现方案1:饿汉式*/
public class Singleton1 {
    public static void main(String[] args) {
        //5.在main()中,不通过对象,直接通过类名,调用静态方法
        MySingle single1 = MySingle.getSingle();
        MySingle single2 = MySingle.getSingle();
        //6.用==检验是否是同一个对象
        System.out.println(single1 == single2);//true
        System.out.println(single1);
        System.out.println(single2);
    }
}
//0.创建自己的单例程序
class MySingle{
    //1.提供构造方法,并将构造方法私有化
    /*1.构造方法私有化的目的:为了防止外界随意创建本类对象*/
    private MySingle(){ }

    //2.创建本类对象,并将对象也私有化
    //4.2由于静态资源只能调用静态资源,所以single对象也需要设置成静态
    private static MySingle single = new MySingle();

    //3.提供公共的访问方式,返回创建好的对象
    //4.1为了不通过对象,直接调用本方法,需要将本方法设置为静态
    public static MySingle getSingle(){
        return single;
    }
}

懒汉式实现方式

package com.common.test.base;

/*本类用于实现单例设计模式优化实现方案2:懒汉式
 * 关于单例设计模式的两种实现方式:
 * 1.饿汉式:不管你用不用这个类的对象,都会直接先创建一个
 * 2.懒汉式:先不给创建这个类的对象,等你需要的时候再创建--延迟加载的思想
 * 延迟加载的思想:是指不会在第一时间就把对象创建好占用内存
 *               而是什么时候用到,什么时候再去创建对象
 * 3.线程安全问题:由于我们存在唯一的对象single2,并且多条语句都操作了这个变量
 *   如果将程序放到多线程的环境下,就容易出现数据安全的问题,所以解决方案:
 *   1) 将3条语句都使用同步代码块包裹,保证同步排队的效果
 *   2) 由于getSingle2()只有这3条语句,所以也可以将本方法设置为同步方法*/
public class Singleton2 {
    public static void main(String[] args) {
        //5.调用方法查看结果
        MySingle2 single1 = MySingle2.getSingle2();
        MySingle2 single2 = MySingle2.getSingle2();
        System.out.println(single1 == single2);
        System.out.println(single1);
        System.out.println(single2);
    }
}
//0.创建自己的单例程序
class MySingle2{
    //6.2创建一个静态的唯一的锁对象
    static Object o = new Object();
    //1.私有化本类的构造方法
    private MySingle2(){ }
    //2.创建的是本类对象的引用类型变量,用来保存对象的地址值,默认值是null
    private static MySingle2 single2 ;
    //3.提供公共的get方法
    synchronized public static MySingle2 getSingle2(){
        //4.判断之前是否创建过对象,之前创建过就直接走return
        //之前如果没有创建过,才走if,创建对象并将对象返回
        //6.有共享数据+多条语句操作数据,所以尽量提前处理,避免多线程数据安全隐患
        //6.1 解决方案1:加同步代码块
        //6.2 解决方案2:将本方法getSingle2()设置为同步方法
        //因为这个方法里所有的语句都需要同步
        synchronized (o) {//静态方法中使用的锁对象也得是静态的
            if (single2 == null) {//single2还是默认值,说明之前没有创建过对象
                single2 = new MySingle2();//没创建过才创建,并赋值给single2
            }
            return single2;
        }
    }
}


16 注释

16.1注解的分类

注解一共分为3大类,我们先来认识一下:

JDK自带注解
元注解
自定义注解

16.1.1 JDK注解

JDK注解的注解,就5个:

@Override :用来标识重写方法
@Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
@SuppressWarnings(“deprecation”) 忽略警告
@SafeVarargs jdk1.7出现,堆污染,不常用
@FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用

16.1.2 元注解

用来描述注解的注解,就5个:

@Target 注解用在哪里:类上、方法上、属性上等等
@Retention 注解的生命周期:源文件中、字节码文件中、运行中
@Inherited 允许子注解继承
@Documented 生成javadoc时会包含注解,不常用
@Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用

16.1.2.1 @Target ElementType…

描述注解存在的位置:

ElementType.TYPE 应用于类的元素
ElementType.METHOD 应用于方法级
ElementType.FIELD 应用于字段或属性(成员变量)
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.PACKAGE 应用于包声明
ElementType.PARAMETER 应用于方法的参数

16.1.2.2 @Retention RetentionPolicy…

该注解定义了自定义注解被保留的时间长短,比如某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取。
为何要分字节码文件中有还是没有呢?如果没有时,反射技术就拿不到,从而就无法去识别处理。它的值一共3种:
在这里插入图片描述

SOURCE 在源文件中有效(即源文件保留)
CLASS 在class文件中有效(即class保留)
RUNTIME 在运行时有效(即运行时保留)

16.2自定义注解


package cn.tedu.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*本类用于完成自定义注解*/
public class TestAnnotation {
}
//2.通过@Target注解标记自定义注解的使用位置
/*3.通过元注解@Target规定自定义注解可以使用的位置
* 我们使用"ElementType.静态常量"的方式来指定自定义注解具体可以加在什么位置
* 而且,值可以写多个,格式:@Target({ElementType.XXX,ElementType.XXX}*/
@Target({ElementType.METHOD,ElementType.TYPE})//可以加在方法&类上
//3.通过@Retention注解标记自定义注解的生命周期
/*4.通过元注解@Retention规则自定义注解的生命周期
* 我们使用"RetentionPolicy.静态常量"的方式来指定自定义注解的生命周期
* 注意:值只能写一个:SOURCE CLASS RUNTIME 3选1 */
@Retention(RetentionPolicy.RUNTIME)//到运行时都有效
//1.定义自定义注解
/*1.首先注意:注解定义的语法与Java不同
* 2.定义自定义注解的格式:@interface 注解名*/
@interface Rice{
    //5.我们可以给注解进行功能增强--添加注解的属性
    /*5.注意:int age();不是方法的定义,而是给自定义注解添加了一个age属性*/
    //int age();//给自定义注解添加一个普通属性age,类型是int
    int age() default 0;//给自定义注解的普通属性赋予默认值0
    /*6.注解中还可以添加特殊属性value
    * 特殊属性的定义方式与普通属性一样,主要是使用方式不同
    * 注意:特殊属性的名字必须叫value,但是类型不做限制
    * 特殊属性也可以赋予默认值,格式与普通属性一样,不能简写
    * */
    //String value();//定义一个特殊属性value,类型是String
    String value() default "Lemon";//定义特殊属性并给特殊属性赋予默认值
}

//4.定义一个类用来测试自定义注解
//@Rice
class TestAnno{
    /*测试1:分别给TestAnno类 name属性 eat方法都添加Rice注解
    * 结论:属性上的注解报错了,说明自定义注解可以加在什么位置,由@Target决定*/
    //@Rice//报错了
    String name;
    /*测试2:当我们给Rice注解添加了一个age属性以后,@Rice注解使用时直接报错
    * 结论:当注解没有定义属性时,可以直接使用
    *      当注解定义了属性以后,必须给属性赋值,格式:@Rice(age = 10)*/
    /*测试3:给age属性赋予默认值以后,可以直接使用@Rice注解
    * 不需要给age属性赋值,因为age属性已经有默认值0了*/
    /*测试4:给Rice注解添加了特殊属性value以后,必须给属性赋值
    * 只不过特殊属性赋值时可以简写成 @Rice("Apple")
    * 测试5:如果特殊属性也赋予了默认值,那么可以直接使用这个注解
    * 如果要给注解的所有属性赋值,每条赋值都不能简写*/
    @Rice(age=10,value="orange")
    //@Rice("Apple")
    //@Rice(age = 10)
    //@Rice(10)//报错,不可以简写,普通属性没有这种格式
    public void eat(){
        System.out.println("干饭不积极,思想有问题");
    }
}

17 反射

17.1 什么是反射?

Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,也有称作“自省”。
反射非常强大,它甚至能直接操作程序的私有属性。我们前面学习都有一个概念,被private封装的资源只能类内部访问,外部是不行的,但这个规定被反射赤裸裸的打破了。
反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

17.2 为什么需要反射?

如果想创建对象,我们直接new User(); 不是很方便嘛,为什么要去通过反射创建对象呢?

那我要先问你个问题了,你为什么要去餐馆吃饭呢?
例如:我们要吃个牛排大餐,如果我们自己创建,就什么都得管理。
好处是,每一步做什么我都很清晰,坏处是什么都得自己实现,那不是累死了。牛接生你管,吃什么你管,屠宰你管,运输你管,冷藏你管,烹饪你管,上桌你管。就拿做菜来说,你能有特级厨师做的好?
那怎么办呢?有句话说的好,专业的事情交给专业的人做,饲养交给农场主,屠宰交给刽子手,烹饪交给特级厨师。那我们干嘛呢?
我们翘起二郎腿直接拿过来吃就好了。
再者,饭店把东西做好,不能扔到地上,我们去捡着吃吧,那不是都成原始人了。那怎么办呢?很简单,把做好的东西放在一个容器中吧,如把牛排放在盘子里。

我们在后面的学习中,会学习框架,有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

总结一句,类不是你创建的,是你同事或者直接是第三方公司,此时你要或得这个类的底层功能调用,就需要反射技术实现。有点抽象,别着急,我们做个案例,你就立马清晰。

17.3 反射需要用到的API

17.3.1 获取字节码对象

Class.forName(“类的全路径”);
类名.class
对象.getClass();

17.3.2 常用方法

获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名
获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)
获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)
反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法
反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值