阿里巴巴JAVA代码规范一【Block】

本文列举了Java编程中常见的代码缺陷及业务军规,包括数组、集合、线程池、数值运算等方面的问题,并提供了正例与反例以帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[Block] AlwaysThrows

类型

代码缺陷

描述

编译时检测使用字面量作为部分类的方法的参数进行调用时,必定会导致运行时抛出异常的方法调用。

这些方法包括:

// Time classes
java.time.Duration::parse,
java.time.Instant::parse,
java.time.LocalDate::parse,
java.time.LocalDateTime::parse,
java.time.LocalTime::parse,
java.time.MonthDay::parse,
java.time.OffsetDateTime::parse,
java.time.OffsetTime::parse,
java.time.Period::parse,
java.time.Year::parse,
java.time.YearMonth::parse,
java.time.ZonedDateTime::parse,

// Google map classes
com.google.common.collect.ImmutableMap::of,
com.google.common.collect.ImmutableBiMap::of,
com.google.common.collect.ImmutableMap.Builder::put,
com.google.common.collect.ImmutableBiMap.Builder::put,

// Google bytestring class
com.google.protobuf.ByteString::fromHex;

// UUID class
java.util.UUID::fromString;

正例

LocalDateTime time = LocalDateTime.parse("2022-08-02T22:45:00");

void function(String timeString) {
    LocalDateTime time = LocalDateTime.parse(timeString); // 不检测变量作为参数的场景
}

反例

LocalDateTime time = LocalDateTime.parse("2022-08-02T22:45:00+08:00"); // 需要拿掉时区参数

[Block] ArrayEquals

类型

代码缺陷

描述

在比较数组的相等性时,通常我们希望比较的是数组内容的相等性,而非它们是否为同一对象。但许多常用的比较方法比较的是数组引用而非数组内容是否相等,包括对象自身的equals()方法,Guava的com.google.common.base.Objects#equal()方法,JDK的java.util.Objects#equals()方法等。如果确实需要比较数组引用是否相等,明确使用“==”以避免歧义,否则应该使用java.util.Arrays#equals()来比较数组的内容是否相等。

正例

boolean arrayContentsEquality(Object[] array1, Object[] array2) {
    return Arrays.equals(array1, array2); // 比较内容是否相等
}

boolean arrayReferenceEquality(Object[] array1, Object[] array2) {
    return array1 == array2; // 比较引用是否相等
}

反例

boolean arrayEquality(Object[] array1, Object[] array2) {
    return array1.equals(array2); // 实际比较的是引用是否相等
}

[Block] ArrayFillIncompatibleType

类型

代码缺陷

描述

Arrays.fill(Object[], Object)是用于将对象的引用填充到数组的每一个元素中,如:

String[] foo = new String[42];
Arrays.fill(foo, "life"); // foo数组中的每个元素都是指向“life”字符串在常量池中的引用

然而,由于数组允许子类型的数组赋值,且Arrays.fill方法的参数为Arrays.fill(Object[], Object),因此下面的语句在编译时不会报错:

String[] foo = new String[42];
Arrays.fill(foo, 42); // 但会在运行时抛出ArrayStoreException

这条规则检测上述场景,不允许尝试将不匹配的类型放置到数组中。

正例

String[] foo = new String[42];
Arrays.fill(foo, "life");

反例

String[] foo = new String[42];
Arrays.fill(foo, 42); // 会在运行时抛出ArrayStoreException

[Block] ArrayToString

类型

代码缺陷

描述

数组的toString方法会输出它的引用信息,如[I@4488aabb,这在绝大多数情况下都不表达任何实际含义。考虑使用Arrays.toString方法来输出具有可读性的信息。

正例

Integer[] intArray = new Integer[]{1, 2, 3};
System.out.println(Arrays.toString(intArray)); // 将会输出数组内容[1, 2, 3]

反例

Integer[] intArray = new Integer[]{1, 2, 3};
System.out.println(intArray.toString()); // 将会输出类似[I@4488aabb的信息,不表达任何实际含义

[Block] ArraysAsListPrimitiveArray

类型

代码缺陷

描述

Arrays.asList方法不会如期望地将基础类型的数组自动装箱并生成新的列表,如果想要将基础类型的数组自动装箱,可以考虑使用Guava包中提供的asList方法。如果确实需要创建只包含一个基础类型的数组为元素的列表,明确地使用Collections.singletonList方法来表达意图。

正例

List<Integer> intArrayToIntegerList2(int[] intArray) {
    // 正确使用jdk自建的Arrays类
    List<Integer> integerList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
    return integerList;
}

List<Integer> intArrayToIntegerList(int[] intArray) {
    // 使用guava包中的com.google.common.primitives.Ints类
    List<Integer> integerList = Ints.asList(intArray);
    return integerList;
}

List<Integer> convertToIntArraySingleton(int[] intArray) {
    // 如果确实需要创建只包含一个基础类型的数组为元素的列表,使用Collections.singletonList方法
    List<int[]> intArraySingletonList = Collections.singletonList(intArray);
    return intArraySingletonList;
}

反例

List<Integer> intArrayToInteger(int[] intArray) {
    List<Integer> integerList = Arrays.asList(intArray); // 编译错误,Arrays.asList方法返回值类型为List<int[]>
    return integerList;
}

[Block] AvoidUsingExecutors

类型

业务军规

描述

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式,规避资源耗尽的风险。

正例

// 通过ThreadPoolExecutor方式创建受控的线程池,规避资源耗尽的风险
static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
    coorPoolSize, maxiumPoolSize, 1, TimeUnit.HOURS, new ArrayBlockingQueue<>(maxiumPoolSize));

