万郡绿建一面凉经

110 阅读31分钟

介绍一个项目(要背熟啊)

java基本数据类型

double默认初始值

无论是作为类的成员变量(也称为字段)还是作为局部变量声明但未初始化时,double类型的变量都会默认为0.0。

Comparable和Comparator的区别

Java中的Comparable和Comparator两个接口都用于提供对象之间的比较功能,以便对它们进行排序,但它们之间存在以下几个主要区别:

Comparable接口

  1. 定位: Comparable接口位于java.lang包下,所有实现了Comparable接口的类都可以进行自我比较。
  2. 用途: 实现Comparable接口的类表明其自身支持自然排序。类通过实现compareTo(T other)方法来定义这种排序逻辑。
  3. 内置排序: 当一个类实现了Comparable接口,它的实例可以直接插入到诸如TreeSetTreeMap等基于比较的集合中,并且集合会根据类内定义的比较规则自动排序。
  4. 灵活性: 只能提供一种排序方式,即类内部定义的自然排序。

Comparator接口

  1. 定位: Comparator接口位于java.util包下,它是专门设计用于提供比较器对象的。
  2. 用途: 提供了一种灵活的方式来定义多个不同的排序标准,即使对于那些没有实现Comparable接口的类,也可以通过创建Comparator的实现类来指定排序方式。
  3. 自定义排序: 通过实现compare(T o1, T o2)方法,可以创建自定义的排序逻辑。这样可以按照任何所需的标准对对象进行排序,而不局限于类自身的自然顺序。
  4. 多态性: 同一个类可以有不同的Comparator实现,从而允许按不同的属性或规则进行排序。

总结来说:

  • Comparable 是对象自我比较,是一种内建的排序机制,适用于单一、固有的排序规则。
  • Comparator 是独立于对象之外的比较器,提供了额外的、甚至是临时性的排序策略,适用于多种排序需求或者当类本身不能或不应决定其排序规则时。
import java.util.*;

class Employee implements Comparable<Employee> {
    String name;
    double salary;

    Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    // 实现Comparable接口的compareTo方法
    @Override
    public int compareTo(Employee other) {
        // 按照工资从低到高排序
        return Double.compare(this.salary, other.salary);
    }

    @Override
    public String toString() {
        return "Employee [name=" + name + ", salary=" + salary + "]";
    }

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("Alice", 5000));
        employees.add(new Employee("Bob", 7000));
        employees.add(new Employee("Charlie", 3000));

        Collections.sort(employees); // 使用默认的排序,也就是按照salary排序

        for (Employee e : employees) {
            System.out.println(e);
        }
    }
}

throw和throws的区别

Java中的throwthrows关键字用于处理异常情况,它们的主要区别在于:

  1. throw关键字:

    • 在代码中使用throw是为了在程序运行时主动抛出一个具体的异常对象。
    • 通常用于在方法内部,当你检测到某个错误条件并且希望立即停止当前方法的执行,并将异常传递给调用者时使用。
    • 示例代码:
      public void divide(int dividend, int divisor) {
          if (divisor == 0) {
              throw new ArithmeticException("Divisor cannot be zero");
          }
          int result = dividend / divisor;
          System.out.println("Result: " + result);
      }
      
      上述代码中,如果除数为0,throw会抛出一个ArithmeticException异常,并附带一条错误消息。
  2. throws关键字:

    • throws出现在方法签名中,用来声明方法可能会抛出的异常类型,它不会抛出异常实例,而是告诉方法的调用者这些异常可能是该方法运行过程中产生的。
    • 调用者必须处理这些声明的异常,要么通过捕获异常并处理,要么在自己的方法签名中继续声明这些异常。
    • 示例代码:
      public void readFile(String filePath) throws FileNotFoundException {
          File file = new File(filePath);
          if (!file.exists()) {
              throw new FileNotFoundException("File not found: " + filePath);
          }
          // 假设接下来的代码读取文件...
      }
      
      在上述代码中,readFile方法声明它可能会抛出FileNotFoundException,这意味着调用此方法的地方需要捕获这个异常,如下所示:
      public static void main(String[] args) {
          try {
              readFile("nonexistent.txt");
          } catch (FileNotFoundException e) {
              System.out.println("Error: " + e.getMessage());
          }
      }
      

总结起来,throw用于实际抛出异常,而throws则是在方法签名中告知调用者该方法可能抛出哪些类型的异常。

