JAVA23-字符串与反射

字符串(String)是一个用于表示文本数据的类。它是不可变的(Immutable),即一旦创建,其内容就不能被修改。字符串在 Java 中非常常用,几乎所有的程序都会用到字符串来处理文本信息。由于字符串的不可变性,它在多线程环境中是安全的,并且可以通过字符串池优化内存使用。对于频繁的字符串操作,建议使用 StringBuilder 或 StringBuffer 以提高性能。

字符串是不可变的,为什么?

一是为了线程安全(不可变代表着它是线程安全的),二是为了存储安全

例如 HashMap,我们知道HashMap在存储的时候,会给元素计算一个hashCode值,假如你存储了一个字符串对象,如果你修改了它,那么这个hashCode就和原来的值不一致了,这会违反了Object类中关于hashCode的约定,因此为了我们能够在哈希桶中安全的使用String,字符串必须是不可变的

不可变也导致了每当修改的时候都需要重复创建新的对象,因此请不要在循环中创建字符串对象

StringBuilder

优先使用,线程不安全,速度快

手写StringBuilder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class MyStringBuilder {
    public final List<Character> charList = new ArrayList<>();

    public MyStringBuilder() {
    }

    // 在末尾添加一个字符
    public MyStringBuilder append(char ch) {
        charList.add(charList.size(), ch);
        return this;
    }

    // 在末尾添加一个字符串,其数据需要从bytes字节数组中按照charsetName字符集解码得到
    // 请思考一下字节和字符串(字符串本质上是字节数组)之间d关系
    // 并查找相关API
    public MyStringBuilder append(byte[] bytes, String charsetName) {
        new String(bytes, Charset.forName(charsetName)).chars().forEach(ch -> charList.add((char) ch));
        return this;
    }

    // 在index指定位置添加一个字符ch
    public MyStringBuilder insert(int index, char ch) {
        charList.add(index, ch);
        return this;
    }

    // 删除位于index处的字符
    public MyStringBuilder deleteCharAt(int index) {
        charList.remove(index);
        return this;
    }

    @Override
    public String toString() {
        return charList.stream().map(Object::toString).collect(Collectors.joining());
    }
}

StringBuffer

线程安全,速度相对StringBuilder要慢一些

编码与解码

  • 将人类能看懂的字符变成字节,我们称为编码
  • 将字节变成人类能看懂的字符,我们称为解码

读取GBK文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class GbkFileReader {
    public static void main(String[] args) {
        File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        System.out.println(new GbkFileReader().readFileWithGBK(new File(projectDir, "gbk.txt")));
    }

    public String readFileWithGBK(File file) {
        StringBuilder stringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(new FileInputStream(file), Charset.forName("GBK")))) {
            stringBuilder.append(bufferedReader.readLine());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return stringBuilder.toString();
    }
}

类型与反射

Java中的类型与Class对象

  • 一个Class对象就是一个类的说明书
    • JVM根据这个说明书创造出一个类的实例
    • 静态变量就是Class对象中的变量,它归属与这个Class对象
  • RTTI(Run-Time-Type-Identification) 运行时类型识别
    • 任何一个对象,在运行时JVM都清楚的知道它是什么类型的,就是因为它知道这个对象是根据哪个Class对象创造出来的
    • instanceof 就是利用了RTTI这个特性
    • 强制类型转换时也是利用了这个特性,JVM清楚的知道这个对象能不能被转换成其他类型

Class对象从哪里来的?

  • Class对象的生命周期 Class对象的生命周期
  • Class对象在类第一次被使用时加载
  • Class对象通过ClassLoader(类加载器加载)
    • ClassLoader负责从外部系统中加载一个类
      • 这个类的.java(源)文件不一定需要存在
      • 这个类的.class(字节码)文件也不一定需要存在
