反射

反射

反射

##反射:框架设计的灵魂

框架:半成品软件。可以在框架的基础上进行软件开发,简化编码

反射:将类的各个组成部分封装为其他对象,这就是反射机制

画图分析一下反射

  • 好处:
  1. 可以在程序运行过程中,操作这些对象。
  2. 可以解耦,提高程序的可扩展性。

##获取Class对象的方式:

  1. Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
  • 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  1. 类名.class:通过类名的属性class获取
  • 多用于参数的传递
  1. 对象.getClass():getClass()方法在Object类中定义着。
  • 多用于对象的获取字节码的方式
  • 结论:
    同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
package com.qf.domain;

public class Person {
    private String name;
    private int age;

    private static String city="广州";

    public String a;
    protected String b;
    String c;
    private String d;

    public Person() {
    }

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


    public void eat(){
        System.out.println("吃饭。。。。");
    }


    public void sleep(){
        System.out.println("睡觉。。。。");
    }


}

package com.qf.reflect;

import com.qf.domain.Person;

public class ReflectDemo1 {
    public static void main(String[] args) {
        /*
        1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象

        - 多用于配置文件,将类名定义在配置文件中。读取文件,加载类

        2. 类名.class:通过类名的属性class获取

        - 多用于参数的传递

        3. 对象.getClass():getClass()方法在Object类中定义着。

        - 多用于对象的获取字节码的方式

         */

        //1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
        try {
            Class<?> aClass = Class.forName("com.qf.domain.Person");

            //类名.class:通过类名的属性class获取
            Class<Person> personClass = Person.class;
            //3. 对象.getClass():getClass()方法在Object类中定义着。
            Person person = new Person();
            Class<? extends Person> aClass2 = person.getClass();

            System.out.println("aClass==personClass:"+(aClass==personClass));
            System.out.println("aClass2==personClass:"+(aClass==personClass));


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


    }
}

##Class对象功能:

  • 获取功能:
    ###1. 获取成员变量们
  • Field[] getFields() :获取所有public修饰的成员变量
  • Field getField(String name) 获取指定名称的 public修饰的成员变量
  • Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
  • Field getDeclaredField(String name)

###2. 获取构造方法们

  • Constructor<?>[] getConstructors()
  • Constructor getConstructor(类<?>… parameterTypes)
  • Constructor getDeclaredConstructor(类<?>… parameterTypes)
  • Constructor<?>[] getDeclaredConstructors()

###3. 获取成员方法们:

  • Method[] getMethods() :获取类里面所有的public方法,包括父类里面的public方法

  • Method getMethod(String name, 类<?>… parameterTypes)

  • Method[] getDeclaredMethods() :获取类里面声明的所有的方法,不包括父类的方法

  • Method getDeclaredMethod(String name, 类<?>… parameterTypes)

###4. 获取全类名

  • String getName()

  • 操作:

Field:成员变量

  1. 设置值
  • void set(Object obj, Object value)
  1. 获取值
  • get(Object obj)
  1. 忽略访问权限修饰符的安全检查
  • setAccessible(true):暴力反射
package com.qf.reflect;

import com.qf.domain.Person;
import org.junit.Test;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    @Test
    public void test1() throws Exception {

        /*
        - Field[] getFields() :获取所有public修饰的成员变量
        - Field getField(String name)   获取指定名称的 public修饰的成员变量
        - Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
        - Field getDeclaredField(String name)

         */
        Class<Person> personClass = Person.class;
        //
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("-----------");

        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }

        /*
        1. 设置值

        - void set(Object obj, Object value)

        2. 获取值

        - get(Object obj)

        3. 忽略访问权限修饰符的安全检查

        - setAccessible(true):暴力反射

         */
        System.out.println("------------------");
        Field a = personClass.getField("a");

        Person p = new Person();

        a.set(p,"zhongguo");

        //获取值
        Object o = a.get(p);
        System.out.println("o:"+o);

        //a.setAccessible(true);
        Field name = personClass.getDeclaredField("name");
        /*
        Class com.qf.reflect.ReflectDemo2 can not access a member of class com.qf.domain.Person with modifiers "private"
         */
        System.out.println("name:"+name.get(p));


    }
}


