资讯动态

看到个问题“final修饰的变量一定是不可变的吗”,想到啥写啥。

发布时间:2026/10/14 10:03:55 来源:尧图企业网站定制
final目录• final修饰的变量一定不可修改吗• final修饰基本类型 vs 引用类型• 基本类型——真的不可修改• 引用类型——引用不可变对象内容可变• List.of()和Arrays.asList()• 如何让引用对象也不可修改• 使用不可变类• Collections.unmodifiableXXX包装• 防御性拷贝• 自定义不可变类• 应用final修饰的变量一定不可修改吗final的作用很多人脱口而出final修饰的变量不可修改但这话只对了一半。final修饰基本类型变量时值确实不可变但final修饰引用类型变量时只是引用不可变对象本身的内容是可以修改的。下面详细看。final修饰基本类型 vs 引用类型基本类型真的不可修改final修饰基本类型赋值之后就不能再修改了重新赋值连编译都过不了final int a 10; a 20;引用类型引用不可变对象内容可变final修饰引用类型对象、数组等意思是这个变量保存的地址值不能变也就是不能再指向另一个对象但当前指向的对象里面的内容是可以改的。public class Test01 { public static void main(String[] args) { final Person person new Person(张三, 23); System.out.println(person); person.setName(李四); person.setAge(24); System.out.println(person); // 重新指向另一个对象——编译报错 // person new Person(王五, 40); } }结果Person{name张三, age23} Person{name李四, age24}再来看个Listpublic class Test02 { public static void main(String[] args) { final ListString list new ArrayList(); list.add(Java); list.add(IDEA); System.out.println(list); list.remove(IDEA); list.add(MySQL); System.out.println(list); // 编译报错 // list new ArrayList(); } }运行结果[Java, IDEA] [Java, MySQL]看到这里应该有人想到了两个东西List.of()和Arrays.asList()List.of()不允许add,remove,set否则UnsupportedOperationExceptionArrays.asList()返回固定大小列表内容可变大小不能变。允许set不允许remove,addpublic class Test03 { public static void main(String[] args) { ListString list List.of(Java, IDEA); try { list.add(JAVA); System.out.println(list); }catch (Exception e){ System.out.println(List.of add失败 e.getClass().getSimpleName()); } try{ list.set(0, JAVA); System.out.println(list); }catch (Exception e){ System.out.println(List.of set失败 e.getClass().getSimpleName()); } try{ list.remove(Java); System.out.println(list); }catch (Exception e){ System.out.println(List.of remove失败 e.getClass().getSimpleName()); } ListString list2 Arrays.asList(Java, IDEA); try { list2.add(JAVA); System.out.println(list2); }catch (Exception e){ System.out.println(Arrays.asList add失败 e.getClass().getSimpleName()); } try{ list2.set(0, JAVA); System.out.println(list2); }catch (Exception e){ System.out.println(Arrays.asList set失败 e.getClass().getSimpleName()); } try { list2.remove(Java); System.out.println(list2); }catch (Exception e){ System.out.println(Arrays.asList remove失败 e.getClass().getSimpleName()); } try { list2.remove(JAVA); System.out.println(list2); }catch (Exception e){ System.out.println(Arrays.asList remove失败 e.getClass().getSimpleName()); } } }运行结果List.of add失败UnsupportedOperationException List.of set失败UnsupportedOperationException List.of remove失败UnsupportedOperationException Arrays.asList add失败UnsupportedOperationException [JAVA, IDEA] [JAVA, IDEA] Arrays.asList remove失败UnsupportedOperationException可以看到找不到元素的时候Arrays.asList()并没有抛出异常。原因先根据list2.remove()进入remove进入的是一个接口boolean remove(Object o);是哪个里面的remove在上面代码后加上下面代码// 打印list2的实际类型 System.out.println(list2.getClass()); // 用反射直接看remove(Object)这个方法是在哪个类里定义的 try { Method m list2.getClass().getMethod(remove, Object.class); System.out.println(remove方法定义在: m.getDeclaringClass()); } catch (Exception e) { e.printStackTrace(); }运行结果List.of add失败UnsupportedOperationException List.of set失败UnsupportedOperationException List.of remove失败UnsupportedOperationException Arrays.asList add失败UnsupportedOperationException [JAVA, IDEA] [JAVA, IDEA] Arrays.asList remove失败UnsupportedOperationException class java.util.Arrays$ArrayList remove方法定义在: class java.util.AbstractCollection进入AbstractCollection找到removepublic boolean remove(Object o) { IteratorE it iterator(); if (onull) { while (it.hasNext()) { if (it.next()null) { it.remove(); return true; } } } else { while (it.hasNext()) { if (o.equals(it.next())) { it.remove(); return true; } } } return false; }在remove里先找到之后才会进入it.remove否则就直接返回false而不是抛异常。进入it.removedefault void remove() { throw new UnsupportedOperationException(remove); }调用这个remove才抛异常不然不抛所以上面出现俩[JAVA, IDEA]是因为set的时候把Java set成了JAVA没有Java了找不到所以没走it.remove而是直接返回了false所以没有抛异常。扯远了回到原来final的部分既然final修饰引用对象的时候对象里面的内容是可以变的有什么办法能不让它变如何让引用对象也不可修改光加final根本拦不住别人改对象内容。真正让对象不可修改有以下方案。使用不可变类JDK自带的一些类本身就是不可变的比如String、基本类型的包装类Integer、Long等。final Integer a 10; a 20; // 编译报错这是final的作用 // Integer没有setter内部value是private final的没法改内容这是不可变类的作用 // Integer里有个Value private final int value;Collections.unmodifiableXXX包装public class Test04 { public static void main(String[] args) { ListString list new ArrayList(); list.add(java); list.add(IDEA); // 包装成不可变类 final ListString unmodifiableList Collections.unmodifiableList(list); try{ unmodifiableList.add(JAVA); }catch (Exception e){ System.out.println(操作失败 e.getClass().getSimpleName()); } } }运行结果操作失败UnsupportedOperationException看着似乎对了但其实有隐患。Collections.unmodifiableList(LIST)返回的List实际上和原来的List指的还是同一个对象。ListString list new ArrayList(); final ListString unmodifiableList Collections.unmodifiableList(list);用unmodifiableList调用add修改会抛异常但是用list不会而且list.add之后unmodifiableList也会受到影响。修改Test04public class Test04 { public static void main(String[] args) { ListString list new ArrayList(); list.add(java); list.add(IDEA); // 包装成不可变类 final ListString unmodifiableList Collections.unmodifiableList(list); try{ unmodifiableList.add(JAVA); }catch (Exception e){ System.out.println(操作失败 e.getClass().getSimpleName()); } list.add(MySQL); System.out.println(unmodifiableList); } }运行结果操作失败UnsupportedOperationException [java, IDEA, MySQL]解决方案让俩list指向不同的对象包装的时候new一个ArrayList包这个new的不包装原来的public class Test04 { public static void main(String[] args) { ListString list new ArrayList(); list.add(java); list.add(IDEA); // 包装成不可变类 // final ListString unmodifiableList Collections.unmodifiableList(list); final ListString unmodifiableList Collections.unmodifiableList(new ArrayList(list)); try{ unmodifiableList.add(JAVA); }catch (Exception e){ System.out.println(操作失败 e.getClass().getSimpleName()); } list.add(MySQL); System.out.println(unmodifiableList); } }运行结果操作失败UnsupportedOperationException [java, IDEA]这样就做到了unmodifiableList无法改变对象内容产生unmodifiableList的list即使发生了修改也不会影响到unmodifiableList而这个new的过程就是下面说的防御性拷贝。防御性拷贝给外界一个副本外界怎么改都不影响原来的内部数据保护内部数据不被外部修改。public class DefensiveCopyTest { // 内部数据 private final ListString list new ArrayList(); public DefensiveCopyTest(){ list.add(Java); list.add(IDEA); } // 返回副本不返回原始内部数据 public ListString getList(){ return new ArrayList(list); } public static void main(String[] args) { DefensiveCopyTest test new DefensiveCopyTest(); ListString list test.getList(); // 这里拿的是副本list怎么改都不会影响test里的list list.add(MySQL); System.out.println(list); System.out.println(原始内部数据的List内容是 test.getList()); } }运行结果[Java, IDEA, MySQL] 原始内部数据的List内容是[Java, IDEA]自定义不可变类所有字段都私有final构造方法初始化赋值集合用unmodifiableList包装防止修改只提供getter不提供setter即可。这里就不写了。可以看Integer实现里面有个valueprivate修饰没提供setter而且还是final修饰的改不了。应用场景一个Constant类对外暴露提供的已经写好的变量但是不能修改里面的变量内容public class Constant { private Constant(){} private static final ListString LIST new ArrayList(); public static final Integer COUNT 100; static { LIST.add(java); LIST.add(IDEA); } public static ListString getList(){ // 常量类基本不会出现说提供LIST的修改方法所以这里就不new ArrayList(LIST)了 return Collections.unmodifiableList(LIST); } }public class Test05 { public static void main(String[] args) { ListString list Constant.getList(); System.out.println(拿到的Constant的List为 list); // Constant.COUNT 10; // 这里直接编译报错 try{ // 尝试修改 list.add(MySQL); System.out.println(add成功 list); }catch (Exception e){ System.out.println(add失败 e.getClass().getSimpleName()); } } }运行结果拿到的Constant的List为[java, IDEA] add失败UnsupportedOperationException最后再来说一下Collections的unmodifiableList(list)为啥调用修改方法就抛异常进入这个unmodifiableList方法public static T ListT unmodifiableList(List? extends T list) { if (list.getClass() UnmodifiableList.class || list.getClass() UnmodifiableRandomAccessList.class) { return (ListT) list; } return (list instanceof RandomAccess ? new UnmodifiableRandomAccessList(list) : new UnmodifiableList(list)); }if判断是不是那俩类型是的话就直接返回不重复包装如果不是的话就包装返回看ArrayListpublic class ArrayListE extends AbstractListE implements ListE, RandomAccess, Cloneable, java.io.Serializable { // 省略。。。}ArrayList实现了RandomAccessreturn那句就包装成了UnmodifiableRandomAccessList再看UnmodifiableRandomAccessListstatic class UnmodifiableRandomAccessListE extends UnmodifiableListE implements RandomAccess { UnmodifiableRandomAccessList(List? extends E list) { super(list); } public ListE subList(int fromIndex, int toIndex) { return new UnmodifiableRandomAccessList( list.subList(fromIndex, toIndex)); } java.io.Serial private static final long serialVersionUID -2542308836966382001L; /** * Allows instances to be deserialized in pre-1.4 JREs (which do * not have UnmodifiableRandomAccessList). UnmodifiableList has * a readResolve method that inverts this transformation upon * deserialization. */ java.io.Serial private Object writeReplace() { return new UnmodifiableList(list); } }UnmodifiableRandomAccessList没有add之类的方法但继承了UnmodifiableList看UnmodifiableListstatic class UnmodifiableListE extends UnmodifiableCollectionE implements ListE { java.io.Serial private static final long serialVersionUID -283967356065247728L; SuppressWarnings(serial) // Conditionally serializable final List? extends E list; UnmodifiableList(List? extends E list) { super(list); this.list list; } public boolean equals(Object o) { return o this || list.equals(o); } public int hashCode() { return list.hashCode(); } public E get(int index) { return list.get(index); } public E set(int index, E element) { throw new UnsupportedOperationException(); } public void add(int index, E element) { throw new UnsupportedOperationException(); } public E remove(int index) { throw new UnsupportedOperationException(); } public int indexOf(Object o) { return list.indexOf(o); } public int lastIndexOf(Object o) { return list.lastIndexOf(o); } public boolean addAll(int index, Collection? extends E c) { throw new UnsupportedOperationException(); } Override public void replaceAll(UnaryOperatorE operator) { throw new UnsupportedOperationException(); } Override public void sort(Comparator? super E c) { throw new UnsupportedOperationException(); } public ListIteratorE listIterator() { return listIterator(0); } public ListIteratorE listIterator(final int index) { return new ListIteratorE() { private final ListIterator? extends E i list.listIterator(index); public boolean hasNext() { return i.hasNext(); } public E next() { return i.next(); } public boolean hasPrevious() { return i.hasPrevious(); } public E previous() { return i.previous(); } public int nextIndex() { return i.nextIndex(); } public int previousIndex() { return i.previousIndex(); } public void remove() { throw new UnsupportedOperationException(); } public void set(E e) { throw new UnsupportedOperationException(); } public void add(E e) { throw new UnsupportedOperationException(); } Override public void forEachRemaining(Consumer? super E action) { i.forEachRemaining(action); } }; } }UnmodifiableList里的add,remove等方法都直接抛异常了。流程用Collections.unmodifiableList方法后看参数类型是啥类型最后返回的类型只有俩种UnmodifiableRandomAccessList或者UnmodifiableList而UnmodifiableRandomAccessList里的add等方法全是用的UnmodifiableList里的UnmodifiableList里的add等方法都是直接抛异常的所以上面的测试代码调用add等方法无法修改内部元素。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