类变量

什么是类变量

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

下面来看一段代码

class Child{
    private String name;
    public static int count=0;	//定义一个变量count,是一个类变量(静态变量),被Child类的所有对象实例共享
    public Child(String name) {
        this.name = name;
    }
    public void join(){
        System.out.println(name+" 加入了游戏..");
        count++;
    }
}

静态域因jdk版本不同,可能存放在堆里,也有可能在方法区里

但可以肯定的是,静态变量被所有对象共享,而且在类加载的时候就生成了(new的时候加载一次,且只加载一次)

关于上面代码里的count变量,也就是static变量

jdk8以前,会认为是在这个方法区里面,但jdk8以后静态域放在了堆里面

当类被加载的时候,会在堆里生成一个class对象,而static变量就被保存在class实例的尾部,所以认为静态变量在堆里面

这里顺便提一句,局部变量在使用完之后内存会被释放,但静态变量会保留内存,直到程序结束

访问类变量

  • 类名.类变量名
  • 对象名.类变量名

这里更推荐使用前者,看起来更规范一些

而实例变量不能通过前者访问

因为类变量随着类的加载而创建,所以没有创建对象实例也可访问类变量

因此,类变量的生命周期是随类的加载开始,随着类消亡而销毁

同时,类变量的访问也要遵守修饰符的访问权限,和普通属性是一样的

什么时候需要用类变量

当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量

例如:定义学生类,统计所有学生共交多少钱

类变量与实例变量(普通变量)的区别

类变量是该类的所有对象共享的,而实例变量是每个对象独享的

加上static的变量称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量

类变量就先记录这么多吧…

类方法

类方法也叫静态方法

当方法使用了static修饰后,该方法就是静态方法,静态方法就可以访问静态属性/变量

静态方法不能调用非静态变量

形式如下:

  • 访问修饰符 static 数据返回类型 方法名(){}
  • static 访问修饰符 数据返回类型 方法(){}

这里也是为了规范,推荐使用前者

类方法的调用

  • 类名.类方法名
  • 对象名.类方法名

跟前面的类变量一样,要满足访问修饰符的访问权限和范围

使用场景

我们可以发现在工具类中提供的方法基本都是静态方法

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。

在开放中往往将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等…

如果我们希望不创建实例,也可以调用某个方法,这时候静态方法就是一个很好的使用场景

注意事项

  • 类方法也和类变量一致,随着类的加载而加载,将结构信息存储在方法区
  • 类方法中无this的参数,而普通方法中隐含着this的参数
  • 类方法可以通过类名调用,也可以通过对象名调用,不能通过类名调用
  • 类方法中不允许使用和对象有关的关键字,比如thissuper。但普通方法可以使用
  • 类方法(静态方法)中只能访问静态变量和静态方法,但普通方法即可以访问普通变量和方法,也可以访问静态变量和方法(但必须遵守访问权限)

理解main方法语法

深入理解main方法

解释main方法的形式

public static void main(String[] args) {}
  1. main方法时虚拟机调用的
  2. Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
  5. main的形参,就是把Java 运行的类名 参数1 参数2 参数3中最后面的参数形成一个数组带进去,而且可以在主方法里再取出来

同上面的类方法,在main()方法中,我们可以直接调用main()方法所在类的静态方法或静态属性,但是,不能直接访问该类的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

main动态传值

刚刚上面讲的Java 运行的类名 参数1 参数2 参数3是在命令行中的传值方法

下面我们用下面这段代码来说一下idea里怎么进行传递参数

public class Main {
    public static void main(String[] args) {
        for (int i=0;i< args.length;i++){
            System.out.println("args["+i+"]="+args[i]);
        }
    }
}

我们在idea右上角选择Edit Configurations

image-20221112150649553

Program arguments里写入参数即可

image-20221112150845084

如图,这里我们传入杭州 宁波 温州看看效果

image-20221112151018475

运行后可以看到我们成功传入了参数

image-20221112151111789

main方法就到这…

代码块

基本介绍

代码块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。

基本语法

[修饰符]{
	代码
};

语法说明:

  1. 修饰符可选,要写的话,也只能写static
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
  3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写上,也可以省略

代码块的好处:

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作,顺序优先于构造器
  2. 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性

细节(重点)

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。

  2. 类什么时候被加载

    ①创建对象实例时(new)

    ②创建子类对象实例,父类也会被加载

    ③使用类的静态成员时(静态属性、静态方法)

  3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行

  4. 静态代码块和静态属性初始化的优先级一样,按他们定义的顺序调用,普通代码块和普通属性初始化的优先级也一样,但是会先调用静态,其次按他们定义的顺序调用,最后调用构造器,总结一下就是静态>普通>构造器

  5. 构造器的最前面其实隐含了super()和调用普通代码块(先父类按顺序,再子类按顺序)

  6. 调用顺序

    ①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)

    ②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)

    ③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

    ④父类的构造方法

    ⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

    ⑥子类的构造方法

  7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员

单例设计模式

什么是设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式

什么是单例模式

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  2. 单例模式有两种方式:

    ①饿汉式

    ②懒汉式

单例模式应用实例-饿汉式

实现步骤

  1. 构造器私有化==>防止直接new
  2. 类的内部创建静态对象
  3. 向外暴露一个静态的公共方法 getInstance
  4. 代码实现

这里解释一下,之所以要将getInstance方法设置为静态方法,是因为避免去new一个对象,那又因为要让getInstance这个静态方法能够使用到类内部创建的对象,所以需要将对象设置为静态的

下面看一下具体的代码

public class SingleTon01 {
    public static void main(String[] args) {
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        if (instance==instance2){			//可以看到两次获取到的都是同一对象
            System.out.println("true");
        }
    }
}
class GirlFriend{
    private String name;
    private static GirlFriend tank=new GirlFriend("Tank");		//类的内部创建静态对象
    private GirlFriend(String name) {					//构造器私有化
        this.name = name;
    }
    public static GirlFriend getInstance(){				//向外暴露一个静态的公共方法
        return tank;
    }
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

题外话

为什么叫饿汉式?

因为不管你有没有使用这个对象,它都已经帮你创建好了一个对象,所以叫饿汉式…相反,不去用它,它就不会创建,被称为懒汉式…

单例模式应用实例-懒汉式

饿汉式不使用对象却也会创建对象,造成了一种资源的浪费,而懒汉式就解决了这一个问题

实现步骤

