Java语法技巧

2021-02-10 fishedee 后端

1 概述

Java语法技巧,介绍一些重要而且有别于其他语言的特性

2 注解

注解是一种在类或者方法或者字段上面,加入注释的能力。该注释能设置为在运行时可读取,程序可以通过反射来获取类或者方法或者字段上面级别的注释描述,从而执行不同的操作。注解为Java提供了,针对某些类,方法或者字段开启或者关闭指定功能的方法。

2.1 基础功能

代码在这里

package annotation_test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String Name() default "";
    Class<?>[] Import() default Object.class;
}

定义一个注解,用@interface来定义注解,方法来确定注解可以填写的字段,以及该字段的默认值。最后@Target描述了这个注解可以用在什么地方,类,方法,还是字段。@Retention描述了该注解可以保留在什么地方,编译时,运行时?特别注意的是,要支持运行时的注解解析,要用运行时的注解。

package annotation_test;

import java.util.Arrays;

@MyAnnotation(Name="123",Import={MyUse.class,App.class})
class MyUse{

}

public class App 
{
    public static void main( String[] args ) {
        MyAnnotation annotation = MyUse.class.getAnnotation(MyAnnotation.class);
        System.out.println(Arrays.toString(annotation.Import()));
        System.out.println(annotation.Name());
    }
}

在类上面使用注解,然后用反射来读取注解

2.2 注解继承与省略字段名

代码在这里

package annotation_test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String Name() default "";
    Class<?>[] Import() default Object.class;
}

定义一个注解

package annotation_test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation2 {
    int value() default 0;
}

定义另外一个注解,注意,方法为value的,代表了这个字段可以不用写 Name=XXX的形式,直接用@MyAnnotation2(456)就标识了value字段的值,等效于@MyAnnotation2(value=456)的形式。

package annotation_test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation(Name="fish")
@MyAnnotation2(520)
public @interface CombineAnnotation {

}

然后我们定义一个新注解,注解上面相当于继承了两个小注解。

package annotation_test;

import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.core.annotation.AnnotationUtils;

import java.util.Arrays;

@CombineAnnotation
class MyUse{

}

public class App 
{
    public static void main( String[] args ) {
        MyAnnotation annotation =  AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation.class);
        //查找子注解,要用Spring的AnnotationUtils方法
        //MyAnnotation annotation =  MyUse.class.getAnnotation(MyAnnotation.class);
        System.out.println(Arrays.toString(annotation.Import()));
        System.out.println(annotation.Name());

        MyAnnotation2 annotation2 = AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation2.class);
        System.out.println(annotation2.value());
    }
}

然后,我们用CombineAnnotation标识类的时候,同时就标识了他的两个父注解的值了。注意,这个方法的注解读取要用Spring的AnnotationUtils方法,默认的反射方法做不到这个效果。

2.3 AliasFor同级别名

代码在这里

package annotation_test;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation3 {
    @AliasFor(value="name")
    String value() default "";

    @AliasFor(value="value")
    String name() default "";
}

使用AliasFor对于,一个注解的两个字段,设置为互为别名

package annotation_test;

import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.core.annotation.AnnotationUtils;

import java.util.Arrays;

@MyAnnotation3("fish1")
class MyUse{

}

@MyAnnotation3(name="fish2")
class MyUse2{

}

public class App 
{
    public static void main( String[] args ) {
        //这个例子,演示了AliasFor对两个不同名的字段,实现了两个字段互为别名的效果.

        MyAnnotation3 annotation =  AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation3.class);
        //MyUse只设置了value,但是因为AliasFor,自动设置了name
        System.out.println(annotation.name());
        System.out.println(annotation.value());

        MyAnnotation3 annotation2 =  AnnotationUtils.findAnnotation(MyUse2.class,MyAnnotation3.class);
        //MyUse2只设置了name,但是因为AliasFor,自动设置了value
        System.out.println(annotation2.name());
        System.out.println(annotation2.value());
    }
}

那么,我们随意设置其中一个字段,另外一个字段都会设置为相同的值了。注意,依然需要使用Spring的AnnotationUtils来读取注解。

2.4 AliasFor父级别名

代码在这里

package annotation_test;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation4 {
    @AliasFor(value="name")
    String value() default "";

    @AliasFor(value="value")
    String name() default "";
}

定义一个普通的注解


