2024-08-13

在Java中,protectedfinal关键字可以用来修饰类成员(包括字段、方法),以控制其可见性和能否被覆盖或重新分配。

  1. protected关键字允许被修饰的成员在定义它的类的子类中被访问。如果不在同一个包中,那么只有通过子类的实例才能访问。
  2. final关键字意味着成员不能被覆盖或重写。对于字段,这通常意味着它是常量;对于方法,意味着它不能被覆盖。对于类,意味着它不能被继承。

protectedfinal一起使用时,意味着成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问,但不能被修改。

以下是一个简单的例子,演示了如何使用protectedfinal关键字:




package org.example;
 
public class Parent {
    protected final int VALUE = 10; // 可以被同包中的类访问和使用
 
    protected final void printValue() { // 可以被同包中的类访问,但不能被重写
        System.out.println(VALUE);
    }
}
 
package org.example;
 
public class Child extends Parent {
    public void demonstrate() {
        printValue(); // 正确,可以访问由父类保护的成员函数
        // VALUE++; // 错误,不能修改final字段的值
    }
}
 
public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.demonstrate(); // 输出 10
    }
}

在这个例子中,Child类继承自Parent类,可以访问并使用Parent中用protected final修饰的字段VALUE和方法printValue()。但是,Child类不能修改VALUE的值,因为它被声明为final

2024-08-13

在 IntelliJ IDEA 中将 Java 项目打包成一个可执行的 JAR 文件,你可以遵循以下步骤:

  1. 打开你的 Java 项目。
  2. 选择“File” > “Project Structure...”。
  3. 在“Project Structure”对话框中,选择“Artifacts”。
  4. 如果你的列表中还没有artifact,点击“+” > “JAR”,选择“From modules with dependencies...”。
  5. 在出现的对话框中,选择你的主模块。
  6. 在“Extract to the /META-INF/MANIFEST.MF file”部分,确保你有正确的主类配置。
  7. 配置你的 JAR 文件名和存储位置。
  8. 点击“OK”来保存你的 artifact 配置。
  9. 返回到主界面,选择“Build” > “Build Artifacts...”。
  10. 在“Build Artifacts”对话框中,选择你的 artifact,然后点击“Build”。
  11. 等待构建过程完成,你的 JAR 文件将会在你指定的输出目录中生成。

以下是一个简单的示例,演示如何在 IntelliJ IDEA 中创建一个 artifact:




// 假设这是你的主类,包含 main 方法
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

在 IntelliJ IDEA 中配置 artifact 的步骤:

  1. 打开“File” > “Project Structure...”。
  2. 在左侧菜单选择“Artifacts”。
  3. 点击“+” > “JAR” > “From modules with dependencies...”。
  4. 选择你的主模块(包含 main 方法的类所在的模块)。
  5. 在“Extract to the /META-INF/MANIFEST.MF file”部分,确保主类配置正确。
  6. 指定 JAR 文件名和输出目录。
  7. 点击“OK”保存配置。
  8. 构建 artifact:“Build” > “Build Artifacts...”,然后选择你的 artifact 并构建。

请注意,如果你的项目依赖于外部库,确保这些依赖也被包含在你的 JAR 包中。

2024-08-13

在Java中,BF(Brute Force)算法和KMP算法是用于字符串匹配的两种常见算法。

BF算法的核心思想是遍历主字符串,对每个字符开始向后比对是否与模式字符串匹配。

KMP算法的核心思想是通过一个next数组预处理模式字符串,当出现字符不匹配时,可以知道模式字符串中的哪些部分已经与主字符串匹配,可以直接跳过这些部分,从另一个位置开始比对。

以下是Java中实现这两种算法的示例代码:




// BF算法
public class BruteForce {
    public static int search(String txt, String pat) {
        int M = pat.length();
        int N = txt.length();
        for (int i = 0; i <= N - M; i++) {
            int j;
            for (j = 0; j < M; j++) {
                if (pat.charAt(j) != txt.charAt(i + j))
                    break;
            }
            if (j == M)
                return i;
        }
        return -1;
    }
}
 
// KMP算法
public class KMP {
    private int[] computePrefixFunction(String key) {
        int M = key.length();
        int[] prefix = new int[M];
        int j = 0;
        for (int i = 1; i < M; i++) {
            while (j > 0 && key.charAt(i) != key.charAt(j)) {
                j = prefix[j - 1];
            }
            if (key.charAt(i) == key.charAt(j)) {
                j++;
            }
            prefix[i] = j;
        }
        return prefix;
    }
 