  1. 仍然构造器私有化
  2. 定义一个static静态属性对象
  3. 提供一个publicstatic方法,可以返回一个Cat对象
  4. 懒汉式,只有当用户使用getInstance时,才返回cat对象,从而再次调用时,会返回上次创建的Cat对象

从而保证了单例

public class SingleTon02 {
    public static void main(String[] args) {
        System.out.println(Cat.n1);
        Cat instance=Cat.getInstance();
        System.out.println(instance);
        Cat instance2=Cat.getInstance();
        System.out.println(instance);
    }
}
class Cat{
    private String name;
    public static int n1=999;
    private static Cat cat;
    private Cat(String name) {
        System.out.println("构造器被调用...");
        this.name = name;
    }
    public static Cat getInstance(){
        if (cat==null){		//如果对象还未创建,则创建一个
            cat=new Cat("小可爱");
        }
        return cat;
    }
}

总结单例设计模式

饿汉式VS懒汉式

  1. 最主要的区别就是创建的时机不同,饿汉式在类加载就创建了对象实例,而懒汉式是在使用时才创建
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(后续介绍)
  3. 饿汉式存在浪费资源的可能
  4. javaSE标准类中,java.lang.Runtime就是经典的单例模式

final关键字

基本介绍

final可以修饰类、属性、方法和局部变量

使用场景

  1. 当不希望类被继承时,可以用final修饰

    final class A{}				//在不想被继承的类前加final
    class B extends A{}			//此处会报错,无法继承A类
  2. 当不希望父类的某个方法被子类覆盖/重写时,可以用final关键字修饰

    具体格式为访问修饰符 final 返回类型 方法名

    class C {
        public final void hi(){}	//这里使用final修饰方法
    }
    class D extends C{
        @Override
        public void hi() {		//此处会报错,只能继承但不能重写C类的hi方法
            System.out.println("重写C类的hi方法");
        }
    }
  3. 当不希望类的某个属性的值被修改,可以使用final修饰

    public class Final01 {
        public static void main(String[] args) {
            E e = new E();
            e.n1=888;		//报错,无法修改
        }
    }
    class E{
        public final int n1=999;	//这里用final修饰变量
    }
  4. 当不希望某个局部变量被修改,可以用final修饰

    class F{
        public void cry(){
            final double Num=0.01;	//finall修饰局部变量
            Num=0.9;	//报错,无法修改
            System.out.println("Num= "+Num);
        }
    }

注意事项

  1. final修饰的属性又叫常量,一般用 XX_XX_XX来命名

  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一

    ①定义时:如public final double TAX_RATE=0.08;

    ②在构造器中

    ③在代码块中

    public final double TAX_RATE=0.01;		//第一种
    public final double TAX_RATE2;			//第二种
    public final double TAX_RATE3;			//第三种
    public AA(){			//第二种:在构造器中赋初值
        TAX_RATE2=1.1;			
    }
    {						//第三种:在代码块中赋初值
        TAX_RATE3=2.2;
    }
  3. 如果final修饰的属性是静态的,则初始化的位置只能是

    ①定义时

    ②在静态代码块,不能再构造器中赋值

    public static final double TAX_RATE=0.01;
    public static final double TAX_RATE2;	//报错
    public static final double TAX_RATE3;	
    public BB(){
        TAX_RATE2=1.1;		//构造器不能赋值(静态)
    }
    
    static {
        TAX_RATE3=2.2;		//静态代码块可以赋值
    }
  4. final类不能继承,但是可以实例化对象

    public class FinalDetail01 {
        public static void main(String[] args) {
            CC cc = new CC();		//实例化对象不会报错
        }
    }
    final class CC{}		//final类
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

    public class FinalDetail01 {
        public static void main(String[] args) {
            new EE().cal();		//可以正常调用
        }
    }
    class DD{
        public final void cal(){	//此处含有final方法
            System.out.println("cal()方法");
        }
    }
    class EE extends DD{}	//可以继承DD
  6. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法

  7. final不能修饰构造方法(即构造器)

  8. final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

    public static void main(String[] args) {
            System.out.println(BBB.num);	//输出999和BBB被执行
    }
    class BBB{
        public static int num=999;
        static{
            System.out.println("BBB 的静态代码块被执行");
        }
    }
    public static void main(String[] args) {
        System.out.println(BBB.num);		//仅输出999
    }
    class BBB{
        public final static int num=999;
        static{
            System.out.println("BBB 的静态代码块被执行");	//此句不输出
        }
    }
  9. 包装类(Integer,Double,Float,Boolean等都是final),String也是final类

抽象类

引入抽象类

当父类的某些方法,需要声明,但是又不确定如何实现时,可以用abstract关键字来修饰方法,将其声明为抽象方法,那么这个类就是抽象类

具体写法

这里要注意,如果要将方法设为抽象方法,那么需要在类的前面也加上abstract,否则会报错,同时抽象方法没有方法体,否则会报错

具体格式为访问修饰符 abstract 返回类型 方法名(参数列表);

abstract class Animal{				//类的前面加上abstract
    private String name;

    public Animal(String name) {
        this.name = name;
    }
    public abstract void eat();		//抽象方法
}

而且,抽象类一般来说会被继承,由其子类来实现抽象方法

使用细节

  1. 抽象类不能被实例化

    public class Abstract02 {
        public static void main(String[] args) {
            new A();	//此处报错,不能实例化抽象类
        }
    }
    abstract class A{}
  2. 抽象类不一定要包含abstract方法,换句话说,抽象类可以没有abstract方法,还可以有实现的方法

    abstract class A{}		//用上面的代码来举例,此处并不会报错
    abstract class A{
    	public void hi(){
    		System.out.println("hi");
    	}
    }
  3. 一旦类包含了abstract方法,则这个类必须声明为abstract

    class B{
        public abstract void hi();		//报错,因为包含了抽象方法,所以必须是抽象类
    }
  4. abstract只能修饰类和方法,不能修饰属性和其他

    class C{
        public abstract int a;			//报错,abstract只能修饰类和方法
    }
  5. 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等

  6. 抽象方法不能有主体,即不能实现(也就是开头所说的不能有方法体)

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract

    abstract class E{
        public abstract void hi();
    }
    class F extends E{}		//这里继承了E抽象类,但没有实现所有的抽象方法,所以会报错
    abstract class E{
        public abstract void hi();
    }
    abstract class F extends E{}	//那么我们在子类前加上abstract就不会报错
    abstract class E{
        public abstract void hi();
    }
    class G extends E{
        @Override
        public void hi() {}			//所谓实现方法,就是要有方法体(空也可以)
    }
  8. 抽象方法不能用private、final、static来修饰,因为这些关键字都是和重写相违背的

抽象模板设计

首先我们来看下面一段代码(分别在三个文件中)

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.job();
        BB bb = new BB();
        bb.job();
    }
}
public class AA {
    public void job(){
        long start=System.currentTimeMillis();
        long num=0;
        for (long i=1;i<=800000;i++){
            num+=i;
        }
        long end=System.currentTimeMillis();
        System.out.println("执行时间:"+(end-start));
    }
}
public class BB {
    public void job(){
        long start=System.currentTimeMillis();
        long num=0;
        for (long i=1;i<=80000;i++){
            num*=i;
        }
        long end=System.currentTimeMillis();
        System.out.println("执行时间:"+(end-start));
    }
}

