菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

23 篇文章 1 订阅
23 篇文章 0 订阅

注解(Annotation)

注解时JDK5.0引入的新技术,它不是程序本身,却可以对程序做出解释。编辑器可以对其进行读取。注解可以附加在package、class、method和field上面,给它们添加相应的辅助消息,比如我们之前常用的**@Override**。注意注释不是注解,注释是没有任何意义的,换句话说注释是给我们看的,而注解是给编译器看的。

JDK内置注解

  • @Override 表示当前方法覆盖了父类的方法,加了这个注解不重写方法会有横线提示。
  • @Deprecation 表示方法已经过时,方法上有横线,使用时会有警告。
  • @SuppviseWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)

其中@SuppviseWarnings中可以添加相关参数来指定需要压制的警告种类,如:
@SuppviseWarnings(“unchecked”)
@SuppviseWarnings(value = {“unchecked”,“deprecation”})
实例:
接口:

public interface Person {

	public String name();
	public int age();
	//2.加上@Deprecated这个注解,表示这个方法已经过时了(然后就自动带上灰色横线)
	@Deprecated
	public void sing();
}

实现类:

public class Child implements Person{

	//1.当孩子类继承person类的时候,系统会提示加上@Override注解,此时@Override注解告诉我们,也告诉编译器当前类的name方法已经覆盖了person类的方法
	@Override
	public String name() {
		return null;
	}

	@Override
	public int age() {
		return 0;
	}

	@Override
	public void sing() {
		System.out.println("啦啦啦");
	}

}

main方法

public class test {

	//3.此时想要在调用这个唱歌的sing方法,就得加上@SuppressWarnings("deprecation"),该注解会忽略警告的功能
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		Person a=new Child();
		a.sing();
	}
}

自定义注解

处理JDK给我们提供的注解之外我们还可以自己定义一些注解,一般通过@interface关键字来实现,使用时会自动继承java.lang.annotation.Annotation接口。

元注解(meta-annotation)

元注解的作用就是负责注解其他注解,Java定义了4个标准的元注解类型,它们被用来对其他的annotation类型作说明。
@Target
用于描述注解的使用范围(被描述的注解可以用在什么地方)可以对其设置value值,比如:@Target(value=ElementType.TYPE)
value可取值如下:

public enum ElementType {
    TYPE, // 类、接口、枚举类
    FIELD, // 成员变量(包括:枚举常量)
    METHOD, // 成员方法
    PARAMETER, // 方法参数
    CONSTRUCTOR, // 构造方法
    LOCAL_VARIABLE, // 局部变量
    ANNOTATION_TYPE, // 注解类
    PACKAGE, // 可用于修饰:包
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}

@Retention
表示需要在什么级别保存该注解信息,用于描述注解生命周期,它的可取值如下:

public enum RetentionPolicy {
 
    SOURCE,    // 源文件保留
    CLASS,       // 编译期保留,默认值
    RUNTIME   // 运行期保留,可通过反射去获取注解信息
}

@Documented
描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。这个注解没有参数,添加该注解后如果我们生成一份JDKAPI文档时会保留注解信息。
@Inherited
该注解只能修饰类,使被它修饰的注解具有继承性。什么意思那,被改注解修饰的类的注解参数信息在子类中通过反射也可以获得(反射后面再说)。

四个元注解用法:

@Inherited
@Documented
@Retention(RetentionPoilcy.RUNTIME)
@Target(ElementType.TYPE)//只有一个参数时value=可以不写
public @interface Field{
	String nationality() default "China";//参数名和参数类型
	String skinColour(); //不设置默认值则必须在使用时赋值
}

该注解使用时:

@Field(skinColour = "yellow")//默认添加nationality属性时"China",也可以手动修改
public interface Person {
	public String name();
	public int age();
	//2.加上@Deprecated这个注解,表示这个方法已经过时了(然后就自动带上灰色横线)
	@Deprecated
	public void sing();
}

自定义注解在ORM框架中运用较广,我们可以通过注解将数据库表和类对应起来。等讲完反射后通过一个案例一起演示。

反射(reflection)

动态语言