void function(Runnable task) {
    THREAD_POOL_EXECUTOR.submit(task);
}

反例

// 通过Executors创建的线程池未设定线程数上限,存在导致资源耗尽的风险
static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool();

void function(Runnable task) {
    EXECUTOR_SERVICE.submit(task);
}

[Block] BadShiftAmount

类型

代码缺陷

描述

以对int类型进行移位运算为例:移位运算的距离参数d显然应当小于32,否则应当以d%32的结果作为移位运算的参数输入。因此移位运算会潜在地将超过31的值映射到0~31的范围内。参考JLS文档:JLS#15.19

正例

long l = 114514L << 32; // 长整形位长度为64,因此左移32位是有意义的,

反例

long l = 114514 << 32; // 114514没有长整形标记L,因此类型为int,对int左移32位等同于左移0位等同于不进行任何操作

[Block] BigDecimalLiteralDouble

类型

业务军规

描述

避免使用new BigDecimal(double d)构造器将double转换为BigDecimal,这种方式通常都会导致出现奇怪的精度损失,推荐使用工厂方法BigDecimal.valueOf(double d)或是new BigDecimal(String s)构造器。

正例

// 使用工厂方法
BigDecimal oneFifth = BigDecimal.valueOf(0.2);
System.out.println(oneFifth);

// 或者使用String为参数的构造器
BigDecimal oneFifth = new BigDecimal("0.2");
System.out.println(oneFifth);

反例

// 避免使用double为参数的构造器
BigDecimal oneFifth = new BigDecimal(0.2);
// 输出 0.200000000000000011102230246251565404236316680908203125
System.out.println(oneFifth);

[Block] BoxedPrimitiveEquality

类型

代码缺陷

描述

对于基本类型的包装类,比较它们的引用而非值的相等性。对包装类引用的比较更容易出现问题:基本类型的包装类缓存了部分(但不是全部)值,因此只有在存在缓存的值之间,其==运算与equals()方法的返回值是一致的。而缓存也会随着运行时和库的不同而变化,导致对Java运行时或库的升级也可能会导致缓存策略的变化。此外,基本类型包装类的引用通常也是无意义的,且提供了equals()方法去比较它们值的相等性。

正例

void compareBoxedPrimitive(Long x, Long y) {
    return Objects.equals(x, y);
}

反例

void compareBoxedPrimitive(Long x, Long y) {
    return x == y; // 假如Long对于-65536~65535范围内的值存在缓存,那么仅当x,y均位于这个范围内时,这个方法能正确地返回结果,否则不会
}

[Block] CollectionIncompatibleType

类型

代码缺陷

描述

查询一个集合中不可能存在的某个元素基本可以认定为是bug。对于一般的集合类型来说,诸如Map.get(Object)和Collection.remove(Object)的查询方法接受任意传入的参数,并尝试在集合中进行查找。这条规则会检测由于传入的参数与集合指定的泛型参数不兼容因而该参数不可能存在于这个集合中的情况。一个典型的例子是:

Set<Long> values = ...
if (values.contains(42)) { ... }

上面的代码看起来很合理,但存在着一个问题:Set包含的是Long类型的实例,而contains方法传入的参数却是Integer类型。又因为Integer类型和Long类型之间不可能存在两个相等的实例,所以这里调用的contains方法一定会返回false,显然这并不是我们期望的结果。

正例

Map<Long, String> idToValueMap = Map.of(
        1L, "1",
        2L, "2",
        3L, "3");
System.out.println(idToValueMap.get(1L));

反例

Map<Long, String> idToValueMap = Map.of(
        1L, "1",
        2L, "2",
        3L, "3");
// 由于(Integer)1与(Long)1不匹配,输出为null
System.out.println(idToValueMap.get(1));

[Block] CollectionToArraySafeParameter

类型

代码缺陷

描述

Collection类的toArray方法有三种不同的重载,分别是toArray(),toArray(IntFunction<T[]> generator),toArray(T[] a),除无参的重载返回Object[]外,其余重载均会尝试将集合放置到传入的数组参数中并返回对应类型的数组。但当向toArray方法传入与原集合类型不一致类型的数组时,编译器不会在编译期给出警告,但在运行时若集合中任何元素无法转换为传入数组元素的类型时,toArray方法会抛出ArrayStoreException。为了避免发生上述情况,本条规则检测传入的数组元素类型与原集合类型参数不一致的情况并给出警告。如果确实需要进行类型转换,应当使用Stream类进行类型转换后调用Stream类的toArray(IntFunction<T[]> generator)方法获取对应类型的数组。

正例

Integer[] collectionToArray(Collection<Integer> collection) {
    return collection.toArray(new Integer[0]);
}

String[] collectionToArrayWithCast(Collection<Integer> collection) {
    return collection.stream().map(String::valueOf).toArray(String[]::new);
}

反例

String[] collectionToArray(Collection<Integer> collection) {
    return collection.toArray(new String[0]);
}

[Block] ComparableType

类型

代码缺陷

描述