这里提一句,currentTimeMillis()可以以毫秒为单位获取系统当前的时间,返回long数据类型的数据

我们发现,AA类和BB类有相当一部分是重复的,下面进行改进,体会一下抽象模板设计

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();
        BB bb = new BB();
        bb.calculateTime();
    }
}
abstract public class Template {
    public abstract void job();
    public void calculateTime(){
        long start=System.currentTimeMillis();
        job();			//动态机制绑定到AA或BB的job执行
        long end=System.currentTimeMillis();
        System.out.println("任务执行时间:"+(end-start));
    }
}
public class AA extends Template{
    public void job(){		//重写抽象类里的job
        long num=0;
        for (long i=1;i<=800000;i++){
            num+=i;
        }
    }
}
public class BB extends Template {
    public void job(){		////重写抽象类里的job
        long num=0;
        for (long i=1;i<=80000;i++){
            num*=i;
        }
    }
}

从上面的实践可以看出来抽象类对代码的复用性和简洁性还是有很大帮助的…

接口

快速入门

看下面的代码模拟一下现实中的USB接口…

public class Interface01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Camera camera = new Camera();
        Computer computer = new Computer();
        computer.work(phone);      //把手机接入到计算机
        System.out.println("===============");
        computer.work(camera);      //把相机接入到计算机
    }
}
public interface UsbInterface {     //接口中abstract和public可以省略
    public void start();
    public void stop();
}
public class Phone implements UsbInterface{ 	//Phone类实现了UsbInterface
    @Override
    public void start() {		//实现UsbInterface接口规定/声明的方法
        System.out.println("手机开始工作...");
    }
    @Override
    public void stop() {		//实现UsbInterface接口规定/声明的方法
        System.out.println("手机停止工作...");
    }
}
public class Camera implements UsbInterface{ 	//Camera类实现了UsbInterface
    @Override
    public void start() {
        System.out.println("相机开始工作了...");
    }
    @Override
    public void stop() {
        System.out.println("相机停止工作了...");
    }
}
public class Computer {
    public void work(UsbInterface usbInterface){	//计算机工作
        usbInterface.start();
        usbInterface.stop();
    }
}

Phone类实现了UsbInterface,即Phone类需要实现UsbInterface接口规定/声明的方法

基本介绍

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:

interface 接口名{
	//属性
	//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
class 类名 implements 接口{
	//自己属性
	//自己方法
	//必须实现接口的抽象方法
}

注意:在JDK7.0前,接口里的所有方法都没有方法体,即都是抽象方法。JDK8.0后接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现

默认实现方法,需要使用default关键字修饰,如下

default public void ok(){
    System.out.println("OK");
}

注意事项

接口不能被实例化

接口中所有的方法是public方法,接口中抽象方法,可以不用abstract修饰

一个普通类实现接口,就必须将该接口的所有方法都实现

这里有一个快捷键,在下图报错的位置,使用Alt+Enter,选择Implement methods即可快速导入接口中所有要实现的方法

image-20221120190852637

抽象类实现接口,可以不用实现接口的方法

interface IA{
    void say();
    void hi();
}
abstract class Tiger implements IA{}		//不会报错,抽象类可以不实现

一个类同时可以实现多个接口

interface IB{
    void hi();
}
interface IC{
    void say();
}
class Pig implements IB,IC{		//Pig类实现了IB和IC两个接口
    public void hi() {}
    public void say() {}
}

接口中的属性,只能是final的,而且是public static final修饰符

接口中属性的访问形式:接口名.属性名

接口不能继承其他的类,但是可以继承多个别的接口

interface IB{
    void hi();
}
interface IC{
    void say();
}
class Pig implements IB,IC{		//同时实现了多个接口,用逗号分隔
    public void hi(){}
    public void say(){}
}

接口的修饰符只能是public和默认,这点和类的修饰符是一样的

接口和继承的区别

继承的价值主要在于:解决代码的复用性和可维护性

接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法

子类继承了父类,就自动的拥有父类的功能,如果子类需要扩展功能,可以通过实现接口的方式扩展,可以理解实现接口是对 单继承机制的一种补充

继承是满足is-a的关系,而接口只需满足like-a的关系

接口在一定程度上实现了代码解耦,即:接口规范性+动态绑定机制

接口的多态特性

多态参数,在前面的代码里UsbInterface usb,即可以接收手机对象,又可以接收相机对象,体现了借口多态,且接口类型的变量可以指向实现了接口的对象实例

多态数组,在Usb数组中,存放Phone和Camera对象,

public class InterfacePolyArr {
    public static void main(String[] args) {
        Usb[] usb=new Usb[2];
        usb[0]=new Phone_();
        usb[1]=new Camera_();			//可以存放不同的对象
        for (int i=0;i< usb.length;i++){
            usb[i].work();      	//动态绑定机制
            if (usb[i] instanceof Phone_){	//判断运行类型是否一致
                ((Phone_) usb[i]).call();
            }
        }
    }
}
interface Usb{
    void work();
}
class Phone_ implements Usb{
    public void call(){
        System.out.println("手机可以打电话...");
    }
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb{
    public void work() {
        System.out.println("相机工作中...");
    }
}

多态传递,用下面的例子来说,如果IG继承了IH接口,而Teacher类实现了IG接口,那么,实际上就相当于Teacher类也实现了IH接口

public class InterfacePolyPass {
    public static void main(String[] args) {
        IG ig=new Teacher();
        IH ih=new Teacher();
    }
}
interface IH{}
interface IG extends IH{}
class Teacher implements IG{}

内部类

基本介绍

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套其他类的类称为外部类,内部类最大的特点就是可以直接访问私有属性,并且可以提现类与类之间的包含关系

基本语法:

class Outer{	//外部类
    class Inner{	//内部类
        
    }
}
class Other{	//外部其他类
}

内部类的分类

定义在外部类局部位置上(比如方法内):

  1. 局部内部类(有类名)
  2. 匿名内部类(没有类名)

定义在外部类的成员位置上:

  1. 成员内部类(没用static修饰)
  2. 静态内部类(使用static修饰)

局部内部类的使用

  1. 定义在外部类的局部位置,比如方法中,并且有类名

  2. 可以直接访问内外部类的所有成员,包含私有的

    class Outer02{
        private int n1=100;
        private void m2(){}
        public void m1(){
            class Inner02{	//局部内部类
                public void f1(){
                    System.out.println("n1="+n1);	//访问了外部类的n1(私有属性)
                    m2();		//访问了外部类的m2(私有方法)
                }
            }
        }
    }
  3. 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。但是可以使用final修,因为局部变量也可以使用final