Constructor:构造方法

  • 创建对象:
  • T newInstance(Object… initargs)
  • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
package com.qf.reflect;

import com.qf.domain.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectDemo3 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /*
        2. 获取构造方法们

        - Constructor<?>[] getConstructors()
        - Constructor<T> getConstructor(类<?>... parameterTypes)
        - Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
        - Constructor<?>[] getDeclaredConstructors()

         */
        Class<Person> personClass = Person.class;

        //获取有参的构造方法
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        System.out.println(constructor);
        //创建对象1
        Person person1 = constructor.newInstance("张三", 23);
        System.out.println(person1);

        //获取无参的构造方法
        Constructor<Person> constructor2 = personClass.getConstructor();
        System.out.println("constructor2:"+constructor2);

        //创建对象2
        Person person2 = constructor2.newInstance();
        System.out.println("person2:"+person2);


        //创建对象3
        Person person3 = personClass.newInstance();
        System.out.println("person3:"+person3);

        System.out.println("------------");
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        System.out.println("-------------");
        //构造器暴力访问
        //constructor2.setAccessible(true);


    }
}

Method:方法对象

  • 执行方法:
  • Object invoke(Object obj, Object… args)
  • 获取方法名称:
  • String getName:获取方法名
package com.qf.reflect;

import com.qf.domain.Person;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectDemo4 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        /*
        3. 获取成员方法们:

        - Method[] getMethods()
        - Method getMethod(String name, 类<?>... parameterTypes)
        - Method[] getDeclaredMethods()
        - Method getDeclaredMethod(String name, 类<?>... parameterTypes)

         */

        Class<Person> personClass = Person.class;

        //获取指定名称的方法
        Method eat = personClass.getMethod("eat");
        //
        Person p = new Person();
        //执行方法
        eat.invoke(p);

        //获取指定名称,并且带参数的方法
        Method eat2 = personClass.getMethod("eat", String.class);
        //执行带参数的方法
        eat2.invoke(p,"wowo");
        System.out.println("--------------");

        //获取所有public修饰的方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
            //方法名
            System.out.println(method.getName());
        }


        System.out.println("-----------------");
        //获取类名
        String name = personClass.getName();
        System.out.println("类名:"+name);


    }
}

##案例:

  • 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
  • 实现:
    1. 配置文件
    2. 反射
  • 步骤:
    1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
    2. 在程序中加载读取配置文件
    3. 使用反射技术来加载类文件进内存
    4. 创建对象
    5. 执行方法
package com.qf.reflect;

import com.qf.domain.Person;
import com.qf.domain.Student;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
    public static void main(String[] args) {

        //普通方式----调用
        Person p = new Person();
        p.eat();

        Student stu = new Student();
        stu.study();

        System.out.println("-------------");
        //1,加载配置文件
       

        //2,获取全类名
      
        //3,获取Class对象
       
        //4,构造函数
      
        //5,调用方法
       


    }
}

枚举

概念

枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

枚举在各个语言当中都有着广泛的应用,通常用来表示诸如颜色、方式、类别、状态等等数目有限、形式离散、表达又极为明确的量。Java从JDK5开始,引入了对枚举的支持。

为什么使用枚举

在枚举出现之前,如果想要表示一组特定的离散值,往往使用一些常量。例如:

package com.qf.enumdemo;

public class Entity {

    public static final int VIDEO = 1;//视频
    public static final int AUDIO = 2;//音频
    public static final int TEXT = 3;//文字
    public static final int IMAGE = 4;//图片

    private int id;
    private int type;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }


}

例如,针对上述的Entity类,如果要对Entity对象的type属性进行赋值,一般会采用如下方法:

	Entity e = new Entity();
	e.setId(10);
	//e.setType(2);
	//而这样的话,问题又来了。这样做,客户端必须对这些常量去建立理解,才能了解如何去使用这个东西。
    //说白了,在调用的时候,如果用户不到Entity类中去看看,还真不知道这个参数应该怎么传、怎么调
	e.setType(Entity.AUDIO);

