面经——java后端第一天

1.java和c++区别是?

性能⾼:C++ 是⼀种编译型语⾔,可以⽣成⾼效的机器代码,所以在处理⼤量数据和对计算效率有要求的应⽤场景下具有优势;

C++ 和 Java 是两种编程语言,它们之间有很多区别,主要包括以下几个方面:

  1. 编程范式:

    • C++ 是一门多范式编程语言,支持面向对象编程、过程式编程、泛型编程等。
    • Java 主要是一门面向对象编程语言,更强调面向对象的思想。
  2. 编译与解释:

    • C++ 是一种编译型语言,源代码需要经过编译器编译成机器代码,然后才能运行。
    • Java 是一种半编译半解释型语言。Java源代码首先被编译成字节码,然后在Java虚拟机(JVM)上解释执行。
  3. 内存管理:

    • C++ 提供了显式内存管理,程序员需要手动分配和释放内存,这也可能导致内存泄漏和悬挂指针等问题。
    • Java 使用自动内存管理,具有垃圾回收机制,程序员无需手动管理内存。
  4. 平台依赖性:

    • C++ 代码通常具有更高的平台依赖性,因为生成的机器码直接运行在特定平台上。
    • Java 通过JVM实现了平台无关性,Java程序可以在支持JVM的各种平台上运行。
  5. 安全性:

    • Java 具有更严格的安全性,防止了一些常见的安全漏洞,如缓冲区溢出。
    • C++ 更容易受到一些安全漏洞的威胁,需要程序员更加谨慎地编写代码。
  6. 标准库:

    • C++ 的标准库(STL)提供了丰富的数据结构和算法,适用于各种编程任务。
    • Java 有自己的标准库,也提供了许多数据结构和类,用于不同的应用。
  7. 性能:

    • 通常情况下,C++ 在性能上具有优势,尤其是对于需要高计算效率的应用。
    • Java 的性能通常较 C++ 稍差,但由于虚拟机的不断改进,性能差距逐渐减小。

总之,C++ 和 Java 在语言设计、应用场景和特性上有很多不同之处,开发人员在选择语言时需要考虑项目的具体需求和优劣势。

2.Go的轻量级线程在企业级应用中有什么体现?

并发能⼒强:Go 具有轻量级的线程(goroutine)和基于消息传递的通道(channel),可以⽅便地实现并发编程;

Go 语言的轻量级线程(goroutine)和通道(channel)使并发编程变得更加容易和高效,这些特性在企业级应用中有许多体现。以下是一个简单的示例,演示如何在 Go 中使用 goroutine 和 channel 处理并发任务。

假设你有一个需求:从多个数据源获取数据,然后对这些数据进行处理并将结果发送到另一个地方。你可以使用 goroutine 和 channel 来并发执行这些任务。

package main

import (
	"fmt"
	"time"
)

func fetchData(source string, ch chan string) {
	// 模拟从数据源获取数据的操作
	time.Sleep(2 * time.Second)
	data := fmt.Sprintf("Data from %s", source)
	ch <- data // 将数据发送到通道
}

func main() {
	startTime := time.Now()
	ch := make(chan string) // 创建一个字符串通道

	dataSources := []string{"Source1", "Source2", "Source3"}

	// 启动多个goroutine并发获取数据
	for _, source := range dataSources {
		go fetchData(source, ch)
	}

	// 从通道接收数据
	for range dataSources {
		result := <-ch
		fmt.Println(result)
	}

	elapsedTime := time.Since(startTime)
	fmt.Printf("Total time taken: %s\n", elapsedTime)
}

在这个示例中,我们创建了一个字符串通道 ch,然后使用 go 关键字启动了三个并发的 fetchData 函数,每个函数模拟从不同数据源获取数据。主函数中通过 <-ch 从通道中接收数据,并将数据打印出来。

