Java基础:一些常用代码片二

在 Java 开发中,我们经常需要进行字符串的拼接和处理集合中的元素。此外,在某些特定场景下,如性能监控或代码调试时,也需要查看 GC 日志以及字节码信息。本文将详细介绍这些常用技术及其最佳实践。

字符串拼接方法

使用 org.apache.commons.lang.StringUtils 拼接

1
2
3
import org.apache.commons.lang3.StringUtils;

String result = StringUtils.join(array, "-");

该方法使用指定的分隔符(如这里的’-‘)将数组中的元素连接成一个字符串。

使用 Google Guava 的 Joiner

1
2
import com.google.common.base.Joiner;
String result = Joiner.on('-').join(array);

Guava 库提供的Joiner类可以更加灵活地拼接字符串,支持多种分隔符和自定义转换函数。

获取集合中的第一个元素

使用 Iterables.getOnlyElement 方法

1
2
import com.google.common.collect.Iterables;
Object first = Iterables.getOnlyElement(collection);

该方法用于确保集合中只有一个元素时获取该元素,如果多个或为空则会抛出异常。适用于确认集合内容的情况下。

获取第一个元素并允许指定默认值

1
Object firstOrDefault = Iterables.getFirst(collection, defaultValue);

Iterables.getFirst 方法允许在集合为空时返回一个默认值,增加了代码的灵活性和健壮性。

直接从迭代器中获取第一个元素

1
Object first = collection.iterator().next();

直接使用iterator()并调用next()是最基本的方法。适用于快速而简单的场景。

查看 GC 日志

为了调试应用程序中的内存问题,可以通过以下参数启动 Java 虚拟机(JVM)以查看详细的垃圾收集(GC)活动:

1
java -XX:+PrintGCDetails -jar yourApp.jar

此命令会输出每次 GC 事件的详细信息。

查看字节码

为了更好地理解类的工作原理或者用于反编译目的,可以使用javap工具查看类文件的内容。例如:

1
javap -c class_path

集合操作:并集、交集和差集等

示例代码展示集合的各种操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("B");
list1.add("C");

List<String> list2 = new ArrayList<>();
list2.add("C");
list2.add("B");
list2.add("D");

// 并集
List<String> unionList = new ArrayList<>(list1);
unionList.addAll(list2);

// 去重复并集
Set<String> tempSet = new HashSet<>();
tempSet.addAll(unionList);
unionList.clear();
unionList.addAll(tempSet);

System.out.println("去重后的并集: " + unionList);

// 交集
List<String> intersectionList = new ArrayList<>(list1);
intersectionList.retainAll(list2);
System.out.println("交集: " + intersectionList);

// 差集
List<String> diffList = new ArrayList<>(list1);
diffList.removeAll(list2);
System.out.println("差集: " + diffList);
}

通过枚举属性获取枚举实例

枚举示例代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
enum Gender {
MALE(0),
FEMALE(1);

private int code;

private static final Map<Integer, Gender> MAP = new HashMap<>();

static {
for (Gender item : Gender.values()) {
MAP.put(item.code, item);
}
}

public int getCode() {
return code;
}

private Gender(int code) {
this.code = code;
}
}

public class EnumUtil {

@SuppressWarnings("unchecked")
public static <E extends Enum<?>> E getByCode(Class<E> enumClass, int code) {
try {
for (E e : enumClass.getEnumConstants()) {
Field field = e.getClass().getField("code");
Object value = field.get(e);
if (value.equals(code)) {
return e;
}
}
} catch (Exception ex) {
throw new RuntimeException("根据code获取枚举实例异常" + enumClass.getName() + " code:" + code, ex);
}
return null;
}

public static void main(String[] args) {
System.out.println(EnumUtil.getByCode(Gender.class, 0));
}
}

国际化处理

使用 .properties 文件实现国际化

在 Java 程序中,我们可以通过创建和使用.properties文件来支持多种语言的国际化的操作。这些文件通常包含键值对的形式存储文本信息,而当需要特定语言版本时,则加载相应的.properties文件。

.properties文件中文字符与 Unicode 字符之间的转换

  • 使用native2ascii.exe工具可以将.properties文件中的中文字符转为 Unicode 格式。
    1
    2
    native2ascii resources.properties tmp.properties 或者
    native2ascii -encoding Unicode resources.properties tmp.properties
  • 若需要再将其转化为中文,使用如下命令:
    1
    native2ascii -reverse -encoding GB2312 resources.properties tmp.properties

switch vs. if-else

性能比较

