Java基础【上】韩顺平(反射、类加载、final接口、抽象类、内部类)

news2024/11/16 10:25:53

涵盖知识点:反射、类加载、单例模式、final、抽象类、接口、内部类(局部内部类、匿名内部类、成员内部类、静态内部类)

 P711 反射机制原理

创建如下目录结构,在模块下创建src文件夹,文件夹要设置为Sources文件夹,在src下创建re.properties,写入如下代码:

classfullpath=com.hspedu.Cat
method=hi

在src/com/hspedu/reflection/question下创建ReflectionQuestion类,写入如下代码:

public class ReflectionQuestion {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("testtest\\src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        System.out.println("classfullpath="+classfullpath);
        System.out.println("method="+method);
    }
}

在src/com/hspedu下创建Cat类,写入如下代码:

public class Cat {
    private String name = "招财猫";
    public void hi(){
        System.out.println("hi"+name);
    }
}

P712 反射快速入门

可以看到调用newInstance方法之后返回的便是classfullpath类全限定名对应的类的实例对象:

在ReflectionQuestion中写入如下代码:

public class ReflectionQuestion {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("testtest\\src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        System.out.println("classfullpath="+classfullpath);
        System.out.println("method="+methodName);
        //加载类,返回一个Class类型的对象
        Class cls = Class.forName(classfullpath);
        //通过cls得到你加载的类com.hspedu.Cat的对象实例
        Object o = cls.newInstance(); //创建的是Cat类的实例对象
        //通过cls得到加载的类com.hspedu.Cat的methodName"hi"的方法对象
        //即:在反射中,可以把方法视为对象(万物皆对象)
        Method method = cls.getMethod(methodName);
        //通过method调用方法:即通过方法对象来实现调用方法
        method.invoke(o);//传统方法是对象.方法(),反射机制是方法.invoke(对象)
    }
}

 在Cat类中新增cry方法:

public class Cat {
    private String name = "招财猫";
    public void hi(){
        System.out.println("hi"+name);
    }
    public void cry(){
        System.out.println(name+"喵喵叫");
    }
}

改re.properties配置文件代码如下:

classfullpath=com.hspedu.Cat
method=cry

 可以发现通过外部的配置文件,能在不修改源码的情况下, 通过修改配置文件的配置信息,来控制程序输出,符合设计模式的开闭原则。

P713 反射原理图

反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。

加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象可以得到类的结构。这个Class对象就像一面镜子,透过镜子能够看到类的结构,所以形象的称之为:反射。

源代码会被编译(javac)成字节码文件(Cat.class),字节码文件里面包含:属性、构造器、成员变量、泛型、异常、成员方法。

在Class类加载的阶段,类加载器会把字节码文件加载到内存的堆中,生成一个Class类对象,对象中包含成员变量、构造器、成员方法。

在运行阶段生成的Cat对象是存放在堆中,该对象知道他所属的Class对象,在得到Class对象之后能够创建对象,调用对象方法,也能操作属性等。

P714 反射相关类

反射机制可以完成:

1.在运行时判断任意一个对象所属的类

2.在运行时构造任意一个类的对象

3.在运行时得到任意一个类所具有的成员变量和方法。

4.在运行时调用任意一个对象的成员变量和方法

5.生成动态代理

获得类的成员变量用getField,但getField不能得到私有的属性。

//java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
//getField不能得到私有的属性
Field nameField = cls.getField("age");
System.out.println(nameField.get(o));

获得类的构造器可以用getConstructor方法。 

//java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
Constructor constructor = cls.getConstructor();
System.out.println(constructor);
Constructor constructor1 = cls.getConstructor(String.class);
System.out.println(constructor1);

此时Cat类的代码如下:

public class Cat {
    private String name = "招财猫";
    public int age=12;
    public String s;
    public Cat(){}
    public Cat(String s){
        this.s = s;
    }
    public void hi(){
        System.out.println("hi"+name);
    }
    public void cry(){
        System.out.println(name+"喵喵叫");
    }
}

P715 反射调用优化

反射的优点和缺点:

优点:可以动态的创建和使用对象,使用灵活,没有反射机制,框架技术将失去底层支撑。

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

在src/com/hspedu/reflection下面创建Reflection01,写入如下代码:

public class Reflection01 {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        m1();
        m2();
    }
    //传统方法来调用hi
    public static void m1(){
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for(int i=0;i<2000000000;i++){
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方法调用hi 耗时="+(end-start));
    }
    //反射方法来调用hi
    public static void m2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.hspedu.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for(int i=0;i<2000000000;i++){
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("传统方法调用hi 耗时="+(end-start));
    }
}

反射调用优化:关闭访问检查。

1.Method和Field、Constructor对象都有setAccessible()方法。

2.setAccessible作用是启动和禁用访问安全检查的开关。