这个示例演示了如何在 Go 中使用轻量级的 goroutine 实现并发任务,每个 goroutine 在不同的数据源上执行任务,然后通过 channel 将结果发送给主函数。这种并发编程模型在处理大规模数据、高并发的情况下非常有用,可以充分利用多核 CPU 和提高应用性能。

对比 JAVA

在 Java 中,你也可以执行类似的并发任务,但与 Go 不同,Java 的并发编程通常涉及线程和对象之间的协同工作,因此可能需要更多的代码和复杂性。下面是一个使用 Java 的示例,执行与前面 Go 示例相似的任务。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class JavaConcurrentExample {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        int numDataSources = 3;

        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(numDataSources);
        
        // 使用阻塞队列来存储结果
        BlockingQueue<String> results = new ArrayBlockingQueue<>(numDataSources);

        for (int i = 1; i <= numDataSources; i++) {
            final int sourceNumber = i;
            executor.submit(() -> {
                // 模拟从数据源获取数据的操作
                try {
                    Thread.sleep(2000); // 模拟2秒的延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String data = "Data from Source" + sourceNumber;
                results.offer(data); // 将数据放入队列
            });
        }

        // 关闭线程池
        executor.shutdown();
        try {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        while (!results.isEmpty()) {
            String result = results.poll();
            System.out.println(result);
        }

        long elapsedTime = System.currentTimeMillis() - startTime;
        System.out.println("Total time taken: " + elapsedTime + " ms");
    }
}

这个 Java 示例使用 ExecutorService 创建一个线程池,然后启动三个任务来模拟从不同数据源获取数据。获取到的数据存储在 BlockingQueue 中,并且主线程等待所有任务完成后从队列中获取结果。

需要注意的是,Java 中的并发编程需要更多的线程管理和同步机制,相对于 Go 来说可能显得更加复杂。此外,Java 的线程和内存管理通常需要更多的注意和谨慎,以避免潜在的问题。

总之,虽然 Java 可以实现类似的任务,但在某些情况下,Go 提供了更加轻量级和直观的并发编程模型,适用于大规模的并发处理。但 Java 依然是一种强大的编程语言,用于处理复杂的企业级应用。

相关问题

这段 Java 代码演示了如何使用线程池和阻塞队列来处理并发任务。让我来详细解释一下代码的原理:

  1. ExecutorService 和 线程池:

    • ExecutorService 是 Java 中用于管理线程池的接口。
    • ExecutorService 提供了一个高层次的接口,用于管理和执行多线程任务。线程池在企业应用中非常有用,因为它们可以提高多线程任务的效率。
    • 在示例中,Executors.newFixedThreadPool(numDataSources) 创建了一个固定大小的线程池,该线程池可以同时执行 numDataSources 个任务。
  2. 阻塞队列(BlockingQueue):

    • 阻塞队列是一种线程安全的队列,它支持在队列为空时阻塞等待数据或在队列已满时阻塞等待空间可用。
    • 在这个示例中,BlockingQueue<String> results = new ArrayBlockingQueue<>(numDataSources) 创建了一个阻塞队列,用于存储任务的结果。
    • 阻塞队列是一种常用的并发编程工具,它可以帮助协调多个线程之间的数据交换。
  3. 线程执行任务:

    • 使用 executor.submit(...) 提交任务到线程池中执行。在示例中,我们提交了三个任务,每个任务都是一个独立的线程。
    • 每个任务模拟从数据源获取数据的操作,然后将数据存储在 results 阻塞队列中。
  4. 关闭线程池:

    • executor.shutdown() 用于关闭线程池。一旦线程池被关闭,它将不再接受新的任务。
    • executor.awaitTermination(10, TimeUnit.SECONDS) 是一个阻塞调用,等待线程池中的任务完成。它会等待最多 10 秒,如果超过 10 秒仍有任务未完成,它将继续等待,直到所有任务完成。
  5. 结果提取:

    • 最后,我们从 results 阻塞队列中提取任务的结果,并将其打印出来。

这段代码的主要目的是展示如何使用线程池来管理并发任务,以及如何使用阻塞队列来存储任务的结果。它在多线程环境中执行三个任务,模拟了从不同数据源获取数据并将数据存储在一个结果队列中的过程。

trycatch 是 Java 中用于处理异常的语句块。try 中的代码可能会引发异常,而 catch 中的代码用于捕获和处理异常。在这个示例中,我们使用 trycatch 来处理可能的 InterruptedException 异常,该异常可能在等待任务完成时抛出。
在上面的代码中,Thread.sleep(2000)是用来模拟从数据源获取数据的操作时的延迟。Thread.sleep方法可以让当前线程休眠指定的时间,以模拟等待数据源响应或执行其他任务时的等待时间。

在Java中,Thread.sleep方法声明了一个可能会抛出InterruptedException的异常。这是因为线程在休眠的过程中,其他线程可能会中断休眠的线程。如果在休眠过程中发生了中断操作,InterruptedException异常将被抛出。因此,在使用Thread.sleep时,为了处理潜在的异常情况,需要进行异常捕获。

在上述代码中,捕获InterruptedException异常后,使用e.printStackTrace()语句打印异常信息。这是一个良好的做法,因为它可以帮助开发人员在出现异常时追踪问题,即使这里的异常处理方式比较简单,只是打印异常信息。在实际应用中,你可以选择更复杂的异常处理逻辑,如记录日志、释放资源等。总之,捕获InterruptedException异常是一种良好的编程实践,以确保在线程休眠时能够处理潜在的中断情况,使代码更加健壮和可靠。

这段代码演示了一种并发编程模型,使用线程池来管理任务的执行,同时通过阻塞队列实现了线程间的协同工作,以便高效地处理并发任务。

阻塞队列理解:

阻塞队列就像一个装满食物的餐盘,多个人(线程)在等待吃饭。这里有两个关键点:

  1. 线程安全:阻塞队列是线程安全的,就像餐盘上有食物一样,多个人可以安全地同时尝试拿取食物。

  2. 阻塞等待:如果餐盘是空的,那么拿取食物的人会被阻塞,直到有食物可用。类似地,如果队列是空的,尝试从中获取数据的线程将会被阻塞,直到有数据可用。同样,如果餐盘已满,那么添加食物的人也会被阻塞,直到有更多的空间。这就是阻塞队列的核心特性:在合适的时候等待,以保证数据安全和协同工作。

阻塞队列在多线程编程中非常有用,因为它可以帮助线程之间安全地交换数据,而不需要复杂的手动同步机制。阻塞队列的主要作用是确保线程之间的协同工作,使它们能够有序、安全地访问共享的数据,从而避免了竞态条件和死锁等问题。这就像多个人在餐桌前有秩序地等待食物,以确保每个人都能满足自己的需求,而没有人会争抢或饥饿。

因此,阻塞队列是多线程编程中的一个强大工具,它使线程协同工作变得更加容易和安全。

如果没有阻塞队列,多线程编程可能会变得更加复杂和容易出现问题,因为线程需要手动实现同步和协调机制。以下是一些可能发生的情况:

  1. 竞态条件:多个线程同时访问共享数据,没有适当的同步机制,可能导致数据竞争和不确定的行为。

  2. 死锁:如果线程之间的同步不正确,可能导致死锁,其中线程相互等待对方释放资源,导致所有线程被阻塞。

  3. 饥饿:某些线程可能会因为没有机会获得共享资源而陷入饥饿状态,无法继续执行。

  4. 编程复杂性:手动实现同步和协调逻辑通常需要复杂的编程,容易出现错误,调试困难。

  5. 性能问题:手动实现同步逻辑可能会导致性能开销,如频繁的锁竞争。

阻塞队列作为一种线程安全的数据结构,提供了可靠的数据共享和协同工作机制。它消除了竞态条件、死锁和饥饿的问题,减少了编程复杂性,提高了性能。因此,阻塞队列是多线程编程中的有力工具,使并发编程更加容易和安全。

基本逻辑和代码示例

以下是阻塞队列的基本逻辑和相关代码示例:

  1. 初始化阻塞队列: 创建一个阻塞队列对象,通常通过实现类来实例化。Java提供了多种阻塞队列实现,例如ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue等。

  2. 添加数据到队列: 在生产者线程中,使用put()offer()方法将数据添加到队列。如果队列已满,put()将会阻塞等待空间可用,而offer()会返回false

  3. 从队列获取数据: 在消费者线程中,使用take()poll()方法从队列中获取数据。如果队列为空,take()将会阻塞等待数据到来,而poll()会返回null

下面是一个简单的示例代码,演示了使用ArrayBlockingQueue实现的阻塞队列:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        // 创建一个容量为3的ArrayBlockingQueue
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        // 生产者线程
        new Thread(() -> {
            try {
                queue.put("Data 1");
                queue.put("Data 2");
                queue.put("Data 3");
                System.out.println("Producer: Data produced");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 消费者线程
        new Thread(() -> {
            try {
                Thread.sleep(2000); // 模拟等待
                String data = queue.take();
                System.out.println("Consumer: Data consumed - " + data);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

在上述示例中,生产者线程使用put()方法将数据添加到队列,而消费者线程使用take()方法从队列中获取数据。如果队列已满或为空,相应的操作将会阻塞等待,以确保线程安全的数据交换。

这个示例展示了阻塞队列的典型逻辑,它在多线程编程中非常有用,可以协调线程之间的数据交换,从而确保数据的安全性和可靠性。

3、JDK 动态代理和 CGLIB 动态代理的区别是什么?

Java JDK动态代理是一种代理模式,它允许你在运行时为一个或多个接口动态创建代理实例,而不需要提前知道这些接口的具体实现类。在这个示例中,我们将使用JDK动态代理来为UserService接口创建代理,并在代理方法中添加日志记录。

下面是代码的逐行注释和代码原理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 创建一个接口,这将是代理的目标接口
interface UserService {
    void saveUser(String username);
}

// 2. 创建一个实现目标接口的具体类
class UserServiceImpl implements UserService {
    public void saveUser(String username) {
        System.out.println("Saving user: " + username);
    }
}

// 3. 创建一个实现InvocationHandler接口的代理处理器类
class LogHandler implements InvocationHandler {
    private Object target;

    // 4. 构造函数,接收目标对象
    public LogHandler(Object target) {
        this.target = target;
    }

    // 5. invoke方法在代理对象上调用被代理的方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 6. 在代理方法前执行日志记录
        System.out.println("Entering method: " + method.getName());
        // 7. 调用被代理对象的方法
        Object result = method.invoke(target, args);
        // 8. 在代理方法后执行日志记录
        System.out.println("Exiting method: " + method.getName());
        // 9. 返回方法的结果
        return result;
    }
}

public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        // 10. 创建目标对象
        UserService userService = new UserServiceImpl();
        // 11. 创建代理处理器对象,并传入目标对象
        LogHandler logHandler = new LogHandler(userService);
        // 12. 使用Proxy类的newProxyInstance方法创建代理对象
        //    参数包括类加载器、目标接口、和代理处理器
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                logHandler);

        // 13. 调用代理对象的方法
        proxy.saveUser("Alice");
    }
}

在这个示例中,JDK动态代理通过Proxy.newProxyInstance方法创建了代理对象,该代理对象实现了UserService接口,并在代理方法中通过LogHandler处理器实现了日志记录。当调用代理对象的saveUser方法时,代理处理器的invoke方法会执行前后的日志记录,然后调用目标对象的方法。这允许你在不修改目标对象的情况下添加额外的行为,例如日志记录。

代理模式是什么?是设计模式吗?创建代理对象的目的是什么?

Java JDK动态代理是一种代理模式,它允许你在运行时为一个或多个接口动态创建代理实例,而不需要提前知道这些接口的具体实现类。在这个示例中,我们将使用JDK动态代理来为UserService接口创建代理,并在代理方法中添加日志记录。

下面是代码的逐行注释和代码原理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 创建一个接口,这将是代理的目标接口
interface UserService {
    void saveUser(String username);
}

// 2. 创建一个实现目标接口的具体类
class UserServiceImpl implements UserService {
    public void saveUser(String username) {
        System.out.println("Saving user: " + username);
    }
}

// 3. 创建一个实现InvocationHandler接口的代理处理器类
class LogHandler implements InvocationHandler {
    private Object target;

    // 4. 构造函数,接收目标对象
    public LogHandler(Object target) {
        this.target = target;
    }

    // 5. invoke方法在代理对象上调用被代理的方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 6. 在代理方法前执行日志记录
        System.out.println("Entering method: " + method.getName());
        // 7. 调用被代理对象的方法
        Object result = method.invoke(target, args);
        // 8. 在代理方法后执行日志记录
        System.out.println("Exiting method: " + method.getName());
        // 9. 返回方法的结果
        return result;
    }
}

public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        // 10. 创建目标对象
        UserService userService = new UserServiceImpl();
        // 11. 创建代理处理器对象,并传入目标对象
        LogHandler logHandler = new LogHandler(userService);
        // 12. 使用Proxy类的newProxyInstance方法创建代理对象
        //    参数包括类加载器、目标接口、和代理处理器
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                logHandler);

        // 13. 调用代理对象的方法
        proxy.saveUser("Alice");
    }
}

