• 面试总结之Java基础


    1、反射

    1.1、概述

    • 反射:加载类(通过反射将类的字节码文件加载到内存中),并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)
    • 反射需要掌握的内容:
      1)记载类,获取类的字节码:Class对象
      2)获取类的构造器:Constructor对象
      3)获取类的成员变量:Field对象
      4)获取类的成员方法:Method对象

    1.2、获取类的字节码

    • 获取Class对象可以通过如下三种方式:
      1)Class c1 = 类名.class
      2)调用Class类提供的forName方法:public static Class forName(String className)
      3)Object提供的getClass方法: public final native Class getClass();
    • 示例代码:
            Class<Person> p1 = Person.class;
            System.out.println(p1.getName());       // 类的全路径
            System.out.println(p1.getSimpleName()); // 类简称: Person
    
            Class<?> p2 = Class.forName("com.example.interviewStudy.reflect.Person");  // forName方法中的参数需要是类的全路径
            System.out.println(p1 == p2);   // true, p1和p2指向同一个对象
    
            Person person = new Person();
            Class p3 = person.getClass();
            System.out.println(p1 == p3);  // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3、获取类的构造器

    在这里插入图片描述

    • 获取类构造器的作用:
      在这里插入图片描述
    • 示例代码:
            Class p1 = Person.class;
            Constructor<?>[] con1 = p1.getConstructors();
            for (Constructor<?> c : con1) {
                System.out.println(c.getName() +"<--->获取构造器参数总个数:" +c.getParameterCount());
            }
    
            System.out.println("获取所有构造器(包括私有的) ==>");
            Constructor<?>[] con2 = p1.getDeclaredConstructors();
            for (Constructor<?> c : con2) {
                System.out.println(c.getName() +"<--->参数总个数:" +c.getParameterCount());
            }
    
            System.out.println("根据传入参数的类型 调用指定的构造器来创建对象 ==》");
            Constructor  publicPerson = p1.getConstructor(Long.class, String.class);
            Person person1 = (Person) publicPerson.newInstance(10189L, "张三");
            System.out.println(person1);
    
            System.out.println("根据传入参数的类型 获取指定的 私有构造器 ==》");
            Constructor<Person> privatePerson = p1.getDeclaredConstructor(String.class);
            privatePerson.setAccessible(true);   // 暴力反射,禁止检查访问权限,如果不禁止权限,则会报IllegalAccessException
            
            // 调用无参构造器(将Person类中无参构造改为private修饰)
            Person person2 = privatePerson.newInstance();
            System.out.println(person2);
            // 调用传入pname属性的构造器
            Person person3 = privatePerson.newInstance("张小敬");
            System.out.println(person3);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注意:如果不调用构造器对象的setAccessible,不禁止检查访问权限,就会报如下的
    在这里插入图片描述

    1.4、获取类的成员变量

    在这里插入图片描述
    为测试获取Person类的私有属性,需要将Person的pname属性改为private修饰

    • 示例代码:
            Class p1 = Person.class;
            //  p1.getConstructors(): 只能获取public修饰的属性
            //  p1.getDeclaredFields(): 能获取所有的属性
            Field[] fields = p1.getDeclaredFields();
            // 遍历Person类中的成员变量
            for (Field f : fields) {
                System.out.println(f.getName() + "<--->" + f.getType());
            }
            
            // p1.getField(): 只能指定获取public修饰的成员变量
            Field pname = p1.getDeclaredField("pname");
            Person person = new Person();
            // 给private修饰的成员变量赋值,需要禁止检查访问权限
            pname.setAccessible(true);
            pname.set(person,"朱重八");
            System.out.println("person = " + person);
    
            // 获取成员变量
            // 从person 对象中获取pname
            String name = (String) pname.get(person);
            System.out.println(name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.5、获取类的成员方法

    在这里插入图片描述

    • 示例代码:
            Class p1 = Person.class;
            Method[] methods = p1.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method.getName() + "<-->" + method.getParameterCount() + "<-->访问修饰符:" + method.getModifiers());
            }
    
            Person person = new Person();
            Method method1 = p1.getDeclaredMethod("printInfo", Long.class, String.class, Integer.class);
            // 暴力反射
            method1.setAccessible(true);
    
            // 执行包含参数且有返回值的的成员方法,res1就是原本调用方法的返回值
            Object res1 = method1.invoke(person, Long.valueOf(100897), "盛明兰", 26);
            System.out.println(res1);
    
            // 执行包含无参且没有返回值的成员方法,其原本方法的返回值就是null
            Method method2 = p1.getDeclaredMethod("message");
            method2.setAccessible(true);
            Object res2 = method2.invoke(person);
            System.out.println(res2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.6、反射的作用和应用场景

    1)反射的作用

    • 可以得到类全部的属性、构造器、成员变量、成员方法后进行其他操作
    • 破坏封装性
    • 适合实现Java框架,基本上主流的框架都会基于反射设计出一些通用的功能

    2)反射的应用场景

    • 需求:

    实现一个简易版的持久化框架,对于任意一个对象,该框架都可以将对象的字段名和对应值,保存到txt格式的文本中

    • 实现步骤:
      1)定义一个方法,可以接收任意对象
      2)每接收到一个对象,都通过反射获取到该对象的Class对象,根据Class对象来获取全部的成员变量
      3)遍历所有的成员变量并获取到该变量在对象中的具体值
      4)将变量和对应的值通过IO流的方式输出到文本中
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.PrintStream;
    import java.lang.reflect.Field;
    
    public class ObjectFrame {
        public static void saveEntity(Object entity) throws IllegalAccessException, FileNotFoundException {
            // append追加文本的形式将数据输出data.txt文本中
            PrintStream printStream = new PrintStream(new FileOutputStream("D:\\data1.txt",true));
            Class clazz = entity.getClass();
    
            String simpleName = clazz.getSimpleName();
            printStream.println("=========" + simpleName + "==========");
            // 获取entity对象所有的成员变量
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String dataKey = field.getName();
                String dataValue = field.get(entity) + "";
                printStream.println(dataKey + " = " + dataValue);
            }
            printStream.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 测试:
        public static void main(String[] args) throws FileNotFoundException, IllegalAccessException {
            Person p1 = new Person(1009876L,"张小敬",28);
            Person p2 = new Person(2091823L,"雷东宝",32);
            Person p3 = new Person(2091823L,"宋运辉",26);
    
            ObjectFrame.saveEntity(p1);
            ObjectFrame.saveEntity(p2);
            ObjectFrame.saveEntity(p3);
    
    
            Teacher t1 = new Teacher(101L,"李知恩","语文");
            Teacher t2 = new Teacher(104L,"高启强","数学");
    
            ObjectFrame.saveEntity(t1);
            ObjectFrame.saveEntity(t2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    此外,反射在实际项目中还可以实现日志输出管理、公共字段/属性的自动填充(createUser、createTime、updateUser、updateTime)、事务、权限控制等,这需要结合AOP来实现

    1.7、反射的优缺点

    • 反射的优点:
      • 在运行时可以获取任意类的属性、构造方法、成员方法,以及动态调用这些方法和修改属性
      • 提高代码的复用率,如:在动态代理的场景中,使用动态生成的代理类来提升代码的复用性; 在Spring框架中,用反射来实例化Bean对象,用反射实现对象的属性拷贝的BeanUtils.copyProperties()方法;使用反射来自定义注解;
        注意:使用BeanUtils.copyProperties()来完成对象属性的拷贝需要注意泛型擦除的问题
    • 反射的缺点:
      • 反射会涉及动态类型的解析,JVM无法对这些代码进行优化,导致性能比非反射调用低
      • 反射可以绕过一些限制访问的属性和方法(如使用setAccessible方法来禁止检查访问权限),会破坏代码的封装性

    2、注解

    2.1、概述

    在这里插入图片描述

    2.2、自定义注解

    在这里插入图片描述

    2.3、注解的解析

    在这里插入图片描述

    2.4、应用场景

    注解被用错位置在编译期间就会报错

    • 元注解:指修饰注解的注解
      在这里插入图片描述
      在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    3、单例模式

    3.1、什么是单例模式

    • 单例模式属于创建型模式,提供了一种创建对象的最佳方式,是指仅在内存中只创建一次对象的设计模式,在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象
    • 单例模式的优点:提升使用对象的效率,又可以让类自行把控实例的实现细节,对外部业务代码不产生任何影响
    • 单例模式的应用场景:HttpClient、Spring中的对象、打印日志的logger、数据库连接池对象

    3.2、单例模式原则

    • 构造器私有(防止类被通过常规方法实例化)
    • 以静态方法或枚举返回实例(保证实例的唯一性)
    • 确保实例只有一个,尤其是在多线程环境下(保证在创建实例时的线程安全)
    • 确保反序列化时不会重新构建对象(在有序列化、反序列化的场景下防止单例被莫名破坏,造成未考虑到的后果)

    在这里插入图片描述

    3.3、Java实现单例模式有哪些方式

    • 概括起来,实现单例需要关注如下几点:
      • 构造函数需要是private访问权限,以此避免外部通过new创建实例
      • 考虑是否支持延迟加载
      • 考虑对象创建时的线程安全问题
      • 考虑获取实例的getInstance()方法性能是否够高(是否加锁)
    1)懒汉式单例
    • 懒汉式单例:需要实例对象时,调用getInstance方法才去创建对象(适用于对象比较大的情况,不希望类被加载时就实例化从而拖慢程序的启动时长)
    /***
     * 懒汉模式
     */
    public class LazySingleton {
    
        private static LazySingleton instance;
    
        private LazySingleton(){ }
    
        // 如果不加锁,则不能保证线程安全
        // 锁的粒度较粗,同JDK1.0中的HashTable的设计,故HashTable不建议使用
        public static synchronized LazySingleton getInstance(){
            if (instance == null){
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2)饿汉式单例
    • 饿汉式单例:可保证线程安全,不管需不需要单例对象instance ,都在类加载时进行初始化,会浪费内存资源,基于ClassLoader机制避免了多线程的同步问题
    public class HungrySingleton {
        
        private static HungrySingleton instance = new HungrySingleton();
        
        private HungrySingleton(){ }
        
        public static HungrySingleton getInstance(){
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    3)双重锁式单例
    // DCL: Double Check Lock,双重锁校验
    public class DCLSingleton {
    
         // 基于volatile来保证可见性、有序性,禁止指令重排
        private static volatile DCLSingleton instance;
    
        private DCLSingleton() {
        }
    
        public static DCLSingleton getInstance() {
            if (instance == null) {  // 第一次检查
                // 加锁保证线程安全
                synchronized (DCLSingleton.class) {
                    if (instance == null) {  // 再次检查防止同时进入synchronized 
                        instance = new DCLSingleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 为什么双重锁式单例中instance要有volatile修饰?
      在JVM执行过程中,instance实例的创建主要分为三步:
      1)在堆中为对象分配内存空间
      2)初始化对象(为属性赋值)
      3)将实例对象指向堆内存中的内存空间
      但由于JVM指令重排的优化,可能会出现
      同时,volatile是轻量级的同步机制,可及时刷新内存,但volatile不能保证原子性,故需要配合synchronized来保证其可靠性
    4)静态内部类式单例
    • 基于延迟加载的方式,静态内部类实现单例模式:
    /**
     *  静态内部类实现单例,线程安全
     * StaticInnerSingleton类被装载了,instance不一定被实例化,因为SingletonHolder类没有被主动使用
     * 当执行getInstance时,才会显示地装载SingletonHolder类,实例化instance
     */
    public class StaticInnerSingleton {
    
        private StaticInnerSingleton() {
        }
    
        private static class SingletonHolder {
            private static final StaticInnerSingleton instance = new StaticInnerSingleton();
        }
    
        public static final StaticInnerSingleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    5)枚举式单例
    public enum EnumSingleton {
    
        INSTANCE;
    
        // 执行业务代码
        public void doSomeThing(){
            System.out.println("执行业务逻辑....");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    6)CAS式单例
    import java.util.concurrent.atomic.AtomicReference;
    
    public class Singleton {
    
        priavte static final AtomicReference<Singleton> casInstance = new AtomicReference<>();
    
        public static final Singleton getInstance() {
            for (; ; ) {
                Singleton singleton = casInstance.get();
                if (singleton != null) return singleton;
                // 如果casInstance为null,则创建单例对象并返回
                casInstance.compareAndSet(null,new Singleton());
                return casInstance.get();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.4、关于设计模式

    • 按照设计模式的应用目的进行分类,设计模式可以分为创建型模式、结构型模式、行为型模式
    • 创建型模式:是对象在创建过程中各种问题和解决方案的总结,包括工厂模式、单例模式、原型模式、构建者模式
    • 结构型模式:是针对软件设计结构的总结,关注于类、对象、组合方式的实践经验,常见的结构型模式有桥接模式、适配器模式、门面模式、装饰者模式、代理模式、享元模式等
    • 行为型模式:是类或对象之间交互、职责划分等角度总结的设计模式,常见的行为型模式有策略模式、观察者模式、迭代器模式、适配器模式、模板方法模式、访问者模式等

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    什么是区块链粉尘攻击?
    刷题日记1
    2022HBCPC 优美的字符串
    ant design pro v6如何引入第三方js?如腾讯地图等!
    Leetcode 850. 矩形面积 II
    Verilog“七宗罪”
    为k8s节点预留资源,防止pod占用资源过多导致雪崩
    系统架构师笔记——计算机网络
    【学习总结】什么是弹性负载均衡? LB和ELB的区别
    基于蚁群结合遗传算法的路径规划问题附Matlab代码
  • 原文地址:https://blog.csdn.net/FlyingFish868/article/details/133783604