浅谈Java开发规范与开发细节(下),offer拿到手软

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException(“Cannot reflectively create enum objects”);


return inst;
}

从这我们可以看出,枚举为了**保证不能被克隆,维持单例的状态,禁止了clone和反射创建实例。**那么我们接着来看序列化,由于所有的枚举都是Eunm类的子类及其实例,而Eunm类默认实现了SerializableComparable接口,所以默认允许进行排序和序列化,而排序的方法compareTo的实现大概如下:

/**

  • Compares this enum with the specified object for order. Returns a
  • negative integer, zero, or a positive integer as this object is less
  • than, equal to, or greater than the specified object.
  • Enum constants are only comparable to other enum constants of the
  • same enum type. The natural order implemented by this
  • method is the order in which the constants are declared.
    */
    public final int compareTo(E o) {
    Enum<?> other = (Enum<?>)o;
    Enum self = this;
    if (self.getClass() != other.getClass() && // optimization
    self.getDeclaringClass() != other.getDeclaringClass())
    throw new ClassCastException();
    return self.ordinal - other.ordinal;
    }

而ordinal则是代表每个枚举常量对应的申明顺序,说明枚举的排序方式默认按照申明的顺序进行排序,那么序列化和反序列化的过程是什么样的呢?我们来编写一个序列化的代码,debug跟代码以后,可以看到最终是调用了java.lang.Enum#valueOf 方法来实现的反序列化的。而序列化后的内容大概如下:

arn_enum.CoinEnum?xr?java.lang.Enum?xpt?PENNYq?t?NICKELq?t?DIMEq~?t?QUARTER

大概可以看到,序列化的内容主要包含枚举类型和枚举的每个名称,接着我们看看java.lang.Enum#valueOf方法的源码:

/**

  • Returns the enum constant of the specified enum type with the
  • specified name. The name must match exactly an identifier used
  • to declare an enum constant in this type. (Extraneous whitespace
  • characters are not permitted.)
  • Note that for a particular enum type {@code T}, the

  • implicitly declared {@code public static T valueOf(String)}
  • method on that enum may be used instead of this method to map
  • from a name to the corresponding enum constant. All the
  • constants of an enum type can be obtained by calling the
  • implicit {@code public static T[] values()} method of that
  • type.
  • @param The enum type whose constant is to be returned
  • @param enumType the {@code Class} object of the enum type from which
  • to return a constant
  • @param name the name of the constant to return
  • @return the enum constant of the specified enum type with the
  • specified name
  • @throws IllegalArgumentException if the specified enum type has
  • no constant with the specified name, or the specified
  • class object does not represent an enum type
  • @throws NullPointerException if {@code enumType} or {@code name}
  • is null
  • @since 1.5
    */
    public static <T extends Enum> T valueOf(Class enumType,
    String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
    return result;
    if (name == null)
    throw new NullPointerException(“Name is null”);
    throw new IllegalArgumentException(
    "No enum constant " + enumType.getCanonicalName() + “.” + name);
    }

从源码和注释中我们都可以看出来,如果此时A服务使用的枚举类为旧版本,只有五个常量,而B服务的枚举中包含了新的常量,这个时候在反序列化的时候,由于name == null,则会直接抛出异常,从这我们也终于看出来,为什么规范中会强制不允许使用枚举类型作为参数进行序列化传递了

慎用可变参数

在翻阅各大规范手册的时候,我看到阿里手册中有这么一条:

【强制】相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Obje
ct 。说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)
正例: public List listUsers(String type, Long… ids) {…}

吸引了我,因为在以前开发过程中,我就遇到了一个可变参数埋下的坑,接下来我们就来看看可变参数相关的一个坑。

相信很多人都编写过企业里使用的工具类,而我当初在编写一个Boolean类型的工具类的时候,编写了大概如下的两个方法:

private static boolean and(boolean… booleans) {
for (boolean b : booleans) {
if (!b) {
return false;
}
}
return true;
}

private static boolean and(Boolean… booleans) {
for (Boolean b : booleans) {
if (!b) {
return false;
}
}
return true;
}

这两个方法看起来就是一样的,都是为了传递多个布尔类型的参数进来,判断多个条件连接在一起,是否能成为true的结果,但是当我编写测试的代码的时候,问题出现了:

public static void main(String[] args) {
boolean result = and(true, true, true);
System.out.println(result);
}

这样的方法会返回什么呢?其实当代码刚刚编写完毕的时候,就会发现编译器已经报错了,会提示:

Ambiguous method call. Both and (boolean…) in BooleanDemo and and (Boolea
n…) in BooleanDemo match.

模糊的函数匹配,因为编译器认为有两个方法都完全满足当前的函数,那么为什么会这样的呢?我们知道在Java1.5以后加入了自动拆箱装箱的过程,为了兼容1.5以前的jdk版本,将此过程设置为了三个阶段:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

而我们使用的测试方法中,在第一阶段,判断jdk版本,是不是不允许自动装箱拆箱,明显jdk版本大于1.5,允许自动拆箱装箱,因此进入第二阶段,此时判断是否存在更符合的参数方法,比如我们传递了三个布尔类型的参数,但是如果此时有三个布尔参数的方法,则会优先匹配此方法,而不是匹配可变参数的方法,很明显也没有,此时就会进入第三阶段,完成装箱拆箱以后,再去查找匹配的变长参数的方法,这个时候由于完成了拆箱装箱,两个类型会视为一个类型,发现方法上有两个匹配的方法,这时候就会报错了。