在这个示例中,JDK动态代理通过Proxy.newProxyInstance方法创建了代理对象,该代理对象实现了UserService接口,并在代理方法中通过LogHandler处理器实现了日志记录。当调用代理对象的saveUser方法时,代理处理器的invoke方法会执行前后的日志记录,然后调用目标对象的方法。这允许你在不修改目标对象的情况下添加额外的行为,例如日志记录。

代理模式是一种设计模式,它允许一个对象(代理对象)代表另一个对象(真实对象)来控制对该对象的访问。代理模式是一种结构型设计模式,它通常涉及到创建一个代理对象,该代理对象充当客户端和真实对象之间的中介,从而可以在访问真实对象时添加额外的功能或控制。

代理模式的主要目的包括:

  1. 控制访问:代理对象可以限制客户端对真实对象的直接访问,以便进行权限检查或认证。

  2. 增加额外功能:代理对象可以在调用真实对象的方法前后添加额外的操作,如日志记录、性能监视、事务管理等。

  3. 延迟加载:代理对象可以延迟加载真实对象,只有在需要时才实际创建和初始化真实对象,从而提高性能。

  4. 实现懒加载:代理对象可以在真正需要时才加载真实对象,以减少资源消耗。

  5. 实现远程代理:代理对象可以代表远程对象,允许客户端通过网络访问远程服务。

  6. 实现虚拟代理:代理对象可以代表大对象,只在需要时加载和显示部分数据,以减少内存占用。

