复习-JavaGuide-基础

语法糖

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

简介 #

语法糖(Syntactic Sugar)也称糖衣语法,指的是在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用,简而言之,让程序更加简洁,有更高的可读性

Java中有哪些语法糖 #

Java虚拟机并不支持这些语法糖,这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖

  • javac命令可以将后缀为.java的源文件编译为后缀名为.class可以运行于Java虚拟机的字节码。其中,com.sun.tools.javac.main.JavaCompiler的源码中,compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的
  • Java中的语法糖,包括 泛型变长参数条件编译自动拆装箱内部类

switch支持String与枚举 #

switch本身原本只支持基本类型,如int、char
ly-20241212141927344

int是比较数值,而char则是比较其ascii码,所以其实对于编译器来说,都是int类型(整型),比如byteshortchar(ackii 码是整型)以及intly-20241212141927646

ly-20241212141927801 而对于enum类型,
ly-20241212141927960

对于switch中使用String,则:

public class switchDemoString {
    public static void main(String[] args) {
        String str = "world";
        switch (str) {
        case "hello":
            System.out.println("hello");
            break;
        case "world":
            System.out.println("world");
            break;
        default:
            break;
        }
    }
}
//反编译之后
public class switchDemoString
{
    public switchDemoString()
    {
    }
    public static void main(String args[])
    {
        String str = "world";
        String s;
        switch((s = str).hashCode())
        {
        default:
            break;
        case 99162322:
            if(s.equals("hello"))
                System.out.println("hello");
            break;
        case 113318802:
            if(s.equals("world"))
                System.out.println("world");
            break;
        }
    }
}

即switch判断是通过**equals()hashCode()**方法来实现的

...

java_spi

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

简介 #

为了实现在模块装配的时候不用再程序里面动态指明,这就需要一种服务发现机制。JavaSPI就是提供了这样的一个机制:为某个接口寻找服务实现的机制。有点类似IoC的思想,将装配的控制权交到了程序之外

SPI介绍 #

SPI,ServiceProviderInterface 使用SPI:Spring框架、数据库加载驱动、日志接口、以及Dubbo的扩展实现

ly-20241212141926732

感觉下面这个图不太对,被调用方应该 一般模块之间都是通过接口进行通讯,

实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。

当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。[可以理解成业务方,或者说使用方。它使用了这个接口,而且制定了接口规范,但是具体实现,由被调用方实现]

我的理解:被调用方(提供接口的人),调用方(使用接口的人),但是其实这里只把调用方–>使用接口的人 这个关系是对的。

也就是说,正常情况下由被调用方自己提供接口和实现,即API。而现在,由调用方(这里的调用方其实可以理解成上面的被调用方),提供了接口还使用了接口,而由被调用方进行接口实现

实战演示 #

SLF4J只是一个日志门面(接口),但是SLF4J的具体实现可以有多种,如:Logback/Log4j/Log4j2等等

ly-20241212141927020

简易版本 #

  • ServiceProviderInterface

  • 目录结构

    │  service-provider-interface.iml
    │
    ├─.idea
    │  │  .gitignore
    │  │  misc.xml
    │  │  modules.xml
    │  └─ workspace.xml
    │
    └─src
        └─edu
            └─jiangxuan
                └─up
                    └─spi
                            Logger.java
                            LoggerService.java
                            Main.class
    
    • Logger接口,即SPI 服务提供者接口,后面的服务提供者要针对这个接口进行实现

      package edu.jiangxuan.up.spi;
      
      public interface Logger {
          void info(String msg);
          void debug(String msg);
      }
      
    • LoggerService类,主要是为服务使用者(调用方)提供特定功能,这个类是实现JavaSPI机制的关键所在

      ...

unsafe类

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

sun.misc.Unsafe

提供执行低级别不安全操作的方法,如直接访问系统内存资源自主管理内存资源等,效率快,但由于有了操作内存空间的能力,会增加指针问题风险。且这些功能的实现依赖于本地方法,Java代码中只是声明方法头,具体实现规则交给本地代码 ly-20241212141925562

为什么要使用本地方法 #

  • 需要用到Java中不具备的依赖于操作系统的特性,跨平台的同时要实现对底层控制
  • 对于其他语言已经完成的现成功能,可以使用Java调用
  • 时间敏感/性能要求非常高,有必要使用更为底层的语言