3.参数为true时表示反射的对象在使用时取消访问检查,提高反射效率。参数值为false表示反射的对象执行访问检查。

//反射调用优化+关闭访问检查来调用hi
public static void m3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
    Class cls = Class.forName("com.hspedu.Cat");
    Object o = cls.newInstance();
    Method hi = cls.getMethod("hi");
    hi.setAccessible(true);
    long start = System.currentTimeMillis();
    for(int i=0;i<2000000000;i++){
        hi.invoke(o);
    }
    long end = System.currentTimeMillis();
    System.out.println("反射方法调用hi 耗时="+(end-start));
}

 

P716 Class类分析

1.Class也是类,因此也继承Object类。

2.Class类对象不是new出来的,而是系统创建的。

3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次。

4.每个类的实例都会记得自己是由哪个Class实例所生成。

5.通过Class对象可以完整地得到一个类的完整结构,通过一系列API。

6.Class对象是存放在堆的。

7.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括:方法代码、变量名、方法名、访问权限等等。)

P717 Class常用方法

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String classAllPath="com.hspedu.Car";
        //<?>表示不确定的Java类型
        Class<?> cls = Class.forName(classAllPath);
        //1.显示cls对象,是哪个类的Class对象,com.hspedu.Car
        System.out.println(cls);
        //2.输出cls运行类型,java.lang.Class
        System.out.println(cls.getClass());
        //3.得到包名
        System.out.println(cls.getPackage().getName());
        //4.得到全类名
        System.out.println(cls.getName());
        //5.通过cls创建对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car);
        //6.通过反射获取属性
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));
        //7.通过反射给属性赋值
        brand.set(car,"奔驰");
        System.out.println(brand.get(car));
        //8.遍历得到所以的属性
        Field[] fields = cls.getFields();
        for(Field f:fields){
            System.out.println(f.getName());
        }
    }
}

P718 获取Class对象六种方式

1.如果已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException。

实例:Class cls1 = Class.forName("java.lang.Cat")

应用场景:多用于配置文件,读取类全路径,加载类。

//1.Class.forName
String classAllPath="com.hspedu.Car";
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);

2.如果已知具体的类,通过类的clss获取,该方法最为安全可靠,程序性能最高。

实例:Class cls2 = Cat.class

应用场景:多用于参数传递,比如通过反射得到对应构造器对象。

//2.类名.class,适用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);

3.如果已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:

Class clazz = 对象.getClass();

//3.对象.getClass(),应用场景:有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);

4.其它方式(比如类加载器方式)

ClassLoader cl = 对象.getClass().getClassLoader();

Class clazz4 = cl.loadClass("类的全类名");

//4.通过类加载器【4种】来获取到类的Class对象
//(1)先得到类加载器car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);

5.基本数据按如下方式得到Class类对象

Class cls = 基本数据类型.class

//5.基本数据按如下方式得到Class对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);

6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象

Class cls = 包装类.TYPE

//6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class<Integer> type = Integer.TYPE;
System.out.println(type);

P719 哪些类型有Class对象

P720 动态和静态加载

静态加载:编译时加载相关的类(编译时马上加载),如果没有则报错,依赖项太想。

动态加载:运行时加载需要的类(运行时才会加载),如果运行时不用该类,则不报错,降低了依赖项。

P721 类加载流程图

一、加载流程:

1.加载

2.连接(分为3小步):

验证:安全校验,文件格式,元数据,字节码,符号引用是否正确。

准备:针对静态变量进行默认初始化。

解析:虚拟机把常量池中的符号引用替换为直接引用。

3.初始化:执行类中定义的初始代码。

二、类加载后内存布局:

在方法区中保存有类的字节码二进制数据。

在堆区中有类的Class对象。

加载:是由类加载器把类的class文件读入内存,在堆中创建一个Class对象。

连接:把类的二进制数据合并到JRE中。

初始化:JVM负责对类进行初始化,主要是对静态成员进行初始化。

P722 类加载五个阶段(1)

加载阶段:

将来源于不同的数据源(class文件、jar包、网络)的字节码转化为二进制字节流加载到内存中(把类的字节码二进制数据加载到方法区,同时在堆中生成java.lang.Class对象)。

连接阶段-验证:

确保Class文件的字节流文件中包含的信息符合当前虚拟机要求。

包括:文件格式验证(魔数是否以cafebabe开头)、元数据验证、字节码验证和符号引用验证。

可以使用-Xverify:none来关闭类验证措施。

连接阶段-准备:

JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、null、false等)。这些变量所实验的内存都在方法区中进行分配。

连接阶段-解析:

虚拟机将常量池中的符号引用替换为直接引用的过程。

属性==成员变量==字段

非静态变量==实例属性,不与类相关。

举例1:public static int n2 =20; 在初始化阶段n2的值才会变成20,在连接的准备阶段n2会被赋初值0,同时被分配内存。