代理模式通常涉及两种角色:

  1. 客户端:客户端是与代理对象交互的类,它不直接访问真实对象,而是通过代理对象来访问真实对象。

  2. 代理对象:代理对象实现了与真实对象相同的接口或继承相同的父类,它充当了客户端和真实对象之间的中介。代理对象通常包含对真实对象的引用,并根据需要在调用真实对象的方法前后执行一些额外操作。

  3. 真实对象:真实对象是代理对象的背后实现,它执行真正的业务逻辑。客户端在访问真实对象时通过代理对象进行间接访问。

代理模式有多种变体,包括静态代理、动态代理、远程代理、虚拟代理等,可以根据不同的需求选择合适的变体。

总之,代理模式是一种有助于控制对对象访问并添加额外功能的设计模式,它在很多应用中都有广泛的用途,如日志记录、权限管理、性能监视等。

远程代理实例

代理模式在现实世界中的应用非常广泛。一个常见的例子是网络代理服务器。网络代理服务器充当客户端和互联网服务器之间的中介,用于执行各种功能,如缓存、安全过滤、加密、日志记录等。下面是一个简单的 Java 示例,演示如何创建一个网络代理服务器的代理模式应用:

// 接口:定义网络访问的方法
interface Network {
    void connect(String serverHost);
}