对于同一本地方法,不同的操作系统可能通过不同的方式来实现的

Unsafe创建 #

sun.misc.Unsafe部分源码

public final class Unsafe {
  // 单例对象
  private static final Unsafe theUnsafe;
  ......
  private Unsafe() {
  }
    
  //Sensitive : 敏感的 英[ˈsensətɪv]
  @CallerSensitive
  public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
    if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
      throw new SecurityException("Unsafe");
    } else {
      return theUnsafe;
    }
  }
}

会先判断当前类是否由Bootstrap classloader加载。即只有启动类加载器加载的类才能够调用Unsafe类中的方法

如何使用Unsafe这个类

  1. 利用反射获得Unsafe类中已经实例化完成的单例对象theUnsafe

    private static Unsafe reflectGetUnsafe() {
        try {
          Field field = Unsafe.class.getDeclaredField("theUnsafe");
          field.setAccessible(true);
          return (Unsafe) field.get(null);
        } catch (Exception e) {
          log.error(e.getMessage(), e);
          return null;
        }
    }
    
  2. 通过Java命令行命令-Xbootclasspath/a调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载

    ...

big_decimal

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

精度的丢失 #

float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false

为什么会有精度丢失的风险

这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示

使用BigDecimal来定义浮点数的值,然后再进行浮点数的运算操作即可

BigDecimal常见方法 #

  • 我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象

  • 加减乘除

    BigDecimal a = new BigDecimal("1.0");
    BigDecimal b = new BigDecimal("0.9");
    System.out.println(a.add(b));// 1.9
    System.out.println(a.subtract(b));// 0.1
    System.out.println(a.multiply(b));// 0.90
    System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常
    System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11
    

    使用divide方法的时候,尽量使用3个参数版本(roundingMode.oldMode)

  • 保留规则

    public enum RoundingMode {
       // 2.5 -> 3 , 1.6 -> 2
       // -1.6 -> -2 , -2.5 -> -3
    			 UP(BigDecimal.ROUND_UP), //数轴上靠近哪个取哪个
       // 2.5 -> 2 , 1.6 -> 1
       // -1.6 -> -1 , -2.5 -> -2
    			 DOWN(BigDecimal.ROUND_DOWN), //数轴上离哪个远取哪个
    			 // 2.5 -> 3 , 1.6 -> 2
       // -1.6 -> -1 , -2.5 -> -2
    			 CEILING(BigDecimal.ROUND_CEILING),
    			 // 2.5 -> 2 , 1.6 -> 1
       // -1.6 -> -2 , -2.5 -> -3
    			 FLOOR(BigDecimal.ROUND_FLOOR), ////数轴上 正数:远离哪个取哪个  负数:靠近哪个取哪个
       	// 2.5 -> 3 , 1.6 -> 2
       // -1.6 -> -2 , -2.5 -> -3
    			 HALF_UP(BigDecimal.ROUND_HALF_UP),// 数轴上 正数:靠近哪个取哪个  负数:远离哪个取哪个
       //......
    }
    
  • 大小比较
    使用compareTo

    ...

Java代理模式

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

代理模式 #

使用代理对象来代替对真实对象的访问,就可以在不修改原目标对象的前提下提供额外的功能操作扩展目标对象的功能,即在目标对象的某个方法执行前后可以增加一些自定义的操作

静态代理 #

静态代理中,我们对目标对象的每个方法的增强都是手动完成的(*后面会具体演示代码*),非常不灵活(*比如接口一旦新增加方法,目标对象和代理对象都要进行修改*)且麻烦(*需要对每个目标类都单独写一个代理类*)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

代码:

//定义发送短信的接口
public interface SmsService {
    String send(String message);
}
//实现发送短信的接口
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
//创建代理类并同样实现发送短信的接口
public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}
//实际使用
public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}
//打印结果
before method send()
send message:java
after method send()

动态代理 #

从JVM角度来说,动态代理是在运行时动态生成类字节码,并加载到JVM中的。 SpringAOP和RPC等框架都实现了动态代理

...

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;
          }
      }
      
    • 继承
      不通类型的对象,相互之间有一定数量的共同点,同时每个对象定义了额外的特性使得他们与众不同。继承是使用已存在的类的定义作为基础建立新类的技术

      ...