在 Java 中,switch-case结构常用于多分支选择。它与if-else语句在功能上有一定的重叠。然而,在实际应用时,两者在效率上存在区别:

  1. 二叉树算法switch使用了 Binary Tree 算法而if-else顺序比较。
  2. 跳转表生成:对于多个分支的场景下(大于等于四个),switch会创建一个最大 case 常量+1 大小的 jump table,这比简单的线性查找效率更高。

反射与注解

获取方法上的注解

从 Java 7 开始,可以通过反射机制获取类上或方法上的注解信息。下面展示了在 JDK 版本为 7 和 8 时的不同代码实现:

1
2
3
4
5
// JDK7方式:
RequestMapping methodDeclaredAnnotation = method.getAnnotation(RequestMapping.class);

// JDK8方式:
RequestMapping methodDeclaredAnnotation = method.getDeclaredAnnotation(RequestMapping.class);

阅读文件中的内容

使用 Java 标准库可以轻松从资源或文件中读取数据,例如:

1
2
3
4
InputStream stream = this.getClass().getClassLoader().getResourceAsStream("test.md");
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "utf-8"));
List<String> list = reader.lines().collect(Collectors.toList());
String content = Joiner.on("\n").join(list);

Java 枚举类的特性

为什么Enum不能被继承?

在 Java 中,当一个类使用enum关键字定义后,默认会继承java.lang.Enum。这意味着该枚举类型是不可再扩展的,并且会被编译器标记为 final 以防止任何子类型的创建。

Timer 类高级用法

六种执行任务的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerUtil {
public static void main(String[] args) throws Exception {
System.out.println(new Date().getTime() + "-------开始定时任务--------");
timer1();
timer2();
timer3();
timer4();
timer5();
timer6();
}
// 第一种方法:指定任务task在指定时间time执行
// schedule(TimerTask task, Date time)
public static void timer1() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0); // 控制时
calendar.set(Calendar.MINUTE, 0); // 控制分
calendar.set(Calendar.SECOND, 0); // 控制秒
Date time = calendar.getTime(); // 得出执行任务的时间,此处为今天的00:00:00
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务1--------");
}
}, time);
}
// 第二种方法:指定任务task在指定延迟delay后执行
// schedule(TimerTask task, long delay)
public static void timer2() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务2--------");
}
}, 2000); // 指定延迟2000毫秒后执行
}
// 第三种方法:指定任务task在指定时间firstTime执行后,进行重复固定延迟频率period的执行
// schedule(TimerTask task, Date firstTime, long period)
public static void timer3() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0); // 控制时
calendar.set(Calendar.MINUTE, 0); // 控制分
calendar.set(Calendar.SECOND, 0); // 控制秒
Date time = calendar.getTime(); // 得出执行任务的时间,此处为今天的00:00:00
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务3--------");
}
}, time, 1000 * 60 * 60 * 24);
}
// 第四种方法:指定任务task在指定延迟delay后,进行重复固定延迟频率period的执行
// schedule(TimerTask task, long delay, long period)
public static void timer4() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务4--------");
}
}, 1000, 5000);
}
// 第五种方法:指定任务task在指定时间firstTime执行后,进行重复固定延迟频率period的执行
// scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
public static void timer5() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0); // 控制时
calendar.set(Calendar.MINUTE, 0); // 控制分
calendar.set(Calendar.SECOND, 0); // 控制秒
Date time = calendar.getTime(); // 得出执行任务的时间,此处为今天的00:00:00
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务5--------");
}
}, time, 1000 * 60 * 60 * 24);
}
// 第六种方法:指定任务task在指定延迟delay后,进行重复固定延迟频率period的执行
// scheduleAtFixedRate(TimerTask task, long delay, long period)
public static void timer6() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(new Date().getTime() + "-------定时任务6--------");
}
}, 1000, 2000);
}
}

Cron 表达式详解

Cron 表达式的格式由至少 6 个部分组成,每一部分使用空格分隔:

  1. 秒 (0-59)
  2. 分钟 (0-59)
  3. 小时 (0-23)
  4. 日期 (0-31)
  5. 月份 (0-11 或 JAN-DEC)
  6. 星期几 (0-7 或 SUN-SAT,其中 0 和 7 都表示周日)

常用实例

每隔若干时间执行

  • 每隔 5 秒:*/5 * * * * ?
  • 每隔 1 分钟:0 */1 * * * ?

定时任务

  • 每天 23 点执行:0 0 23 * * ?
  • 每日凌晨 1 点执行:0 0 1 * * ?
  • 每月 1 号凌晨 1 点执行:0 0 1 1 * ?

复杂任务调度

  • 每天的 0 点、13 点、18 点和 21 点触发一次任务:0 0 0,13,18,21 * * ?
  • 在每天上午 10:15 执行:0 15 10 * * ?