举例2:public static final int n3 = 30; 在连接的准备阶段n3就会被赋初值30,因为加上final是常量,不是静态变量,一旦被赋值便不可改变。

P723 类加载五个阶段(2)

初始化阶段:

1.真正开始执行类中定义的Java程序代码,执行初始化方法。

2.初始化方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,进行合并。

3.虚拟机保证一个类的初始化方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的初始化方法,其它线程都要阻塞等待。

P724 获取类结构信息(1)

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

    }
    @Test
    //第一组方法API
    public void api_01() throws ClassNotFoundException, NoSuchMethodException {
        //1.得到Class对象
        Class<?> personCls = Class.forName("com.hspedu.reflection.classfile.Person");
        //2.getName:获取全类名
        System.out.println(personCls.getName());
        //3.getSimpleName:获取简单类名
        System.out.println(personCls.getSimpleName());
        //4.getField:获取所有public修饰的属性,包含本类以及父类的
        Field[] fields = personCls.getFields();
        for(Field field:fields){
            System.out.println("本类以及父类的属性="+field.getName());
        }
        //5.getDeclaredFields:获取本类中所有属性
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredFiled : declaredFields){
            System.out.println("本类中所有属性="+declaredFiled.getName());
        }
        //6.getMethods:获取所有public修饰的方法,包含本类以及父类的
        Method[] methods = personCls.getMethods();
        for(Method method:methods){
            System.out.println("本类及父类的方法的方法="+method.getName());
        }
        //7.getDeclaredMethods:获取本类中所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for(Method declaredMethod : declaredMethods){
            System.out.println("本类中所有方法="+declaredMethod.getName());
        }
        //8.getConstructors:获取本类所有public修饰的构造器
        Constructor<?>[] constructors = personCls.getConstructors();
        for(Constructor<?> constructor:constructors){
            System.out.println("本类以及父类的构造器="+constructor.getName());
        }
        //9.getDeclaredConstructors:获取本类中所有构造器
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有构造器="+declaredConstructor);
        }
        //10.getPackage:以Package形式返回包信息
        System.out.println(personCls.getPackage());
        //11,getSuperClass:以Class形式返回父类信息
        Class<?> superclass = personCls.getSuperclass();
        System.out.println("父类的class对象="+superclass);
        //12.getInterfaces:以Class[]形式返回接口信息
        Class<?>[] interfaces = personCls.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("接口信息="+anInterface);
        }
        //13。getAnnotations:以Annotation[]形式返回注解信息
        Annotation[] annotations = personCls.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解信息="+annotation);
        }
    }
}

class A {
    public String hobby;
    public void hi(){}
    public A(){}
}
interface IA{ }
interface IB{ }
@Deprecated
class Person extends  A implements IA, IB {
    public String name;
    protected int age;
    String job;
    private double sal;
    //构造器
    public Person(){}
    public Person(String s){}
    public void m1(){}
    protected void m2(){}
    void m3(){}
    private void m4(){}
}

P725 获取类结构信息(2)

public class Class02 {
    @Test
    //第一组方法API
    public void api_02() throws ClassNotFoundException, NoSuchMethodException {
        //得到Class对象
        Class<?> personCls = Class.forName("com.hspedu.reflection.classfile.Person");
        //getDeclaredFields:获取本类中所有属性
        Field[] declaredFields = personCls.getDeclaredFields();
        for (Field declaredFiled : declaredFields) {
            System.out.println("本类中所有属性=" + declaredFiled.getName()
                    + "  该属性的修饰符值=" + declaredFiled.getModifiers()
                    + "  该属性的类型=" + declaredFiled.getType());
        }
        //getDeclaredMethods:获取本类中所有方法
        Method[] declaredMethods = personCls.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("本类中所有方法=" + declaredMethod.getName()
                    + "  该方法的访问修饰符值=" + declaredMethod.getModifiers()
                    + "  该方法返回类型" + declaredMethod.getReturnType());
            //输出当前这个方法的形参数组情况
            Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该方法的形参类型=" + parameterType);
            }
        }
        //getDeclaredConstructors:获取本类中所有构造器
        Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("本类中所有构造器=" + declaredConstructor.getName());
            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该构造器的形参类型="+parameterType);
            }
        }
    }
}


class A {
    public String hobby;
    public void hi(){}
    public A(){}
}
interface IA{ }
interface IB{ }
@Deprecated
class Person extends  A implements IA, IB {
    public String name;
    protected static int age;
    String job;
    private double sal;
    //构造器
    public Person(){}
    public Person(String s){}
    public void m1(String name,int age,double sal){}
    protected void m2(){}
    void m3(){}
    private void m4(){}
}

P726 反射爆破创建实例

加入constructor是私有的构造器对象,调用下面这段代码:

constructor.setAccessible(true)