Object()类有什么方法?

Object类是Java语言中的核心类,是所有类的超类(或称为父类)。因此,所有Java对象都继承自Object类,从而可以调用其方法。以下是Object类中的一些主要方法:

  1. toString() :返回对象的字符串表示。默认情况下,它返回对象的类型、@符号和对象的哈希码的无符号十六进制表示。通常,如果希望自定义对象的字符串表示,可以重写此方法。
  2. equals(Object obj) :用于比较两个对象是否相等。默认情况下,此方法比较的是对象的引用是否相等,即比较两个对象是否指向同一个内存地址。如果需要实现自定义的相等比较(例如,比较对象的内容而不是引用),应重写此方法。
  3. hashCode() :返回对象的哈希码值。哈希码值是根据对象的内容计算得出的一个整数,常用于数据结构(如哈希表)中。默认情况下,此方法返回的是对象的存储地址的整数表示。如果重写了equals()方法,通常也应重写hashCode()方法,以确保两个相等的对象具有相同的哈希码。
  4. getClass() :返回对象的运行时类对象。这个方法通常与Java反射机制一起使用,用于获取对象的类型信息。
  5. clone() :创建并返回此对象的一个副本。默认情况下,Object类的clone()方法是受保护的,并且抛出CloneNotSupportedException异常。要让一个类支持克隆,需要实现Cloneable接口并重写clone()方法。
  6. finalize() :当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类可以重写此方法以提供清理资源的代码,但通常不建议这样做,因为依赖finalize()方法进行资源清理是不安全的。

此外,Object类还包含一些与线程相关的方法,如wait()notify(), 和 notifyAll(),这些方法用于线程间的通信和同步。

请注意,虽然Object类提供了这些方法,但并非所有方法都需要在子类中重写。具体是否需要重写取决于你的应用需求和设计决策。

RuntimeException

RuntimeException是那些可能在Java虚拟机正常运行期间抛出的异常的超类。 记4~5种即可。

NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
NegativeArrayException —— 数组负下标异常
ArrayIndexOutOfBoundsException - 数组下标越界异常
EOFException - 文件已结束异常
FileNotFoundException - 文件未找到异常
NumberFormatException - 字符串转换为数字异常
SQLException - 操作数据库异常
IOException - 输入输出异常
NoSuchMethodException - 方法未找到异常

在项目中是怎么用异常的

全局异常类,多看看代码

final的用法

在Java中,final是一个关键字,它可以用来修饰类、方法、变量和常量。以下是final的几种用法及其含义:

  1. 修饰类:当一个类被声明为final时,它不能被继承。这通常用于设计那些不应该有子类的类,例如String类。

    java复制代码
    	final class FinalClass {  
    
    	    // 类的内容  
    
    	}
    
  2. 修饰方法:当一个方法被声明为final时,该方法不能被子类重写(override)。这通常用于那些不应该被子类改变其行为的方法。

    java复制代码
    	class MyClass {  
    
    	    final void finalMethod() {  
    
    	        // 方法内容  
    
    	    }  
    
    	}
    
  3. 修饰变量final修饰变量时,表示该变量是一个常量,即其值在初始化之后不能被修改。对于基本数据类型的变量,这意味着其值不可变;对于引用类型的变量,这意味着引用本身不可变,但引用的对象内容仍然可变(除非对象本身也是不可变的)。

    java复制代码
    	final int MY_CONSTANT = 10; // 基本数据类型常量  
    
    	final String str = "Hello"; // 引用类型常量,但str指向的字符串内容本身可变
    
  4. 修饰参数:在方法定义中,final还可以用来修饰参数,表示该参数在方法内部不能被重新赋值。

    java复制代码
    	public void myMethod(final int param) {  
    
    	    // 这里不能给param赋新值  
    
    	}
    
  5. 修饰实例变量final修饰的实例变量必须在声明时或构造器中初始化,之后不能被修改。

    java复制代码
    	class MyClass {  
    
    	    final int myFinalVar;  
    
    	      
    
    	    public MyClass(int value) {  
    
    	        myFinalVar = value; // 必须在构造器中初始化  
    
    	    }  
    
    	}
    
  6. 修饰静态变量final修饰的静态变量也必须在声明时初始化,或者在静态初始化块中初始化,并且之后不能被修改。

    java复制代码
    	class MyClass {  
    
    	    public static final int MY_STATIC_FINAL_VAR = 42; // 在声明时初始化  
    
    	}
    

