2024-08-14

在Java中,封装、继承和多态是面向对象编程的三个主要特性。

  1. 封装

    封装是将对象的状态(数据)和行为(方法)打包在一起,隐藏对象的内部实现细节,只提供公开的接口(getter和setter方法)供外部访问。




public class Person {
    private String name;
    private int age;
 
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
  1. 继承

    继承是子类继承父类的特性(包括数据和方法),并且可以添加自己的特性。




public class Employee extends Person {
    private double salary;
 
    public Employee(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }
 
    public double getSalary() {
        return salary;
    }
 
    public void setSalary(double salary) {
        this.salary = salary;
    }
}
  1. 多态

    多态是同一个行为具有多个不同表现形式或形态的能力,多态性分为编译时多态性和运行时多态性。

编译时多态是通过方法重载实现的,运行时多态是通过方法重写实现的。




// 运行时多态 - 方法重写
public class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}
 
public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks.");
    }
}
 
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 向上转型
        animal.makeSound(); // 输出 "Dog barks."
    }
}

以上代码展示了多态的一个简单例子。在Main类的main方法中,创建了一个Dog类型的对象,但是将其向上转型为Animal类型。调用makeSound方法时,实际执行的是Dog类中重写的makeSound方法。这就是多态的一个运行时表现。

2024-08-14

在Spring Boot项目中,你可以使用Apache POI库来给Word文档添加水印。以下是一个简单的例子,演示如何实现这一功能:

首先,确保你的pom.xml中包含了Apache POI的依赖:




<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>YOUR_POI_VERSION</version>
</dependency>

然后,你可以使用以下代码来给Word文档添加水印:




import org.apache.poi.xwpf.usermodel.*;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
 
public class WordWatermark {
    public static void main(String[] args) throws Exception {
        // 加载现有的Word文档
        FileInputStream fis = new FileInputStream("path/to/your/document.docx");
        XWPFDocument document = new XWPFDocument(fis);
 
        // 创建水印对象
        XWPFParagraph paragraph = document.createParagraph();
        XWPFRun run = paragraph.createRun();
        run.setText("水印文字");
        run.setFontSize(20); // 设置水印字体大小
        run.setColor("FFFFFF"); // 设置水印字体颜色为白色
        run.setUnderline(UnderlinePatterns.DASH); // 设置下划线
 
        // 将水印设置为背景
        XWPFShape shape = paragraph.createRun().getCTR().addNewDrawing().addNewInline().addNewGraphic().addNewGraphicData().addNewPic();
        shape.getPic().getSpPr().addNewEffectDag().addNewBlipFill().addNewBlip().setEmbed(run.getEmbeddedPictures().get(0).getPackageRelationship().getId());
        shape.setAnchor(new XWPFPictureData(run.getEmbeddedPictures().get(0).getPackageRelationship().getId(), run.getEmbeddedPictures().get(0).getData()));
 
        // 输出文档
        FileOutputStream out = new FileOutputStream("path/to/output/document-with-watermark.docx");
        document.write(out);
        out.close();
        document.close();
    }
}

在这个例子中,我们首先加载了一个现有的Word文档。然后,我们创建了一个段落和一个运行时的实例,并在其中设置了我们想要的水印文本。接着,我们设置了水印的样式,比如字体大小、颜色和下划线。最后,我们通过XWPFShape将水印作为背景图片添加到文档中。

请注意,你需要替换path/to/your/document.docxpath/to/output/document-with-watermark.docx为你的实际文件路径。

这个代码示例是一个基本的实现,你可以根据自己的需求进一步调整水印的样式和位置。

2024-08-14

在Java中,new 关键字用于创建对象的实例,而 clone() 方法用于创建一个对象的浅拷贝。以下是两者的区别和使用示例:

  1. new 关键字:

    使用 new 关键字时,会创建一个新的对象,并执行对象的构造函数。每次使用 new 都会创建一个新的实例。




MyClass obj = new MyClass();
  1. clone() 方法:

    使用 clone() 方法时,它创建一个新对象,并将原始对象的内容复制到新对象中。clone() 方法需要在类中被重写,并实现 Cloneable 接口。




