我是靠谱客的博主 自由鸡,最近开发中收集的这篇文章主要介绍final 变量可以修改,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

在之前java 开发的认知中,final 修饰的变量一旦初始化,就不能被修改,如果是类变量,只能在构造方法中初始化,在其他方法中如果初始化,编译器也会报错,IDE也会拒绝编译。如下:

这个没问题,这是所有开发者的共识,但是如果遇到了反射,会有些不同,如下:

public class OneCity {
	private final ArrayList<String> names;
	
	public OneCity() {
		names = new ArrayList<>();
		names.add("hello");
	}
	
    public  String getValue() {
	   return names.toString();
	}

}

public class TestString {

	public static void main(String[] args) {
		OneCity oneCity = new OneCity();
		System.out.println("反射前:" + oneCity.getValue());
		try {
			Field nameField;
			nameField = OneCity.class.getDeclaredField("names");
			nameField.setAccessible(true); // 这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
			ArrayList<String> other = new ArrayList<>();
			other.add("world");
			nameField.set(oneCity,other );
			System.out.println("反射后:" + oneCity.getValue());
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

输出的是:

反射前:[hello]
反射后:[world]

显然oneCity的names被替换了,这让我一脸懵逼,之前的认知瞬间被推翻,反射的威力实在太强大了,反射完美绕开了编译器的限制,那究竟背后藏了什么玄机,反射的威力这么强大呢,值得一探究竟。跟踪源码需要sun.reflect包,这个jdk源码没有包含,不过不要紧,sun.reflect包存在于jre/lib/rt.jar,反编译就可以,如果有源码的无所谓了。Field.set 方法的源码如下

    public void set(Object obj, Object value)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        getFieldAccessor(obj).set(obj, value);
    }

可以看到调用了getFieldAccessor(obj)返回的FieldAccessor的set方法,这个FieldAccessor是个接口。getFieldAccessor()的源码如下

  private FieldAccessor getFieldAccessor(Object obj)
        throws IllegalAccessException
    {
        boolean ov = override;
        FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor;
        return (a != null) ? a : acquireFieldAccessor(ov);
    }

初始时,overrideFieldAccessor和fieldAccessor都为null,变量a肯定null,接着调用acquireFieldAccessor,

    private FieldAccessor acquireFieldAccessor(boolean overrideFinalCheck) {
        // First check to see if one has been created yet, and take it
        // if so
        FieldAccessor tmp = null;
        if (root != null) tmp = root.getFieldAccessor(overrideFinalCheck);
        if (tmp != null) {
            if (overrideFinalCheck)
                overrideFieldAccessor = tmp;
            else
                fieldAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
            setFieldAccessor(tmp, overrideFinalCheck);
        }

        return tmp;
    }

核心代码就是 tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);reflectionFactory的类型是ReflectionFactory,我们查看,这时候反编译ReflectionFactory.class。reflectionFactory.newFieldAccessor源码如下:

 public FieldAccessor newFieldAccessor(Field var1, boolean var2) {
        checkInitted();
        return UnsafeFieldAccessorFactory.newFieldAccessor(var1, var2);
    }

跟中下去,因为方法比较长,不贴了,实际返回的是

UnsafeQualifiedObjectFieldAccessorImpl的对象,

这样返回到Field.set,实际就是调用UnsafeQualifiedObjectFieldAccessorImpl的set,源码如下:

   public void set(Object var1, Object var2) throws IllegalArgumentException, IllegalAccessException {
        this.ensureObj(var1);
        if (this.isReadOnly) {
            this.throwFinalFieldIllegalAccessException(var2);
        }

        if (var2 != null && !this.field.getType().isAssignableFrom(var2.getClass())) {
            this.throwSetIllegalArgumentException(var2);
        }

        unsafe.putObjectVolatile(var1, this.fieldOffset, var2);
    }

关键代码是unsafe.putObjectVolatile,unsafe就是大名鼎鼎的Unsafe的对象,熟悉Unsafe的都知道,它可以直接访问系统内存资源,putObjectVolatile,这个方法名很奇怪,感觉和volatile有关系,先不管,这篇分析两者关系,https://blog.csdn.net/hanshengjian/article/details/86612767

    public native void putObject(Object var1, long var2, Object var4);

是个native 方法,在Unsafe.cpp中实现,真正实现的是Unsafe_SetObjectVolatile方法

UNSAFE_ENTRY(void, Unsafe_SetObjectVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
  UnsafeWrapper("Unsafe_SetObjectVolatile");
  oop x = JNIHandles::resolve(x_h);
  oop p = JNIHandles::resolve(obj);
  void* addr = index_oop_from_field_offset_long(p, offset);
  OrderAccess::release();
  if (UseCompressedOops) {
    oop_store((narrowOop*)addr, x);
  } else {
    oop_store((oop*)addr, x);
  }
  OrderAccess::fence();
UNSAFE_END

核心就是调用了oop_store方法,从方法意思就是替换偏移量offset的字段指向更新为传入的x_h对象,这个x_h就是Field.set传入的第二个参数。

 

总结一下:Field.set其实是修改了字段在内存中的值,所以编译器规则失效。

 

 

最后

以上就是自由鸡为你收集整理的final 变量可以修改的全部内容,希望文章能够帮你解决final 变量可以修改所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部