package annotation_test;

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by fish on 2021/2/10.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation4
public @interface ChildAnnotation {
    @AliasFor(annotation = MyAnnotation4.class,value="name")
    String path() default "";
}

然后定义一个新注解ChildAnnotation,同时继承于MyAnnotation4注解。而且,设置AliasFor,这样使得对于ChildAnnotation的path字段的设置,会自动应用于父级MyAnnotation4注解的name字段。

package annotation_test;

import org.springframework.beans.annotation.AnnotationBeanUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;

import java.util.Arrays;

@ChildAnnotation(path="/com/hello")
class MyUse{

}

public class App 
{
    public static void main( String[] args ) {
        //这个例子,演示了AliasFor对两个父子两个注解,通过子注解的字段,映射到父注解的字段.
        //注意用了AnnotatedElementUtils.findMergedAnnotation方法

        MyAnnotation4 annotation =  AnnotatedElementUtils.findMergedAnnotation(MyUse.class,MyAnnotation4.class);
        //MyUse只设置了ChildAnnotation的path字段,但是因为AliasFor,自动设置了MyAnnotation4的name字段
        System.out.println(annotation.name());
    }
}

但是要注意,这种方法需要Spring的AnnotatedElementUtils工具来读取注解。

3 类加载器

Java是动态代码解析器,像Golang和C/C++,在编译以后,要运行新的代码只能重新编译打包。或者借用操作系统的共享链接库的功能(.so和.dll等等)。而Java由于是有虚拟机的,它是在运行时编译和执行代码的。因此它非常轻松地实现,在运行时,动态加载新的代码文件的功能。而ClassLoader就是Java提供给用户的接口,它描述了新的class应该在哪里查找和加载。

代码在这里

3.1 结构

Class clazz = loader.loadClass("test.MyAdd");

双亲委托是ClassLoader的默认特性,但对一个classLoader执行loadClass操作时,会:

  • 先查找自身有没有这个类文件,没有的话,先尝试调用父级的loadClass。如果父级的loadClass也是没有的话,才会由自身来尝试加载这个类文件。
  • 同理,父级的classLoader也是执行同样的操作,先查找本地缓存,没有的话尝试查找父级的loadClass。

于是,查找和加载逻辑是这样的:

  • 查找classLoader,是从子到父的过程。
  • 加载classLoader,是从父到子的过程。

双亲委托的规则保证了基础的类库是由Java的Bootstrap ClassLoader来实现的,保证了安全性和性能。在特殊的情况下,用户可以改变这个双亲委托的规则,但要小心清楚这样做的后果。

Class.ForName();
this.getClass.getClassLoader()
Thread.currentThread().getContextClassLoader()

默认情况下,Java在未指定classLoader的情况下会使用当前调用位置的class的ClassLoader作为类加载器。用户也可以使用当前线程级别的类加载器,而不是所在方法类的类加载器。

FolderClassLoader loader = new FolderClassLoader("lib");
Class clazz = loader.loadClass("test.MyAdd");

当然,在用户已经获得类加载器实例的情况下,你可以直接用该类加载器来创建类。

3.2 使用

package classloader_test;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * Created by fish on 2021/2/10.
 */
public class FolderClassLoader extends ClassLoader {
    private String folder;
    public FolderClassLoader(String folder){
        this.folder = folder;
    }
    public FolderClassLoader(String folder,ClassLoader parent){
        super(parent);
        this.folder = folder;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        byte []classData = getClassData(name);
        if( classData == null ){
            throw new ClassNotFoundException();
        }
        clazz = defineClass(name,classData,0,classData.length);
        return clazz;
    }