MyClass obj = new MyClass();
MyClass copy = null;
try {
    copy = obj.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

区别:

  • new 创建新对象,每次都会调用构造函数。
  • clone() 创建对象拷贝,可以避免调用构造函数,直接复制对象状态。
  • clone() 方法要求实现 Cloneable 接口并覆盖 clone() 方法。
  • clone() 可能会抛出 CloneNotSupportedException,需要处理。

注意:从 Java 9 开始,clone() 方法已被标记为不推荐使用,并可能在未来的版本中移除。如果需要复制对象,可以考虑使用对象序列化或者构造函数复制对象状态的替代方法。

2024-08-14

在Java中,数据长度获取方式主要取决于数据结构。以下是三种常见的获取数据长度的方式:

  1. 数组:使用.length属性获取数组长度。



int[] array = new int[10];
int length = array.length; // 长度为10
  1. 字符串:使用.length()方法获取字符串长度。



String str = "Hello";
int length = str.length(); // 长度为5
  1. 集合:使用.size()方法获取集合长度。



List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
int size = list.size(); // 长度为2

每种数据结构都有其特定的获取长度的方式,数组使用.length,字符串使用.length(),集合使用.size()

2024-08-14

Java IO流提供了多种字节和字符流类,用于读写数据到文件、网络或其他数据源。

字节流:

  • InputStream

    • FileInputStream
    • BufferedInputStream
  • OutputStream

    • FileOutputStream
    • BufferedOutputStream

字符流:

  • Reader

    • InputStreamReader
    • BufferedReader
  • Writer

    • OutputStreamWriter
    • BufferedWriter

这里是一个简单的字节输入流示例,用于从文件中读取数据:




import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
 
public class FileReadExample {
    public static void main(String[] args) {
        String filePath = "example.txt";
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(filePath);
            int byteContent;
            while ((byteContent = inputStream.read()) != -1) {
                System.out.print((char) byteContent);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

对于字符输出流,以下是写入字符到文件的示例:




import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
 
public class FileWriteExample {
    public static void main(String[] args) {
        String filePath = "example.txt";
        OutputStream outputStream = null;
        Writer writer = null;
        try {
            outputStream = new FileOutputStream(filePath);
            writer = new OutputStreamWriter(outputStream);
            writer.write("Hello, World!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

以上代码展示了如何使用FileInputStreamFileOutputStream读取和写入文件,并使用OutputStreamWriterInputStreamReader在字节和字符之间转换。在实际应用中,你可能会发现使用BufferedInputStream, BufferedOutputStream, BufferedReader, 和 BufferedWriter 提供了更好的性能,尤其是在处理大量数据时。

2024-08-14

代理模式是常用的设计模式之一,其定义如下:给某个对象提供一个代理或者占位符,并由代理对象来控制对原对象的访问。

在Java中,代理模式通常包含三个要素:

  1. 接口(Interface):定义代理类和真实主题类的公共接口,这样就可以在任何使用真实对象的地方使用代理对象。
  2. 真实主题类(RealSubject):实现接口,定义了代理类所代表的真实对象。
  3. 代理类(Proxy):也实现了接口,代理对象包含对真实主题对象的引用,并且可以在对真实主题对象的操作前后进行一些处理,比如访问控制和缓存等。

以下是一个简单的代理模式示例:




// 接口
public interface Subject {
    void request();
}
 
// 真实主题类
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling the request.");
    }
}
 
// 代理类
public class Proxy implements Subject {
    private RealSubject realSubject;
 
    @Override
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.request(); // 调用真实主题的方法
        postRequest();
    }
 
    private void preRequest() {
        System.out.println("Pre-processing before request.");
    }
 
    private void postRequest() {
        System.out.println("Post-processing after request.");
    }
}
 
// 客户端
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.request();
    }
}

在这个例子中,Proxy类作为RealSubject的代理,在调用RealSubjectrequest方法前后进行了预处理和后处理。这样的设计模式在需要增加额外处理的情况下非常有用,比如权限控制、事务管理、日志记录等。

2024-08-14

Java Bean的生命周期通常指的是它被Java环境(如Java EE容器,例如JBoss、Tomcat)创建、初始化、使用和销毁的过程。以下是Java Bean生命周期的几个关键阶段:

  1. 实例化:Java Bean通常是通过反射进行实例化的,这是通过类的默认构造器完成的。
  2. 属性设置:在Bean实例化后,可能需要设置一些属性,例如依赖注入。
  3. 初始化:一旦所有必要的属性都被设置,Bean可以通过调用void ejbCreate()方法(在EJB中)或其他初始化方法来进行初始化。
  4. 业务方法调用:Bean现在可以接收来自外部调用的业务方法了。
  5. 销毁:当Bean不再需要时,它可以被销毁,在EJB中通常是由容器控制。

以下是一个简单的Java Bean的生命周期示例代码:




public class SimpleBean {
    private String dependency;
 
    // 默认构造器
    public SimpleBean() {
        System.out.println("Bean 实例化");
    }
 
    // 设置依赖项的setter方法
    public void setDependency(String dependency) {
        this.dependency = dependency;
        System.out.println("依赖项设置为: " + dependency);
    }
 
    // Bean初始化方法
    public void init() {
        System.out.println("Bean 初始化");
    }
 
    // 业务方法
    public void businessMethod() {
        System.out.println("业务方法调用");
    }
 
    // Bean销毁方法
    public void destroy() {
        System.out.println("Bean 销毁");
    }
}

在这个例子中,SimpleBean 展示了一个Java Bean的生命周期,包括实例化、属性设置、初始化、业务方法调用以及可能的销毁。这是一个非常基础的例子,实际的Bean可能会涉及更复杂的生命周期,包括各种依赖注入方法、上下文和事务管理等。

2024-08-14

在Java中,创建线程的传统方式是定义一个类,该类继承Thread类并重写run方法。然后创建这个类的实例,即创建了线程。

以下是一个简单的例子:




public class MyThread extends Thread {
    public void run() {
        System.out.println("线程正在运行...");
    }
}
 
public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

在上述代码中,MyThread类继承了Thread类并重写了run方法。在main方法中,我们创建了MyThread类的实例,并调用了start方法来启动线程,这会导致JVM调用run方法。

从Java 5开始,Java提供了通过Runnable接口创建线程的方式,这种方式不需要创建一个新的类继承Thread类,而是可以实现Runnable接口,并将线程的运行代码放在run方法中。

以下是使用Runnable接口的例子:




public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("线程正在运行...");
    }
}
 
public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.start();
    }
}