Comparable接口的类型参数应当与声明实现它的类的类型相同。否则根据Comparable接口的约定要求:对于任意的x、y,x.compareTo(y) == -y.compareTo(x)。如果x和y是两个不同的类,显然无法保证这条性质成立。

正例

class A implements Comparable<A> {

    @Override
    public int compareTo(@NotNull A other) {
        // ...
    }
}

反例

class A implements Comparable<B> {

    @Override
    public int compareTo(@NotNull B other) {
        // ...
    }
}

[Block] ComparingThisWithNull

类型

代码缺陷

描述

显然this不可能为null,否则必定会先抛出NullPointerException。因此布尔运算this == null 必定为false, this != null 必定为 true。

反例

void function() {
    if (this == null) { // this == null 始终返回false
    }
    if (this != null) { // this != null 始终返回true
    }
}

[Block] ConditionalExpressionNumericPromotion

类型

代码缺陷

描述

使用两个不同类型的数值作为条件运算符的第二、三个参数可能会得到一个意料之外的结果。例如:

Object getZeroOnCondition(boolean flag) {
    return flag ?
            Double.valueOf(0) :
            Integer.valueOf(0);
}

Object o1 = getZeroOnCondition(true);
// 将输出java.lang.Double,符合预期
System.out.println(o1.getClass());
Object o2 = getZeroOnCondition(false);
// 将输出java.lang.Double,与预期不符
System.out.println(o2.getClass());

即使我们明确地声明希望在两种不同的情况下分别获得Double和Integer的0值,结果却始终返回的是Double类型的0值。这是因为根据JLS#15.25,不同的数值类型间的运算首先会经过双目类型提升(Binary Numeric Promotion)将不同类型的数值转化为同一类型后再进行计算。因此上述的运算实际上与下面的运算是等同的:

Object getZeroOnCondition(boolean flag) {
    return Double.valueOf(
            flag ?
                    Double.valueOf(0).doubleValue() :
                    (double) Integer.valueOf(0).intValue());
}

如果想要在两种不同的情况下获得不同类型的0值,可以直接显式地将数值类型转换为非数值类型,或者使用分支语句。

正例

// 显式地将数值类型转换为非数值类型
Object getZeroOnConditionWithCast(boolean flag) {
    return flag ?
            (Object) Double.valueOf(0) :
            (Object) Integer.valueOf(0);
}

// 或者使用分支语句
Object getZeroOnConditionWithBranch(boolean flag) {
    if (flag) {
        return Double.valueOf(0);
    } else {
        return Integer.valueOf(0);
    }
}

反例

Object getZeroOnCondition(boolean flag) {
    return flag ?
            Double.valueOf(0) :
            Integer.valueOf(0);
}

[Block] ConstantOverflow

类型

代码缺陷

描述

编译期表达式结果溢出通常都是潜在的bug来源。不以L结尾的数值常量类型为int,对其进行乘法运算的结果完全有可能超出int能表示的范围而导致溢出。

正例

// 显式声明24为长整形,避免结果溢出
static final long NANOS_PER_DAY = 24L * 60 * 60 * 1000 * 1000 * 1000;

反例

// 由于先执行乘法运算获取结果后最后进行类型转换,而中间结果超过INT_MAX,导致溢出
static final long NANOS_PER_DAY = 24 * 60 * 60 * 1000 * 1000 * 1000; 

[Block] DeadException

类型

代码缺陷

描述

创建了新的异常实例,且从未抛出或传递其引用,通常都是忘记抛出该异常。

正例

void function() {
    SomeException someException = new SomeException();
    // ...
    throw someException;
}

// 如果需要获取堆栈信息,使用Thread#getStackTrace()方法
StackTraceElement[] ste = Thread.currentThread().getStackTrace();

反例

void function() {
    SomeException someException = new SomeException(); 
    // ...
    // 异常被声明但从未被抛出过或传递其引用
}

// 如果需要获取堆栈信息,不要直接创建新的Exception并调用getStackTrace()方法
StackTraceElement[] ste = (new Exception()).getStackTrace();

[Block] DeadThread

类型

代码缺陷

描述

创建了新的Thread实例但是从未调用过start()方法或传递其引用。Thread需要调用start()方法来实际执行。

正例

void function() {
    Thread someThread = new Thread();
    // ...
    someThread.start();
}

反例

void function() {
    Thread someThread = new Thread(); // 线程被声明但从未被使用或调用start方法
    // ...
}

[Block] EqualsNull

类型

代码缺陷

描述

Object.equals方法约定:对于任意不为null的引用值x,x.equals(null)应当返回false,因此调用x.equals(null)要么返回false ,要么会在x为null的情况下抛出NullPointerException。如果需要判断x是否为null,直接使用“==”运算符。

正例

void function(Object obj) {
    if (obj == null) {
        // ...
    }
}

反例

void function(Object obj) {
    // obj.equals(null)根据约定应该始终返回false,或因为obj为null导致抛出NullPointerException
    if (obj.equals(null)) {
        // ...
    }
}

[Block] EqualsWrongThing

类型

代码缺陷

描述

equals方法比较了两个不相关的属性。

正例

class Frobnicator {
    private int a;
    private int b;

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof Frobnicator)) {
            return false;
        }
        Frobnicator that = (Frobnicator) other;
        return a == that.a && b == that.b;
    }
}

反例

