如果我们使用反射更改最终 属性 值,则不会抛出 UnsupportedEncodingException

UnsupportedEncodingException is not getting thrown, if we change final property value using reflection

package com.java.random.practice;
    
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
    
public class App 
{
    private static final String ENCODING = "\UTF-8";
    public static void main( String[] args ) throws UnsupportedEncodingException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
        {      
            URLEncoder.encode("anyValue", ENCODING);
        }
}

上面的代码在使用"\"进行编码时抛出异常UnsupportedEncodingException,但是当我们使用反射修改值时却没有任何异常,请看下面的代码:

package com.java.random.practice;
    
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
    
public class App 
{
    private static final String ENCODING = "UTF-8";
    public static void main( String[] args ) throws UnsupportedEncodingException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
        {      
            App app=new App(); 
            Field field = app.getClass().getDeclaredField("ENCODING");
            field.setAccessible(true); 
            Field modifiersField =
            Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true); modifiersField.setInt(field,
            field.getModifiers() & ~Modifier.FINAL); field.set(null, "\UTF-8");
             
            String s= URLEncoder.encode("anyValue", ENCODING);
            System.out.println(s);
        }
    }

为什么会有这种奇怪的行为?

编译时间常数的值由编译器内联

这是什么意思?
假设有 private static final String STR = "Foo";。在这里我们确定(使用标准语言规则,不包括反射,因为它是一种打破语言强制执行的所有保证的工具,例如:阻止访问 private 成员、修改 final 等)STR 应该总是 "Foo" 并且该信息在编译时也是已知的。

这允许编译器优化像 System.out.println(STR); 这样的代码,并且不需要查找 STR 的值,而是直接使用它,这将产生与我们编写 System.out.println("Foo"); 相同的字节码(因为值是编译器已知的,并且“理论上”总是相同的)。

因此,即使我们使用反射并将新值重新分配给 STR,它也不会影响表示 System.out.println("Foo"); 的字节码,因为它不再依赖于 STR.

你的情况

String s= URLEncoder.encode("anyValue", ENCODING);

将像您将其编写为

一样进行编译
String s= URLEncoder.encode("anyValue", "UTF-8");

(因为 ENCODINTprivate static final String ENCODING = "UTF-8"; 值是 "UTF-8" 并且在使用 ENCODINT 的地方被内联)。因此,即使您将新值分配给 ENCODINT(此处为“\UTF-8”),它也不会影响表示 URLEncoder.encode("anyValue", "UTF-8") 的字节码,因为它不会 use/refer至 ENCODING.

如果您想防止内联,请不要使 ENCODING 成为 编译 时间常数。换句话说,确保分配的值需要在运行时“计算”。例如,您可以使用 private static final String ENCODING = "UTF-8".substring(0);.