    class Outer02{
        public void m1(){
            public class Inner02{	//报错,局部内部类不能添加访问修饰符
            }
        }
    }
    class Outer02{
        public void m1(){
            final class Inner02{		//此处使用final修饰
            }
            class Inner03 extends Inner02{}		//报错,final修饰后无法继承
        }
    }
  4. 作用域:仅仅在定义它的方法或代码块中

  5. 局部内部类—访问—>外部类的成员[访问方式:直接访问]

    class Outer02{
        private int n1=100;
        private void m2(){}
        public void m1(){
            class Inner02{	//局部内部类
                public void f1(){
                    System.out.println("n1="+n1);	//访问了外部类的n1(私有属性)
                    m2();		//访问了外部类的m2(私有方法)
                }
            }
        }
    } 
  6. 外部类—方法—>局部内部类的成员[访问方式:创建对象,再访问(注意:必须在作用域内)]

    class Outer02{
        private int n1=100;
        private void m2(){}
        public void m1(){
            final class Inner02{  //局部内部类
                public void f1(){
                    System.out.println("n1="+n1);
                    m2();
                }
            }
            Inner02 inner02 = new Inner02();	//先创建对象
            inner02.f1();		//再访问内部类的成员
        }
    }
  7. 外部其他类—不能访问—>局部内部类(因为局部内部类地位是一个局部变量)

  8. 如果外部类和局部内部类的成员重名时,默认遵守就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

    class Outer02{
        private int n1=100;
        public void m1(){
            final class Inner02{  //局部内部类
                private int n1=800;
                public void f1(){
                    System.out.println("n1="+n1);	//输出n1=800(内部类n1)
                    System.out.println("n1="+Outer02.this.n1);	//输出n1=100(外部类n1)
                }
            }
        }
    }

注意:

  1. 局部内部类定义在方法中/代码块
  2. 作用域在方法体或者代码块中
  3. 本质仍然是一个类

匿名内部类

匿名内部类是定义在外部类的局部位置,比如在方法中,并且没有类名

  1. 本质是类
  2. 内部类
  3. 该类没有名字
  4. 同时还是一个对象

基本语法:

new 类或接口(参数列表){
	类体
};
  1. 匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征。也有创建对象的特征。对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法

  2. 可以直接访问外部类的所有成员,包含私有的

  3. 不能添加访问修饰符,因为它的地位就是一个局部变量

  4. 作用域仅仅在定义它的方法或代码块中

  5. 匿名内部类—访问—>外部类成员[访问方式:直接访问]

  6. 外部其他类—不能访问—>匿名内部类(因为匿名内部类是一个局部变量)

  7. 如果外部类和匿名内部类的成员重名时,匿名内部类去访问的话,默认遵循就近原则,如果想访问外部类成员,则可以使用(外部类名.this)去访问

成员内部类

  1. 可以直接访问外部类所有成员,包括私有的

  2. 可以添加任意访问修饰符,因为它的地位就是一个成员

  3. 作用域和其它外部类的其它成员一样,为整个类体

  4. 外部成员访问成员内部类

    // 第一种方式 outer08.new Inner08(),相当于把new Inner08()当做是outer08成员
    Outer08.Inner08 inner08 = outer08.new Inner08();
    inner08.say();
    // 第二种方式 在外部类中,编写一个方法,可以返回Inner08对象
    Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
    inner08Instance.say();

静态内部类

  1. 与成员内部类基本相同,只不过增添一个static的前提
  2. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

枚举和注解

枚举介绍

枚举是一组常量的集合

属于一种特殊的类,里面只包含一组有限的特定的对象

自定义类实现枚举

  1. 构造器私有化,防止直接new
  2. 不需要提供 setxxx 方法,因为枚举对象值通常为只读
  3. 对枚举对象/属性使用final+static共同修饰,实现底层优化
  4. 枚举对象名通常使用全部大写,常量的命名规范
  5. 枚举对象根据需要,也可以有多个属性

enum关键字实现枚举

  1. 使用关键字enum时,会隐式的继承Enum类、不能再extends其他类,但是可以实现接口
  2. 多个常量用逗号,隔开
  3. 要求将定义常量对象写在前面
  4. 如果使用的是无参构造器,创建常量对象,可以省略()

enum常用方法

toString():Enum 类已经重写过了Object类的toString 方法,返回的是当前对象名
name():返回当前对象名(常量名),子类中不能重写
ordinal():返回当前对象的位置号,默认从 0 开始
values():返回当前枚举类中所有的对象数组
valueOf():将字符串转换成枚举对象,要求传入的字符串必须 为已有的对象名,否则报异常
compareTo(Enum e):比较两个枚举常量,比较是位置号

image-20230103204801435

JDK内置的基本注解类型

  1. @Override

    • 表示指定重写父类的方法(在编译层面),如果不是则会报错

    • 不写@Override,但构成方法的重写也不影响

    • 如果写了@Override又没重写就会报错

  2. @Deprecated

  • @Deprecated修饰某个元素,表示该元素已经过时

  • 不推荐使用,但仍然可以使用

    • @Deprecated源码可以看出,可以用来修饰方法,类,包,字段,参数,等等

    • @Deprecated可以做版本升级过渡使用

  1. @SuppressWarnings

    • 当我们不希望看到警告的时候,可以用@SuppressWarnings来抑制警告信息

    • 在{“ “}中,可以写入你希望抑制的警告信息

    • 作用范围与@SuppressWarnings的位置有关

异常

选中代码后ctrl + alt + t

语法错误和逻辑错误不是异常

异常体系图

image-20230108204620832

image-20230108204513271

常见运行时异常

  1. NullPointerException 空指针异常

    当应用程序试图在需要对象的地方使用null时,抛出该异常

  2. ArithmeticException 数学运算异常

    当出现异常的运算条件时,抛出此异常(比如除数为0的除法)

  3. ArrayIndexOutOfBoundsException 数组下标越界异常

    用非法索引访问数组时,抛出的异常

  4. ClassCastException 类型转换异常

    当试图将对象强制转换为不是实例的子类时,抛出此异常

  5. NumberFormatException 数字格式不正确异常

    当试图将字符串转换成一种数值类型,但该字符不能转为适当格式时,抛出异常

常见编译时异常

  1. SQLException

    操作数据库时,查询表可能发生异常

  2. IOException

    操作文件时,发生的异常

  3. FileNotFoundException

    当操作一个不存在的文件时,发生异常

  4. ClassNotFoundException

    加载类,而该类不存在时,异常

  5. EOFException

    操作文件,到文件未尾,发生异常

  6. ILLegalArgumentException