class Frobnicator {
    private int a;
    private int b;

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof Frobnicator)) {
            return false;
        }
        Frobnicator that = (Frobnicator) other;
        return a == that.a && b == that.a; // 应该为b == that.b;
    }
}

[Block] FloatValueEquality

类型

业务军规

描述

避免使用“==”运算符或equals方法对浮点数之间的相等性进行比较,浮点数之间相等性比较应当通过BigDecimal类实现,如果确实需要直接比较浮点数之间的相等性,应当给定对应的误差值。

正例

// 通过BigDecimal类实现比较
boolean floatEquals(double x, double y) {
    return BigDecimal.valueOf(x).equals(BigDecimal.valueOf(y));
}

反例

boolean floatEquals(double x, double y) {
    return x == y;
}

[Block] FormatString

类型

代码缺陷

描述

对字符串进行格式化需要遵循java.util.Formatter的文档。若格式化字符串要求的参数与提供的参数类型或数量不一致时,问题代码在编译时不会报错,但在执行到问题代码处时会抛出“IllegalFormatConversionException”。

正例

String format(int id, String path) {
    return String.format(
                // 格式化字符串要求两个参数
                "User:%d(%s)%n",
                // 提供两个参数
                id, path);
}

反例

String format(int id, String path) {
    return String.format(
                // 格式化字符串要求四个参数
                "User:%d(%s)%nNameSpace:%d(%s)",
                // 仅仅提供两个参数,只有当执行到此处时才会抛出IllegalFormatConversionException
                id, path);
}

[Block] GetClassOnAnnotation

类型

代码缺陷

描述

调用注解接口实例的getClass()方法通常会返回一个随机的代理类,如果想要获取实际的注解类型,应当使用annotationType()方法。

正例

@Deprecated
public class Test {
    static void printAnnotationClass(Annotation annotation) {
        // 将会输出java.lang.Deprecated
        System.err.println(annotation.annotationType());
    }

    public static void main(String[] args) {
        printAnnotationClass(Test.class.getAnnotation(Deprecated.class));
    }
}

反例

@Deprecated
public class Test {
    static void printAnnotationClass(Annotation annotation) {
        // 将会输出com.sun.proxy.$Proxy1
        System.err.println(annotation.getClass());
    }

    public static void main(String[] args) {
        printAnnotationClass(Test.class.getAnnotation(Deprecated.class));
    }
}

[Block] HashtableContains

类型

代码缺陷

描述

Hashtable和ConcurrentHashMap的contains方法是用于检测传入的实例是否存在于值集合(Value Set)中。这个方法经常会被误用为containsKey,而containsKey方法是用于检测传入的参数是否存在于其键集合(Key Set)中。如果希望检测某个值是否存在于值集合中,应当明确调用containsValue方法。

正例

Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("1", "01");
hashtable.put("2", "02");
hashtable.put("3", "03");
hashtable.containsKey("1"); // true
hashtable.containsValue("01"); // true

反例

Hashtable<String, String> hashtable = new Hashtable<>();
hashtable.put("1", "01");
hashtable.put("2", "02");
hashtable.put("3", "03");
hashtable.contains("1"); // false
hashtable.contains("01"); // true

[Block] IdentityBinaryExpression

类型

代码缺陷

描述

使用同一对象或表达式作为二元运算的参数一般来说都是误用,因为这样的二元表达式的值都是不变的:

  • a && a, a || a, a & a, a | a 的结果始终为a

  • a <= a, a >= a, a == a 的结果始终为true

  • a < a, a > a, a != a,a ^ a的结果始终为false

  • a / a 的结果始终为1

  • a % a, a - a 的结果始终为0

如果对该对象的引用或执行该表达式有其他副作用,应当明确地声明一个临时变量以标识。

正例

class SomeClass {
    int useCount;

    public boolean check() {
        boolean checkResult;
        // check方法存在逻辑
        useCount++;
        // ...
        return checkResult;
    }
}

void function(SomeClass instance) {
    // 声明临时变量以标识
    boolean checkResult = instance.check();
    if (checkResult && instance.check()) {
        // ...
    }
}

反例

class SomeClass {
    int useCount;

    public boolean check() {
        boolean checkResult;
        // ...
        return checkResult;
    }
}

void function(SomeClass instance) {
    if (instance.check() && instance.check()) {
        // ...
    }
}

[Block] IdentityHashMapBoxing

类型

代码缺陷

描述

由于IdentitiyHashMap通过引用来判断键值的相等性,而基础类型包装类的引用相等性存在着严重的潜在问题:基础类型包装类会缓存部分(而非所有)的值,因此位于缓存范围内值的对象会指向同一引用,而缓存范围外的对象的引用则各不相同。此外,缓存范围会随着JDK版本与库的不同而变化。使用基础类型的包装类作为IdentityHashMap的键值存在严重的风险。

正例

void function() {
    Random random = new Random();
    // 不要使用IdentityHashMap
    Map<Integer, String> map = new HashMap<>();
    for (int i = 0; i < 100; ++i) {
        int randomInt = random.nextInt();
        map.put(randomInt, String.valueOf(randomInt));
        String value = map.get(randomInt);
        // HashMap使用equals方法进行值比较,不会出现问题
        if (value == null) {
            throw new RuntimeException("Value is null");
        }
    }
}

反例