这样做的缺点有:

(1)代码可读性差、易用性低。由于setType()方法的参数是int型的,在阅读代码的时候往往会让读者感到一头雾水,根本不明白这个2到底是什么意思,代表的是什么类型。
(2)类型不安全。在用户去调用的时候,必须保证类型完全一致,同时取值范围也要正确。像是setType(-1)这样的调用是合法的,但它并不合理,今后会为程序带来种种问题。

(3)耦合性高,扩展性差。假如,因为某些原因,需要修改Entity类中常量的值,那么,所有用到这些常量的代码也就都需要修改——当然,要仔细地修改,万一漏了一个,那可不是开玩笑的。同时,这样做也不利于扩展。

使用枚举

枚举(在Jave中简称为enum)是一个特定类型的类。所有枚举都是Java中的新类java.lang.Enum的隐式子类。此类不能手工进行子类定义。一个简单的枚举可以是这样:

public enum TypeEnum {
    VIDEO,AUDIO,TEXT,IMAGE;
}

上面的Entity类就可以改成这样:

package com.qf.enumdemo;

public class Entity2 {

    private int id;
    private TypeEnum type;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public TypeEnum getType() {
        return type;
    }
    public void setType(TypeEnum type) {
        this.type = type;
    }
}

在为Entity对象赋值的时候,就可以这样:

Entity2 e = new Entity2();
e.setId(10);
e.setType(TypeEnum.AUDIO);

在调用setType()时,可选值只有四个,否则会出现编译错误,因此可以看出,枚举是类型安全的,不会出现取值范围错误的问题。同时,客户端不需要建立对枚举中常量值的了解,使用起来很方便,并且可以容易地对枚举进行修改,而无需修改客户端。如果常量从枚举中被删除了,那么客户端将会失败并且将会收到一个错误消息。

在Java中一个枚举就是一个类,它也可以有属性和方法,并且实现接口。只是所有的枚举都继承自java.lang.Enum类,因此enum不可以再继承其他的类。

下面给出在枚举中声明属性和方法的示例:

 
public enum TypeEnum {
	VIDEO(1), AUDIO(2), TEXT(3), IMAGE(4);
	
	int value;
	
	TypeEnum(int value) {
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
}

如果要为每个枚举值指定属性,则在枚举中必须声明一个参数为属性对应类型的构造方法(不能是public)。否则编译器将给出The constructor TypeEnum(int, String) is undefined的错误。在此例中,属性为int型,因此构造方法应当为int型。除此之外,还可以为枚举指定多个属性

枚举构造器为什么不能是public?

如果其含有public构造器,那么在类的外部就可以通过这个构造器来新建实例,显然这时实例的数量和值就不固定了,这与定义枚举类的初衷相矛盾,为了避免这种形象,就对枚举类的构造器默认使用private修饰。如果为枚举类的构造器显式指定其它访问控制符,则会编译出错。

public enum TypeEnum {
	VIDEO(1, "视频"), AUDIO(2, "音频"), TEXT(3, "文本"), IMAGE(4, "图像");
	
	int value;
	String name;
	
	TypeEnum(int value, String name) {
		this.value = value;
		this.name = name;
	}
	
	public int getValue() {
		return value;
	}
	
	public String getName() {
		return name;
	}
}

##enum内置方法
enum还内置了许多方法,常用的如下:
int compareTo(E o)
比较此枚举与指定对象的顺序。

Class getDeclaringClass()
返回与此枚举常量的枚举类型相对应的 Class 对象。

String name()
返回此枚举常量的名称,在其枚举声明中对其进行声明。

int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

String toString()
返回枚举常量的名称,它包含在声明中。

static <T extends Enum> T valueOf(Class enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。

static T[] values()

	 返回该枚举的所有值。
package com.qf.enumdemo;


public class EnumDemo {

    public static void main(String[] args){

        //创建枚举数组
        Day[] days=new Day[]{Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY,
                Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY};

        for (int i = 0; i <days.length ; i++) {
            System.out.println("day["+i+"].ordinal():"+days[i].ordinal());
        }

        System.out.println("-------------------------------------");
        //通过compareTo方法比较,实际上其内部是通过ordinal()值比较的
        System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1]));
        System.out.println("days[0].compareTo(days[2]):"+days[0].compareTo(days[2]));