final的使用有助于增强代码的可读性和健壮性,因为它限制了变量、方法和类的某些行为。例如,使用final修饰的常量提供了更好的命名约定,让其他开发者知道这个值在程序中是不可变的。同时,通过限制方法和类的继承或重写,可以确保某些关键行为在程序的生命周期内保持一致。

重载与重写的区别

重载(Overloading)

重载是指在同一个类中,有多个方法具有相同的名称但参数列表不同(参数类型、参数个数或参数顺序)。重载方法允许在运行时根据提供的参数来确定调用哪个方法。

重载的特点:

  1. 方法名相同,参数列表不同(类型、个数或顺序)。
  2. 返回类型可以相同也可以不同。
  3. 重载方法发生在同一个类中。
  4. 重载与方法的访问修饰符和返回类型无关,只与参数列表有关。

重写(Overriding)

重写是子类对父类允许访问的方法的实现过程进行重新编写,返回类型和形参都不能改变。即子类有一个与父类相同名称和参数列表的方法,通过子类创建的实例对象调用该方法时,将调用子类中的定义方法,这相当于把父类中该同名方法的内容进行了覆盖,因此也称为方法覆盖。

重写的特点:

  1. 方法名、参数列表都相同。
  2. 返回类型与被重写方法的返回类型相同或是其子类型。
  3. 访问权限不能低于被重写方法的访问权限。
  4. 重写发生在父类和子类之间。

多态性:重载与多态性无关;重写是多态性的基础,通过子类对象调用父类中被重写的方法时,将执行子类中的方法。

HashMap的put方法,初始容量,负载因子

此方法执行以下操作:

  1. 调用hash()方法计算机key对应的hash键,然后通过i = (n-1)&hash确定是否有元素。
  2. 如果 HashMap 中已经存在一个与指定键 key 关联的映射关系,则使用指定的值 value 替换旧值(并返回旧值)。
  3. 如果 HashMap 中以前没有与指定键 key 关联的映射关系,则插入指定的键-值对。
  4. 如果键 key 为 null,并且 HashMap 允许 null 键(这是默认的),则将 null 值与键关联。最多允许一个 null 键。
  5. 如果值 value 为 null,则将 null 值与键关联。最多可以有一个 null 值。

初始容量

  • HashMap的初始容量是HashMap在创建时的初始桶数量。HashMap的默认初始容量是16,长度始终保持2的n次方。
  • 在键值对数量小于HashMap初始容量与负载因子的乘积时,HashMap不会进行扩容。例如,当负载因子为0.75时,如果键值对数量小于12(16 * 0.75),HashMap不会进行扩容。
  • 当我们尝试为HashMap设置一个初始容量时,JDK会帮我们计算一个相对合理的值作为实际的初始容量。具体来说,它会找到第一个大于或等于传入值的2的幂作为实际初始容量。这是为了提高性能和空间效率。

负载因子

  • 负载因子是HashMap中定义的一个阈值,用于确定何时触发扩容操作。HashMap的默认负载因子是0.75。
  • 当HashMap中的元素个数超过了哈希表容量与负载因子的乘积时,HashMap会进行扩容操作。例如,当负载因子为0.75,且HashMap中的元素数量达到初始容量16的75%即12个时,HashMap会执行扩容操作。
  • 负载因子的值是一个平衡的结果,旨在在性能和内存占用之间提供合理的折中。如果负载因子设置得过高,可能会导致哈希碰撞频繁发生,从而影响性能;如果设置得过低,虽然减少了哈希碰撞,但可能导致频繁的扩容操作,浪费内存并降低性能。

TreeSet和SortedSet的区别

在Java中,TreeSetSortedSet都是关于有序集合的接口或类。

  1. TreeSet

    • TreeSet是Java集合框架中的一个具体类,它实现了Set接口,同时它也是一个有序集,内部采用红黑树(Red-Black Tree)数据结构进行存储。
    • TreeSet自动维护其元素的排序,排序的依据可以是元素自身实现Comparable接口的自然排序,或者通过构造函数传入一个自定义的Comparator来进行排序。
    • 除了基本的添加、删除、查找等功能外,还可以方便地获取第一个元素(最小值)、最后一个元素(最大值)以及范围内的元素。
  2. SortedSet

    • SortedSet是Java集合框架中的一个接口,它继承自Set接口,并增加了一些与排序相关的操作方法。
    • 任何实现了SortedSet接口的集合类,如TreeSet,都保证其元素按照某种排序顺序排列,比如自然顺序、升序。
    • 它提供了如first()last()headSet(E toElement)tailSet(E fromElement)subSet(E fromElement, E toElement)等方法,用于获取排序后的子集或特定元素。