void function() {
    Random random = new Random();
    Map<Integer, String> map = new IdentityHashMap<>();
    for (int i = 0; i < 100; ++i) {
        int randomInt = random.nextInt();
        map.put(randomInt, String.valueOf(randomInt));
        String value = map.get(randomInt);
        if (value == null) {
            // 由于包装类的缓存机制,在缓存内的值将返回同一实例,而缓存外的值则会创建一个新的实例之后返回
            throw new RuntimeException("Value is null");
        }
    }
}

[Block] IndexOfChar

类型

代码缺陷

描述

String.indexOf(int, int)方法中的两个整型参数,第一个参数应为需要查找的字符对应的整型值,第二个参数为开始查找的起始下标。

正例

int findIndexOfChar(String s, char ch, int fromIndex) {
    // indexOf方法的签名为String.indexOf(int ch, int fromIndex),即字符在前,开始搜索的位置在后
    return s.indexOf(ch, fromIndex); 
}

反例

int findIndexOfChar(String s, char ch, int fromIndex) {
    // 但由于两个参数类型均为int,所以调换参数也可以通过编译,但是会导致运行结果不符合预期
    return s.indexOf(fromIndex, ch); 
}

[Block] InexactVarargsConditional

类型

代码缺陷

描述

对于可变长参数方法,它接受一个明确的数组数组作为参数,或是单独的每个参数:

void varargsFunction(Object... args) {
    System.out.println(Arrays.deepToString(args));
}

// 两种方式传递的参数是相同的,都会输出[1, 2, 3.0]
varargsFunction(new Object[]{"1", 2, 3.0});
varargsFunction("1", 2, 3.0);

如果变长参数是一个条件表达式,而且存在一个分支不是数组,那么表达式的结果会潜在地被包装在一个新的数组中,例如:

// 将会输出[1]
varargsFunction(true ? 1 : 2);
// 将会输出[2]
varargsFunction(false ? 1 : 2);

因此如果在条件表达式中混用数组和非数组参数的话,会导致数组分支的参数成为一个高维数组,而这一般都不是我们期望的结果。

正例

void varargsFunction(Object... args) {
    System.out.println(Arrays.deepToString(args));
}

// 将非数组分支的参数也包装在数组中
varargsFunction(flag ? new Object[] {1, 2} : new Object[] {3});

反例

void varargsFunction(Object... args) {
    System.out.println(Arrays.deepToString(args));
}

// 存在非数组分支,导致数组分支的参数成为了高维数组
varargsFunction(flag ? new Object[] {1, 2} : 3); // 将会输出 [[1, 2]] 或 [3],即数组被额外包装了一层

[Block] InfiniteRecursion

类型

代码缺陷

描述

一个总是递归调用自身的方法会导致堆栈溢出。

正例

void function(boolean condition) {
    boolean newCondition;
    // 头递归
    if (condition) {
        function(newCondition);
    }
    // 尾递归
}

反例

void function() {
    // 递归调用自身,必然导致堆栈溢出
    function();
}

[Block] IsInstanceIncompatibleType

类型

代码缺陷

描述

静态检测Classs.isInstance(Object)方法是否总是会返回true/false

正例