    参数异常

try-catch-finally

try {
	//可疑代码
	//将异常生成对应的异常对象,传递给catch块
} catch (Exception e) {
    //异常发生时
    //系统将异常封装成Exception 对象e,传递给catch
    //得到异常对象后,程序员自行处理
} finally{
    //不管try代码块是否有异常,始终执行finally
    //通常在这里释放资源
}
  1. 可以有多个catch语句,捕获不同的异常,(进行不同的业务处理),但是要求父类异常在后,子类异常在前
  2. 可以进行try-finally配合使用,这种相当于没有捕获异常,因此程序会退出

throws

try-catch-finallythrows二选一

默认是throws

image-20230108211316663

  1. 编译异常必须处理

  2. 运行时的异常,程序中没有处理,默认就是throws

  3. 子类重写的方法,抛出的异常类型要么和父类一致,要么是父类抛出的异常类型的子类型

  4. throws过程中,如果由try-catch,就相当于有异常处理,不必throws

自定义异常

  1. 如果继承Exception,属于编译异常;
  2. 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException),即把自定义异常做成运行时异常,好处是,我们可以显式的使用默认的处理机制;
public class CustomException {
    public static void main(String[] args) {
        int age=1;
        if (!(age>=18&&age<=120)){
            throw new AgeException("年龄需要在18~120之间");
        }
        System.out.println("你的年龄范围正确");
    }
}
//自定义一个异常
class AgeException extends RuntimeException{
    public AgeException(String message) {
        super(message);
    }
}

throw、throws区别

image-20230109205530812

常用类

包装类

image-20230110180714318

image-20230110181735595

装箱和拆箱

  1. JDK5以前手动装箱和拆箱,JDK5以后自动装箱和拆箱
  2. 自动装箱底层调用的是valueOf方法,比如Integer.valueOf()
int n1=100;
//手动装箱
Integer integer=new Integer(n1);
Integer integer1=Integer.valueOf(n1);
//手动拆箱
int i=integer.intValue();
int n2=200;
//自动装箱
Integer integer2=n2;
//自动拆箱
int n3=integer2;

三元运算符是一个整体,类型转为高一级别的

Object obj1=true? new Integer(1):new Double(2.0);	//类型转为高一级别的Double
System.out.println(obj1);	//输出的结果是1.0
Object obj2;
if (true){
    obj2=new Integer(1);
}
else {
    obj2=new Double(2.0);
}
System.out.println(obj2);	////输出的结果是1

Integer类

底层缓存了-128~127的值

image-20230112103450867

如果有基本数据类型,判断的则是值是否相同

Integer a=127;
int b=127;
System.out.println(a==b);	//比值,True

String类

  1. 使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字符

  2. 可以串行化(可以在网络传输),可以比较大小

    image-20230112110835899

  3. Stringfinal 类,不能被其他的类继承

  4. String 有属性private final char value[]; 用于存放字符串内容

  5. value 是一个final类型,(地址)不可以修改:不能指向新的地址,但单个字符内容可以变化

创建String对象的两种方式

  1. 直接赋值 String s = "dzr";

    • 先从常量池查看是否有"dzr"的数据空间,如果有,直接指向;如果没有,则重新创建,然后指向

    • s最终指向的是常量池的空间地址

  2. 调用构造器 String s2 = new String("dzr");

    • 先在堆中创建空间,里面维护了value属性,指向常量池的dzr空间

    • 如果常量池没有"dzr",重新创建,如果有,直接通过value指向

    • 最终指向的是堆中的空间地址

image-20230114194941296

String a = "dzr";
String b =new String("dzr");
System.out.println(a.equals(b));	//T
System.out.println(a==b);	//F
System.out.println(a==b.intern());	//T
System.out.println(b==b.intern());	//F

public String intern():最终返回的是常量池的地址(对象)

image-20230114200549179

String a = "hello";
   String b = "abc";
   String c = a + b;
   String d = "helloabc";
   System.out.println(c == d);//F
   String e = "hello" + "abc";//e指向常量池
   System.out.println(d == e);//T

String c = a + b的底层:

先创建一个 StringBuilder sb = StringBuilder(),执行sb.append("hello");然后执行sb.append("abc");再执行 String c= sb.toString(),最后c指向堆中的对象(String) value[]指向池中 "helloabc"

总结:

常量相加,看的是池;变量相加,看的是堆

class Test1 {
    String str = new String("dzr");
    final char[] ch = {'j', 'a', 'v', 'a'};
    public void change(String str, char ch[]) {
        str = "java";
        ch[0] = 'h';
    }
    public static void main(String[] args) {
        Test1 ex = new Test1();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");
        System.out.println(ex.ch);
    }
}
//最终输出dzr and hava

image-20230114210939156

常用方法:

equals前面已经讲过了. 比较内容是否相同,区分大小写

equalsIgnoreCase忽略大小写的判断内容是否相等

indexOf获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1

lastIndexOf获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1

substring截取指定范围的子串

trim去前后空格

charAt获取某索引处的字符

toUpperCase转换成大写

toLowerCase转换成小写

concat拼接字符串

replace替换字符串中的字符

split分割字符串

compareTo比较两个字符串的大小,如果前者大,则返回正数,如果相等,返回0

以下内容于2023.3.1开始继续更新

StringBuffer和StringBuilder类

StringBuffer类

基本介绍

可变的字符序列,可以对字符串内容进行增删

很多方法与String相同,但是StringBuffer是可变长度的

String和StringBuffer区别

  • String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址(传创建新对象),效率较低;//private final char value[];其中字符串常量在常量池中

  • StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址(除非分配的内存不够了,需要重新分配),效率较高;//char[] value;其中字符串变量在堆中

String 和 StringBuffer 相互转换

public class StringAndStringBuffer {
    public static void main(String[] args) {
 
        //String——>StringBuffer
        String str = "字符串";
        //方式1 使用构造器
        //注意: 返回的才是StringBuffer对象,对str 本身没有影响
        StringBuffer stringBuffer = new StringBuffer(str);
        //方式2 使用的是append方法
        StringBuffer stringBuffer1 = new StringBuffer();
        stringBuffer1 = stringBuffer1.append(str);
 
        //StringBuffer ->String
        StringBuffer stringBuffer3 = new StringBuffer("字符串");
        //方式1 使用StringBuffer提供的 toString方法
        String s = stringBuffer3.toString();
        //方式2: 使用构造器来搞定
        String s1 = new String(stringBuffer3);
 
    }
}

StringBuffer 类常见方法

public class StringBufferMethod {
    public static void main(String[] args) {
 
        StringBuffer s = new StringBuffer("hello");
 
        //增
        s.append(",张三丰");//"hello,张三丰"
 
        //删
        s.delete(11, 14);//删除 11~14的字符 [11, 14)
 
        //改
        s.replace(9, 11, "周芷若");//替换索引9-11的字符 [9,11)
        
        //查
        int indexOf = s.indexOf("张三丰");//查找第一次出现的索引,如果找不到返回-1
 
        //插
        s.insert(9, "赵敏");//在索引为9的位置插入"赵敏"
 
        //长度
        System.out.println(s.length());
 
    }
}

StringBuilder类

基本介绍