所以,可以说TreeSetSortedSet的一个实现,它具有排序特性,确保集合中的元素有序。而SortedSet是一个接口,定义了一组有序集合应具备的方法。

MyBatis中#和$的区别

在MyBatis中,# 和 $ 都是用于参数绑定的符号,但它们之间有着显著的区别,主要体现在预编译处理和安全性上。

  1. 预编译处理

    • #{}:这是预编译处理。MyBatis会使用PreparedStatement的set方法来对占位符进行设值。这样做的好处是提高了性能,并且可以防止SQL注入攻击,因为预编译的SQL语句参数是值绑定的,MyBatis不会对其进行SQL解析,而是直接采用占位符的方式进行值绑定。
    • ${}:这是字符串替换。MyBatis在处理这个符号时,会将其直接替换为变量的值,并嵌入到SQL语句中。这种方式不会进行预编译处理,因此有潜在的SQL注入风险。
  2. 安全性

    • 由于#{}使用了预编译的方式,它可以有效防止SQL注入攻击,因此在使用时更加安全。
    • ${}由于是直接替换变量的值,没有进行预编译处理,所以如果变量来源于不可信的输入,就可能会引发SQL注入问题。
  3. 使用场景

    • 在大多数情况下,应优先使用#{}进行参数绑定,因为它既安全又高效。
    • ${}主要用于一些特殊场景,比如动态拼接表名、列名等。但使用时必须确保变量的值是可信的,以避免SQL注入风险。

总结来说,#{}${}在MyBatis中各有其用途,但使用时需要注意它们之间的区别,特别是安全性方面的考虑。在大多数情况下,推荐使用#{}进行参数绑定,以确保SQL语句的安全性和性能。

Spring中的IOC,DI的方法

  1. 构造函数注入
    构造函数注入是通过类中的构造函数给成员变量赋值。这种注入方式在创建对象时就完成了依赖关系的注入,确保在对象被使用之前,所有必需的依赖项都已得到赋值。
  2. Setter方法注入
    这是最常用的依赖注入方式。通过调用类的setter方法将依赖项注入到类的成员变量中。这种方式具有灵活性高的优点,因为setter方法允许在对象创建后的任何时间进行依赖注入。

Bean的作用域

singleton(单例) :这是Spring的默认作用域。在Spring IoC容器中,singleton作用域的Bean只会有一个实例存在。当容器被初始化时,该Bean会被创建,并在后续请求中返回同一个实例。单例Bean在容器关闭前都是有效的。

prototype(原型) :每次通过容器的getBean方法请求该Bean时,Spring都会创建一个新的Bean实例。因此,如果存在多次请求,则每次都会获得不同的Bean实例。这个作用域适用于那些无状态且不需要共享数据的Bean。

request:该作用域表明Bean的作用范围仅在一次HTTP请求中。每次HTTP请求时,Spring容器会为该请求创建一个新的Bean实例,并且该实例仅在当前请求内有效。当请求结束时,该Bean实例会被销毁。这个作用域仅适用于WebApplicationContext环境。

session:该作用域表示Bean的作用范围仅在HTTP Session的生命周期内。对于每次HTTP请求,Spring容器都会根据Bean的定义来创建一个新的Bean实例,并且该实例仅在当前HTTP Session中有效。当Session结束时,该Bean实例会被销毁。这个作用域也仅适用于WebApplicationContext环境。

application/globalSession:这两个作用域在Web应用中特别有用。application作用域表示Bean的作用范围在整个ServletContext生命周期内,即整个Web应用中。而globalSession作用域类似于标准的HTTP Session作用域,但是它仅仅在基于portlet的web应用中才有意义,因为portlet规范定义了全局Session的概念。

RequestMapping和GetMapping()的区别

Spring框架@RequestMapping完整指南 - 知乎 (zhihu.com)

反射创建对象的方法

