复习

java-reflex

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

何为反射 #

赋予了我们在运行时分析类以及执行类中方法的能力;运行中获取任意一个类的所有属性方法,以及调用这些方法属性

应用场景 #

Spring/Spring Boot 、MyBatis等框架都用了大量反射机制,以下为

  • JDK动态代理

    • 接口及实现类

          package proxy;
      
          public interface Car {
      
              public void run();
          }
          //实现类
          package proxy;
      
          public class CarImpl implements Car{
      
              public void run() {
                  System.out.println("car running");
              }
      
          }
      
    • 代理类 及main方法使用 [ˌɪnvəˈkeɪʃn] 祈祷

      package proxy;
      
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        //JDK动态代理代理类 
        public class CarHandler implements InvocationHandler{
            //真实类的对象
            private Object car;
            //构造方法赋值给真实的类
            public CarHandler(Object obj){
                this.car = obj;
            }
        //代理类执行方法时,调用的是这个方法
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before");
                Object res = method.invoke(car, args);
                System.out.println("after");
                return res;
            }
        }
        //main方法使用
      package proxy;
      
      import java.lang.reflect.Proxy;
      
      public class main {
      
            public static void main(String[] args) {
                CarImpl carImpl = new CarImpl();
                CarHandler carHandler = new CarHandler(carImpl);
                Car proxy = (Car)Proxy.newProxyInstance(
                        main.class.getClassLoader(), //第一个参数,获取ClassLoader
                        carImpl.getClass().getInterfaces(), //第二个参数,获取被代理类的接口
                        carHandler);//第三个参数,一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
                proxy.run();
            }
        }
      //输出
      before
      car running
      after
      
  • Cglib动态代理(没有实现接口的Car

    ...

Java序列化详解

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

什么是序列化?什么是反序列化 #

当需要持久化Java对象,比如将Java对象保存在文件中、或者在网络中传输Java对象,这些场景都需要用到序列化

即:

  • 序列化:将数据结构/对象转换成二进制字节流
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于Java,序列化的是对象(Object),也就是实例化后的类(Class)

序列化的目的,是通过网络传输对象,或者说是将对象存储到文件系统、数据库、内存中,如图: ly-20241212141924954

实际场景 #

  • 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化
  • 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
  • 对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化

序列化协议对于TCP/IP 4层模型的哪一层 #

4层包括,网络接口层,网络层,传输层,应用层 如下图所示:
ly-20241212141925161

OSI七层协议模型中,表示层就是对应用层的用户数据,进行处理转换成二进制流;反过来的话,就是将二进制流转换成应用层的用户数据,即序列化和反序列化,
因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分

常见序列化协议对比 #

kryo 英音 [k’rɪəʊ] ,除了JDK自带的序列化,还有hessiankryoprotostuff

  • JDK自带的序列化,只需要实现java.io.Serializable接口即可

    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    @Builder
    @ToString
    public class RpcRequest implements Serializable {
        private static final long serialVersionUID = 1905122041950251207L;
        private String requestId;
        private String interfaceName;
        private String methodName;
        private Object[] parameters;
        private Class<?>[] paramTypes;
        private RpcMessageTypeEnum rpcMessageTypeEnum;
    }
    

    serialVersionUID用于版本控制,会被写入二进制序列,反序列化如果发现和当前类不一致则会抛出InvalidClassException异常。一般不使用JDK自带序列化,1 不支持跨语言调用 2 性能差,序列化之后字节数组体积过大

    ...

为什么Java中只有值传递

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

  • 形参&&实参

    • 实参(实际参数,Arguments),用于传递给函数/方法的参数,必须有确定的值

    • 形参(形式参数,Parameters),用于定义函数/方法,接收实参,不需要有确定的值

      String hello = "Hello!";
      // hello 为实参
      sayHello(hello);
      // str 为形参
      void sayHello(String str) {
          System.out.println(str);
      }
      
  • 值传递&&引用传递

    • 程序设计将实参传递给方法的方式分为两种,值传递:方法接收实参值的拷贝,会创建副本;引用传递:方法接受的是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参
  • Java中只有值传递,原因:

    • 传递基本类型参数

      public static void main(String[] args) {
          int num1 = 10;
          int num2 = 20;
          swap(num1, num2);
          System.out.println("num1 = " + num1);
          System.out.println("num2 = " + num2);
      }
      
      public static void swap(int a, int b) {
          int temp = a;
          a = b;
          b = temp;
          System.out.println("a = " + a);
          System.out.println("b = " + b);
      }
      //输出
      a = 20
      b = 10
      num1 = 10
      num2 = 20
      
    • 传递引用类型参数 1

      ...

javaGuide基础3

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

异常 #

  • unchecked exceptions (运行时异常)
    checked exceptions (非运行时异常,编译异常)

  • Java异常类层次结构图 ly-20241212141923567

    ly-20241212141923848

  • Exception和Error有什么区别

    • 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常

    • Exception : 程序本身可以处理的异常(可通过catch捕获)

      • Checked Exception ,受检查异常,必须处理(catch 或者 throws ,否则编译器通过不了) IOException,ClassNotFoundException,SQLException,FileNotFoundException

      • Unchecked Exception , 不受检查异常 , 可以不处理

(算数异常,类型转换异常,不合法的线程状态异常,下标超出异常,空指针异常,参数类型异常,数字格式异常,不支持操作异常) ArithmeticException,ClassCastException,IllegalThreadStateException,IndexOutOfBoundsException

  NullPointerException,IllegalArgumentException,NumberFormatException,SecurityException,UnsupportedOperationException 


  ```illegal 英[ɪˈliːɡl] 非法的```  
  ```Arithmetic 英[əˈrɪθmətɪk] 算术```
  • Error: 程序无法处理的错误 ,不建议通过catch 捕获,已办错误发生时JVM会选择线程终止
    OutOfMemoryError (堆,Java heap space),VirtualMachineError,StackOverFlowError,AssertionError (断言),IOError

  • Throwable类常用方法

    • String getMessage() //简要描述
    • String toString() //详细
    • String getLocalizedMessage() //本地化信息,如果子类(Throwable的子类)没有覆盖该方法,则与gtMessage() 结果一样
    • void printStackTrace() //打印Throwable对象封装的异常信息
  • try-catch-finally如何使用 try后面必须要有catch或者finally;无论是否捕获异常,finally都会执行;当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

    ...

javaGuide基础2

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

面向对象基础 #

  • 区别

    • 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
    • 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
    • 面向对象编程 易维护易复用易扩展
  • 对象实例与对象引用的不同
    new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。

    一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。

  • 对象的相等一般比较的是内存中存放的内容是否相等;引用相等一般比较的是他们指向的内存地址是否相等

  • 如果一个类没有声明构造方法,该程序能正确执行吗? 如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了

    • 构造方法特点:名字与类名相同;没有返回值但不能用void声明构造函数;生成类的对象时自动执行
    • 构造方法不能重写(override),但能重载 (overload)
  • 面向对象三大特征

    • 封装
      把一个对象的状态信息(属性)隐藏在对象内部不允许直接访问,但提供可以被外界访问的方法来操作属性

      public class Student {
          private int id;//id属性私有化
          private String name;//name属性私有化
      
          //获取id的方法
          public int getId() {
              return id;
          }
      
          //设置id的方法
          public void setId(int id) {
              this.id = id;
          }
      
          //获取name的方法
          public String getName() {
              return name;
          }
      
          //设置name的方法
          public void setName(String name) {
              this.name = name;
          }
      }
      
    • 继承
      不通类型的对象,相互之间有一定数量的共同点,同时每个对象定义了额外的特性使得他们与众不同。继承是使用已存在的类的定义作为基础建立新类的技术

      ...

javaGuide基础1

转载自https://github.com/Snailclimb/JavaGuide (添加小部分笔记)感谢作者!

基础概念及常识 #

  • Java语言特点

    • 面向对象(封装、继承、多态)
    • 平台无关性(Java虚拟机)
    • 等等
  • JVM并非只有一种,只要满足JVM规范,可以开发自己专属JVM

  • JDK与JRE

    • JDK,JavaDevelopmentKit,包含JRE,还有编译器(javac)和工具(如javadoc、jdb)。能够创建和编译程序
    • JRE,Java运行时环境,包括Java虚拟机、Java类库,及Java命令等。但是不能创建新程序
  • 字节码,采用字节码的好处

    • Java中,JVM可以理解的代码称为字节码(.class文件),不面向任何处理器,只面向虚拟机
    • Java程序从源代码到运行的过程 ly-20241212141922068
      • java代码必须先编译为字节码,之后呢,.class–>机器码,这里JVM类加载器先加载字节码文件,然后通过解释器进行解释执行(也就是字节码需要由Java解释器来解释执行)
      • Java解释器是JVM的一部分
  • 编译与解释并存

    • 编译型:通过编译器将源代码一次性翻译成可被该平台执行的机器码,执行快、开发效率低
    • 解释型:通过解释器一句一句的将代码解释成机器代码后执行,执行慢,开发效率高
    • 如图 ly-20241212141922280
  • 为什么说 Java 语言“编译与解释并存”?

    这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须由 Java 解释器来解释执行

  • Java与C++区别

    • 没学过C++,Java不提供指针直接访问内存
    • Java为单继承;但是Java支持继承多接口
    • Java有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存
  • 注释分为 单行注释、多行注释、文档注释 ly-20241212141922440

  • 标识符与关键字 标识符即名字,关键字则是被赋予特殊含义的标识符

  • 自增自减运算符 当 b = ++a 时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++ 时,先赋值(赋值给 b),再自增(自己增加 1)

  • continue/break/return

    • continue :指跳出当前的这一次循环,继续下一次循环。
    • break :指跳出整个循环体,继续执行循环下面的语句。
    • return 用于跳出所在方法,结束该方法的运行。
  • 变量

    • 成员变量和局部变量
      • 成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰
      • 从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
      • 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡(即方法栈弹出后消亡)。
      • final必须显示赋初始值,其他都自动以类型默认值赋值
    • 静态变量:被类所有实例共享
  • 字符型常量与字符串常量区别

    ...

作用域及事务

四种作用域 #

  • singleton:默认值,当IOC容器一创建就会创建bean实例,而且是单例的,每次得到的是同一个
  • prototype:原型的,IOC容器创建时不再创建bean实例。每次调用getBean方法时再实例化该bean(每次都会进行实例化)
  • request:每次请求会实例化一个bean
  • session:在一次会话中共享一个bean

事务 #

事务是什么 #

逻辑上的一组操作,要么都执行,要么都不执行

事务的特性 #

ACID

  • Atomicity /ˌætəˈmɪsəti/原子性 , 要么全部成功,要么全部失败
  • Consistency /kənˈsɪstənsi/ 一致性 , 数据库的完整性
  • Isolation /ˌaɪsəˈleɪʃn/ 隔离性 , 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致 , 这里涉及到事务隔离级别
  • Durability /ˌdjʊərəˈbɪləti/ 持久性 , 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

Spring支持两种方式的事务管理 #

  • 编程式事务管理 /ˈeksɪkjuːt/ execute
    使用transactionTemplate

    @Autowired
    private TransactionTemplate transactionTemplate;
    public void testTransaction() {
    
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    
                    try {
    
                        // ....  业务代码
                    } catch (Exception e){
                        //回滚
                        transactionStatus.setRollbackOnly();
                    }
    
                }
            });
    }
    

    使用transactionManager

    ...