        //获取该枚举对象的Class对象引用,当然也可以通过getClass方法
        Class<?> clazz = days[0].getDeclaringClass();
        System.out.println("clazz:"+clazz);

        System.out.println("-------------------------------------");

        //name()
        System.out.println("days[0].name():"+days[0].name());
        System.out.println("days[1].name():"+days[1].name());
        System.out.println("days[2].name():"+days[2].name());
        System.out.println("days[3].name():"+days[3].name());

        System.out.println("-------------------------------------");

        System.out.println("days[0].toString():"+days[0].toString());
        System.out.println("days[1].toString():"+days[1].toString());
        System.out.println("days[2].toString():"+days[2].toString());
        System.out.println("days[3].toString():"+days[3].toString());

        System.out.println("-------------------------------------");

        Day d=Enum.valueOf(Day.class,days[0].name());
        Day d2=Day.valueOf(Day.class,days[0].name());
        System.out.println("d:"+d);
        System.out.println("d2:"+d2);
    }
    /**
     执行结果:
     day[0].ordinal():0
     day[1].ordinal():1
     day[2].ordinal():2
     day[3].ordinal():3
     day[4].ordinal():4
     day[5].ordinal():5
     day[6].ordinal():6
     -------------------------------------
     days[0].compareTo(days[1]):-1
     days[0].compareTo(days[2]):-2
     clazz:class com.qf.enumdemo.Day
     -------------------------------------
     days[0].name():MONDAY
     days[1].name():TUESDAY
     days[2].name():WEDNESDAY
     days[3].name():THURSDAY
     -------------------------------------
     days[0].toString():MONDAY
     days[1].toString():TUESDAY
     days[2].toString():WEDNESDAY
     days[3].toString():THURSDAY
     -------------------------------------
     d:MONDAY
     d2:MONDAY
     */

}
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}


注解

##概念

  • 概念:说明程序的。给计算机看的

  • 注释:用文字描述程序的。给程序员看的

  • 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  • 概念描述:

  • JDK1.5之后的新特性

  • 说明程序的

  • 使用注解:@注解名称

  • 作用分类:
    ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
    ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

##JDK中预定义的一些注解

  • @Override :检测被该注解标注的方法是否是继承自父类(接口)的
  • @Deprecated:该注解标注的内容,表示已过时
  • @SuppressWarnings:压制警告
  • 一般传递参数all @SuppressWarnings(“all”)
package com.qf.annotation;


@SuppressWarnings("all")
public class AnnotationDemo1 {

    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated
    public void test(){
        System.out.println("this is deprecated...");
    }

    public void test2(){
        System.out.println("this is old....");
    }


    public static void main(String[] args) {
        AnnotationDemo1 annotationDemo1 = new AnnotationDemo1();
        annotationDemo1.test();
    }
}


从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs : Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface : Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable : Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

@SafeVarargs例子:

package com.qf.annotation;

public class VarargsWaring {
    @SafeVarargs
    public static<T> T useVarargs(T... args){
        System.out.println(args.length);
        return args.length > 0?args[0]:null;
    }
}
package com.qf.annotation;



import java.util.ArrayList;

public class Safe{

    public static void main(String[] args) {
        /*
        为什么会使用@SafeVarargs呢?
        其大致原因如下:

     可变长度的方法参数的实际值是通过数组来传递的,二数组中存储的是不可具体化的泛型类对象,
     自身存在类型安全问题。因此编译器会给出相应的警告消息。
         */
        System.out.println(VarargsWaring.useVarargs(new ArrayList<String>()));

    }

}

@Repeatable例子:

package com.qf.annotation;



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

/*
在生活中一个人往往是具有多种身份,如果我把每种身份当成一种注解该如何使用???

先声明一个Roles类用来包含所有的身份
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public   @interface Roles {
    Role[] value();
}
package com.qf.annotation;

import java.lang.annotation.Repeatable;

/*
@Repeatable括号内的就相当于用来保存该注解内容的容器。
 */