// 真实网络连接类
class RealNetwork implements Network {
    @Override
    public void connect(String serverHost) {
        System.out.println("连接到服务器:" + serverHost);
    }
}

// 代理类:网络代理服务器
class NetworkProxy implements Network {
    private Network realNetwork;
    private String proxyServer;

    public NetworkProxy(String proxyServer) {
        this.proxyServer = proxyServer;
        this.realNetwork = new RealNetwork();
    }

    @Override
    public void connect(String serverHost) {
        if (isBlocked(serverHost)) {
            System.out.println("访问被拒绝,代理服务器阻止连接到 " + serverHost);
        } else {
            realNetwork.connect(proxyServer); // 通过代理服务器连接
            System.out.println("代理服务器连接到 " + serverHost);
        }
    }

    private boolean isBlocked(String serverHost) {
        // 在实际应用中,可以实现更复杂的逻辑来检查是否阻止访问特定服务器
        return serverHost.contains("blocked");
    }
}

public class ProxyPatternExample {
    public static void main(String[] args) {
        NetworkProxy proxy = new NetworkProxy("proxy-server.com");
        proxy.connect("example.com");  // 正常连接
        proxy.connect("blocked-server.com");  // 阻止连接
    }
}

在这个示例中,我们定义了一个 Network 接口,包括 connect 方法。然后,我们创建了一个 RealNetwork 类来实现真正的网络连接,以及一个 NetworkProxy 类来实现代理服务器。