可以使得反射访问private构造器,这就是爆破,理解为暴力破解,把门拆了然后进入。

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //1.先获取到User类的Class对象
            Class<?> userClass = Class.forName("com.hspedu.reflection.classfile.User");
        //2.通过public的无参构造器创建实例
            Object o = userClass.newInstance();
            System.out.println(o);
        //3.通过public的有参构造器创建实例
            //constructor对象就是User类中带一个参数的构造器
            //先得到对应构造器
            Constructor<?> constructor = userClass.getConstructor(String.class);
            //创建实例并传入参数
            Object hsp = constructor.newInstance("hsp");
            System.out.println(hsp);
        //4.通过非public的有参构造器创建实例
            //先得到私有的构造器对象
            Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
            //创建实例
            constructor1.setAccessible(true); //爆破【暴力破解】,使用反射可以访问private构造器
            Object user2 = constructor1.newInstance(100, "张三丰");
            System.out.println(user2);
    }
}
class User {
    private int age;
    private String name="韩顺平教育";
    public User() {}
    public User(String name){
        this.name = name;
    }
    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public String toString() {
        return "User age=" + age + ", name=" + name;
    }
}

P727 反射爆破创建属性

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.得到Student类对应的Class对象
        Class<?> stuClass = Class.forName("com.hspedu.reflection.classfile.Student");
        //2.创建对象
        Object o = stuClass.newInstance(); //o的运行类型是Student
        System.out.println(o.getClass());
        //3.使用反射得到age属性对象
        Field age = stuClass.getField("age");
        age.set(o,88); //通过反射来操作属性
        System.out.println(o);
        System.out.println(age.get(o));
        //4.使用反射操作name属性
        Field name = stuClass.getDeclaredField("name");
        name.setAccessible(true);
        //name.set(o,"hsp");
        name.set(null,"hsp"); //因为name是static属性,因此o也可以写成null
        System.out.println(o);
        System.out.println(name.get(o)); //获取属性值
        System.out.println(name.get(null)); //获取属性值,要求name是static
    }
}
class Student {
    public int age;
    private static String name;
    public Student() {}
    public String toString() {
        return "Student age=" + age + ", name=" + name + "}";
    }
}

P728 反射爆破创建方法

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.得到Boss类对应的Class对象
        Class<?> bossCls = Class.forName("com.hspedu.reflection.classfile.Boss");
        //2.创建对象
        Object o = bossCls.newInstance();
        //3.调用public的hi方法
        Method hi = bossCls.getMethod("hi",String.class);
        //4.因为say方法是private,所以需要爆破
        hi.invoke(o,"韩顺平教育");
        Method say = bossCls.getDeclaredMethod("say", int.class,String.class, char.class);
        say.setAccessible(true);
        //5.调用并输出
        System.out.println(say.invoke(o, 100, "张三", '男'));
        //6.在反射中如果方法有返回值,统一返回Object,但运行类型和方法定义的返回类型一致
        Object reVal = say.invoke(null, 300, "王五", '男');
        System.out.println("reVal的运行类型="+reVal.getClass());
    }
}
class Boss { //类
    public int age;
    private static String name;
    public Boss() { //构造函数
    }
    private static String say(int n, String s, char c) { //静态方法
        return n + " " + s + " " + c;
    }
    public void hi(String s) {
        System.out.println("hi " + s);
    }
}

P729 反射课后练习

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.得到privateTest类对应的Class对象
        Class<PrivateTest> privateTestClass = PrivateTest.class;
        //2.创建对象实例
        PrivateTest privateTestObj = privateTestClass.newInstance();
        //3。得到name
        Field name = privateTestClass.getDeclaredField("name");
        //4.爆破name
        name.setAccessible(true);
        name.set(privateTestObj,"天龙八部");
        //5.得到getName方法对象
        Method getName = privateTestClass.getMethod("getName");
        //6.因为getName()是public所以直接调用
        Object invoke = getName.invoke(privateTestObj);
        System.out.println("name属性值="+invoke);
    }
}
class PrivateTest { //类
    private String name="hellokitty";
    public String getName(){
        return name;
    }
}
public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //1.Class类的forName方法得到File类的class对象
        Class<?> fileCls = Class.forName("java.io.File");
        //2.得到所有的构造器
        Constructor<?>[] declaredConstructors = fileCls.getDeclaredConstructors();
        //遍历输出
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println("File构造器="+declaredConstructor);
        }
        //3.单独得到public java.io.File(java.lang.String)
        Constructor<?> declaredConstructor = fileCls.getDeclaredConstructor(String.class);
        String fileAllPath = "C:\\data\\mynew.txt";
        //file的运行类型就是File
        Object file = declaredConstructor.newInstance(fileAllPath);
        //4.得到createNewFile的方法对象
        Method createNewFile = fileCls.getMethod("createNewFile");
        createNewFile.invoke(file);
        System.out.println(file.getClass());
        System.out.println("创建文件成功");
    }
}
class PrivateTest { //类
    private String name="hellokitty";
    public String getName(){
        return name;
    }
}