    private byte[] getClassData(String name)throws ClassNotFoundException{
        File file = null;
        InputStream fileInputStream = null;
        try {
             String namePath = name.replace('.','/');
             file = new File(this.folder + "/"+namePath+".class");
             fileInputStream = new FileInputStream(file);
            byte[] result = new byte[fileInputStream.available()];
            fileInputStream.read(result);
            return result;
        }catch(Exception e){
            e.printStackTrace();
            throw new ClassNotFoundException();
        }finally{
            if(fileInputStream != null){
                try{
                    fileInputStream.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

定义一个自己的类加载器,构造函数中可以指定parent,没有parent的话用当前类FolderClassLoader的类加载器作为parent。如果不修改双亲规则的话,重写findClass函数就可以了

 //加载class
FolderClassLoader loader = new FolderClassLoader("lib");
Class clazz = loader.loadClass("test.MyAdd");

//得到class以后,尝试调用我们已知道的函数
Method method = clazz.getMethod("add",int.class,int.class);
Object obj = clazz.newInstance();
Object result = method.invoke(obj,1,2);
System.out.println(result);

加载类,并执行类查找,和实例化

public static void printClassLoaderTree(ClassLoader classLoader){
    System.out.println("begin --- classLoader trace ---");
    do{
        System.out.println(classLoader);
        classLoader = classLoader.getParent();
    }while(classLoader!= null);
    System.out.println("end --- classLoader trace ---");
}

打印classLoader树

public static void loadResourceByClassLoader(ClassLoader loader)throws IOException{
    //根据classLoader获取资源
    InputStream stream = loader.getResourceAsStream("abc.xml");
    System.out.println(stream);
    System.out.println(App.getStreamData(stream));
}

使用classLoader来加载资源

//设置线程变量上的classLoader
classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("default threadClassLoader: "+classLoader);

Thread.currentThread().setContextClassLoader(loader);
classLoader = Thread.currentThread().getContextClassLoader();
System.out.println("setAfter threadClassLoader: "+classLoader);

设置线程级的类加载器,方便其他人使用

4 反射

Java反射相比Golang的特点在于:

  • 速度比原生调用性能下降不多,没有Golang的那种恶劣下降的情况,做好缓存再加上JIT的情况下,性能损失可以忽略。
  • 反射都是同实例,Java反射得到的结果都是同一个实例,不需要创建实例,更不需要考虑被GC。Golang的反射更为节省内存,但是每个反射结果都是新的实例,GC的压力更大。
  • 特性多更复杂。因为Java需要支持方法重载特性,接口特性,继承特性,多访问权限特性,嵌套类特性,所以它的反射接口更加复杂。

代码在这里

4.1 类反射

package java_test;


import java.util.logging.Logger;

public class User extends Person implements Walker{

    public static class Address{
        String country;

        String city;
    }

    protected class Contact{
        String homePhone;

        String workPhone;
    }

    private int id;

    public String name;

    private Address address;

    protected Contact contact;

    public static Logger log;

    public User(){

    }

    protected User(String name){
        this.name = name;
    }

    private void setNameInner(String name){
        this.name = "MM_"+name;
    }

    public static void setPrefix(){

    }

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

    public String getName(){
        return this.name;
    }

    protected void setAddress(Address addr){
        this.address = addr;
    }

    public void walk(){
        System.out.println("walk");
    }
}

先定义一个User类,这个类既有继承,也有接口。字段既有public和private,也有static的。方法既有public和private,也有static。

package java_test;

public class Person {
    public int saySomething;

    public void say(){
        System.out.println("say");
    }
}

基类,带有字段和方法的

package java_test;

public interface Walker {
    void walk();
}

方法

package java_test;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Modifier;

import static org.junit.jupiter.api.Assertions.*;

public class ClassReflect {

    public Class getClass1(){
        return User.class;
    }

    @Test
    public void test(){
        Class clazz = getClass1();
        //基础信息
        assertEquals("java_test.User",clazz.getName());
        assertEquals("User",clazz.getSimpleName());
        assertEquals("java_test",clazz.getPackage().getName());

        //访问信息
        assertTrue(Modifier.isPublic(clazz.getModifiers()));

        //继承树的信息
        assertEquals(Person.class,clazz.getSuperclass());
        assertTrue(Object.class.isAssignableFrom(clazz));//注意子类是写在括号里面的
        assertTrue(Person.class.isAssignableFrom(clazz));
        assertFalse(Animal.class.isAssignableFrom(clazz));

        //接口信息
        assertEquals(1,clazz.getInterfaces().length);
        assertEquals(Walker.class,clazz.getInterfaces()[0]);
        assertTrue(Walker.class.isAssignableFrom(clazz));//接口也可以用isAssignableFrom
        assertFalse(Flyer.class.isAssignableFrom(clazz));

        //其他信息
        assertFalse(clazz.isEnum());//是否为枚举类型
    }

    @Test
    public void test2(){
        Class clazz = User.Address.class;

        //访问信息
        assertTrue(Modifier.isPublic(clazz.getModifiers()));
        assertTrue(Modifier.isStatic(clazz.getModifiers()));

        //是否为内部类
        assertTrue(clazz.isMemberClass());

        //非本地类,定义类级别,不需要外层类的this指针
        assertFalse(clazz.isLocalClass());

        //获取外部层的类
        assertEquals(User.class,clazz.getEnclosingClass());
    }

    @Test
    public void test3(){
        Class clazz = User.Contact.class;

        //访问信息
        assertTrue(Modifier.isProtected(clazz.getModifiers()));
        assertFalse(Modifier.isStatic(clazz.getModifiers()));

        //是否为内部类
        assertTrue(clazz.isMemberClass());

        //非本地类,定义类级别,需要外部类的this指针
        assertFalse(clazz.isLocalClass());

        //获取外部层的类
        assertEquals(User.class,clazz.getEnclosingClass());
    }
}

isAssignableFrom用来计算类是否满足基类,或者实现某个接口的。isEnum用来检查它是否为枚举类型的。

4.2 字段反射

package java_test;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

public class FieldReflect {

    public Class getClass2(){
        try{
            return Class.forName("java_test.User");
        }catch(ClassNotFoundException e){
            throw new RuntimeException(e);
        }
    }

    @Test
    public void test_fields(){
        Class clazz = getClass2();

        //getFields只能获取到public的field,包括static的field
        //包括父类的字段
        Field[] fields =  clazz.getFields();
        List<String> fieldsName = Arrays.asList(fields).stream().map((a)->{
            return a.getName();
        }).collect(Collectors.toList());

        assertIterableEquals(Arrays.asList("name","log","saySomething"),fieldsName);

        //getDeclareFields能获取到public,private,protect,package的所有field,包括static的field
        //不包括父类的字段
        Field[] declaredFields =  clazz.getDeclaredFields();
        List<String> declareFieldsName = Arrays.asList(declaredFields).stream().map((a)->{
            return a.getName();
        }).collect(Collectors.toList());

        assertIterableEquals(Arrays.asList("id","name","address","contact","log"),declareFieldsName);
    }

    @Test
    public void test_single_field()throws Exception{
        Class clazz = getClass2();
        //可能抛出java.lang.NoSuchFieldException
        Field idField = clazz.getDeclaredField("id");

        //基础信息
        assertEquals(idField.getName(),"id");

        //类型信息
        assertEquals(idField.getType(),int.class);

        //是否为系统添加的字段,例如this
        assertFalse(idField.isSynthetic());

        //访问信息
        assertTrue(Modifier.isPrivate(idField.getModifiers()));
        assertFalse(Modifier.isStatic(idField.getModifiers()));

        //外部类信息
        assertEquals(clazz,idField.getDeclaringClass());
    }

    @Test
    public void test_field_set_and_get() throws Exception{
        User user = new User();

        Class clazz = getClass2();
        Field nameField = clazz.getField("name");

        //反射设置,基础类型用setInt等方法
        nameField.set(user,"MK");
        assertEquals(user.getName(),"MK");

        //反射获取,基础类型用getInt等方法
        assertEquals(nameField.get(user),"MK");
    }

    @Test
    public void test_field_set_and_get_static() throws Exception{
        User user = new User();

        Class clazz = getClass2();
        Field logField = clazz.getDeclaredField("log");

        //对于static的field,第一个参数传递null
        Logger logger = Logger.getAnonymousLogger();
        logField.set(null, logger);
        assertEquals(logger,logField.get(null));
    }
}

字段反射就比较麻烦了,首先要区分getFields(只有public,但是包含父类),和getDeclaredFields(包含所有权限,但是不包含父类),然后就是isSynthetic方法,字段的set和get方法。

4.3 方法反射

package java_test;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

public class MethodReflect {

    @Test
    public void test_methods(){
        Class clazz = User.class;

        //getMethods只能获取到public的method,包括static的method
        //包括父类的方法
        Method[] methods = clazz.getMethods();
        List<String> methodsName = Arrays.stream(methods).map((a)->{
            return a.getName();
        }).sorted().collect(Collectors.toList());//注意获取的顺序不是固定的,需要排序一下

        assertIterableEquals(Arrays.asList("equals","getClass","getName","hashCode","notify","notifyAll","say","setName","setPrefix","toString","wait","wait","wait","walk"),methodsName);

        //getgetDeclaredMethods能获取到public,private,protect,package的所有method,包括static的method
        //不包括父类的字段
        Method[] methods2 = clazz.getDeclaredMethods();
        List<String> methodsName2 = Arrays.stream(methods2).map((a)->{
            return a.getName();
        }).sorted().collect(Collectors.toList());

        assertIterableEquals(Arrays.asList("getName","setAddress","setName","setNameInner","setPrefix","walk"),methodsName2);
    }

    @Test
    public void test_single_method()throws Exception{
        Class clazz = User.class;
        //可能抛出java.lang.NoSuchMethodException,SecurityException
        //查询的时候注意带上参数,java的方法是支持同名不同参数的
        Method setNameMethod = clazz.getDeclaredMethod("setName",String.class);

        //基础信息
        assertEquals(setNameMethod.getName(),"setName");

        //访问信息
        assertTrue(Modifier.isPublic(setNameMethod.getModifiers()));
        assertFalse(Modifier.isStatic(setNameMethod.getModifiers()));

        //外部类信息
        assertEquals(clazz,setNameMethod.getDeclaringClass());

        //参数信息
        assertEquals(setNameMethod.getParameterCount(),1);
        assertEquals(setNameMethod.getParameterTypes()[0],String.class);
        assertEquals(setNameMethod.getParameters()[0].getName(),"name");//方法的名称

        //返回值信息,注意void也是有class的
        assertEquals(setNameMethod.getReturnType(),void.class);
    }

    @Test
    public void test_method_invoke() throws Exception{
        User user = new User();

        Class clazz = User.class;
        Method setNameMethod = clazz.getDeclaredMethod("setName",String.class);

        //调用反射方法
        setNameMethod.invoke(user,"MK");

        //反射获取,基础类型用getInt等方法
        assertEquals(user.getName(),"MM_MK");
    }

    @Test
    public void test_method_invoke_static() throws Exception{
        User user = new User();

        Class clazz = User.class;
        Method setPrefixMethod = clazz.getDeclaredMethod("setPrefix");

        //对于static的method,第一个参数传递null
        setPrefixMethod.invoke(null);
    }
}

方法反射就类似字段反射,首先要区分getMethods(只有public,但是包含父类),和getDeclaredMethods(包含所有权限,但是不包含父类),然后就是获取指定方法的时候用getDeclaredMethod,需要带参数,因为Java的方法是支持重载的。最后是方法invoke的用法。

4.4 构造器反射

package java_test;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ConstructorReflect {

    @Test
    public void test_constructor(){
        Class clazz = User.class;

        //getConstructors只能获取到public的Constructor
        //构造器没有父类的说法
        Constructor[] constructors = clazz.getConstructors();
        List<String> constructorName = Arrays.stream(constructors).map((a)->{
            return a.getName();
        }).sorted().collect(Collectors.toList());//注意获取的顺序不是固定的,需要排序一下

        assertIterableEquals(Arrays.asList("java_test.User"),constructorName);

        //getDeclaredConstructors可以获取到public,private和protected,package的所有Constructor
        //构造器没有父类的说法
        Constructor[] constructors2 = clazz.getDeclaredConstructors();
        List<String> constructorName2 = Arrays.stream(constructors2).map((a)->{
            return a.getName();
        }).sorted().collect(Collectors.toList());//注意获取的顺序不是固定的,需要排序一下

        assertIterableEquals(Arrays.asList("java_test.User","java_test.User"),constructorName2);
    }

    @Test
    public void test_single_constructor()throws Exception{
        Class clazz = User.class;
        //可能抛出java.lang.NoSuchMethodException,SecurityException
        //根据不同参数来选择构造器
        Constructor stringConstructor = clazz.getDeclaredConstructor(String.class);

        //基础信息
        assertEquals(stringConstructor.getName(),"java_test.User");

        //访问信息
        assertTrue(Modifier.isProtected(stringConstructor.getModifiers()));
        assertFalse(Modifier.isStatic(stringConstructor.getModifiers()));

        //外部类信息
        assertEquals(clazz,stringConstructor.getDeclaringClass());

        //参数信息
        assertEquals(stringConstructor.getParameterCount(),1);
        assertEquals(stringConstructor.getParameterTypes()[0],String.class);
    }

    @Test
    public void test_constructor_invoke()throws Exception{
        Class clazz = User.class;
        //可能抛出java.lang.NoSuchMethodException,SecurityException
        //根据不同参数来选择构造器
        Constructor stringConstructor = clazz.getDeclaredConstructor(String.class);

        Object target = stringConstructor.newInstance("Fish");
        assertEquals(((User)target).getName(),"Fish");
    }

}

构造器的getConstructors和getDeclaredConstructors区别仅仅是权限不同而已,没有父类的构造器的说法。最后是构造器的newInstance的用法

4.5 泛型反射

package java_test;

import com.sun.tools.classfile.Opcode;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

public class User3 extends HashSet<String> {

    List<Integer> datas;

    public void go(LinkedList<Integer> a ){
        System.out.println(a);
    }

    public<T> void add(List<T> a ,T b){
        a.add(b);
    }

    public void login(String name,String password){

    }
}

先定义一个充满泛型的User3类

package java_test;

import org.junit.jupiter.api.MethodDescriptor;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.*;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

public class GenericReflect {

    @Test
    public void test_generic_superClass(){
        Class clazz = User3.class;

        Type type = clazz.getGenericSuperclass();

        //泛型的时候可以做这个类型转换
        ParameterizedType parameterizedType = (ParameterizedType) type;

        //获取泛型的内容参数
        Class containerType = (Class)parameterizedType.getRawType();
        Class argumentType = (Class)parameterizedType.getActualTypeArguments()[0];

        assertEquals(containerType, HashSet.class);
        assertEquals(argumentType,String.class);
    }

    @Test
    public void test_generic_field()throws Exception{
        Class clazz = User3.class;

        Type type = clazz.getDeclaredField("datas").getGenericType();

        //泛型的时候可以做这个类型转换
        ParameterizedType parameterizedType = (ParameterizedType) type;

        //获取泛型的内容参数
        Class containerType = (Class)parameterizedType.getRawType();
        Class argumentType = (Class)parameterizedType.getActualTypeArguments()[0];

        assertEquals(containerType, List.class);
        assertEquals(argumentType,Integer.class);
    }

    @Test
    public void test_generic_method()throws Exception{
        Class clazz = User3.class;

        //拿泛型方法的时候,直接用基础类,List<>就用List.class,T就用Object.class
        Method method = clazz.getDeclaredMethod("add",List.class,Object.class);

        Type[] parameterizedType = (Type[]) method.getGenericParameterTypes();
        ParameterizedType firstParameter = (ParameterizedType) parameterizedType[0];

        //获取泛型的内容参数
        Class containerType = (Class)firstParameter.getRawType();
        TypeVariable argumentType = (TypeVariable)firstParameter.getActualTypeArguments()[0];

        assertEquals(containerType, List.class);
        //参数是一个泛型变量代指
        assertEquals(argumentType.getName(),"T");
    }

    @Test
    public void test_method_with_generic_parameter()throws Exception{
        Class clazz = User3.class;

        //拿泛型方法的时候,直接用基础类,List<>就用List.class,T就用Object.class
        Method method = clazz.getDeclaredMethod("go", LinkedList.class);

        Type[] parameterizedType = (Type[]) method.getGenericParameterTypes();
        ParameterizedType firstParameter = (ParameterizedType) parameterizedType[0];

        //获取泛型的内容参数
        Class containerType = (Class)firstParameter.getRawType();
        Class argumentType = (Class)firstParameter.getActualTypeArguments()[0];

        assertEquals(containerType, LinkedList.class);
        //参数是一个泛型变量代指
        assertEquals(argumentType,Integer.class);
    }

    /*
    //以下这种做法是不行的,你不能在运行时创建一个未知类型的泛型
    @Test
    public void test_generic_constructor_invoke()throws Exception{
        Class clazz = Class.forName("java.util.ArrayList<String>");
        Object target = clazz.newInstance();
        List<String> result = (List<String>)target;

        result.add("!23");
        result.add("mm");
        System.out.println(result);
    }
     */

    @Test
    public void invoke_method_with_generic_parameter()throws Exception{
        Class clazz = User3.class;

        //拿泛型方法的时候,直接用基础类,List<>就用List.class,T就用Object.class
        Method method = clazz.getDeclaredMethod("go", LinkedList.class);

        //go是一个泛型方法,它接受的的是LinkedList<Integer>类型
        Class<?>[] clazzes = method.getParameterTypes();
        Object argument = clazzes[0].newInstance();

        //但我们可以强行转换为LinkedList<String>写入数据
        LinkedList<String> listString = (LinkedList<String>) argument;
        listString.add("ccg");
        System.out.println(listString);

        //并且传递进入go方法都是没问题的
        method.invoke(new User3(),argument);

        //因为Java的泛型仅仅是编译时的检查,在运行时所有元素都是以Object的类型运行,类型在运行时被擦除了,不会被JRE检查
    }
}

反射里面支持泛型是比较麻烦的,注意点有:

  • Class类型是会丢失泛型信息的,要用Type类型来获取完整信息。当Type是一个泛型时,类型是ParameterizedType。当Type是一个普通类型时,类型是Class。当Type是一个参数代指的时候,类型是TypeVariable。
  • getRawType和getOwnerType的区别,OwnerType就是,Map是Map.Entry泛型的Owner。RawType就是泛型自身。
  • invoke_method_with_generic_parameter的测试可以看到,Java中在运行时是没有泛型信息的,类型在底层都是Object实现。所以我们也不用再去纠结如何动态创建一个String元素的List类型的问题,你直接创建一个普通List类型,传给它就可以了,反正运行时都区分不了。

4.6 泛型反射高级

package java_test;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class User4<T1,T2>{

    public List<T1> data;

    public Set<T2> go(Map<T1,T2> data){
        return null;
    }
}

一个普通的泛型类

package java_test;

public class User5 extends User4<Long,String>{
}

一个具体泛型子类

package java_test;

import org.junit.jupiter.api.Test;

import java.lang.reflect.*;
import java.util.HashSet;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class GenericReflect2 {

    @Test
    public void test_generic_superClass()throws Exception{
        //泛型的基础类
        Class clazz = User4.class;

        //获取类的类型参数
        TypeVariable[] variable = clazz.getTypeParameters();
        assertEquals(variable.length,2);
        assertEquals(variable[0].getName(),"T1");
        assertEquals(variable[1].getName(),"T2");

        //获取字段的类型参数
        Field dataField = clazz.getField("data");
        Type genericType = dataField.getGenericType();
        assertEquals(genericType.getTypeName(),"java.util.List<T1>");

        //获取方法的类型参数
        Method goMethod = clazz.getMethod("go", Map.class);
        Type[] parameterTypes = goMethod.getGenericParameterTypes();
        Type returnType = goMethod.getGenericReturnType();
        assertEquals(parameterTypes.length,1);
        assertEquals(parameterTypes[0].getTypeName(),"java.util.Map<T1, T2>");
        assertEquals(returnType.getTypeName(),"java.util.Set<T2>");
    }

    @Test
    public void test_generic_class()throws Exception{
        //获取泛型的具体子类
        Class clazz = User5.class;

        //可以获取到父类的具体参数
        ParameterizedType  genericSuperType = (ParameterizedType)clazz.getGenericSuperclass();
        assertEquals(genericSuperType.getActualTypeArguments()[0].getTypeName(),"java.lang.Long");
        assertEquals(genericSuperType.getActualTypeArguments()[1].getTypeName(),"java.lang.String");
        assertEquals(genericSuperType.getRawType().getTypeName(),"java_test.User4");

        //依然不能获取到字段的具体参数,需要自己匹配
        Field dataField = clazz.getField("data");
        Type genericType = dataField.getGenericType();
        assertEquals(genericType.getTypeName(),"java.util.List<T1>");

        //依然不能获取到方法的具体参数,需要自己匹配
        Method goMethod = clazz.getMethod("go", Map.class);
        Type[] parameterTypes = goMethod.getGenericParameterTypes();
        Type returnType = goMethod.getGenericReturnType();
        assertEquals(parameterTypes.length,1);
        assertEquals(parameterTypes[0].getTypeName(),"java.util.Map<T1, T2>");
        assertEquals(returnType.getTypeName(),"java.util.Set<T2>");
    }

}

从测试中,我们可以看到:

  • Java的泛型反射相当地简单暴力,即使是具体的泛型子类,他也不会提前帮你算好,泛型子类的方法和字段具体是什么类型。他只去告诉你泛型方法和字段的泛型参数是什么,你得自己在运行时匹配计算。
  • Java泛型的简单在于,大幅减少反射包的需要记录的信息。它只需要记录泛型具体的参数信息,以及子类对应的是哪个泛型实例参数就可以了。

4.7 嵌套类反射

package java_test;

public class User2 {

     Object a;

     Object b;

     Object c;

    //User2Inner3内部类是在类下面声明的
    public class User2Inner3{

    }

    public void getData(){
        //User2Inner内部类是在getData方法里面声明的
        class User2Inner{

        }
        this.a = new User2Inner();
    }

    public User2(){
        class User2Inner2{

        }
        //User2Inner2内部类是在User2构造器里面声明的
        this.b = new User2Inner2();
        this.c = new User2Inner3();
    }
}

Java中嵌套类,可以在类定义,也可以在方法和构造器里面定义。

package java_test;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class EnclosingReflect {

    @Test
    public void isMemberClass(){
        User2 user = new User2();
        user.getData();

        assertEquals(false,User2.class.isMemberClass());
        assertEquals(false,user.a.getClass().isMemberClass());//在块里面定义的类
        assertEquals(false,user.b.getClass().isMemberClass());//在块里面定义的类
        assertEquals(true,user.c.getClass().isMemberClass());//在类定义的
    }

    @Test
    public void isLocalClass(){
        User2 user = new User2();
        user.getData();

        assertEquals(false,User2.class.isLocalClass());
        assertEquals(true,user.a.getClass().isLocalClass());//在块里面定义的类
        assertEquals(true,user.b.getClass().isLocalClass());//在块里面定义的类
        assertEquals(false,user.c.getClass().isLocalClass());//在类定义的
    }

    @Test
    public void testEnclosingClass(){
        User2 user = new User2();
        user.getData();

        assertEquals(User2.class,user.a.getClass().getEnclosingClass());
        assertEquals(User2.class,user.b.getClass().getEnclosingClass());
        assertEquals(User2.class,user.c.getClass().getEnclosingClass());
    }

    @Test
    public void testEnclosingMethod() throws Exception{
        User2 user = new User2();
        user.getData();

        assertEquals(User2.class.getDeclaredMethod("getData"),user.a.getClass().getEnclosingMethod());
        assertEquals(null,user.b.getClass().getEnclosingMethod());
        assertEquals(null,user.c.getClass().getEnclosingMethod());
    }

    @Test
    public void testEnclosingConstructor() throws Exception{
        User2 user = new User2();
        user.getData();

        assertEquals(null,user.a.getClass().getEnclosingConstructor());
        assertEquals(User2.class.getDeclaredConstructor(),user.b.getClass().getEnclosingConstructor());
        assertEquals(null,user.c.getClass().getEnclosingConstructor());
    }
}

然后我们有了这样的关于isMember,isLocal,getEnclosingXXX的代码,仔细看一下结果,这里容易混淆。

4.8 方法参数名称

package java_test;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.springframework.core.DefaultParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class MethodParameterNameReflect {

    DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    //JDK 8 以下只能用字节码获取
    @Test
    public void test1()throws Exception{
        Class clazz = User3.class;
        Method loginMethod = clazz.getDeclaredMethod("login",String.class,String.class);
        String[] parameterName =  parameterNameDiscoverer.getParameterNames(loginMethod);

        assertArrayEquals(new String[]{"name","password"},parameterName);
    }

    //JDK 8 以上都可以用这个方法
    @Test
    public void test2()throws Exception{
        Class clazz = User3.class;
        Method loginMethod = clazz.getDeclaredMethod("login",String.class,String.class);
        List<String> parameterName =  Arrays.stream(loginMethod.getParameters()).map((a)->{
            return a.getName();
        }).collect(Collectors.toList());

        assertIterableEquals(Arrays.asList("name","password"),parameterName);
    }
}

在JDK 8以上的增强反射都能获取到参数名称了,在JDK 8之前的反射只能用字节码来获取,这个时候可以用Spring的DefaultParameterNameDiscoverer来代劳。

5 踩过的坑

4.1 ClassNotFoundException

启动服务器程序后,执行指定的操作时,会提示找不到这个类。由于需要的类都打包在一个jar包里面了,怎么可能会找不到了。

实际原因是:

  • 启动进程以后,Java的执行时动态加载类文件的,需要用到的时候才加载类文件。
  • 但是启动完成后一段时间,原jar文件被部署系统覆盖了,变成了新的jar文件
  • Java运行到一半需要加载这个类文件,但是原来的jar文件已经被删除了,所以它无法加载这个类,因此而报错

解决方法:

  • 把原来的进程停掉(注意原来的进程可能不止一个,包括nohup与新用户),重新启动就可以了
  • 修改部署脚本,要先确保停止原来的进程,才去覆盖文件然后启动。

5 参考资料

相关文章