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 ) {
= MyUse.class.getAnnotation(MyAnnotation.class);
MyAnnotation annotation 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 ) {
= AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation.class);
MyAnnotation annotation //查找子注解,要用Spring的AnnotationUtils方法
//MyAnnotation annotation = MyUse.class.getAnnotation(MyAnnotation.class);
System.out.println(Arrays.toString(annotation.Import()));
System.out.println(annotation.Name());
= AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation2.class);
MyAnnotation2 annotation2 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对两个不同名的字段,实现了两个字段互为别名的效果.
= AnnotationUtils.findAnnotation(MyUse.class,MyAnnotation3.class);
MyAnnotation3 annotation //MyUse只设置了value,但是因为AliasFor,自动设置了name
System.out.println(annotation.name());
System.out.println(annotation.value());
= AnnotationUtils.findAnnotation(MyUse2.class,MyAnnotation3.class);
MyAnnotation3 annotation2 //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方法
= AnnotatedElementUtils.findMergedAnnotation(MyUse.class,MyAnnotation4.class);
MyAnnotation4 annotation //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作为类加载器。用户也可以使用当前线程级别的类加载器,而不是所在方法类的类加载器。
= new FolderClassLoader("lib");
FolderClassLoader loader 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();
}
= defineClass(name,classData,0,classData.length);
clazz return clazz;
}
private byte[] getClassData(String name)throws ClassNotFoundException{
File file = null;
InputStream fileInputStream = null;
try {
String namePath = name.replace('.','/');
= new File(this.folder + "/"+namePath+".class");
file = new FileInputStream(file);
fileInputStream byte[] result = new byte[fileInputStream.available()];
.read(result);
fileInputStreamreturn result;
}catch(Exception e){
.printStackTrace();
ethrow new ClassNotFoundException();
}finally{
if(fileInputStream != null){
try{
.close();
fileInputStream}catch(Exception e){
.printStackTrace();
e}
}
}
}
}
定义一个自己的类加载器,构造函数中可以指定parent,没有parent的话用当前类FolderClassLoader的类加载器作为parent。如果不修改双亲规则的话,重写findClass函数就可以了
//加载class
= new FolderClassLoader("lib");
FolderClassLoader loader 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.getParent();
classLoader }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
= Thread.currentThread().getContextClassLoader();
classLoader System.out.println("default threadClassLoader: "+classLoader);
Thread.currentThread().setContextClassLoader(loader);
= Thread.currentThread().getContextClassLoader();
classLoader 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{
= new User();
User user
Class clazz = getClass2();
Field nameField = clazz.getField("name");
//反射设置,基础类型用setInt等方法
.set(user,"MK");
nameFieldassertEquals(user.getName(),"MK");
//反射获取,基础类型用getInt等方法
assertEquals(nameField.get(user),"MK");
}
@Test
public void test_field_set_and_get_static() throws Exception{
= new User();
User user
Class clazz = getClass2();
Field logField = clazz.getDeclaredField("log");
//对于static的field,第一个参数传递null
Logger logger = Logger.getAnonymousLogger();
.set(null, logger);
logFieldassertEquals(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{
= new User();
User user
Class clazz = User.class;
Method setNameMethod = clazz.getDeclaredMethod("setName",String.class);
//调用反射方法
.invoke(user,"MK");
setNameMethod
//反射获取,基础类型用getInt等方法
assertEquals(user.getName(),"MM_MK");
}
@Test
public void test_method_invoke_static() throws Exception{
= new User();
User user
Class clazz = User.class;
Method setPrefixMethod = clazz.getDeclaredMethod("setPrefix");
//对于static的method,第一个参数传递null
.invoke(null);
setPrefixMethod}
}
方法反射就类似字段反射,首先要区分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){
.add(b);
a}
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;
.add("ccg");
listStringSystem.out.println(listString);
//并且传递进入go方法都是没问题的
.invoke(new User3(),argument);
method
//因为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(){
= new User2();
User2 user .getData();
user
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(){
= new User2();
User2 user .getData();
user
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(){
= new User2();
User2 user .getData();
user
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{
= new User2();
User2 user .getData();
user
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{
= new User2();
User2 user .getData();
user
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 {
= new DefaultParameterNameDiscoverer();
DefaultParameterNameDiscoverer parameterNameDiscoverer
//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 参考资料
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!