P730 反射梳理

P392 单例模式饿汉式

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

饿汉式:简单理解为不论有没有使用类或方法都加载,很着急。

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        //GirlFriend xb = new GirlFriend("小白");
    }
}
class GirlFriend {
    private String name;
    private static GirlFriend xhh = new GirlFriend("小红红");
    private GirlFriend(String name){
        this.name=name;
    }
    public static GirlFriend getInstance(){
        return xhh;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

P393 单例模式懒汉式

懒汉式:只有使用时才会创建实例。

public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        System.out.println(GirlFriend.n1);
        System.out.println(GirlFriend.getInstance());
        System.out.println(GirlFriend.getInstance());
        //GirlFriend xb = new GirlFriend("小白");
    }
}
class GirlFriend {
    private String name;
    public static int n1 = 999;
    private static GirlFriend girlFriend;
    private GirlFriend(String name){
        this.name=name;
        System.out.println("构造器被调用...");
    }
    public static GirlFriend getInstance(){
        if(girlFriend==null){
            girlFriend = new GirlFriend("小可爱");
        }
        return girlFriend;
    }
    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}

P394 final基本使用

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

1.当不希望类被继承(不希望类的方法被重写),可以用final修饰

2.当不希望父类的某个方法被子类覆盖/重写时可以用final修饰

3.当不希望类的某个属性的值被修改,可以用final修饰

4.当不希望某个局部变量被修改,可以使用final修饰

P395 final使用细节1

1.final修饰的属性在定义时,必须赋初值,并且不能修改。赋值可以在如下位置:a.定义时。b.在构造器中。c.在代码块中。

2.如果final修饰的属性是静态的,则初始化位置只能在:a.定义时。b.静态代码块内。(注意:不能在构造器内赋值)

3.final类不能被继承,但可以有实例化对象。

4.如果类不是final类,但是含有final方法,虽然该方法不能被重写,但可以被继承。

P396 final使用细节2

P397 final课堂练习

比较简单不赘述。

P398 抽象类引出

当父类的某些方法,需要声明,但又不确定如何实现时,可以将其声明为抽象方法,那这个类就是抽象类。

抽象类主要解决的是:父类方法的不确定性问题,将方法设计为抽象(abstract)方法,所谓抽象方法就是没有实现的方法,所谓没有实现就是指没有方法体,当一个类中存在抽象方法时,需要将改类声明为abstract。

P399 抽象类细节1

1.抽象类不能被实例化(不能创建对象)。

2.抽象类不一定要包含abstract方法(抽象类可以没有abstract方法)。

3.一旦类包含了abstract方法,则这个类必须声明为abstract。

4.abstract只能修饰类和方法,不能修饰属性和其它的。

P400 抽象类细节2

1.抽象类可以有任意成员(比如非抽象方法、构造器、静态属性等。抽象类本质还是类)。

2.抽象方法不能有主体,即不能实现。

3.如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它也是abstract类。

4.抽象方法不能使用private、final和static来修饰(因为这些关键字会阻止重写,抽象类本质需要子类继承去重写方法)。

P401 抽象类课堂练习

比较简单不赘述。

P402 抽象模板模式

在Template中定义job这个抽象方法,然后在AA和BB类中进行了实现,所以不同的类(指AA和BB)调用calculateTime类之后,job会被动态绑定到不同的实现,从而实现模板模式。

创建Template类如下:

abstract public class Template { //抽象类-模板设计模式
    public abstract void job(); //抽象方法
    public void calculateTime(){ //实现方法调用job方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job();
        //得到结束的时间
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间"+(end-start));
    }
}

创建AA类如下:

public class AA extends Template{
    public void job(){ //这里也去,重写了Template的job方法
        long num = 0;
        for (int i = 0; i <= 30000000 ; i++) {
            num += i;
        }
    }
}

创建BB类如下:

public class BB extends Template{
    public void job(){ //这里也去,重写了Template的job方法
        long num = 0;
        for (int i = 0; i <= 80000000 ; i++) {
            num += i;
        }
    }
}

创建TestTemplate类如下:

public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();
        BB bb = new BB();
        bb.calculateTime();
    }
}

P403 接口快速入门

P404 接口基本介绍

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

interface 接口名{
        属性
        方法
}

implements是实现的意思,类去实现接口:

class 类名 implements 接口{
    自己属性
    自己方法
    必须实现接口的抽象方法
}

在Jdk7前,接口里的所有方法都没有方法体,都是抽象方法。

在Jdk8后接口里可以有静态方法、默认方法,可以中可以有方法的具体实现。 但一定要用default来修饰方法:

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

在接口中,抽象方法可以省略abstract关键字。

P405 接口应用场景

提供一种规范,接口内的方法名是固定的,但可以有不同的实现,方便以后统一调用和管理。