当客户端通过代理服务器连接到不同的服务器时,代理服务器会检查服务器的主机名是否被阻止。如果被阻止,代理服务器将拒绝连接。否则,它会通过代理服务器连接到目标服务器。

这个示例演示了代理模式在网络代理服务器中的应用,充分说明了代理模式如何充当中介来控制访问和添加额外功能。请注意,在实际网络代理中,代理服务器通常会执行更多复杂的操作,如安全认证、缓存和数据压缩。

如果还是不明白的宝宝们,可以看看下面的类比法:
当使用JDK动态代理时,你需要了解它的实现原理,即如何通过InvocationHandler接口、Proxy类和反射机制来创建动态代理。让我通过一个比喻和示例来解释它:

比喻
想象一家快递公司(代理工厂),你作为客户(客户端代码),需要发送包裹(方法调用请求)。这家快递公司没有自己的送货员(实际的业务逻辑类),但他们可以雇佣送货员来为你送货。为了使用这家快递公司,你需要提供一份送货清单(接口),快递公司会根据这份清单雇佣合适的送货员来完成送货任务。

示例

  1. 客户(客户端代码)需要一个送货服务,但他们不关心送货员是谁。
  2. 客户(客户端代码)提供了一份送货清单(接口),描述了送货员应该提供的送货服务(方法)。
  3. 代理工厂(JDK动态代理)通过使用这份清单(接口)来雇佣一个送货员(代理对象),并为送货员指定了一个送货地址(InvocationHandler)。
  4. 当客户(客户端代码)需要送货服务时,他们将请求发给快递公司(代理对象),代理对象将请求传递给送货员(实际的业务逻辑类)。
  5. 送货员(实际的业务逻辑类)执行送货任务,并在送货前后添加特殊服务,如记录送货时间。
  6. 送货员(实际的业务逻辑类)完成送货任务后,代理对象返回结果给客户(客户端代码)。