    public int search(String txt, String pat) {
        int M = pat.length();
        int N = txt.length();
        int[] prefix = computePrefixFunction(pat);
        int j = 0;
        for (int i = 0; i < N; i++) {
            while (j > 0 && pat.charAt(j) != txt.charAt(i)) {
                j = prefix[j - 1];
            }
            if (pat.charAt(j) == txt.charAt(i)) {
                j++;
            }
            if (j == M) {
                return (i - M + 1);
            }
        }
        return -1;
    }
}

在这两个类中,search方法分别实现了BF和KMP算法来在主字符串txt中查找模式字符串pat的位置。如果找到,返回模式字符串在主字符串中的起始位置,如果未找到,返回-1。computePrefixFunction是KMP算法中用于计算模式字符串的next数组的辅助方法。

2024-08-13



// 二分查找算法
public class BinarySearch {
    public static int binarySearch(int[] arr, int x) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == x)
                return mid;
            if (arr[mid] > x)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return -1;
    }
 
    public static void main(String[] args) {
        int[] arr = {2, 3, 4, 10, 40, 100};
        int x = 100;
        int index = binarySearch(arr, x);
        if (index != -1)
            System.out.println("元素在数组中的索引为: " + index);
        else
            System.out.println("元素不在数组中");
    }
}
 
// 冒泡排序算法
public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j+1] 和 arr[j]
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
 
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        bubbleSort(arr);
        System.out.println("排序后的数组:");
        for (int val : arr)
            System.out.print(val + " ");
    }
}

这段代码展示了二分查找算法和冒泡排序算法的实现。二分查找算法用于在有序数组中查找特定元素的索引,冒泡排序算法用于对数组进行升序排序。这两个算法都是计算机科学中基础的算法知识,对于学习数据结构和算法有重要的意义。

2024-08-13

peek 方法是 Java 8 引入的流操作,它允许你在流的每个元素上执行一个简单的操作,同时它不会修改流中的元素。这个方法通常用于调试或者日志记录。

peek 方法的签名是这样的:




Stream<T> peek(Consumer<? super T> action)

这里的 Consumer<? super T> 是一个消费者函数式接口,你可以传递一个 lambda 表达式或方法引用来对流中的每个元素执行操作。

下面是一个使用 peek 方法的例子:




import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
 
public class PeekExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
 
        Stream<Integer> stream = numbers.stream();
 
        stream.peek(n -> System.out.println("Processing element: " + n))
              .forEach(n -> System.out.println("Element: " + n));
    }
}

在这个例子中,peek 方法被用来打印出流中每个元素的值,然后在 forEach 中再次打印出相同的值。这是为了演示 peek 方法可以用于调试目的。

注意,peek 方法不应该用于修改流中的元素,因为这可能会影响后续的流操作。如果你需要修改元素,应该使用 map 方法。

2024-08-13

@SneakyThrows 是 Lombok 库中的一个注解,用于在方法中自动捕获异常并转换为运行时异常(RuntimeException)。这样可以避免在使用 Lombok 的项目中必须在每个可能抛出异常的地方显式地添加 try-catch 块。

使用 @SneakyThrows 注解可以使代码更加简洁,但请注意,滥用这个注解可能会隐藏潜在的异常,所以应该谨慎使用,并确保你了解这可能对你的应用程序的健壮性产生负面影响。

以下是 @SneakyThrows 注解的使用方法:




import lombok.SneakyThrows;
 
public class Example {
 
    @SneakyThrows(UnsupportedOperationException.class)
    public void performOperation() {
        // 可能会抛出异常的代码
        throw new UnsupportedOperationException("Operation not supported");
    }
 
    public static void main(String[] args) {
        Example example = new Example();
        try {
            example.performOperation();
        } catch (RuntimeException e) {
            // 捕获到转换后的RuntimeException
            e.printStackTrace();
        }
    }
}

在这个例子中,performOperation 方法中抛出了 UnsupportedOperationException 异常,由于使用了 @SneakyThrows 注解,该异常被自动转换为 RuntimeException 并重新抛出,在 main 方法中被捕获。

2024-08-13

在Java 11之后,JRE(Java Runtime Environment)已经被移除,只保留了JDK(Java Development Kit)。这意味着在Java 11及以后版本中,你不会在JDK的安装目录下找到像以前版本那样的jre目录。

如果你需要设置环境变量以使用Java 11或更高版本,你只需要设置JAVA_HOME指向你的JDK安装目录,并且更新PATH变量以引用JDK中的bin文件夹。

以下是在Windows系统中设置JAVA\_HOME和PATH环境变量的步骤:

  1. 安装Java 11或更高版本的JDK。
  2. 打开“系统属性”(可以通过右击“此电脑”或“我的电脑”并选择“属性”来找到)。
  3. 点击“高级系统设置”。
  4. 在“系统属性”窗口中,点击“环境变量”。
  5. 在“系统变量”区域,点击“新建”以创建一个新的JAVA\_HOME变量,并设置变量值为你的JDK安装路径(例如:C:\Program Files\Java\jdk-11.0.1)。
  6. 在“系统变量”中找到名为“Path”的变量,并编辑它。
  7. 确保%JAVA_HOME%\bin(或者对应你JDK目录的完整路径)已经添加到Path变量的值中。
  8. 点击“确定”保存所有更改。