P406 接口使用细节1

1.接口不能被实例化。

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

3.一个普通类实现接口,就必须要将接口中的所有方法实现。

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

P407 接口使用细节2

1.一个类可以同时实现多个接口,一个类只能继承一个类(单继承)

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

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

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

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

P408 接口课堂练习

P409 接口VS继承

一个类只能继承一个父类(单继承),一个类可以同时实现多个接口

继承的价值:解决代码复用性和可维护性。

接口的价值:设计好规范,让其它类去实现这些方法

接口比继承更加灵活:继承是满足is-a的关系(比如小猴子是猴子),而接口只需要满足like-a(比如猴子可以像鱼一样游泳)。

接口在一定程度上可以实现代码解耦(接口规范性+动态绑定)

P410 接口多态特性

接口的多态体现:

接口类型的变量test可以指向实现了IF接口的对象实例

public interface UsbInterface {
    public static void main(String[] args) {
        IF test = new Monster();
        test = new Car();
    }
}
interface IF{}
class Monster implements IF{}
class Car implements IF{}

继承的多态体现(向上转型):

父类类型的变量a可以指向继承AAA的子类对象实例。 

public interface UsbInterface {
    public static void main(String[] args) {
        AAA a = new BBB();
        a = new CCC();
    }
}
class AAA{}
class BBB extends AAA{}
class CCC extends AAA{}

 if(usbs[i] instanceof Phone_)判断的是运行类型。

public class UsbInterface {
    public static void main(String[] args) {
        Usb[] usbs = new Usb[2];
        usbs[0] = new Phone_();
        usbs[1] = new Camera_();
        for (int i = 0; i < usbs.length; i++) {
            usbs[i].work();
            if(usbs[i] instanceof Phone_){
                ((Phone_)usbs[i]).call();
            }
        }
    }

}

interface Usb{
    void work();
}
class Phone_ implements Usb{
    public void call(){
        System.out.println("手机可以打电话...");
    }
    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb{

    @Override
    public void work() {
        System.out.println("相机工作中...");
    }
}

P411 接口多态传递

IG继承了IH接口,Teacher类实现了IG接口

相当于Teacher类也实现了IH接口

于是可以向下转型

interface Usb{
    IG ig = new Teacher();
    IH ih = new Teacher();
}
interface IH{
}
interface IG extends IH{ }
class Teacher implements IG{
}

P412 接口课堂练习

P413 四种内部类

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其它类的类被称为外部类(outer class)。

类的五大成员:属性、方法、构造器、代码块、内部类。

内部类可以直接访问私有属性。

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

1.局部内部类(有类名)

2.匿名内部类(没有类名,重点)

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

1.成员内部类(没用static修饰)

2.静态内部类(使用static修饰)

P416 局部内部类1

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

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

2.局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量,但可以使用final修饰。

3.局部内部类作用域仅仅在定义它的方法或代码块内。

4.外部类想访问局部内部类成员需要先创建对象再访问。

P415 局部内部类2

1.外部其它类不能访问局部内部类。

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

public class UsbInterface {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.m1();
        //验证:System.out.println(outer);
    }
}
class Outer{
    private int n1=100;
    public void m1(){
        class Inner{
            private int n1 = 800;
            public void f1() {
                //访问重名变量遵循就近原则,默认访问内部类距离更近的变量
                System.out.println(n1);
                //访问外部类中重名的局部变量:外部类.this.变量。
                //Outer02.this本质就是外部类对象,哪个对象调用m1,Outer02.this就是那个对象
                //System.out.println(Outer.this.n1);
               //验证: System.out.println(Outer.this);
            }
        }
        //外部类想调用内部类方法,通过创建对象。
        Inner inner = new Inner();
        inner.f1();
    }
}

P416 匿名内部类本质

匿名内部类:1.本质是类。2.是内部类。3.该类没有名字。4.是一个对象。

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

基本语法:

new 类或接口(参数列表){
    类体
};
基于接口的匿名内部类
1.需求:想使用IA接口,并创建对象
2.传统方式:写一个类,实现该接口,并创建对象
3.现在需求:Tiger只用一次,后面不再使用
4.可以使用匿名内部类来简化开发
5.tiger的编译类型是IA
6.tiger的运行类型是内部类
7.jdk在创建匿名内部类Outer$1之后马上(new)创建了Outer04$1实例,并且把地址返回给tigger
8.匿名内部类使用一次,就不能再使用。
9.tiger是new创建出来的实例可以反复调用。
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}
class Outer{
    private int n1 = 10;
    public void method(){
        IA tiger = new IA() { //匿名内部类
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println(tiger.getClass());
        tiger.cry();
    }
}

interface IA{
    public void cry();
}

P417 匿名内部类使用

P418 匿名内部类细节

匿名内部类从语法上看,既有创建对象的特征,也有创建对象的特征。

匿名内部类既是一个类的定义,其本身也是一个对象。

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}
class Outer{
    private int n1 = 10;
    public void method(){
        //下面2种写法效果是一样的
        //写法1:
        IA ia = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        ia.cry();
        //写法2:
        new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        }.cry();
    }
}