1
2
3
4
5
6
Class不一定非要从文件中读取,还可以从网络上直接读取并加载进JVM虚拟机
Class也不一定非要从.java源码文件编译而来,你可以直接根据JVM虚拟机规范创造一个Class
Java 和 Class 没有任何关系,它们是两种东西,分别遵从两种规范
  1. Java Language Specification 语言规范
  2. Java Virtual Machine Specification 虚拟机规范
这种分离提供了在JVM上运行其他语言的可能,比如Groovy,Kotlin
  • ClassLoader的双亲委派加载模型(避免加载恶意类)
1
2
每一个ClassLoader对象都有一个父亲对象,在加载一个类时,它会先询问父亲类有没有对应的类  
如果有,则由父亲直接加载,并返回,否则自己再查找这个类并加载,这样避免了加载恶意构造的类

实现一个自定义的ClassLoader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class MyClassLoader extends ClassLoader {
    // 存放字节码文件的目录
    private final File bytecodeFileDirectory;

    public MyClassLoader(File bytecodeFileDirectory) {
        this.bytecodeFileDirectory = bytecodeFileDirectory;
    }

    // 还记得类加载器是做什么的么?
    // "从外部系统中,加载一个类的定义(即Class对象)"
    // 请实现一个自定义的类加载器,将当前目录中的字节码文件加载成为Class对象
    // 提示,一般来说,要实现自定义的类加载器,你需要覆盖以下方法,完成:
    //
    // 1.如果类名对应的字节码文件存在,则将它读取成为字节数组
    //   1.1 调用ClassLoader.defineClass()方法将字节数组转化为Class对象
    // 2.如果类名对应的字节码文件不存在,则抛出ClassNotFoundException
    //
    // 一个用于测试的字节码文件可以在本项目的根目录找到
    //
    // 请思考:双亲委派加载模型在哪儿?为什么我们没有处理?
    // 扩展阅读:ClassLoader类的Javadoc文档
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = Files.readAllBytes(new File(bytecodeFileDirectory, name + ".class").toPath());
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }
    }

    public static void main(String[] args) throws Exception {
        File projectRoot = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        MyClassLoader myClassLoader = new MyClassLoader(projectRoot);

        Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
        Object testClassInstance = testClass.getConstructor().newInstance();
        String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
        System.out.println(message);
    }
}

反射

什么是反射

一个人,站在镜子面前,看镜子里的自己,我们称为反射,也叫内省,同理,一个对象,通过一面镜子,能看到自己内部的东西,也叫反射

镜子是谁

Class对象就是镜子

反射解决了什么问题?

根据参数动态创建一个对象

1
2
3
4
5
6
7
8
public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> clazz = Class.forName("java.lang.StringBuilder");
        Object o = clazz.getConstructor().newInstance();
        ((StringBuilder) o).append("Hello World!");
        System.out.println(o);
    }
}

