反射
反射
##反射:框架设计的灵魂
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
反射:将类的各个组成部分封装为其他对象,这就是反射机制
画图分析一下反射
- 好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
##获取Class对象的方式:
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
- 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
- 类名.class:通过类名的属性class获取
- 多用于参数的传递
- 对象.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:成员变量
- 设置值
- void set(Object obj, Object value)
- 获取值
- get(Object obj)
- 忽略访问权限修饰符的安全检查
- 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);
}
}
##案例:
- 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
- 实现:
- 配置文件
- 反射
- 步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
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 {}
-
属性:接口中的抽象方法
-
要求:
-
属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
-
定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
- 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
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.....");
}
}
在程序使用(解析)注解
- 在程序使用(解析)注解:获取注解中定义的属性值
- 获取注解定义的位置的对象 (Class,Method,Field)
- 判断注解是否存在
- isAnnotationPresent(Class)
- 获取指定的注解
- getAnnotation(Class)
- 调用注解中的抽象方法获取配置的属性值
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:
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)链接又分为三个步骤,如下图所示:
1) 装载
加载阶段指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象(JVM规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在方法区中),用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
看下面2图
怎么理解Class对象与new出来的对象之间的关系呢?
new出来的对象以car为例。可以把car的Class类看成具体的一个人,而new car则是人物映像,具体的一个人(Class)是唯一的,人物映像(new car)是多个的。镜子中的每个人物映像都是根据具体的人映造出来的,也就是说每个new出来的对象都是以Class类为模板参照出来的!为啥可以参照捏?因为Class对象提供了访问方法区内的数据结构的接口哇,上面提及过了喔!
总结:
加载阶段简单来说就是:
.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及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
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. 理解双亲委派模式
双亲委派模式工作原理
双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:
双亲委派模式是在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运行时数据区域概览
这里介绍的是JDK1.8 JVM运行时内存数据区域划分。1.8同1.7比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
二、各区域介绍
1. 程序计数器
每个线程一块,指向当前线程正在执行的字节码代码的行号。如果当前线程执行的是native方法,则其值为null。
2. Java虚拟机栈
线程私有,每个线程对应一个Java虚拟机栈,其生命周期与线程同进同退。每个Java方法在被调用的时候都会创建一个栈帧,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。
1)、局部变量表
就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
2)、操作数栈
想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
3)、指向运行时常量池的引用
因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
4)、方法返回地址
当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。
3. 本地方法栈
功能与Java虚拟机栈十分相同。区别在于,本地方法栈为虚拟机使用到的native方法服务。不多说。
4. 堆
堆是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对象的方式:
- Class.forName(“全类名”):
- 类名.class:通过类名的属性class获取
- 对象.getClass():getClass()方法在Object类中定义着。
Class对象功能:
获取功能:
- 获取成员变量们
- Field[] getFields() :获取所有public修饰的成员变量
- Field getField(String name) 获取指定名称的 public修饰的成员变量
- Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
- Field getDeclaredField(String name)
Field:成员变量
- 设置值
- void set(Object obj, Object value)
- 获取值
- get(Object obj)
- 忽略访问权限修饰符的安全检查
- setAccessible(true):暴力反射
- 获取构造方法们
- Constructor<?>[] getConstructors()
- Constructor getConstructor(类<?>… parameterTypes)
- Constructor getDeclaredConstructor(类<?>… parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
Constructor:构造方法
- 创建对象:
- T newInstance(Object… initargs)
- 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
- 获取成员方法们:
- Method[] getMethods()
- Method getMethod(String name, 类<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
Method:方法对象
- 执行方法:
- Object invoke(Object obj, Object… args)
- 获取方法名称:
- String getName:获取方法名
- 获取全类名
- String getName()
- 操作:
Java类加载
Java类加载的过程
Java类加载器
双亲委派模式
JDK1.8 JVM运行时数据区域划分
1,程序计数器
2,Java虚拟机栈
3,本地方法栈
4,堆
5,方法区
6,直接内存