在之前的学习中我们知道,Java在执行过程中需要先编译成class文件,在由JVM对其进行加载执行,这就说明Java语言在程序执行过程中不可以改变程序结构或变量类型。而像Python、Javascript这样的语言在却可以实现上面的功能,比如Javascript中:

function test(){
	var s="var a =3;alert(a);"
	eval(s);
}

像Javascript这类可以在程序执行过程中不可以改变程序结构或变量类型的语言称为动态语言。
Java虽然不是动态语言,但它有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特征。

反射机制的概念

反射机制是指可以在运行时加载、探知或者使用未知的类。
通俗来说,在Java程序运行过程中可以动态加载一个只有名称的类,对于一个已经加载的类,可以获取该类的所有属性和方法,对于任意一个对象,也可以调用它的方法和属性。
反射的的根源是java.lang.Class类,针对任何想动态加载、运行的类都必须先获得相应的Class对象。Class类非常特殊,用来表示java中的类型,Class类的对象包含了某一个被加载类的结构,当一个类被加载时,JVM会自动产生一个Class对象。下面通过一段实例来演示Class类的用法:
首先定义一个person类

public class Proson {
    //私有属性
    private String name;
    //公有属性
    public  Integer age;
    //无参构造
    public Proson() {
    }
    //有参构造
    public Proson(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //私有方法
     private void method1(){
        System.err.println("method1——run");
    }
    //公有方法
    public void method2(String param){
        System.err.println("method1=2——run :"+param);
    }
    @Override
    public String toString() {
        return "Proson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通过这个类来演示反射的过程:

public static void main(String[] args) throws MalformedURLException {

		String className="com.bean.Proson";
		//通常创建对象
         Proson p=new Proson();

        //寻找该名称的类文件,加载进内存,并创建Class对象,类的整个结构信息都会存放在Class对象中。
        Class clazz = Class.forName(className);
        Class clazz2 = Class.forName(className);
        //注意对于同一个类型,通过它的任意对象获得的class均相同
        System.out.println(clazz==clazz2);//打印true,同一个类只会被加载一次
        //通过Class对象的newInstance()创建类对象。默认调用无参构造器,如果没有会报错
        Object o = clazz.newInstance();

		//通过对象获取类信息
		Class strClazz = className.getClass();
		//className为String类型,调用会获取String类信息
		Class strClazz2 = String.class;
		//获取String类信息的另一种方法
 }

通过Class对象可以很方便的获取类信息。

public static void main(String[] args) throws MalformedURLException {

		String className="com.bean.Proson";
        Class clazz = Class.forName(className);
        //获取类名
        System.out.println(clazz.getName());//获取包名+类名com.bean.Proson
        System.out.println(clazz.getSimpleName());//获取类名Proson
		//获取属性信息
		Field[] fields = clazz.getFields();//获取类的所有public属性
		Field[] fields = clazz.getDeclaredFields();获取类的所有属性
		Field field = clazz.getDeclaredField("name");//获取指定属性
		System.out.println(field);//打印private java.lang.String com.bean.Proson.name
		//获取方法
		Method[] methods = clazz.getDeclaredMethods();//获取所有方法
		Method m01 = clazz.getgetDeclaredMethod("method1",null);//获取指定方法,第二个参数为方法的参数类型,void为null,加这个参数的目的是可以获取重载的方法
		Method m02 = clazz.getgetDeclaredMethod("method2", String.class);
		//获得构造器信息
		Constructor[] constructors = clazz.getDeclaredConstructors();
		Constructor c = clazz.getDeclaredConstructor(String.class,Integer.class);//可以通过传入不同的参数类型获得不同的构造器,无参构造器为null
		Person p = c.newInstance("Bob",18);//通过有参构造器实例化对象
		//通过反射调用方法
		m02.invoke(p ,"hello");//相当于p.method2("hello");
		//通过反射设置属性
		field.setAccessible(true);//设置之后可以直接访问private属性
		field.set(p ,"Tom");//相当于p.name="Tom",通过反射可以修改private属性
		System.out.println(field.get(p));//打印Tom,通过反射可以访问private属性
 }

在反射过程中如果启用安全检查会影响性能,我们可以根据需求使用的setAcceptable这个方法来启用或禁用安全检查(上例中已有使用)。一般情况下性能会差30倍左右,下面是一个方法执行10亿次的三种结果,大家有兴趣可以自己做尝试。反射效率

反射操作注解(ORM实例)

反射操作注解与操作属性或者方法时采用的方法差不多,这里我们通过一个ORM实例来将注解与反射做一个综合讲解。
首先给大家普及一下ORM(如果大家对数据库不了解可以先去学习一下MySQL或其他数据库技术,数据库也是Java开发方向的重点),它一般指对象关系映射。简单来说,我们Java程序与数据库交互时,会用一个类与数据库的一个表相对应,这个类也叫做javabean类,获取数据时,通过实例化这个类的对象与数据库中一条一条的数据做对应。基于这个思想我们可以做出自己的代码实现。
首先我们创建一个修饰类的注解:

@Target(ElementType.TYPE)//只能在类型或接口前
@Retention(RetentionPolicy.RUNTIME)//运行期保留,只有这样的注解才能被反射获取
@interface TableStudent{
    String value();
}

在创建一个修饰属性的注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
    String columnName();
    String type();
    int length();
}

在类中使用这两个注解:

@TableStudnet("student")
class Studnet{
    @Filed(columnName = "db_id",type="int",length=10)
    private int id;
    @Filed(columnName = "db_age",type="int",length=10)
    private int age;
    @Filed(columnName = "db_name",type="varchar",length=3)
    private String name;
	//...
}

这样在运行时

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //获得
        Class c1 = Class.forName("li.Studnet");

      //获取TableStudnet注解的value的值
        TableStudnet annotation = (TableStudnet) c1.getAnnotation(TableStudnet.class);
        String value = annotation.value();
        System.out.println(value);
        
        //获得Filed注解上属性的值
        Field[] f = c1.getDeclaredFields();
        for (Field field : f) {
            Filed fAnnotation  = field.getAnnotation(Filed.class);
            System.out.println("columnName="+fAnnotation.columnName());//打印columnName=db_id
            System.out.println("type="+fAnnotation.type());//打印type=int
            System.out.println("length="+fAnnotation.length());//打印length=10
        }
    }

这样我们就可以通过注解将bean对象与数据库表名以及字段类型对应起来,进而与数据库进行交互。

反射操作泛型

当然通过反射还可以对泛型进行操作,Java有ParameterizedType,GenericArrayType,TypeVariable和WildcardType四个类来操作泛型,我们在这里只介绍ParameterizedType,其他类感兴趣可以自己去学习,我们直接举例来说明:

//Proson还是上面的Proson类
public class Demo{  
    //定义两个带泛型的方法
    public void test01(Map<String,Proson> map){
        System.out.println("方法一");
    }   
    public Map<Integer,Proson> test02(){
        System.out.println("方法二");
        return null;
    }
}   

操作过程中:

    public static void main(String[] args) {
        try {           
            //获得指定方法参数泛型信息
            Method m = Demo.class.getMethod("test01", Map.class,List.class);
            Type[] t = m.getGenericParameterTypes();

            for (Type paramType : t) {
                System.out.println("#"+paramType);
                //打印java.util.Map< java.lang.String, java.lang.String> 
                if(paramType instanceof ParameterizedType){
                    //获取泛型中的具体信息
                    Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                    for (Type genericType : genericTypes) {
                        System.out.println("泛型类型:"+genericType);
                        //第一次循环打印泛型类型:class java.lang.String 
                        //第二次循环打印泛型类型:class com.bean.Proson
                    }
                }
            }   

            //获得指定方法返回值泛型信息
            Method m2 = Demo.class.getMethod("test02", null);
            Type returnType = m2.getGenericReturnType();
            if(returnType instanceof ParameterizedType){
                    Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();

                    for (Type genericType : genericTypes) {
                        System.out.println("返回值,泛型类型:"+genericType);
                        //第一次循环打印返回值,泛型类型:class java.lang.Integer 
						//第二次循环打印返回值,泛型类型:class com.bean.Proson
                    }                   
            }       

        } catch (Exception e) {
            e.printStackTrace();
        }   
    }

上一篇:菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)
下一篇:菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值