成员变量与局部变量

代码 #

   static int s;
    int i;
    int j;

    {
        int i = 1;
        i++;
        j++;
        s++;
    }

    public void test(int j) {
        j++;
        i++;
        s++;
    }

    public static void main(String[] args) {
        Exam5 obj1 = new Exam5();
        Exam5 obj2 = new Exam5();
        obj1.test(10);
        obj1.test(20);
        obj2.test(30);

        System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);
        System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);
    }

运行结果 #

2,1,5
1,1,5

分析 #

就近原则 #

代码中有很多修改变量的语句,下面是用就近原则+作用域分析的图 ly-20241212141838186

...

递归与迭代

编程题 #

有n步台阶,一次只能上1步或2步,共有多少种走法

分析 #

  • 分析
    n = 1,1步 f(1) = 1
    n = 2, 两个1步,2步 f(2) = 2
    n = 3, 分两种情况: 最后1步是2级台阶/最后1步是1级台阶, 即 f(3) = f(1)+f(2) n = 4, 分两种情况: 最后1步是2级台阶/最后1步是1级台阶, 即f(4) = f(2)+f(3)

    也就是说,不管有几(n)个台阶,总要分成两种情况:最后1步是2级台阶/最后1步是1级台阶,即 f(n)= f(n-2) + f(n-1)