在这个例子中,我们创建了一个实现了Runnable接口的类MyRunnable,并将线程的运行代码放在了run方法中。然后我们创建了MyRunnable类的实例,并将其作为参数传递给Thread类的构造函数,最后通过调用start方法来启动线程。

在Java中,还可以使用CallableFuture接口来创建有返回值的线程。




import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
public class MyCallable implements Callable<String> {
    public String call() throws Exception {
        return "线程运行完毕";
    }
}
 
public class Main {
    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        Thread myThread = new Thread(futureTask);
        myThread.start();
        System.out.println(futureTask.get());
    }
}

在这个例子中,我们创建了一个实现了Callable接口的类MyCallable,并将线程的运行代码放在了call方法中。然后我们创建了MyCallable类的实例,并将其作为参数传递给FutureTask类的构造函数。之后我们创建了一个新的Thread实例,并将FutureTask对象作为参数传递给它的构造函数。最后,我们调用start方法来启动线程,并调用futureTask.get()来获取线程运行完毕后的返回值。

以上就是Java中创建线程的几种方式。

2024-08-14

在Java中,创建线程池有多种方式,主要依赖于Executors类提供的工厂方法。以下是创建线程池的7种方式,以及如何自定义线程池:

  1. 使用Executors.newCachedThreadPool()创建无限线程池。
  2. 使用Executors.newFixedThreadPool(int nThreads)创建固定大小的线程池。
  3. 使用Executors.newSingleThreadExecutor()创建单线程化的线程池。
  4. 使用Executors.newScheduledThreadPool(int corePoolSize)创建支持定时及周期性任务的线程池。
  5. 使用Executors.newWorkStealingPool()(JDK 1.8引入)创建自适应线程池。
  6. 使用ThreadPoolExecutor构造函数自定义线程池。
  7. 使用ForkJoinPool类创建分割任务的线程池(适用于使用ForkJoin框架的情况)。

以下是创建线程池的示例代码:




import java.util.concurrent.*;
 
public class ThreadPoolExample {
    public static void main(String[] args) {
        // 1. 无限线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
 
        // 2. 固定大小线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
 
        // 3. 单线程化线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
 
        // 4. 支持定时及周期性任务的线程池
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
 
        // 5. 自适应线程池(JDK 1.8+)
        ExecutorService workStealingPool = Executors.newWorkStealingPool();
 
        // 6. 使用ThreadPoolExecutor自定义线程池
        ThreadPoolExecutor customThreadPool = new ThreadPoolExecutor(
                5, // 核心线程数
                10, // 最大线程数
                60L, // 空闲时间60秒
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100), // 任务队列
                Executors.defaultThreadFactory(), // 线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
        );
 
        // 7. 使用ForkJoinPool创建分割任务的线程池(适用于使用ForkJoin框架的情况)
        ForkJoinPool forkJoinPool = new ForkJoinPool(4);
 
        // 关闭线程池
        // cachedThreadPool.shutdown();
        // fixedThreadPool.shutdown();
        // singleThreadExecutor.shutdown();
        // 
2024-08-14

在Java程序遇到OutOfMemoryError时,可以通过设置JVM参数来保存堆栈信息,并使用MAT(Memory Analyzer Tool)等工具进行分析。

  1. 启动Java程序时,可以通过设置-XX:+HeapDumpOnOutOfMemoryError参数来在OOM时保存堆内存快照:



java -XX:+HeapDumpOnOutOfMemoryError -jar your-application.jar
  1. 如果需要指定堆内存快照的保存路径,可以使用-XX:HeapDumpPath参数:



java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof -jar your-application.jar
  1. 当OOM发生后,会在指定的路径生成一个.hprof文件,可以使用MAT等工具打开这个文件进行分析。
  2. 使用MAT打开.hprof文件进行分析的基本步骤:

    • 打开MAT
    • 选择File > Open,然后选择.hprof文件
    • MAT会加载文件并展示内存使用情况的概览
    • 使用MAT提供的各种功能和视图来分析内存泄漏、确定对象保留在内存中的原因等

请注意,具体的解决方案取决于OOM发生的原因和MAT分析后确定的内存泄漏或者不合理的内存使用情况。