在Java中,使用反射(Reflection)来创建对象主要涉及到Class类的newInstance()方法或者Constructor类的newInstance()方法。以下是使用反射创建对象的几种常见方法:

使用Class.newInstance()

如果你的类有一个无参数的构造方法,你可以直接使用Class对象的newInstance()方法创建实例:

java复制代码
	try {  

	    Class<?> clazz = Class.forName("com.example.MyClass");  

	    Object obj = clazz.newInstance(); // 调用无参构造方法创建对象  

	} catch (ClassNotFoundException e) {  

	    e.printStackTrace();  

	} catch (InstantiationException e) {  

	    e.printStackTrace();  

	} catch (IllegalAccessException e) {  

	    e.printStackTrace();  

	}

使用Constructor.newInstance()

如果类没有无参数的构造方法,或者你想要使用特定参数的构造方法,你需要先获取到Constructor对象,然后调用它的newInstance()方法:

java复制代码
	try {  

	    Class<?> clazz = Class.forName("com.example.MyClass");  

	    // 获取带有特定参数的构造方法  

	    Constructor<?> constructor = clazz.getConstructor(String.class, int.class);  

	    // 调用构造方法创建对象,并传入参数  

	    Object obj = constructor.newInstance("someString", 42);  

	} catch (ClassNotFoundException e) {  

	    e.printStackTrace();  

	} catch (NoSuchMethodException e) {  

	    e.printStackTrace();  

	} catch (InstantiationException e) {  

	    e.printStackTrace();  

	} catch (IllegalAccessException e) {  

	    e.printStackTrace();  

	} catch (InvocationTargetException e) {  

	    e.printStackTrace();  

	}

使用Class.getDeclaredConstructor()

如果构造方法是私有的,你需要使用getDeclaredConstructor()方法,并且可能还需要调用setAccessible(true)来允许访问私有构造方法:

java复制代码
	try {  

	    Class<?> clazz = Class.forName("com.example.MyClass");  

	    // 获取私有构造方法  

	    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);  

	    // 允许访问私有构造方法  

	    constructor.setAccessible(true);  

	    // 调用构造方法创建对象,并传入参数  

	    Object obj = constructor.newInstance("someString", 42);  

	} catch (ClassNotFoundException e) {  

	    e.printStackTrace();  

	} catch (NoSuchMethodException e) {  

	    e.printStackTrace();  

	} catch (InstantiationException e) {  

	    e.printStackTrace();  

	} catch (IllegalAccessException e) {  

	    e.printStackTrace();  

	} catch (InvocationTargetException e) {  

	    e.printStackTrace();  

	}

注意:使用反射创建对象时,应尽量避免频繁使用,因为反射操作通常比直接实例化对象要慢得多。此外,反射会破坏封装性,因为它允许代码访问类的私有成员。因此,在设计系统时,应谨慎使用反射。

另外,从Java 9开始,Class.newInstance()方法已被标记为过时,建议使用Constructor.newInstance()Class.getDeclaredConstructor().newInstance()方法替代。

MySQL中索引的类型

进程死锁的解决方法,原因

Java 实例 – 死锁及解决方法 | 菜鸟教程 (runoob.com)

sleep()和wait()

sleep()和wait()方法在Java中的主要区别体现在以下几个方面:

  1. 所属类和方法特性
  • sleep()方法是Thread类的静态方法,可以在任何上下文中调用。它用于暂停当前执行的线程一段指定的时间(以毫秒为单位)。在暂停期间,线程不会释放任何锁资源。
  • wait()方法是Object类的一个方法,任何对象都可以调用它。这个方法使当前线程进入等待状态,直到其他线程调用此对象的notify()方法或notifyAll()方法。通常,wait()方法在同步上下文(例如synchronized方法或代码块)中使用。
  1. 锁的处理
  • 当线程调用sleep()方法时,它不会释放任何锁资源。
  • 当线程调用wait()方法时,它会释放持有的对象锁,从而让其他在此对象上等待的线程有机会获得该对象锁。
  1. 唤醒机制
  • sleep()方法使线程暂停执行一段时间后自动苏醒,并返回到可运行状态(不是运行状态)。指定的时间是线程不会运行的最短时间,因此sleep()方法不能保证该线程睡眠到期后就开始执行。
  • 对于wait()方法,线程在调用后进入等待状态,直到被其他线程通过notify()或notifyAll()方法唤醒。
  1. 异常处理
  • sleep()方法可能会抛出InterruptedException异常,如果线程在等待时被其他线程中断,会抛出此异常。
  • wait()方法在等待时如果被中断,同样会抛出InterruptedException异常,并且还会在调用前后改变线程的中断状态。