根据参数动态调用一个方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Main {
    private static class Demo {
        public void foo() {
            System.out.println("foo() is running...");
        }

        public void bar() {
            System.out.println("bar() is running...");
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Demo demo = new Demo();
        demo.getClass().getMethod("bar").invoke(demo);
    }
}

根据参数动态获取一个属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class Main {
    private static class Demo {
        public String name;
        public int age;

        public Demo(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        Demo demo = new Demo("MaoMao", 3);
        Object name = demo.getClass().getField("name").get(demo);
        System.out.println(name);
    }
}

反射的常用API

  • getClass
  • Class.forName
  • getConstructor
  • getMethod
  • getField
  • invoke

反射练习

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MapBeanConverter {
    // 传入一个遵守Java Bean约定的对象,读取它的所有属性,存储成为一个Map
    // https://github.com/hcsp/map-bean-converter/pull/143/files例如,对于一个DemoJavaBean对象 { id = 1, name = "ABC" }
    // 应当返回一个Map { id -> 1, name -> "ABC", longName -> false }
    // 提示:
    //  1. 读取传入参数bean的Class
    //  2. 通过反射获得它包含的所有名为getXXX/isXXX,且无参数的方法(即getter方法)
    //  3. 通过反射调用这些方法并将获得的值存储到Map中返回
    public static Map<String, Object> beanToMap(Object bean) {
        Map<String, Object> hashMap = new HashMap<>();
        Class<?> clazz = bean.getClass();
        for (Method method : clazz.getMethods()) {
            if (isGet(method)) {
                javaBeanToMap(bean, hashMap, method, 3);
            }
            if (isIs(method)) {
                javaBeanToMap(bean, hashMap, method, 2);
            }
        }
        return hashMap;
    }

    // 传入一个遵守Java Bean约定的Class和一个Map,生成一个该对象的实例
    // 传入参数DemoJavaBean.class和Map { id -> 1, name -> "ABC"}
    // 应当返回一个DemoJavaBean对象 { id = 1, name = "ABC" }
    // 提示:
    //  1. 遍历map中的所有键值对,寻找klass中名为setXXX,且参数为对应值类型的方法(即setter方法)
    //  2. 使用反射创建klass对象的一个实例
    //  3. 使用反射调用setter方法对该实例的字段进行设值
    public static <T> T mapToBean(Class<T> klass, Map<String, Object> map) {
        T bean = newInstance(klass);
        for (Method method : klass.getMethods()) {
            if (isSet(method)) {
                String field = getJavaBeanField(method, 3);
                map.forEach((k, v) -> {
                    if (k.equals(field)) {
                        invoke(method, bean, v);
                    }
                });
            }
        }
        return bean;
    }

    private static boolean isIs(Method method) {
        String name = method.getName();
        if (name.startsWith("is") && name.length() > 2 && method.getParameterCount() == 0) {
            if (Character.isUpperCase(name.toCharArray()[2])) {
                return true;
            }
        }
        return false;
    }

    private static boolean isGet(Method method) {
        String name = method.getName();
        if (name.startsWith("get") && !"getClass".equals(name) && name.length() > 3 && method.getParameterCount() == 0) {
            if (Character.isUpperCase(name.toCharArray()[3])) {
                return true;
            }
        }
        return false;
    }

    private static boolean isSet(Method method) {
        String name = method.getName();
        if (name.startsWith("set") && name.length() > 3) {
            if (Character.isUpperCase(name.toCharArray()[3])) {
                return true;
            }
        }
        return false;
    }

    private static void javaBeanToMap(Object bean, Map<String, Object> hashMap, Method method, int i) {
        hashMap.put(getJavaBeanField(method, i), invoke(method, bean));
    }

    private static String getJavaBeanField(Method method, int i) {
        char[] field = method.getName().substring(i).toCharArray();
        field[0] = Character.toLowerCase(field[0]);
        return String.valueOf(field);
    }

    private static Object invoke(Method method, Object bean, Object... args) {
        try {
            return method.invoke(bean, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private static <T> T newInstance(Class<T> klass) {
        try {
            return klass.getConstructor().newInstance();
        } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        DemoJavaBean bean = new DemoJavaBean();
        bean.setId(100);
        bean.setName("AAAAAAAAAAAAAAAAAAA");
        System.out.println(beanToMap(bean));

        Map<String, Object> map = new HashMap<>();
        map.put("id", 123);
        map.put("name", "ABCDEFG");
        System.out.println(mapToBean(DemoJavaBean.class, map));
    }

    public static class DemoJavaBean {
        private Integer id;
        private String name;
        private String privateField = "privateField";

        public int isolate() {
            System.out.println(privateField);
            return 0;
        }

        public String is() {
            return "";
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public String getName(int i) {
            return name + i;
        }

        public void setName(String name) {
            this.name = name;
        }

        public boolean isLongName() {
            return name.length() > 10;
        }

        @Override
        public String toString() {
            return "DemoJavaBean{"
                    + "id="
                    + id
                    + ", name='"
                    + name
                    + '\''
                    + ", longName="
                    + isLongName()
                    + '}';
        }
    }
}
updatedupdated2025-03-012025-03-01