  • 此类被设计用作StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议优先采取该类,因为在大多数实现总,它比StringBuffer要快

  • 在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据

String、StringBuffer和StringBuilder的比较

  • StringBuilder 和StringBuffer 非常相似,均代表可变的字符序列,而且方法也一样;

  • String:不可变字符序列,效率低,但是复用率高;

  • StringBuffer:可变字符序列、效率较高(增删)、线程安全(适合多线程);

  • StringBuilder:可变字符序列、效率最高、线程不安全(适合单线程);

  • 效率:StringBuilder > StringBuffer > String

String、StringBuffer和StringBuilder的选择

  • 如果字符串存在大量的修改操作,一般男使用StringBuffer和StringBuilder

  • 如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder

  • 如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer

  • 如果字符串存在很少量的修改操作,被多个对象引用,使用String,比如配置信息等

Math类

常见方法

public class MathMethod {
    public static void main(String[] args) {
        
        //1.abs 绝对值
        int abs = Math.abs(-9);
        
        //2.pow 求幂
        double pow = Math.pow(2, 4);//2的4次方
        System.out.println(pow);//16
        
        //3.ceil 向上取整,返回>=该参数的最小整数(转成double);
        double ceil = Math.ceil(3.9);
        System.out.println(ceil);//4.0
        
        //4.floor 向下取整,返回<=该参数的最大整数(转成double)
        double floor = Math.floor(4.001);
        System.out.println(floor);//4.0
        
        //5.round 四舍五入  Math.floor(该参数+0.5)
        long round = Math.round(5.51);
        System.out.println(round);//6
        
        //6.sqrt 求开方
        double sqrt = Math.sqrt(9.0);
        System.out.println(sqrt);//3.0
 
        //7.random 求随机数
        //random 返回的[0,1)之间的一个随机小数
 
        //8.max , min 返回最大值和最小值
        int min = Math.min(1, 9);
        int max = Math.max(45, 90);
 		
    }
}

Date日期类、Calendar日历类以及新的日期

第一代日期类

public class Date01 {
    public static void main(String[] args) throws ParseException {

        //1. 获取当前系统时间
        //2. 这里的Date 类是在java.util包
        //3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
        Date d1 = new Date(); //获取当前系统时间
        Date d2 = new Date(9234567); //通过指定毫秒数得到时间1970-1-1
 
 
        //1. 创建 SimpleDateFormat对象,可以指定相应的格式
        //2. 这里的格式使用的字母是规定好,不能乱写
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
        String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
        System.out.println("当前日期=" + format);//2023年03日01月11:14:41 周三
 
        //1. 可以把一个格式化的String 转成对应的 Date
        //2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
        //3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常
        String s = "1996年01月01日 10:20:30 星期一";
        Date parse = sdf.parse(s);
        System.out.println("parse=" + sdf.format(parse));
 
    }
}

第二代日期类

Calendar类

public class Calendar_ {
    public static void main(String[] args) {
 
        //Calendar是一个抽象类, 并且构造器是private
        //可以通过 getInstance() 来获取实例
        //如果我们需要按照 24小时进制来获取时间, 
        //Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
        Calendar c = Calendar.getInstance(); //获取日历类对象
        System.out.println("c=" + c);
        //2.获取日历对象的某个日历字段
        System.out.println("年:" + c.get(Calendar.YEAR));
        // 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
        System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
        System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
        System.out.println("小时:" + c.get(Calendar.HOUR));
        System.out.println("分钟:" + c.get(Calendar.MINUTE));
        System.out.println("秒:" + c.get(Calendar.SECOND));
        //Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
        System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
                " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
 
    }
}

第三代日期类

LocalDate(年月日)、LocalTime(时分秒)、LocalDateTime(年月日时分秒)

JDK8加入的

LocalDate只包含日期,可以获取日期字段;

LocalTime只包含时间,可以获取时间字段;

LocalDateTime包含日期+时间,可以获取日期和时间字段

DateTimeFormatter 格式日期类

DateTimeFormat dtf = DateTimeFormatter.ofPattern(格式)String str = dtf.format(日期对象)

更多的方法

public class LocalDate_ {
    public static void main(String[] args) {
        //第三代日期
 
        //1. 使用now() 返回表示当前日期时间的对象
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
 
        //2. 使用DateTimeFormatter 对象来进行格式化
        // 创建 DateTimeFormatter对象
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern
        ("yyyy-MM-dd HH:mm:ss");
        String format = dateTimeFormatter.format(ldt);
        System.out.println("格式化的日期=" + format);
 
        System.out.println("年=" + ldt.getYear());
        System.out.println("月=" + ldt.getMonth());
        System.out.println("月=" + ldt.getMonthValue());
        System.out.println("日=" + ldt.getDayOfMonth());
        System.out.println("时=" + ldt.getHour());
        System.out.println("分=" + ldt.getMinute());
        System.out.println("秒=" + ldt.getSecond());
 
        LocalDate now = LocalDate.now(); //可以获取年月日
        LocalTime now2 = LocalTime.now();//获取到时分秒
 
        //plus和minus方法可以对当前时间进行加或者减
        //890天后
        LocalDateTime localDateTime = ldt.plusDays(890);
        System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));
 
        //3456分钟前
        LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
        System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
 
    }
}

Instant时间戳

public class Instant_ {
    public static void main(String[] args) {
 
        //1.通过 静态方法 now() 获取表示当前时间戳的对象
        Instant now = Instant.now();
        System.out.println(now);
        //2. 通过 from 可以把 Instant转成 Date
        Date date = Date.from(now);
        //3. 通过 date的toInstant() 可以把 date 转成Instant对象
        Instant instant = date.toInstant();
 
    }
}

System类

常用方法

  • exit:退出当前程序

  • arraycopy:赋值数组元素,比较适合底层调用,一般使用Array.copyOf完成复制数组

    int[] f1 = {1,2,3};
    int[] f2 = new int[3];
    System.arraycopy(f1,0,f2,0,3);
  • currentTimeMillens:返回当前时间距离1970-1-1的毫秒数;

  • gc:运行垃圾回收机制

public class System_ {
    public static void main(String[] args) {
 
        //exit 退出当前程序
        System.exit(0);
 
        //源数组
        //从源数组的哪个索引位置开始拷贝
        //目标数组,即把源数组的数据拷贝到哪个数组
        //把源数组的数据拷贝到目标数组的哪个索引
        //从源数组拷贝多少个数据到目标数组
        System.arraycopy(src, 0, dest, 0, src.length);
        
        //currentTimeMillens:返回当前时间距离1970-1-1 的毫秒数
        System.out.println(System.currentTimeMillis());
        
        //运行垃圾回收机制
        System.gc();
    }
}