Cron 表达式高级用法

  • 每月最后一天晚上 11 点触发一次任务:0 0 23 L * ?
  • 每年的某个特定星期几(如每周三)的某时执行:0 0 12 ? * WED

特殊字符的意义

  • *: 所有可能值。
  • ,: 分隔列表中的多个值。
  • -: 表示区间,如 6-8/2 表示在第 6、8 分钟时执行任务。
  • /: 指定增量步长。

解决java.lang.OutOfMemoryError: PermGen space

常见的解决方案

1
-server -XX:PermSize=256M -XX:MaxPermSize=512m

这行命令可以通过调整永久代(Permanent Generation)的空间大小来解决内存溢出问题。

使用 CXF 生成客户端代码

Apache CXF 提供了 wsdl2java 工具用于从 WSDL 文件自动生成 Java 客户端代码。其基本使用方法如下:

1
/opt/apache-cxf-3.2.5/bin/wsdl2java -client -encoding utf-8 'http://172.16.4.207/iavpwebservice/CallOut.asmx?wsdl'

JDK 8 中推荐使用的日期处理类

JDK 8 引入了新的日期和时间 API,包括Instant, LocalDateTime, 和 DateTimeFormatter等。这些新工具比旧的DateCalendar更强大且易于使用。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 得到小时
private int getHour(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.HOUR_OF_DAY);
}

// 得到分钟
private int getMinute(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.MINUTE);
}

// 设置小时
private Date setHour(int hour, Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, hour);
return calendar.getTime();
}

// 设置分钟
private Date setMinute(int minute, Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.set(Calendar.MINUTE, minute);
return calendar.getTime();
}

打印 GC 详情

为了能够获得详细的 GC 信息,包括每次垃圾回收的时间、持续时间和应用程序暂停时间等细节数据,可以在启动 JVM 时添加以下选项:

1
2
3
4
5
6
7
8
java -XX:+PrintGCTimeStamps
-XX:-PrintClassHistogram
-XX:+PrintHeapAtGC
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+HeapDumpOnOutOfMemoryError

这些选项的作用分别是:

  1. -XX:+PrintGCTimeStamps - 每次垃圾回收都记录时间戳;
  2. -XX:-PrintClassHistogram - 打印类的直方图(通常在 Full GC 后打印);
  3. -XX:+PrintHeapAtGC - 在每次垃圾回收之前输出堆信息;
  4. -verbose:gc - 输出简要的 GC 日志信息;
  5. -XX:+PrintGCDetails - 打印详细的 GC 日志。

移除字符串中的所有空格

在处理文本数据时,经常需要去除不必要的空白字符。以下是几种有效的移除方法:

使用 Apache Commons Lang 库

最简单直接的方法是使用第三方库如 Apache Commons Lang 的StringUtils.deleteWhitespace()函数。

1
2
3
4
5
6
7
import org.apache.commons.lang3.StringUtils;

public class StringProcessor {
public static void main(String[] args) {
System.out.println(StringUtils.deleteWhitespace(" a b c d "));
}
}

手动实现去除所有空格

  1. 使用正则表达式替换:

    1
    2
    3
    4
    5
    6
    public class MainClass {
    public static void main(String[] args) {
    String str = " hell o ";
    System.out.println(str.replaceAll("\\s+", ""));
    }
    }
  2. 遍历字符串中的每个字符,并使用StringBuilder构建新字符串:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MainClass {
    public static void main(String[] args) {
    String str = " a b c d ";
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < str.length(); i++) {
    if (!Character.isWhitespace(str.charAt(i))) {
    sb.append(str.charAt(i));
    }
    }
    System.out.println(sb.toString());
    }
    }
  3. 使用replaceAll()方法处理连续的空格:

    1
    2
    3
    4
    5
    6
    7
    public class MainClass {
    public static void main(String[] args) {
    String str = " hell o ";
    str = str.replaceAll(" +", "");
    System.out.println(str);
    }
    }

遍历 Properties 对象

Properties类提供了多种遍历其内部数据的方式,以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void printProp(Properties properties) {
System.out.println("---------(方式一)------------");
for (String key : properties.stringPropertyNames()) {
System.out.println(key + "=" + properties.getProperty(key));
}

System.out.println("---------(方式二)------------");
Set<Object> keys = properties.keySet();
for (Object key : keys) {
System.out.println(key.toString() + "=" + properties.get(key));
}

System.out.println("---------(方式三)------------");
Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();
for (Map.Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}

System.out.println("---------(方式四)------------");
Enumeration<?> e = properties.propertyNames();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = properties.getProperty(key);
System.out.println(key + "=" + value);
}
}