递归 #

      public static int f(int n){
            if(n==1 || n==2){
                return n;
            }
            return f(n-2)+f(n-1);
      }
  
        public static void main(String[] args) {
            System.out.println(f(1)); //1
            System.out.println(f(2)); //2
            System.out.println(f(3)); //3
            System.out.println(f(4)); //5
            System.out.println(f(5)); //8
        }
  • debug调试 方法栈 f(4)—->分解成f(2)+f(3) f(2)—返回- f(3)—f(2)返回—f(1)返回 【f(3)分解成f(2)和f(1)】 方法栈的个数: ly-20241212141840077

使用循环 #

    public static int loop(int n){

        if (n < 1) {
            throw new IllegalArgumentException(n + "不能小于1");
        }
        if (n == 1 || n == 2) {
            return n;
        }
        int one=2;//最后只走1步,会有2种走法
        int two=1;//最后走2步,会有1种走法
        int sum=0;
        for(int i=3;i<=n;i++){
            //最后跨两级台阶+最后跨一级台阶的走法
            sum=two+one;
            two=one;
            one=sum;
        }
        return sum;
    }

ly-20241212141840386

...

方法的参数传递机制

代码 #

public class Exam4 {
    public static void main(String[] args) {
        int i = 1;
        String str = "hello";
        Integer num = 2;
        int[] arr = {1, 2, 3, 4, 5};
        MyData my = new MyData();

        change(i, str, num, arr, my);
        System.out.println("i = " + i);
        System.out.println("str = " + str);
        System.out.println("num = " + num);
        System.out.println("arr = " + Arrays.toString(arr));
        System.out.println("my.a = " + my.a); 
    }

    public static void change(int j, String s, Integer n, int[] a,
                              MyData m) {
        j+=1;
        s+="world";
        n+=1;
        a[0]+=1;
        m.a+=1;
    }

}

结果

...