完成以上步骤后,你可以在命令行中输入java -version来验证设置是否成功。

这是一个示例,展示如何在命令行中手动设置这些变量(仅限当前会话):




set JAVA_HOME=C:\Program Files\Java\jdk-11.0.1
set PATH=%JAVA_HOME%\bin;%PATH%

请根据你的JDK安装路径和操作系统进行相应的调整。

2024-08-13

报错解释:

这个错误表明系统无法识别命令'java'。原因可能是'java'不在系统的环境变量PATH中,或者Java并未正确安装在你的计算机上。

解决方法:

  1. 确认Java是否安装:在命令行输入java -version,如果返回版本信息,则Java已安装。
  2. 如果未安装,前往Oracle官网下载并安装Java。
  3. 如果已安装,确保Java的安装目录下的bin目录已经添加到系统的PATH环境变量中。

    • 在Windows上,可以通过"系统属性" > "高级" > "环境变量"来编辑PATH变量,添加Java的bin路径。
    • 在Linux或macOS上,可以在.bashrc.bash_profile中添加如下行:export PATH=$PATH:/path/to/java/bin,然后执行source ~/.bashrcsource ~/.bash_profile使更改生效。
  4. 更改完成后,重新打开命令行窗口,再次输入java -version来验证是否配置成功。
  5. 如果PATH正确配置,但问题仍然存在,可能需要重启计算机。
2024-08-13

在Java中,可以使用以下五种方法来获取两个List集合的交集:

  1. 使用removeAll()方法
  2. 使用Java 8的stream()方法配合filter()方法
  3. 使用retainAll()方法
  4. 使用Collections.disjoint()方法检查是否有交集,然后使用addAll()方法
  5. 使用Apache Commons Collections的CollectionUtils.retainAll()方法

以下是每种方法的示例代码:

  1. 使用removeAll()方法:



List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7));
 
list1.removeAll(list2); // list1现在只包含1,2
  1. 使用Java 8的stream()方法配合filter()方法:



List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7));
 
List<Integer> intersection = list1.stream()
                            .filter(list2::contains)
                            .collect(Collectors.toList());
  1. 使用retainAll()方法:



List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7));
 
list1.retainAll(list2); // list1现在只包含3,4,5
  1. 使用Collections.disjoint()方法检查是否有交集,然后使用addAll()方法:



List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7));
 
if (!Collections.disjoint(list1, list2)) {
    list1.addAll(list2);
}
  1. 使用Apache Commons Collections的CollectionUtils.retainAll()方法:



List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7));
 
CollectionUtils.retainAll(list1, list2); // list1现在只包含3,4,5

注意:使用第5种方法之前需要添加Apache Commons Collections库依赖。

2024-08-13

《Effective Java》是一本由Joshua Bloch撰写的书,它提供了创建高质量Java代码的最佳实践。这本书被广泛认为是学习Java语言特性和最佳实践的必读书籍。

书中的内容非常深入,涵盖了诸如泛型、并发、错误处理等多个方面。以下是一些书中提到的重要概念和示例:

  1. 使用泛型来最大化代码复用性:



public interface Comparable<T> {
    public int compareTo(T that);
}
  1. 避免过度泛型化,只在需要时使用泛型:



public class Pair<T> {
    private final T first;
    private final T second;
 
    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }
 
    public T getFirst() { return first; }
    public T getSecond() { return second; }
 
    // 如果first和second已经是确定类型,则不需要泛型
    public static <T> Pair<T> of(T first, T second) {
        return new Pair<>(first, second);
    }
}
  1. 优先使用接口而不是抽象类:



// 接口提供了更好的抽象
public interface Action {
    void apply();
}
  1. 优先使用静态工厂方法而不是构造器,来提供类的实例:



public class Boolean {
    // 使用静态工厂方法而不是构造器
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
 
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
 
    private Boolean(boolean value) {
        // 私有构造器
    }
}
  1. 要小心使用异常,并尽可能地提供检查异常:



// 正确使用异常的例子
public void exampleMethod() throws MyCheckedException {
    try {
        // 可能抛出异常的代码
    } catch (MyRuntimeException e) {
        throw new MyCheckedException(e);
    }
}

这些只是书中众多实践的一小部分,每一条都能体现出Java编程的优雅和高效。要充分理解和掌握这些最佳实践,需要深入学习和实践Java编程。