@Repeatable(Roles.class)
public  @interface Role {
    String title() default "";
}

package com.qf.annotation;

@Role(title="CEO")
@Role(title="husband")
@Role(title="father")
@Role(title="son")
public  class Man {
    String name="";
}

package com.qf.annotation;

import java.lang.annotation.Annotation;

public class ManTest {
    public static void main(String[] args) {
        Annotation[] annotations = Man.class.getAnnotations();
        System.out.println("annotations.length:"+annotations.length);
        Roles p1=(Roles) annotations[0];
        for(Role t:p1.value()){
            System.out.println(t.title());
        }
    }
}

##自定义注解

  • 格式:
元注解
public @interface 注解名称{
属性列表;
}
  • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口

  • public interface MyAnno extends java.lang.annotation.Annotation {}

  • 属性:接口中的抽象方法

  • 要求:

  1. 属性的返回值类型有下列取值

    • 基本数据类型
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  2. 定义了属性,在使用时需要给属性赋值

    1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
    3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
package com.qf.annotation;

public @interface MyAnno {
//    int show1();
//
//    boolean show2();

    /*
    属性的返回值类型有下列取值
    * 基本数据类型
    * String
    * 枚举
    * 注解
    * 以上类型的数组
     */
//    int age();
//    String name();
//    String url() default "/*";
//    Sex sex() default Sex.Female;
//    MyAnno2 anno2();
    String[] arr();
    String value();//value属性非常特殊,当只有一个属性的时候,可以省略
    String url() default "/*";
    Sex sex() default Sex.Female;


}


public @interface MyAnno2 {
}
package com.qf.annotation;

public enum Sex {
    Male,Female;
}

自定义注解的使用

@MyAnno(value="123",arr={"123","abc"})
public class AnnotationDemo2 {
}

##元注解:用于描述注解的注解

  • @Target:描述注解能够作用的位置
    • ElementType取值:
    • ElementType.TYPE 指定注解只能修饰类或者接口 --重点
    • ElementType.FIELD 指定注解只能修饰成员属性 --重点
    • ElementType.METHOD 指定注解只能修饰方法 --重点
    • ElementType.PARAMETER 指定注解只能修饰方法的参数
    • ElementType.CONSTRUCTOR 指定注解只能修饰构造方法
    • ElementType.LOCAL_VARIABLE 指定注解只能修饰局部变量
    • ElementType.TYPE_USE 指定注解能修饰所有的 --JDK1.8后拥有的
  • @Retention:描述注解被保留的阶段
    • RetentionPolicy.SOURCE 注解信息保留在源文件中
    • RetentionPolicy.CLASS 保留在class文件中
    • RetentionPolicy.RUNTIME 注解信息在运行时保留,搭配反射使用 --重点

@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并> 被JVM读取到

  • @Documented:描述注解是否被抽取到api文档中
  • @Inherited:描述注解是否被子类继承
package com.qf.annotation;


import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})//标识注解作用的位置

@Retention(RetentionPolicy.RUNTIME)//描述注解被保留的阶段
//当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Documented//描述注解是否被抽取到api文档中
@Inherited
public @interface MyAnno3 {

}

package com.qf.annotation;

@MyAnno3
public class Worker {


    private String name;

    @MyAnno3
    public void show(){
        System.out.println("show.....");
    }
}

在程序使用(解析)注解

  • 在程序使用(解析)注解:获取注解中定义的属性值
  1. 获取注解定义的位置的对象 (Class,Method,Field)
  2. 判断注解是否存在
  • isAnnotationPresent(Class)
  1. 获取指定的注解
  • getAnnotation(Class)
  1. 调用注解中的抽象方法获取配置的属性值
package com.qf.annotation;

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

@Target({ElementType.METHOD})
//@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno4 {
    int age() default 0;
}

package com.qf.annotation;

import java.lang.reflect.Method;