Arrays类

  • toString 返回数组的字符串形式
  • sort 排序(自然排序和定制排序)
  • binarySearch 通过二分搜索法进行查找,要求必须排好序
  • copyOf 数组元素的赋值
  • fill 数组元素的填充
  • equals 比较连个数组元素内容是否完全一致
  • asList 将一组值,转换为list

BigInteger类和BigDecimal类

BigInteger类

适合保存比较大的整型

public class BigInteger_ {
    public static void main(String[] args) {
 
        BigInteger bigInteger = new BigInteger("237888888999999");
        BigInteger bigInteger2 = new BigInteger("10099999999999");
        System.out.println(bigInteger);
 
        //对BigInteger 进行加减乘除需要使用对应的方法
        BigInteger add = bigInteger.add(bigInteger2);
        System.out.println(add);//加
        BigInteger subtract = bigInteger.subtract(bigInteger2);
        System.out.println(subtract);//减
        BigInteger multiply = bigInteger.multiply(bigInteger2);
        System.out.println(multiply);//乘
        BigInteger divide = bigInteger.divide(bigInteger2);
        System.out.println(divide);//除
 
    }
}

BigDecimal类

适合保存精度更高的浮点型(小数)

public class BigDecimal_ {
    public static void main(String[] args) {
        
        BigDecimal bigDecimal = new BigDecimal("1999.11");
        BigDecimal bigDecimal2 = new BigDecimal("3");
        System.out.println(bigDecimal);
 
        //对BigDecimal进行加减乘除需要使用对应的方法
        System.out.println(bigDecimal.add(bigDecimal2));
        System.out.println(bigDecimal.subtract(bigDecimal2));
        System.out.println(bigDecimal.multiply(bigDecimal2));
        //System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常ArithmeticException
        //在调用divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
        //如果有无限循环小数,就会保留 分子 的精度
        System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
    }
}

集合

集合的理解

  • 可以动态保存任意多个对象,使用比较方便
  • 提供了一系列方便的操作对象的方法:add,remove,set,get等
  • 使用集合添加,删除新元素的代码简洁
    public static void main(String[] args) {
 
        //Collection
        //Map
        ArrayList arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");
 
        HashMap hashMap = new HashMap();
        hashMap.put("NO1", "北京");
        hashMap.put("NO2", "上海");
    }
}

Collection

特点

  • Collection实现子类可以存放多个元素,每个元素可以是Object

  • 有些Collection的实现类,可以存放重复的元素,有些不可以

  • 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)

  • Collection接口没有直接的实现子类,是通过它的子接口Set 和 List来实现的

常用方法

public static void main(String[] args) {
        List list = new ArrayList();//接口可以指向实现该接口的类
        
        //add:添加单个元素
        list.add("lll");
        list.add(1);//有自动装箱的过程 list.add(new Integer(1));
        list.add(true);//有自动装箱的过程
        System.out.println(list);//list里的元素都是对象
 
        //remove:删除指定元素 【两种方式】
        list.remove("111");//指定删除某个元素
        list.remove(0);//按下标删除第一个元素
 
        //contains:查找元素是否存在
        System.out.println(list.contains("111"));
        //有返回true,无返回false
 
        //size:获取元素个数
        System.out.println(list.size());
 
        //isEmpty:判断是否为空
        System.out.println(list.isEmpty());
 
        //clear:清空
        list.clear();
 
        //addAll:添加多个元素
        ArrayList list1 = new ArrayList();
        list1.add("haha");
        list1.add("3213");
        list.addAll(list1);
        System.out.println(list);
 
        //containsAll:查找多个元素是否存在
        System.out.println(list.containsAll(list1));//都存在返回true
 
        //removeAll:删除多个元素
        list.removeAll(list1);
 
    }
}

Iterator迭代器遍历

  • Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
  • 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器
  • Iterator 的结构
  • Iterator仅用于遍历集合, Iterator本身不存放对象
public class CollectionIterator {
    public static void main(String[] args) {
 
        Collection col = new ArrayList();
 
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));

        //遍历col集合
        Iterator iterator = col.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.pr intln("obj=" + obj);
        }
 
    }
}

class Book {
    private String name;
    private String author;
    private double price;
    ...
}

for循环加强遍历元素

简化版的迭代器遍历

public class CollectionFor {
    public static void main(String[] args) {
        Collection col = new ArrayList(); 
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));
        for (Object o : col) {
            System.out.println("book=" + o);
        }
    }
}

List接口和常用方法

基本介绍

List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

List集合中的每个元素都有其对应的顺序索引(即一个整数型的序号记载其在容器中的文字hi,可以根据序号存取容器中的元素),即支持索引

public class List_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        list.add("dzr");
        list.add("tom");
        System.out.println("list=" + list);
        //List集合中的每个元素都有其对应的顺序索引
        System.out.println(list.get(3));//dzr
    }
}

常用方法

  • void add(int index, Object ele):在index位置插入ele元素,没有index,默认在最后插入
  • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
  • Object get(int index):获取指定index位置的元素
  • int indexOf(Object obj):返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • Object remove(int index):移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合[fromIndex,toIndex)

ArrayList类

注意事项

  • ArrayList可以加入null,并且可以有多个
  • ArrayList 是由数组来实现数据存储的
  • ArrayList 基本等同于Vector,除了ArrayList是线程不安全的(但执行效率高),在多线程情况下,不建议使用ArrayList

底层分析

  • ArrayList中维护了一个object类型的数组elementData
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次田间,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍

Vector

基本介绍

  • Vector底层是一个对象数组,protected Object[] elementData
  • Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  • 开发中,需要线程同步安全时,考虑使用Vector
public class Vector_ {
    public static void main(String[] args) {
        
        Vector vector = new Vector(8);
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector=" + vector);
    }
}

Vector和ArrayList的比较

image-20230302185228525

LinkedList底层结构

LinkedList的说明

  • LinkedList底层实现了双向链表和双端队列的特点
  • 可以添加任意元素(元素可以重复),包括null
  • 线程不安全,没有实现同步

LinkedList的底层操作机制

  • LinkedList底层维护了一个双向链表

  • LinkedList中维护了两个属性first 和 last 分别指向首节点和尾节点

  • 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表

  • 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率比较高

LinkedList 的增删改查和循环遍历案例

@SuppressWarnings({"all"})
public class LinkedListCRUD {
    public static void main(String[] args) {
 
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);
 
        //演示一个删除结点的
        linkedList.remove(); // 这里默认删除的是第一个结点
        //linkedList.remove(2);
 
        System.out.println("linkedList=" + linkedList);
 
        //修改某个结点对象
        linkedList.set(1, 999);
        System.out.println("linkedList=" + linkedList);
 
        //得到某个结点对象
        //get(1) 是得到双向链表的第二个对象
        Object o = linkedList.get(1);
        System.out.println(o);//999
 