综上所述,sleep()和wait()在Java中有着不同的用途和机制。sleep()主要用于线程暂停,而wait()则主要用于线程间的同步和通信。在使用时,应根据具体需求选择合适的方法。

线程的状态(进程)

线程的状态通常包括以下五种:

  1. 新建状态(New) :新创建了一个线程对象,但还没有调用该对象的start方法,此时线程处于创建状态。
  2. 就绪状态(Runnable) :当调用了线程对象的start方法之后,线程就进入了就绪状态。此时线程调度程序还没有把该线程设置为当前线程,它位于可运行线程池中,等待获取CPU的使用权。
  3. 运行状态(Running) :线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  4. 阻塞状态(Blocked) :线程在运行过程中,由于某种原因(如等待某个资源就绪或执行了sleep、suspend、wait等方法)放弃CPU使用权,暂时停止运行,就进入了阻塞状态。阻塞状态又可分为多种情况,如等待阻塞(执行wait()方法后等待获取资源)、同步阻塞(在获取对象的同步锁时被其他线程占用而等待)以及其他阻塞(执行sleep()或join()方法,或发出I/O请求时)。
  5. 死亡状态(Dead) :当线程执行完run方法或因为异常退出,或者调用了stop方法后,线程就会进入死亡状态。处于死亡状态的线程无法再被启动。

线程的这五种状态转换,共同构成了线程在程序运行中的生命周期。开发者需要理解并合理使用这些状态,以确保线程能够正确、高效地执行。

mysql内连接和外连接,多表联查

MySQL中的多表连查是通过JOIN语句实现的,用来从两个或更多个相关的表中提取数据。JOIN操作基于表间的关系(通常是通过共享的公共列或外键关联)来合并来自不同表的数据行。以下是一些基本的JOIN类型及示例:

  1. 交叉连接(CROSS JOIN) 不带连接条件的JOIN,会生成两个表的笛卡尔积,即第一个表中的每一行与第二个表中的每一行都进行组合。在实际应用中较少使用,因为通常会导致大量无意义的组合数据。

    SELECT * FROM TableA CROSS JOIN TableB;
    
  2. 内连接(INNER JOIN) 只返回两个表中满足连接条件的行的集合。

    SELECT A.column1, B.column2 
    FROM TableA AS A
    INNER JOIN TableB AS B ON A.common_column = B.common_column;
    

    上述例子中,TableATableB通过common_column列进行连接,只返回这两个列值相匹配的行。

  3. 左连接(LEFT JOIN / LEFT OUTER JOIN) 返回左表(第一个表)的所有行,即使在右表中找不到匹配的行。在没有匹配的情况下,右表的列显示为NULL。

    SELECT A.column1, B.column2 
    FROM TableA AS A
    LEFT JOIN TableB AS B ON A.common_column = B.common_column;
    
  4. 右连接(RIGHT JOIN / RIGHT OUTER JOIN) 类似于左连接,但返回的是右表(第二个表)的所有行,即使在左表中找不到匹配。左表中没有匹配的行在结果集中对应的部分显示为NULL。

    SELECT A.column1, B.column2 
    FROM TableA AS A
    RIGHT JOIN TableB AS B ON A.common_column = B.common_column;
    
  5. 全外连接(FULL OUTER JOIN) MySQL并不直接支持SQL标准的FULL OUTER JOIN,但可以通过UNION ALL左连接和右连接的结果来模拟全外连接,返回左右两边表中所有匹配以及不匹配的行。

    (SELECT A.column1, B.column2 
     FROM TableA AS A
     LEFT JOIN TableB AS B ON A.common_column = B.common_column)
    UNION ALL
    (SELECT A.column1, B.column2 
     FROM TableA AS A
     RIGHT JOIN TableB AS B ON A.common_column = B.common_column
     WHERE A.common_column IS NULL);
    

在实际场景中,还可能涉及到多表间的连接,即不仅限于两个表的连接,而是涉及三个或更多表的连接。在这种情况下,可以连续使用JOIN关键字,每次连接一对表,逐步扩大查询的结果集。