public class AnnotationDemo3 {
    public static void main(String[] args) throws NoSuchMethodException {
        /*
        - 在程序使用(解析)注解:获取注解中定义的属性值

        1. 获取注解定义的位置的对象  (Class,Method,Field)

        - isAnnotationPresent(Class)

        1. 获取指定的注解

        - getAnnotation(Class)

        3. 调用注解中的抽象方法获取配置的属性值

         */
        Class<Person> personClass = Person.class;
        //
//        System.out.println(personClass.isAnnotationPresent(MyAnno4.class));
//        //获取类上面的注解,获取注解定义的位置的对象
//        MyAnno4 annotation = personClass.getAnnotation(MyAnno4.class);
//
//        //获取注解中定义的属性值
//        int age = annotation.age();
//        System.out.println("age:"+age);

        System.out.println("------------");

        Method test = personClass.getMethod("test");
        System.out.println(test.isAnnotationPresent(MyAnno4.class));
        //获取方法上定义的注解
        if(test.isAnnotationPresent(MyAnno4.class)){
            System.out.println("test方法有定义注解");
            MyAnno4 annotation2 = test.getAnnotation(MyAnno4.class);
            int age2 = annotation2.age();
            System.out.println("age2:"+age2);
        }


    }
}

案例:简单的测试框架

需求:

1,自定义一个注解,
2,如果方法上存在这个注解,那么就调用这个方法;
3,如果调用方法的过程中发生异常,那么将异常写入文件。

package com.qf.lianxi;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}

package com.qf.lianxi;

public class Calculator {

    @Check
    public void add(){
        System.out.println("1+0="+(1+0));
    }
    @Check
    public void sub(){
        System.out.println("1-0="+(1-0));
    }
    @Check
    public void mul(){
        System.out.println("1*0="+(1*0));
    }
    @Check
    public void div(){
        System.out.println("1/0="+(1/0));
    }

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

package demo;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CalculatorTest {
    public static void main(String[] args) throws IOException {

        Calculator cal  = new Calculator();
        //1,获取Class对象
        Class aClass = cal.getClass();

        //2,获取所有的方法
        Method[] methods = aClass.getMethods();
        int num=0;
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        //3,循环遍历所有的方法
       
            //4,如果有Check注解,那么就执行方法,记录异常
            
           //5,倘若有异常,那么记录下来
               

        bw.write("总共捕获到异常:"+num+"次");
        bw.flush();
        bw.close();



    }
}

  • 小结:
  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    1. 编译器
    2. 给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签

扩展知识(了解)

示例1:

class Father2{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father静态代码块");
    }
}

class Son2 extends Father2{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son静态代码块");
    }
}

public class InitativeUseTest2 {
    public static void main(String[] args) {

       System.out.println(Son2.strSon);
        //运行的结果:?
    }
}

示例2:

class YeYe{
    static {
        System.out.println("YeYe静态代码块");
    }
}

class Father extends YeYe{
    public static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father静态代码块");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son静态代码块");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather); 
        //运行的结果:?
    }
}


示例3:

class YeYe{
    static {
        System.out.println("YeYe静态代码块");
    }
}

class Father extends YeYe{
    public final static String strFather="HelloJVM_Father";

    static{
        System.out.println("Father静态代码块");
    }
}

class Son extends Father{
    public static String strSon="HelloJVM_Son";

    static{
        System.out.println("Son静态代码块");
    }
}

public class InitiativeUse {
    public static void main(String[] args) {
        System.out.println(Son.strFather);
        //运行的结果:?
    }
}

Java类加载

1.类的加载过程

JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vO50tPba-1605580543712)(img\类加载的过程.png)]

1) 装载

加载阶段指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象(JVM规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在方法区中),用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

看下面2图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RDA0oTJ-1605580543718)(img\类的加载1.gif)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cz7mPFAN-1605580543720)(img\类的加载2.gif)]

怎么理解Class对象与new出来的对象之间的关系呢?

new出来的对象以car为例。可以把car的Class类看成具体的一个人,而new car则是人物映像,具体的一个人(Class)是唯一的,人物映像(new car)是多个的。镜子中的每个人物映像都是根据具体的人映造出来的,也就是说每个new出来的对象都是以Class类为模板参照出来的!为啥可以参照捏?因为Class对象提供了访问方法区内的数据结构的接口哇,上面提及过了喔!