P419 匿名内部类实践

把匿名内部类当作实参直接传递,简洁高效。

public class AnonymousInnerClass {
    public static void main(String[] args) {
        //匿名内部类写法
        f1(new IL(){
            @Override
            public void show() {
                System.out.println("这是一副名画~~");
            }
        });
        //传统写法
        f1(new Picture());
    }
    public static void f1(IL il){
        il.show();
    }
}

interface IL{
    void show();
}

class Picture implements IL{
    @Override
    public void show() {
        System.out.println("这是一副名画...");
    }
}

 

public class AnonymousInnerClass {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        //1.传递的是实现了Bell接口的匿名内部类
        cellPhone.alarmClock(new Bell(){
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
    }
}
interface Bell{
    void ring();
}
class CellPhone{
    public void alarmClock(Bell bell){
        System.out.println(bell.getClass());
        bell.ring();
    }
}

P420 成员内部类1

成员内部类不同于匿名内部类,不是写在方法里面的,而是与局部变量呈现平级关系,末尾无分号。

成员内部类是定义在外部类的成员,并且没有static修饰

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

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

public class AnonymousInnerClass {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        cellPhone.t1();
    }
}
class CellPhone{
    private int n1=10;
    public String name = "张三";
    class Inner08{
        public void say(){
            //可以直接访问外部类的所有成员,包含私有的
            System.out.println("n1= "+n1+" name= "+name);
        }
    }
    public void t1(){
        Inner08 inner08 = new Inner08();
        inner08.say();
    }
}

P421 成员内部类2

1.成员内部类的作用域为整个外部类范围。

2.成员内部类可以直接访问外部类的属性。

3.外部类想访问成员内部类的属性需要通过创建对象再访问。

4.外部的其他类想访问成员内部类只能通过如下2种特殊方式。

5.外部类和内部类重名时同样遵循就近原则,指定调用外部类重名属性语法在下面代码中。

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
        //外部其它类使用成员内部类的2种方式
        //第1种方式:
        //outer08.new Inner08();相当于把new Inner08()当做是outer08成员
        Outer08.Inner08 inner08 = new Outer08().new Inner08();
        //第2种方式:在外部类中编写一个方法,可以返回Inner08对象。
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();
    }
}
class Outer08{
    private int n1=10;
    public String name = "张三";
    class Inner08{
        private int n1 = 66;
        public void say(){
            //如果成员内部类成员和外部类成员重名,会遵守就近原则
            //可以通过  外部类名.this.属性  来访问外部类的成员
            System.out.println("n1= "+n1+" name= "+name+" 外部类的n1="+Outer08.this.n1);
        }
    }
    public Inner08 getInner08Instance(){
        return new Inner08();
    }
    public void t1(){
        Inner08 inner08 = new Inner08();
        inner08.say();
    }
}

P422 静态内部类1

静态内部类是定义在外部类的成员位置,并且有static修饰。

1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。

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

3.作用域:同其他的成员,为类的整体。