那么我们有木有办法处理这个问题呢?毕竟我们熟悉的org.apache.commons.lang3.BooleanUtils工具类中也有类似的方法,我们都明白,变长参数其实就是会将当前的多个传递的参数装入数组后,再去处理,那么可以在传递的过程中,将所有的参数通过数组包裹,这个时候就不会发生拆箱装箱过程了!例如:

@Test
public void testAnd_primitive_validInput_2items() {
assertTrue(
! BooleanUtils.and(new boolean[] { false, false })
}

而参考其他框架源码大神的写法中,也有针对这个的编写的范例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过此种方法可以保证如果传入的是基本类型,直接匹配当前方法,如果是包装类型,则在第二阶段以后匹配到当前函数,最终都是调用了BooleanUtils中基本类型的and方法

List的去重与xxList方法

List作为我们企业开发中最常见的一个集合类,在开发过程中更是经常遇到去重,转换等操作,但是集合类操作的不好很多时候会导致我们的程序性能缓慢或者出现异常的风险,例如阿里手册中提到过:

【 强 制 】 ArrayList 的 subList 结 果 不 可 强 转 成 ArrayList , 否 则 会 抛 出
ClassCastException 异 常,即 java.util.RandomAccessSubList cannot be cast to
java.util.ArrayList。
【强制】在 SubList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的
遍历、增加、删除产生 ConcurrentModificationException 异常。
【强制】使用工具类 Arrays.asList () 把数组转换成集合时,不能使用其修改集合相关的
方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

而手册中的这些xxList方法则是我们开发过程中比较常用的,那么为什么阿里手册会有这些规范呢?我们来看看第一个方法subList,首先我们先看看SubList类和ArrayList类的区别,从类图上我们可以看出来两个类之间并没有继承关系:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以手册上不允许使用subList强转为ArrayList,那么为什么原集合不能进行增删改查操作呢?我们来看看其源码:

/**

  • Returns a view of the portion of this list between the specified
  • {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If
  • {@code fromIndex} and {@code toIndex} are equal, the returned list is
  • empty.) The returned list is backed by this list, so non-structural
  • changes in the returned list are reflected in this list, and vice-versa.
  • The returned list supports all of the optional list operations.
  • This method eliminates the need for explicit range operations (of

  • the sort that commonly exist for arrays). Any operation that expects
  • a list can be used as a range operation by passing a subList view
  • instead of a whole list. For example, the following idiom
  • removes a range of elements from a list:
  • list.subList(from, to).clear();
  • Similar idioms may be constructed for {@link #indexOf(Object)} and
  • {@link #lastIndexOf(Object)}, and all of the algorithms in the
  • {@link Collections} class can be applied to a subList.
  • The semantics of the list returned by this method become undefined if

  • the backing list (i.e., this list) is structurally modified in
  • any way other than via the returned list. (Structural modifications are
  • those that change the size of this list, or otherwise perturb it in such
  • a fashion that iterations in progress may yield incorrect results.)
  • @throws IndexOutOfBoundsException {@inheritDoc}
  • @throws IllegalArgumentException {@inheritDoc}
    */
    public List subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
    }

我们可以看到代码的逻辑只有两步,第一步检查当前的索引和长度是否变化,第二步构建新的SubList出来并且返回。从注释我们也可以了解到,SubList中包含的范围,如果对其进行增删改查操作,都会导致原来的集合发生变化,并且是从当前的index + offSet进行变化。那么为什么我们这个时候对原来的ArrayList进行增删改查操作的时候会导致SubList集合操作异常呢?我们来看看ArrayList的add方法:

/**

  • Appends the specified element to the end of this list.
  • @param e element to be appended to this list
  • @return true (as specified by {@link Collection#add})
    */
    public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

我们可以看到一点,每次元素新增的时候都会有一个 ensureCapacityInternal(size + 1);操作,这个操作会导致modCount长度变化,而modCount则是在SubList的构造中用来记录长度使用的:

SubList(AbstractList parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount; // 注意:此处复制了 ArrayList的 modCount
}

而SubList的get操作的源码如下:

public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}

可以看到每次都会去校验一下下标和modCount,我们来看看checkForComodification方法:

private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}

可见每次都会检查,如果发现原来集合的长度变化了,就会抛出异常,那么使用SubList的时候为什么要注意原集合是否被更改的原因就在这里了。

那么为什么asList方法的集合不允许使用新增、修改、删除等操作呢?

我们来看下和ArrayList的方法比较:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很明显我们能看出来,asList构建出来的List没有重写add remove 函数,说明该类的集合操作的方法来自父类AbstactList,我们来看看父类的add方法:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

笔者福利

以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。

有了这个,面试踩雷?不存在的!

回馈粉丝,诚意满满!!!




96)]

笔者福利

以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。

有了这个,面试踩雷?不存在的!

回馈粉丝,诚意满满!!!

[外链图片转存中…(img-3zAO2cw7-1711867881597)]
[外链图片转存中…(img-gVNiNM4X-1711867881597)]
[外链图片转存中…(img-4i8k2jPc-1711867881597)]
[外链图片转存中…(img-bPpjkq4j-1711867881598)]

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值