img

总结:

加载阶段简单来说就是:

.class文件(二进制数据)——>读取到内存——>数据放进方法区——>堆中创建对应Class对象——>并提供访问方法区的接口

可以从不同来源加载类的二进制数据,二进制数据通常有如下几种来源:

(1)从本地系统中直接加载

(2)通过网络下载.class文件

(3)从zip,jar等归档文件中加载.class文件

(4)从专用数据库中提取.class文件

(5)将java源文件动态编译为.class文件

2) 链接

验证:

就是为了验证确保Class文件的字节流中包含的信息符合当前虚拟机的要求即可。

准备【重点】

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段

当完成字节码文件的校验之后,JVM 便会开始为类变量分配内存并初始化。准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

这里需要注意两个关键点,即内存分配的对象以及初始化的类型。

内存分配的对象:要明白首先要知道Java 中的变量有类变量以及类成员变量两种类型,类变量指的是被 static 修饰的变量,而其他所有类型的变量都属于类成员变量。在准备阶段,JVM 只会为类变量分配内存,而不会为类成员变量分配内存。类成员变量的内存分配需要等到初始化阶段才开始(初始化阶段下面会讲到)。

举个例子:例如下面的代码在准备阶段,只会为 LeiBianLiang属性分配内存,而不会为 ChenYuanBL属性分配内存。

public static int LeiBianLiang = 666;
public String ChenYuanBL = "jvm";

初始化的类型:在准备阶段,JVM 会为类变量分配内存,并为其初始化(JVM 只会为类变量分配内存,而不会为类成员变量分配内存,类成员变量自然这个时候也不能被初始化)。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的默认值,而不是用户代码里初始化的值。

例如下面的代码在准备阶段之后,LeiBianLiang 的值将是 0,而不是 666。

public static int LeiBianLiang = 666;

但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。例如下面的代码在准备阶段之后,ChangLiang的值将是 666,而不再会是 0。

public static final int ChangLiang = 666;

两个语句的区别是一个有 final 关键字修饰,另外一个没有。而 final 关键字在 Java 中代表不可改变的意思,意思就是说 ChangLiang的值一旦赋值就不会在改变了。既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值。而没有被 final 修饰的类变量,其可能在初始化阶段或者运行阶段发生变化,所以就没有必要在准备阶段对它赋予用户想要的值。

解析

当通过准备阶段之后,进入解析阶段。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

3) 初始化【重点】

到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。

Java程序对类的使用方式可分为两种:主动使用与被动使用。一般来说只有当对类的首次主动使用的时候才会导致类的初始化,所以主动使用又叫做类加载过程中“初始化”开始的时机。那啥是主动使用呢?类的主动使用包括以下六种【超级重点】:

1、 创建类的实例,也就是new的方式

2、 访问某个类或接口的静态变量,或者对该静态变量赋值(凡是被final修饰,其实更准确的说是在编译器把结果放入常量池的静态字段除外)

3、 调用类的静态方法

4、 反射(如 Class.forName(“com.gx.yichun”))

5、 初始化某个类的子类,则其父类也会被初始化

6、 Java虚拟机启动时被标明为启动类的类( JavaTest ),还有就是Main方法的类会首先被初始化

最后注意一点对于静态字段,只有直接定义这个字段的类才会被初始化(执行静态代码块),这句话在继承、多态中最为明显!为了方便理解下文会陆续通过例子讲解

4) 使用

当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。这个使用阶段也只是了解一下就可以了。

5) 卸载

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。这个卸载阶段也只是了解一下就可以了。

4.加载器(ClassLoader)

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0PkAueZ-1605580543724)(img\类加载器.gif)]

1)Bootstrap ClassLoader

负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

2)Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

3)App ClassLoader

负责记载classpath中指定的jar包及目录中class

4)Custom ClassLoader

属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

5. 理解双亲委派模式

双亲委派模式工作原理

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8E5TdHFR-1605580543725)(img\双亲委派模式.png)]

双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,

即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢?

###双亲委派模式优势
第一:避免类的重复加载

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

第二:安全

其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常