这个比喻可以帮助你理解JDK动态代理的工作原理。客户端代码定义了接口,代理工厂根据这个接口创建代理对象,并将方法调用委托给实际的业务逻辑类。代理对象在调用前后可以执行额外的操作,就像快递公司可以在送货前后添加特殊服务。

以下是Java代码示例,演示了如何使用JDK动态代理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义接口,代表送货服务
interface DeliveryService {
    void deliverPackage(String address);
}

// 实际的送货服务类,实现了DeliveryService接口
class RealDeliveryService implements DeliveryService {
    public void deliverPackage(String address) {
        System.out.println("Delivering package to " + address);
    }
}

// 代理处理器,负责在送货前后提供特殊服务
class SpecialServiceHandler implements InvocationHandler {
    private DeliveryService realDeliveryService;

    public SpecialServiceHandler(DeliveryService realDeliveryService) {
        this.realDeliveryService = realService;
    }

    // 这个方法在代理对象的方法调用时被调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在送货前提供特殊服务
        System.out.println("Providing special service before delivery.");
        // 调用实际送货服务类的相应方法
        Object result = method.invoke(realDeliveryService, args);
        // 在送货后提供特殊服务
        System.out.println("Providing special service after delivery.");
        return result;
    }
}

public class ProxyExample {
    public static void main(String[] args) {
        // 创建实际的送货服务对象
        DeliveryService realService = new RealDeliveryService();
        // 创建代理处理器,传入实际的送货服务对象
        InvocationHandler handler = new SpecialServiceHandler(realService);
        
        // 创建代理对象,将会在送货前后提供特殊服务
        DeliveryService proxy = (DeliveryService) Proxy.newProxyInstance(
            DeliveryService.class.getClassLoader(),
            new Class[] { DeliveryService.class },
            handler
        );

        // 使用代理对象调用送货方法
        proxy.deliverPackage("123 Main Street");
    }
}

在这个示例中,DeliveryService接口充当了送货清单,RealDeliveryService是实际的送货员,SpecialServiceHandler是代理对象的送货员,代理对象在送货前后提供特殊服务。客户端代码通过代理对象调用deliverPackage方法,代理对象在实际的业务逻辑类上执行这个方法,然后添加特殊服务。这就是JDK动态代理的实现方式。

在面向对象编程中,子类(派生类)被重写(覆盖)和不被重写(不覆盖)有很大区别,特别涉及到继承和多态性的概念。

  1. 子类被重写(覆盖):

    当子类重写(覆盖)了父类的方法时,子类提供了一个新的实现来替代父类的原始方法。这意味着在运行时,如果我们使用子类的对象调用这个方法,将执行子类中的实现,而不是父类中的实现。

    例如:

    class Animal {
        public void makeSound() {
            System.out.println("Some generic animal sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("Woof!");
        }
    }
    

    如果你创建了一个 Dog 对象并调用 makeSound 方法,它将打印 “Woof!”,而不是 “Some generic animal sound”。

  2. 子类不被重写(不覆盖):

    当子类不重写父类的方法,子类将继承父类的方法实现。这意味着在运行时,如果我们使用子类的对象调用这个方法,将执行父类中的实现。

    例如:

    class Animal {
        public void makeSound() {
            System.out.println("Some generic animal sound");
        }
    }
    
    class Dog extends Animal {
        // Dog 类没有重写 makeSound 方法
    }
    

    如果你创建了一个 Dog 对象并调用 makeSound 方法,它将打印 “Some generic animal sound”,因为 Dog 没有提供自己的实现。

区别和注意事项:

  • 被重写的方法允许子类提供不同的实现,实现方法多态性,而不被重写的方法继承父类的实现。
  • 子类可以选择性地重写父类的方法。如果没有提供重写,将使用父类的实现。
  • 在 Java 中,使用 @Override 注解可以明确表示方法被重写,这有助于提高代码的可读性和可维护性。
  • 当涉及到多态性时,调用哪个方法实现取决于对象的类型,而不是变量的类型。这是面向对象编程的一个关键概念。
  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕昀hui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值