abstract class Plant { // ... }

abstract class Animal { // ... }

class Dog extends Animal { // ... }

class Cat extends Animal { // ... }

<T extends Animal> void function(T animal) {
    if (Cat.class.isInstance(animal)) {
        // ...
    }
}

反例

abstract class Plant { // ... }

abstract class Animal { // ... }

class Dog extends Animal { // ... }

class Cat extends Animal { // ... }

<T extends Animal> void function(T animal) {
    // animal不可能为Plant类
    if (Plant.class.isInstance(animal)) {
        // ...
    }
}

[Block] LockOnBoxedPrimitive

类型

代码缺陷

描述

由于基础类型包装类的实例可能存在缓存,导致不同对象引用同一个实例。因此当对基础类型包装类的对象上锁时,可能会意外地导致上锁的代码段与其他代码段共享同一个锁。

正例

private final Object lock;

void doSomething() {
    synchronized (lock) {
        // ...
    }
}

反例

private final Integer lock;

void doSomething() {
    synchronized (lock) {
        // ...
    }
}

[Block] LoopConditionChecker

类型

代码缺陷

描述

循环条件从未在循环体内被更新,必然导致死循环或循环体根本不会被执行。

正例

while (condition) {
    // ...
    condition = false;
}

反例

while (condition) {
    // ...
    // condition从未被更新
}

[Block] LossyPrimitiveCompare

类型

代码缺陷

描述

使用compare方法对数值类型进行比较时,方法会先将数值进行类型转换后再进行比较,从而导致潜在的精度损失问题。例如:

int result = Float.compare(Integer.MAX_VALUE, Integer.MAX_VALUE - 1);
// 结果将会输出0,即Integer.MAX_VALUE与Integer.MAX_VALUE-1相等,显然此处出现了精度损失
System.out.println(result);

因此需要正确地选择用于比较的数值类型以尽量避免出现精度损失。

正例

Integer.compare(Integer.MAX_VALUE, Integer.MAX_VALUE - 1);

反例

Float.compare(Integer.MAX_VALUE, Integer.MAX_VALUE - 1);

[Block] MathRoundIntLong

类型

代码缺陷

描述

Math.round()方法只接受浮点数类型参数,向其传递整型参数会导致整型数值被截断。

正例

Math.round(3.2);

反例

Math.round(1L + Integer.MAX_VALUE); // 返回值依然为Integer.MAX_VALUE,即:2147483647

[Block] MisusedDayOfYear

类型

代码缺陷

描述

“DD”是指相对当前年的天数,格式化时间时使用“DD”作为日期的参数同时使用月份的参数通常情况下是误用。

正例

DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter.ofPattern("yyyy-DD");

反例

DateTimeFormatter.ofPattern("yyyy-MM-DD");

[Block] MisusedWeekYear

类型

代码缺陷

描述

使用“YYYY”格式化年份而没有对星期参数进行格式化通常情况下是误用。

正例

DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter.ofPattern("YYYY-ww");

反例

DateTimeFormatter.ofPattern("YYYY-MM-dd");

[Block] ModifyingCollectionWithItself

类型

代码缺陷

描述

调用某个集合的方法并传入了这个集合自身作为参数通常情况下是误用。

正例

void function(Collection collectionA, Collection collectionB) {
    collectionA.retainAll(collectionB);
}

反例

void function(Collection collectionA, Collection collectionB) {
    // 实际上等同于没有执行任何操作
    collectionA.retainAll(collectionA);
}

[Block] NCopiesOfChar

类型

代码缺陷

描述

Collections.nCopies方法的第一个参数为拷贝次数,第二个参数才为待拷贝的对象。

正例

// 返回10个'a'组成的集合
Collections.nCopies(10, 'a');

反例

// 实际上返回的是97个10组成的集合
Collections.nCopies('a', 10);

[Block] NullTernary

类型

代码缺陷

描述

如果一个条件表达式可能取值为null,那么在对其进行自动拆箱时会抛出NullPointerException

正例

Integer x = flag ? someInt : null;

反例

// someInt为null或flag为false时都会跑出NullPointerException
int x = flag ? someInt : null;

[Block] OptionalEquality

类型

代码缺陷

描述

Optional类应当使用equals方法比较其包含的值,而非使用“==”或“!=”运算符。

正例

boolean isEqual(Optional<Integer> x, Optional<Integer> y) {
    return x.equals(y);
}

反例

boolean isEqual(Optional<Integer> x, Optional<Integer> y) {
    return x == y;
}

[Block] PojoSelfAssignment

类型

业务军规

描述

Java的pojo类自赋值,无任何实际意义。如果getter/setter方法存在业务逻辑,应当显式地赋值给临时变量后再传递 。

正例

void copyProperties(Pojo x, Pojo y) {
    x.setAttr1(y.getAttr1());
    x.setAttr2(y.getAttr2());
    x.setAttr3(y.getAttr3());
}

void copyProperties(Pojo x, Pojo y) {
    x.setAttr1(y.getAttr1());
    // 如果getter/setter方法存在业务逻辑,应当显式赋值给临时变量后传递
    Object attr2 = x.getAttr2();
    x.setAttr2(attr2);
    x.setAttr3(y.getAttr3());
}

反例

void copyProperties(Pojo x, Pojo y) {
    x.setAttr1(y.getAttr1());
    // pojo类自赋值,应当更正为y.getAttr2()
    x.setAttr2(x.getAttr2());
    x.setAttr3(y.getAttr3());
}

[Block] RandomCast

类型

代码缺陷

描述

Math.random,Random.nextFloat,Random.nextDouble返回的结果位于[0.0, 1.0)。因此将其转换为整型的结果始终为0.

正例

int randomInt = new Random().nextInt();

反例

int randomInt = (int) Math.random();

[Block] RandomModInteger

类型

代码缺陷

描述

由于Random.nexInt可能返回负数,因此Random.nextInt对n取余得到的结果分布在[-(n-1), n-1]范围内的所有整数上(其中为0的概率为1/n,其他整数的概率为1/2n)。而非平均分布在[0, n-1]范围的整数内。

正例

Random random = new Random();
int randomIntWithin100 = random.nextInt(100);
int random = (random.nextBoolean()? 1: -1) * random.nextInt(100);

反例

int random = random.nextInt() % 100;

[Block] SelfAssignment

类型

代码缺陷

描述

赋值语句两侧的变量完全一致,没有任何意义。

正例

public class Person {
    
    private String name;

    public void setName(String name) {
        // 将传入的name赋值给Person实例的name属性
        this.name = name;
    }
}

反例

public class Person {
    
    private String name;

    public void setName(String name) {
        // 自赋值,没有任何意义
        name = name;
    }
}

[Block] SelfComparison

类型

代码缺陷

描述

使用compareTo方法与自身进行比较始终会返回0。

正例

class ImaginaryNumber implements Comparable<ImaginaryNumber> {
    Double realPart;
    Double imaginaryPart;

    @Override
    public int compareTo(ImaginaryNumber other) {
        // 比较虚数的实部大小
        return realPart.compareTo(other.realPart);
    }
}

反例

class ImaginaryNumber implements Comparable<ImaginaryNumber> {
    Double realPart;
    Double imaginaryPart;