java.lang.SecurityException: Prohibited package name: java.lang

所以无论如何都无法加载成功的。

JDK1.8 JVM运行时数据区域划分

一、JDK1.8 JVM运行时数据区域概览

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEwQ66Hj-1605580543726)(img\JVM运行时内存区域概览.jpg)]

这里介绍的是JDK1.8 JVM运行时内存数据区域划分。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存

二、各区域介绍

1. 程序计数器

每个线程一块,指向当前线程正在执行的字节码代码的行号。如果当前线程执行的是native方法,则其值为null。

2. Java虚拟机栈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cR5hK71-1605580543727)(img\Java虚拟机栈.jpg)]

线程私有,每个线程对应一个Java虚拟机栈,其生命周期与线程同进同退。每个Java方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。

1)、局部变量表

就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。

2)、操作数栈

想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

3)、指向运行时常量池的引用

因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

4)、方法返回地址

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。

3. 本地方法栈

功能与Java虚拟机栈十分相同。区别在于,本地方法栈为虚拟机使用到的native方法服务。不多说。

4. 堆

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uatNZJoM-1605580543728)(img\堆.jpg)]

堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:几乎所有的对象实例及数组都在对上进行分配。1.7后,字符串常量池从永久代中剥离出来,存放在堆中。堆有自己进一步的内存分块划分,按照GC分代收集角度的划分请参见上图。

4.1 堆空间内存分配(默认情况下)
  • 老年代 : 三分之二的堆空间
  • 年轻代 : 三分之一的堆空间
    • eden区: 8/10 的年轻代空间
    • survivor0 : 1/10 的年轻代空间
    • survivor1 : 1/10 的年轻代空间

一道推算题

默认参数下,如果仅给出eden区40M,求堆空间总大小

根据比例可以推算出,两个survivor区各5M,年轻代50M。老年代是年轻代的两倍,即100M。那么堆总大小就是150M。

4.2 字符串常量池

JDK1.7以后把字符串常量池从永久代中剥离出来,存放在堆空间中。

5. 方法区

永久代和元数据区都是对方法区的实现。

方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。

元数据区取代了1.7版本及以前的永久代。元数据区和永久代本质上都是方法区的实现。方法区存放虚拟机加载的类信息,静态变量,常量等数据。

6. 直接内存

jdk1.4引入了NIO,它可以使用Native函数库直接分配堆外内存。

总结

反射:将类的各个组成部分封装为其他对象,这就是反射机制

获取Class对象的方式:

  1. Class.forName(“全类名”):
  2. 类名.class:通过类名的属性class获取
  3. 对象.getClass():getClass()方法在Object类中定义着。

Class对象功能:

获取功能:

  1. 获取成员变量们
  • Field[] getFields() :获取所有public修饰的成员变量
  • Field getField(String name) 获取指定名称的 public修饰的成员变量
  • Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
  • Field getDeclaredField(String name)

Field:成员变量

  1. 设置值
  • void set(Object obj, Object value)
  1. 获取值
  • get(Object obj)
  1. 忽略访问权限修饰符的安全检查
  • setAccessible(true):暴力反射
  1. 获取构造方法们
  • Constructor<?>[] getConstructors()
  • Constructor getConstructor(类<?>… parameterTypes)
  • Constructor getDeclaredConstructor(类<?>… parameterTypes)
  • Constructor<?>[] getDeclaredConstructors()

Constructor:构造方法

  • 创建对象:
  • T newInstance(Object… initargs)
  • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
  1. 获取成员方法们:
  • Method[] getMethods()
  • Method getMethod(String name, 类<?>… parameterTypes)
  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, 类<?>… parameterTypes)

Method:方法对象

  • 执行方法:
  • Object invoke(Object obj, Object… args)
  • 获取方法名称:
  • String getName:获取方法名
  1. 获取全类名
  • String getName()
  • 操作:

Java类加载

Java类加载的过程

Java类加载器

双亲委派模式

JDK1.8 JVM运行时数据区域划分

1,程序计数器

2,Java虚拟机栈

3,本地方法栈

4,堆

5,方法区

6,直接内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值