P423 静态内部类2

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        Outer10.Inner10 inner101 = outer10.getInner10();
        inner101.say();
    }
}
class Outer10{
    private static int n1 = 101;
    private static String name ="张三";
    static class Inner10{
        private int n1 = 998;
        public void say(){
            System.out.println(Outer10.n1);
        }
    }
    public void m1(){
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
    public Inner10 getInner10(){
        return new Inner10();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1541232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git使用:实现文件在不同设备之间进行同步

一、注册Gitee&#xff0c;创建远程仓库 注册网址&#xff1a;登录 - Gitee.com 打开Gitee&#xff0c;注册完进行登录&#xff0c;点击右上角【】创建一个仓库 新建仓库&#xff1a; 点击创建&#xff0c;仓库创建完毕。 二、下载Git安装包&#xff0c;并创建本地仓库 下载网…

正则表达式具体用法大全~持续更新

# 正则表达式&#xff1a; ## 单字符匹配&#xff1a; python # 匹配某个字符串&#xff1a; # text "abc" # ret re.match(b,text) # print(ret.group()) # 点&#xff08;.&#xff09;&#xff1a;匹配任意的字符(除了\n)&#xff1a; # text "\nabc&quo…

day02_mysql-DDLDMLDQL_课后练习 - 参考答案

文章目录 day02_mysql_课后练习第1题第2题 day02_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库test02_library 2、创建表格books 字段名字段说明数据类型b_id书编号int(11)b_name书名varchar&#xff08;50&#xff09;authors作者varchar(100)price价格floatpub…

【C语言】——指针四:字符指针与函数指针变量

【C语言】——指针四&#xff1a;字符指针与函数指针变量 一、字符指针二、函数指针变量2.1、 函数指针变量的创建2.2、两段有趣的代码 三、typedef关键字3.1、typedef的使用3.2、typedef与define比较 四、函数指针数组 一、字符指针 在前面的学习中&#xff0c;我们知道有一种…

FaceBook广告账号验证教程

1.登录facebook账号,点击左边的ads manager。 2.点击Create ad创建广告。 3.选择广告投放意向。 4.填写广告信息。 5.创建广告后选择付款方式&#xff0c;这里我是使用信用卡付款。这里我是使用Fomepay的虚拟卡进行绑定的。 6.填写信用卡的持卡人姓名 卡号 有效期 安全码 7.填写…

【Jenkins】群晖 配置 ssh over push 插件

群晖 配置 ssh over push 插件 前提 部署好 Jenkins 且 安装 好 ssh over push 插件 开启 群晖 ssh 服务 及 SFTP 服务 配置 Jenkins Jenkins ——系统管理——publish over ssh 测试下&#xff1a; 遇到的问题&#xff1a; jenkins.plugins.publish_over.BapPublishe…

数据容器-序列-集合-Python

师从黑马程序员 序列 序列的常用操作-切片 切片&#xff1a;从一个序列中&#xff0c;取出一个子序列 语法&#xff1a;序列[起始下标:结束下标&#xff0c;步长] 注&#xff1a;此操作不会影响序列本身&#xff0c;而是会得到一个新的序列 my_list[0.1,2,3,4,5,6] result1…

LeetCode-热题100:79. 单词搜索

题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…

解决长尾问题,BEV-CLIP:自动驾驶中复杂场景的多模态BEV检索方法

解决长尾问题&#xff0c;BEV-CLIP&#xff1a;自动驾驶中复杂场景的多模态BEV检索方法 理想汽车的工作&#xff0c;原文&#xff0c;BEV-CLIP: Multi-modal BEV Retrieval Methodology for Complex Scene in Autonomous Driving 链接&#xff1a;https://arxiv.org/pdf/2401.…

C++函数返回机制,返回类型

return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。 return语句有两种形式 return;return expression; 无返回值函数 没有返回值的return语句只能用在返回类型是void的函数中。 返回void的函数不要求必须有return语句&#xff0c;因为这类函数的最后一句…

手撕算法-接雨水

描述 分析 i位置能积累的雨水量&#xff0c;等于其左右两边最大高度的最小值。为了能获取i位置左右两边的最大高度。使用动态规划。两个dp数组&#xff1a; leftMaxrightMax 其中 leftMax[i] 代表i位置左边的最大高度rightMax[i] 代表i位置右边的最大高度 初始状态&#x…

新手装修:卫生间渗水原因及解决方法。福州中宅装饰,福州装修

引言 瓷砖渗水问题常常发生在卫生间区域&#xff0c;需要及时处理以免造成地面滑倒和墙面霉菌等问题&#xff0c;为了解决这一问题&#xff0c;我们应该怎么做呢&#xff1f; 首先要检查水管是否漏水&#xff0c;可以进行打压测试来确认是否存在漏水情况。如果发现水管破损造成…

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

什么是 PDAF?它是如何工作的?相位检测自动对焦解释

常见问题解答 什么是相位对焦 PDAF 代表相位检测自动对焦。这是一种自动对焦方法,可以检测光线进入相机时的行进和交汇位置。在智能手机中,这是在传感器级别完成的。为了使物体聚焦,光线应该在同一点相遇。如果不这样做,系统将确定如何调整镜头以达到焦点。 PDAF 好用吗…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

UE5 C++ 3D血条 响应人物受伤 案例

一.3Dwidget 1.创建C Userwidget的 MyHealthWidget&#xff0c;声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…

基于Springboot+Vue的在线考试系统!免费领取源码

今天给大家分享一套基于SpringbootVue的在线考试系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) 一、系统运行图 1、登陆页面 2、后台管理 3、全套环境资源 二、源码免费领取方式 关注本号&#xff0c;回复 考试 关注本号&#xff0c;回复…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

数据结构:堆和二叉树遍历

堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆&#xff1a;左右节点都小于根节点 小堆&#xff1a;左右节点都大于根节点 堆的应用&#xff1a;堆排序&#xff0c;topk问题 堆排序 堆排序的思路&#xff1a; 1.升序排序&#xff0c;建小堆。堆顶就是这个堆最小…

设计模式-访问者(Visitor)模式详解和应用

文章目录 前言访问者模式介绍结构包含的角色应用场景代码示例访问者模式的扩展访问者模式优缺点总结 前言 最近在做一个根据数学表达式生成java执行代码的功能&#xff0c;其中用到了访问者模式。使我对访问者模式有了更深入的理解。故写下此篇文章分享出来&#xff0c;不足之…