        //因为LinkedList 是 实现了List接口, 遍历方式
        System.out.println("===LinkeList遍历迭代器====");
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("next=" + next);
 
        }
 
        System.out.println("===LinkeList遍历增强for====");
        for (Object o1 : linkedList) {
            System.out.println("o1=" + o1);
        }
        System.out.println("===LinkeList遍历普通for====");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }
 
 
        //源码阅读.
        /* 1. LinkedList linkedList = new LinkedList();
              public LinkedList() {}
           2. 这时 linkeList 的属性 first = null  last = null
           3. 执行 添加
               public boolean add(E e) {
                    linkLast(e);
                    return true;
                }
            4.将新的结点,加入到双向链表的最后
             void linkLast(E e) {
                final Node<E> l = last;
                final Node<E> newNode = new Node<>(l, e, null);
                last = newNode;
                if (l == null)
                    first = newNode;
                else
                    l.next = newNode;
                size++;
                modCount++;
            }
         */
 
        /*
          读源码 linkedList.remove(); // 这里默认删除的是第一个结点
          1. 执行 removeFirst
            public E remove() {
                return removeFirst();
            }
         2. 执行
            public E removeFirst() {
                final Node<E> f = first;
                if (f == null)
                    throw new NoSuchElementException();
                return unlinkFirst(f);
            }
          3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
            private E unlinkFirst(Node<E> f) {
                // assert f == first && f != null;
                final E element = f.item;
                final Node<E> next = f.next;
                f.item = null;
                f.next = null; // help GC
                first = next;
                if (next == null)
                    last = null;
                else
                    next.prev = null;
                size--;
                modCount++;
                return element;
            }
         */
    }
}

ArrayList 和 LinkedList 比较

ArrayList 和 LinkedList 比较

随机更新

因为看mybatis的时候用到了反射所以来补一补

反射

image-20230319162919155

优点

可以动态的创建和适用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑

缺点

使用反射基本是解释执行,对执行速度有影响

反射调用优化

使用setAccessible,禁用访问检查,true为禁用

Class类

常用方法

image-20230319162833449

获取方式

  • 一直一个类的全名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能会抛出ClassNotFoundException

    • Class cls1 = Class.forName(“java.lang.Cat”);
    • 多用于配置文件,读取类全路径,加载类
  • 若已知具体的类,通过类的class获取

    • Class cls2 = Cat.class;
    • 多用于参数传递,比如通过反射得到对应构造器对象
  • 已知某个类的实例,调用该实例的getClass()方法获取Class对象

    • Class cls3 = 对象.getClass();//运行类型
    • 通过创建好的对象,获取Class对象
  • 其他方式:ClassLoader cl = 对象.getClass().getClassLoader();

    • class cls4 = cl.loadClass(“类的全类名”);
  • 基本数据按如下方式得到Class类对象

    • Class cls5 = 基本数据类型.class;
  • 基本数据类型可以通过.TYPE得到Class类对象

    • Class cls6 = 包装类.TYPE

哪些类型有Class对象

  • 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  • interface
  • 数组
  • enum
  • annotation
  • 基本数据类型
  • void

类加载

基本说明

  • 静态加载
    • 编译时加载相关的类,如果没有则报错,依赖性太强
  • 动态加载
    • 运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性

类加载时机

  • 当创建对象时(new)
    • 静态加载
  • 当子类被加载时,父类也加载
    • 静态加载
  • 调用类中的静态成员时
    • 静态加载
  • 通过反射
    • 动态加载

image-20230319163626754

image-20230319163645917

反射获取类的结构信息

Class类

image-20230319163728743

Field类

image-20230319163752740

Method类

image-20230319163812652

Constructor类

image-20230319163841128

通过反射创建对象

  • 调用类中的public修饰的无参构造器

  • 调用类中的指定构造器

  • Class类相关方法

    • newInstance:调用类中的无参构造器,获取对应类的对象

    • getConstructor(Class…class):根据参数列表,获取对应的构造器对象

    • getDecalaredConstructor(Class…class):根据参数列表,获取对应的构造器对象

  • Constructor类相关方法

    • setAccessible:暴破
    • newInstance(Object…obj):调用构造器

通过反射访问类中的成员

访问属性

  • 根据属性名获取Field对象
    • Field f = class对象.getDeclaredField(属性名);
  • 暴力破解
    • f.setAccessible(true);
    • f 是Field
  • 访问
    • f.set(o,值)
    • 如果是静态属性,则set和get中的参数o,可以写成null

访问方法

  • 获取对象
    • Object o = class.newInstance();
  • 暴力破解
    • m.setAccessible(true);
  • 访问
    • Object returnValue = m.invoke(o,实参列表);
  • 注意
    • 如果是静态方法, 则invoke的参数o,可以写成null

Lambda表达式

格式

(匿名内部类被重写方法的形参列表)->{
    被重写方法的方法体代码
}
注:->是语法形式,无实际意义

实例

@FunctionalInterface//加上这个注解必须是函数式接口,且只有一个抽象方法
interface People{
    void run();
}
//简化之前的匿名内部类
People p = new People(){
    @Override
    public void run(){
        System.out.println("小学生跑的很慢!");
    }
};
//简化之后
People p = () -> {
        System.out.println("小学生跑的很慢!");
};

也可以将其作为参数代入方法中去使用

//在原来的基础上定义一个pk方法
public static void pk(People p){
    System.out.println("跑步情况如何:")
    p.run();
}
//不使用Lambda的情况下:
pk(p);//需要在定义匿名内部类之后,将p代入;
//使用Lambda的情况:
People p = () -> {
        System.out.println("小学生跑的很慢!");
};
pk(() -> {
        System.out.println("小学生跑的很慢!");
});
//由于等号右边即是People创建的对象p,因此可以可以直接将其代入

省略规则

  • 参数类型可以省略不写
//精简之前:
Arrays.sort(grade,new Comparator<Integer>(){
    @Override
    public int compare(Integer o1,Integer o2){
        return o2 -o1;
	}
});
//精简之后:
Arrays.sort(grade,(Integer o1,Integer o2) -> {
        return o2 -o1;
});
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略
//简单引用一个单个参数的例子,不需要了解其具体含义
btn.addActionListener((ActionEvent e) -> {
    System.out.println("我是简单的例子。");
});
//简化之后:
btn.addActionListener( e -> {
    System.out.println("我是简单的例子。");
});
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号
//参照上一条规则的例子
btn.addActionListener( e -> {
    System.out.println("我是简单的例子。");
});
//简化之后:
btn.addActionListener( e -> System.out.println("我是简单的例子。") );
  • 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略”;”不写
//参照第一条规则的代码
Arrays.sort(grade,(Integer o1,Integer o2) -> {
        return o2 -o1;
});
//简化之后:
Arrays.sort(grade,( o1, o2)-> o2 -o1);