" />
文章

看看你都知道吗?Java基础面试题随记(一)

位移符

<< 左移运算符

5 << 2= 5*2^2=20

>> 右移运算符

20 >>2=20/2^2=5

>>> 无符号右移运算符

无符号右移运算符 >>> 不考虑符号位,左边始终补 0。让我们来看具体的步骤:

  • 原始二进制表示(补码):11111111 11111111 11111111 11101100(-20)

  • 无符号右移两位,左边补 0,结果为:

    00111111 11111111 11111111 11111011

这个新的二进制值对应的十进制数是 1073741819,这是因为无符号右移使负数的二进制补码转换成了一个非常大的正数。

equals()

比较的是内存地址

hashCode()&equals() should comply

hashCode()equals() 的关系

hashCode()equals() 是共同作用的。它们必须遵守一个重要的合同(规则),特别是在使用哈希表时:

  • 如果两个对象根据 equals() 方法被认为是相等的,那么它们的 hashCode() 必须相等。

  • 如果两个对象的 hashCode() 不相等,那么它们必定不相等(根据 equals() 的比较)。

  • 如果两个对象的 hashCode() 相等,它们不一定相等(根据 equals() 的比较),但可能发生哈希冲突。

finally的return必会执行

代码示例:

public static void main(String[] args) {
    System.out.println(f(2));
}
​
public static int f(int value) {
    try {
        return value * value;
    } finally {
        if (value == 2) {
            return 0;
        }
    }
}

输出:

0

反射机制

反射的主要用途包括:

  • 动态加载类:可以在运行时根据名称动态加载类,而不是在编译时确定要加载的类。

  • 创建实例:通过反射动态创建对象实例,而不需要显式调用构造函数。

  • 调用方法:可以通过反射在运行时调用对象的方法,这些方法在编译时可能是不确定的。

使用场景

  • 加载类名在编译时未知的类(例如,通过配置文件或用户输入来动态指定类名)。

  • 在框架或库中提供通用逻辑,而不依赖具体类(例如,JavaBeans 规范,Spring 框架中使用反射来注入依赖)。

示例解释

Class<?> clazz = Class.forName("MyClass"); // 动态加载类
Object instance = clazz.newInstance(); // 创建类的实例
Method method = clazz.getMethod("greet", String.class); // 获取方法
method.invoke(instance, "Bob"); // 调用方法并传递参数

在这个例子中,反射允许你根据类的完全限定名动态加载类,并调用它的某个方法。这种方式特别有用当类名、方法名、参数在编译时不确定时。

动态代理(JDK 动态代理和 CGLIB 动态代理)

与直接使用反射不同,动态代理是一种高级用法,常用于拦截方法调用并在调用前后添加逻辑,例如日志记录、事务管理、权限检查等。动态代理依赖反射来实现,但它的目的是增强或替代原有方法的行为,而不是单纯地调用方法。

JDK 动态代理:

  • 核心目标:生成代理对象,并在调用代理对象的方法时通过 InvocationHandler 拦截并处理这些方法调用。

  • 它使用反射机制来拦截接口方法调用,生成一个代理对象,该对象可以执行一些额外的逻辑,比如日志记录、方法调用前后通知等。

CGLIB 动态代理:

  • 核心目标:生成一个目标类的子类,拦截目标类的方法调用。与 JDK 动态代理不同,它不依赖于接口。

区别与联系:

联系:

  • 都使用了反射机制

    :无论是直接通过

    Class.forName()

    动态加载类和调用方法,还是通过动态代理机制生成代理对象,反射机制在背后都起到了重要作用。

    • 动态代理通过反射来获取方法并执行方法调用(使用 Method.invoke()),这与普通反射操作类似。

区别:

  • 直接反射与代理的区别

    • 直接反射是针对一个已知的类或对象,动态加载类、创建实例并调用特定方法。

    • 动态代理则更高级,它允许在方法调用时动态增强原有方法的行为,比如在调用方法前后增加额外的逻辑(如日志记录或事务处理)。代理是用来拦截所有方法调用并执行自定义的逻辑。

  • 目的不同

    • 使用反射的代码(例如通过 Class.forName() 加载类)主要是为了在运行时执行特定类的操作,通常用于灵活加载和调用

    • 动态代理的目的是为了增强和控制对象的行为,例如为接口添加横切关注点(如日志、事务、权限管理等)。

举个对比的例子:

反射调用示例:

// 动态加载 MyClass,调用其 greet 方法
Class<?> clazz = Class.forName("MyClass");
Object instance = clazz.newInstance();
Method method = clazz.getMethod("greet", String.class);
method.invoke(instance, "Bob");

这个代码的目的是在运行时根据类名加载 MyClass 类,创建其实例,并调用 greet 方法。它并没有增强或改变 greet 方法的逻辑。

动态代理示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
​
public class DebugInvocationHandler implements InvocationHandler {
    private final Object target;
​
    public DebugInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method " + method.getName());
        Object result = method.invoke(target, args); // 调用目标对象的方法
        System.out.println("After method " + method.getName());
        return result;
    }
}
​

import java.lang.reflect.Proxy;
​
public class Main {
    public static void main(String[] args) {
        // 创建实际对象
        Service target = new ServiceImpl();
​
        // 创建代理对象
        Service proxyInstance = (Service) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new DebugInvocationHandler(target)
        );
​
        // 调用代理对象的方法
        proxyInstance.performTask();
    }
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
class Service {
    public void performTask() {
        System.out.println("Performing task...");
    }
}
​
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method " + method.getName());
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method " + method.getName());
                return result;
            }
        });
​
        Service proxyService = (Service) enhancer.create();
        proxyService.performTask();
    }
}
​

这个代码通过动态代理增强了 Service 接口的方法调用,在 performTask() 方法调用之前和之后添加了日志记录。这里,DebugInvocationHandler 通过反射调用了 target 的方法。

总结:

  1. 反射 (Class.forName()Method.invoke()) 是 Java 提供的一种基础机制,允许程序在运行时动态加载类、创建实例并调用方法。

  2. 动态代理 是一种高级机制,依赖于反射来实现方法调用的拦截与增强。通过代理对象,你可以在方法调用前后添加额外的逻辑。

  3. 直接反射的使用场景通常是为了动态灵活地加载和操作类,而动态代理的目的是为了增强对象行为,特别是在面向切面编程(AOP)中

这两者的区别在于:反射是用来动态操作类和方法的,而代理是在方法调用时进行额外的逻辑处理。

Java是值传递

一个有意思的案例

public class Person {
    private String name;
   // 省略构造函数、Getter&Setter方法
}
​
public static void main(String[] args) {
    Person xiaoZhang = new Person("小张");
    Person xiaoLi = new Person("小李");
    swap(xiaoZhang, xiaoLi);
    System.out.println("xiaoZhang:" + xiaoZhang.getName());
    System.out.println("xiaoLi:" + xiaoLi.getName());
}
​
public static void swap(Person person1, Person person2) {
    Person temp = person1;
    person1 = person2;
    person2 = temp;
    System.out.println("person1:" + person1.getName());
    System.out.println("person2:" + person2.getName());
}

输出:

person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李

License:  CC BY 4.0