SELECT ...
FROM Table1
JOIN Table2 ON Table1.key = Table2.key
JOIN Table3 ON Table2.other_key = Table3.other_key
WHERE ... ;

以上各类型的JOIN可以根据实际业务需求灵活运用,并且结合WHERE子句和其他SQL语句进一步筛选和处理数据。

JDK代理和CGLIB代理

动态代理:03、动态代理:实际应用、好处_哔哩哔哩_bilibili

JDK与CGLIB: 老大突然问我基于JDK和CGLib实现动态代理的区别和适用场景_jdk动态代理和cglib动态代理的使用场景-CSDN博客

面试被问静态代理、jdk、cglib动态代理 搞不清? 看这个视频就懂了_哔哩哔哩_bilibili

还是底子薄了,多看看测开吧。

Notes:

字符串常量池

Java中的字符串常量池是一个特殊的存储区域,用于存储字符串字面量。当创建字符串字面量时,JVM会首先检查字符串常量池中是否已存在相同的字符串。如果存在,则返回对该字符串的引用;否则,在字符串常量池中创建一个新的字符串对象并返回其引用。这种机制有助于提高性能并减少内存消耗。但是,使用new关键字创建的字符串对象不会放入字符串常量池中。

String str = new String("Hello, World!");

JVM会在堆内存中为String对象分配空间,并且str这个引用变量会指向这个新创建的堆内存中的对象。尽管这个String对象的内部可能包含对字符串常量池中某个字符串字面量的引用(用于存储实际的字符数据),但String对象本身(即包含其他可能属性和方法的String类的实例)是存储在堆内存中的。

为什么String是不可变的

  1. 安全性:不可变性提供了线程安全性。由于String对象是不可变的,多个线程可以共享同一个String对象而无需担心数据被其他线程修改。这在并发编程中是非常有用的,因为它简化了多线程环境下的数据处理。
  2. 字符串池(String Pool)和缓存:由于String是不可变的,Java的字符串池(String Pool)可以有效地工作。字符串池允许JVM在内存中存储字符串字面量的唯一副本,这样,每次当相同的字符串字面量被创建时,都会返回对池中已有字符串的引用,而不是创建一个新的对象。这不仅节省了内存,还提高了性能。
  3. 哈希码(HashCode)的稳定性:String常被用作哈希表(如HashMap和HashSet)的键。由于String是不可变的,它的哈希码(hashCode)在创建之后就不会改变,这使得String对象在哈希表中作为键时能够保持其哈希码的一致性,从而提高哈希表的性能。