    @Override
    public int compareTo(ImaginaryNumber other) {
        // realPart与自身进行比较
        return realPart.compareTo(realPart);
    }
}

[Block] SelfEquals

类型

代码缺陷

描述

使用equals方法判断变量与自身的相等性始终会返回true。

正例

class Person {
    String id;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Person)) {
            return false;
        }
        Person that = (Person) obj;
        // 与传入对象的id进行比较
        return id.equals(that.id);
    }
}

反例

class Person {
    String id;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Person)) {
            return false;
        }
        Person that = (Person) obj;
        // id自比较,始终返回true
        return id.equals(id);
    }
}

[Block] SizeGreaterThanOrEqualsZero

类型

代码缺陷

描述

集合的size方法在集合为空时会返回0,size()>=0调用始终会返回true。

正例

void function(Collection collection) {
    // 判断集合是否为空
    if (collection.isEmpty()) {
        // ...
    }
}

反例

void function(Collection collection) {
    // 始终为true
    if (collection.size() >= 0) {
        // ...
    }
}

[Block] StreamToString

类型

代码缺陷

描述

Stream的toString方法会输出它的引用信息,如java.util.stream.ReferencePipeline$Head@6d06d69c,这在绝大多数情况下都不表达任何实际含义。

正例

public void function(Stream stream) {
    System.out.println(Arrays.toString(stream.toArray()));
}

反例

public void function(Stream stream) {
    System.out.println(stream.toString());
}

[Block] StringBuilderInitWithChar

类型

代码缺陷

描述

StringBuilder没有字符参数的构造器,传入字符参数会调用整形参数的构造器并创建一个初始大小为对应ASCII码值的StringBuilder实例。

正例

// 初始化字符串为“a”
StringBuilder stringBuilder =new StringBuilder("a");

反例

// 返回初始大小为97的StringBuilder实例
StringBuilder stringBuilder =new StringBuilder('a');

[Block] SubstringOfZero

类型

代码缺陷

描述

subString(int)方法返回包括从传入参数下标开始的子字符串,因此传递0作为参数会返回与原字符串相同的字符串。

正例

String function(String str) {
    // 使用正确的下标
    return str.substring(1);
}

反例

String function(String str) {
    // 传入0会返回与原字符串相同的字符串,无意义
    return str.substring(0);
}

[Block] SuspiciousForLoop

类型

业务军规

描述

for循环中退出条件为i<=value,而循环值却为递减;或退出条件为i>=value,而循环值却为递增;通常会导致死循环。

正例

void function(Collection<String> collection) {
    for (int i = 0; i < collection.size(); i++) {
        // ...
    }
}

反例

void function(Collection<String> collection) {
    // 要么循环根本不会进行,要么当collection为空时死循环
    for (int i = 0; i >= collection.size(); i++) {
        // ...
    }
}

[Block] TransactionalNonPublicMethod

类型

业务军规

描述

Spring提供的org.springframework.transaction.annotation.Transactional类不应当注解在非public方法上,否则该注解将无效,这是由于Spring的切片注解方式决定的,详见:Spring Transactional

正例

@Transactional
public void writeToDB(BusinessDTO dto) {
    // ...
}

反例

@Transactional
private void writeToDB(BusinessDTO dto) {
    // ...
}

[Block] UnusedCollectionModifiedInPlace

类型

代码缺陷

描述

java.util.Collections中的一些方法,如sort和shuffle,会修改集合本身。如果在一个新创建的集合上调用了这些方法,又没有在之后使用这个新创建的集合,那么这个方法调用显然是冗余的,或者很有可能是忘记返回新创建的集合的引用。

正例

public Collection<String> sort(Collection<String> foos) {
  List<String> sortedFoos = new ArrayList<>(foos);
  Collections.sort(sortedFoos);
  return sortedFoos;
}

反例

public Collection<String> sort(Collection<String> foos) {
  Collections.sort(new ArrayList<>(foos));
  return foos;
}

<think>好的,用户问的是Java中如何解决多线程并发问题。这个问题挺常见的,但需要详细分析不同的解决方案。首先得考虑用户可能的背景,可能是刚学多线程的新手,或者在实际开发中遇到了并发问题需要解决。他们可能需要全面的方法,而不仅仅是几个关键字。 首先,我应该从基础开始,比如同步代码块,这是最直接的。但需要解释清楚synchronized关键字的用法,包括对象锁和类锁的区别。可能用户对锁的概念不太清楚,所以要举例子,比如卖票系统的例子,这样比较直观。 接下来是Lock接口,比如ReentrantLock。这里要对比synchronized和Lock的区别,比如Lock更灵活,支持尝试获取锁、超时中断等。可能需要提到Condition的使用,这样用户知道如何更细粒度地控制线程通信。 然后是原子类,比如AtomicInteger。这部分要强调原子操作的原理,CAS机制,以及适用场景,比如计数器。用户可能不了解CAS,所以需要简单解释下,避免他们误用。 线程安全容器也是重要的点,比如ConcurrentHashMap和CopyOnWriteArrayList。需要说明它们是如何实现线程安全的,以及各自适用的场景。比如读多写少的情况用CopyOnWriteArrayList比较合适。 ThreadLocal也是个关键点,它解决的是线程间数据隔离的问题。可能用户会遇到需要每个线程有自己独立变量的情况,比如SimpleDateFormat,这时候ThreadLocal就很有用。要提醒他们正确使用,避免内存泄漏。 接下来是volatile关键字,这里需要明确它的作用,保证可见性和禁止指令重排,但并不能保证原子性。可能用户会误解volatile能解决所有并发问题,需要举例说明适用的场景,比如状态标志位。 线程池部分要强调通过管理线程资源来减少并发问题。比如使用Executors工具类创建不同类型的线程池,但也要提到阿里巴巴规范不推荐直接使用Executors,而是通过ThreadPoolExecutor自定义参数,避免资源耗尽的风险。 并发工具类比如CountDownLatch、CyclicBarrier和Semaphore,这些在高并发场景下很常用。需要分别说明它们的用途和示例,比如CountDownLatch用于等待多个线程完成,CyclicBarrier用于线程同步,Semaphore控制资源访问数量。 最后是避免共享状态,推荐使用不可变对象和线程封闭。这里可能需要提到设计模式,比如使用不可变的类或者将对象限制在特定线程内。 总结部分要简明扼要,回顾所有方法,并建议根据具体场景选择合适方案。同时提醒用户深入理解原理,避免死锁和资源竞争问题。 需要检查是否有遗漏的重要方法,比如CAS、Fork/Join框架,或者CompletableFuture,但可能用户的问题更偏向基础,所以先集中在常用的解决方案上。另外,要确保例子简单易懂,避免过于复杂让用户困惑。语法和格式方面,按照系统指令使用正确的LaTeX,比如变量用$符号包裹,独立公式单独成段。最后用中文自然流畅地组织内容,结构清晰,分点说明。</think>在Java中解决多线程并发问题,需要根据具体场景选择合适的同步机制。以下是逐步解决方案: --- ### 、同步代码块(Synchronized Block) 通过`synchronized`关键字实现对共享资源的互斥访问: ```java private final Object lock = new Object(); public void safeMethod() { synchronized(lock) { // 获取对象锁 // 临界区代码(如共享变量操作) } } ``` - **原理**:基于$monitor$机制,同时间只允许个线程进入临界区 - **适用场景**:简单的同步需求(如卖票系统:$ticketCount = n$) --- ### 二、Lock接口(显式锁) 使用`ReentrantLock`提供更灵活的锁控制: ```java private final Lock lock = new ReentrantLock(); public void safeMethod() { lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须保证释放锁 } } ``` - **优势**: 1. 支持尝试获取锁(`tryLock()`) 2. 可设置超时时间(`tryLock(long time, TimeUnit unit)`) 3. 支持公平锁(`new ReentrantLock(true)`) --- ### 三、原子类(Atomic Classes) 通过CAS(Compare-And-Swap)实现无锁线程安全: ```java AtomicInteger counter = new AtomicInteger(0); public void increment() { counter.getAndIncrement(); // 原子操作 } ``` - **常用类**: - `AtomicInteger` - `AtomicLong` - `AtomicReference<T>` - **适用场景**:计数器、状态标志等简单操作 --- ### 四、线程安全容器 使用JUC(`java.util.concurrent`)包中的线程安全集合: ```java ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); ``` - **实现原理**: - `ConcurrentHashMap`:分段锁(JDK8后改为CAS+synchronized) - `CopyOnWriteArrayList`:写时复制(适合读多写少场景) --- ### 五、ThreadLocal 为每个线程创建独立变量副本: ```java private static ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); ``` - **应用场景**: - 数据库连接管理 - 日期格式化工具(避免`SimpleDateFormat`线程不安全问题) --- ### 六、volatile关键字 保证变量可见性和禁止指令重排序: ```java private volatile boolean flag = false; ``` - **特性**: 1. 写操作立即刷新到主内存 2. 读操作直接从主内存读取 - **限制**:不保证原子性(如`i++`仍需同步) --- ### 七、线程池管理 通过`ExecutorService`规范线程使用: ```java ExecutorService executor = Executors.newFixedThreadPool(4); executor.submit(() -> { // 线程任务 }); ``` - **优点**:避免线程频繁创建/销毁,控制并发规模 --- ### 八、并发工具类 使用JUC中的高级同步工具: 1. **CountDownLatch**(倒计时门闩): ```java CountDownLatch latch = new CountDownLatch(3); latch.await(); // 等待计数器归零 latch.countDown(); // 减少计数器 ``` 2. **CyclicBarrier**(循环栅栏): ```java CyclicBarrier barrier = new CyclicBarrier(3, ()->System.out.println("All threads reached")); barrier.await(); // 线程等待集合点 ``` 3. **Semaphore**(信号量): ```java Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问 semaphore.acquire(); // 获取许可 semaphore.release(); // 释放许可 ``` --- ### 九、避免共享状态 1. **不可变对象**: ```java public final class ImmutableObject { private final int value; // 构造函数初始化后不可修改 } ``` 2. **线程封闭**:通过栈封闭或ThreadLocal将对象限制在单个线程内 --- ### 总结 | 方法 | 适用场景 | 特点 | |--------------------|---------------------------------|-------------------------| | 同步代码块 | 简单同步需求 | 简单但粒度粗 | | Lock接口 | 需要高级锁功能 | 灵活但需手动释放 | | 原子类 | 简单原子操作 | 无锁高效 | | 线程安全容器 | 集合类并发访问 | 优化过的并发实现 | | volatile | 状态标志可见性保证 | 不保证复合操作原子性 | **最佳实践建议**: 1. 优先使用线程安全容器和原子类 2. 需要精细控制时选择Lock接口 3. 高并发场景优先考虑无锁编程 4. 避免过早优化,先保证正确性再考虑性能 需要根据具体业务场景(如读/写比例、性能要求、数据致性级别)选择最合适的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值