try、catch、finally、return执行顺序超详解析(针对面试题

Error和Exception有什么区别

Error和Exception在Java中都是Throwable类的子类,但它们在处理方式和表示的问题类型上有显著的差异。

Error通常表示那些严重的问题,这些问题是由Java虚拟机(JVM)或者底层系统引起的,并且通常不应该被捕获或处理。这些错误标识故障发生于虚拟机自身或者发生在虚拟机试图执行应用时,一般表示了无法恢复的错误状态。当出现Error时,通常意味着系统可能已经无法正常运行,并且进一步的执行可能会导致不确定的结果。常见的Error类型包括OutOfMemoryError(当JVM无法为对象分配足够的内存空间时抛出的错误)和StackOverflowError(堆栈溢出)等。

Exception则代表程序本身可以处理的异常,这些异常通常是由编程错误或偶然的外在因素导致的一般性问题。例如,NullPointerException(当应用程序试图在需要对象的地方使用null时抛出)和ArrayIndexOutOfBoundsException(当应用程序试图访问数组的无效索引时抛出)就是两种常见的Exception。这些异常可以通过针对性的代码进行处理,使程序在发生异常时能够采取适当的行动,如记录错误、回滚事务或提供用户友好的错误消息。

总结来说,Error和Exception的主要区别在于:Error表示的是系统级或JVM级的严重问题,通常无法恢复或处理;而Exception则是程序运行过程中可能遇到的异常情况,可以通过编程来捕获和处理。

HashMap的扩容过程,安全性

HashMap的扩容过程是一个动态调整内部数组大小的过程,当HashMap中的元素数量超过了其容量与负载因子的乘积时,就会触发扩容操作。

具体的扩容步骤如下:

  1. 创建一个新的数组,其长度是原数组长度的两倍。
  2. 遍历原数组中的每个元素,重新计算每个元素在新数组中的位置。这个位置的计算是通过元素的hash值和新数组的长度进行位运算得到的。如果运算结果为0,则元素在新数组中的位置不变;否则,位置会变为“原位置 + 原数组长度”。
  3. 将原数组中的元素按照新计算出的位置放入新数组中。这个过程可能会涉及到链表或红黑树的拆分和合并。

至于HashMap的线程安全性,HashMap本身是线程不安全的。在多线程环境下,如果多个线程同时对HashMap进行写操作,可能会导致数据不一致的问题。这主要体现在以下几个方面:

  1. 在扩容过程中,如果有其他线程正在对HashMap进行写操作,可能会导致数据丢失或错位。
  2. 如果两个线程同时修改HashMap的同一个桶(bucket),可能会导致一个线程的修改被另一个线程覆盖。

因此,如果需要在多线程环境下使用Map,可以考虑使用线程安全的ConcurrentHashMap或者其他同步机制来确保线程安全。

ConcurrentHashMap通过分段锁(在1.7版本中)或CAS操作(在1.8版本及以后)等方式实现了高效的并发读写,从而保证了线程安全。

请注意,即使使用了线程安全的Map实现,也仍然需要在编写代码时注意避免竞态条件和其他并发问题。

get和post有什么区别

GET和POST是HTTP协议中最常见的两种请求方法,它们在客户端与服务器端交互时有着明显的区别:

  1. 功能目的:

    • GET:主要用来从服务器检索数据,请求数据可以从服务器获取,但不应该用于修改服务器上的资源。
    • POST:主要用于向服务器发送数据以创建或更新资源,也可以用来上传文件或提交表单数据。
  2. 参数传递方式:

    • GET:参数包含在URL中,作为查询字符串附加在URL后面,形如http://example.com/resource?param1=value1&param2=value2,用户可以在浏览器地址栏看到参数内容。
    • POST:参数不在URL中显示,而是放在HTTP请求体中,因此数据对用户不可见,更加安全。
  3. 数据大小限制:

    • GET:由于URL长度有限制(一般为2048个字符左右),因此GET请求携带的数据量较小。
    • POST:理论上讲,POST请求没有特定的数据大小限制,适合传输大量数据。
  4. 缓存:

    • GET:具有缓存性,响应可被浏览器或其他中间件缓存。
    • POST:一般情况下不被缓存,但可以根据实际情况通过设置头信息控制缓存行为。
  5. 安全性:

    • GET:因为参数直接暴露在URL中,不适合传递敏感信息,安全性较低。
    • POST:相比GET较为安全,因为数据不会显示在URL中,但仍然建议对敏感信息加密处理。
  6. 可书签化:

    • GET:由于URL包含了请求的所有信息,因此GET请求的结果可以被用户保存为书签。
    • POST:因为请求参数在报文体中,所以POST请求不能被轻易地保存为书签。
  7. 后退按钮的影响:

    • GET:刷新页面或点击后退按钮不会导致副作用,多次请求同一资源只会重新获取数据。
    • POST:重复提交POST请求可能导致资源被多次创建或更新,浏览器通常会在用户尝试刷新时给出警告。
  8. HTTP请求历史:

    • GET:请求的信息会被保留在浏览器历史记录和服务器日志中。
    • POST:虽然请求的具体数据通常不会被保存在浏览器历史记录中,但请求的URL和发生过POST的事实仍会被记录在服务器日志里。
  9. SEO(搜索引擎优化):

    • GET:搜索引擎爬虫能够抓取并理解带有查询参数的GET请求,有助于SEO。
    • POST:由于POST数据并不体现在URL中,通常搜索引擎爬虫不会执行或解析POST请求,不利于SEO。
  10. RESTful原则:

    • GET通常用于资源获取,被认为是幂等(idempotent)操作,即多次执行结果不变。
    • POST常用于非幂等操作,如创建资源,服务器可能会根据相同的POST请求多次生成新的资源。

总的来说,GET方法更适合简单、安全、无副作用的数据获取,而POST方法更适合处理复杂的、包含隐私信息或者会对服务器状态造成改变的操作。

创建对象的5种方法

  1. new
  2. java.lang.Class的newInstance()方法
  3. Constructor类的newInstance()方法
  4. 对象的clone()方法
  5. 反序列化