类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。
连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化
就是我们以前讲过的初始化步骤
类初始化时机:
1.创建类的实例
2.访问类的静态变量,或者为静态变量赋值
3.调用类的静态方法
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
类加载器
负责将.class文件加载到内在中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
类加载器的组成
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载。比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录
Sysetm ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。
反射:就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种****动态获取****的信息以及动态调用对象的方法的功能称为java语言的反射机制
//要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象
对于我们学习反射,我们在操作中一般会获取类的成员 Constructor Field Method。但是要想获取这些对象,必须首先得到其Class,通过Class再来获得其它对象
Java反射常用API
Java中的Class可以代表任意的类或接口类型
在java.lang.reflect包下有三个类
三种获取Class对象的方式
1:如果持有一个对象,可以直接从Object类继承的getClass()方法获取
Person p = new Person();
Class c = p.getClass();
2:可以直接通过类包(接口)直接调用其静态属性.class获取
Class c2 = Person.class;
任意数据类型都具备一个class静态属性,看上去要比第一种方式简单.
3:可以通过Class类中提供的forName方法获取(将类名作为字符串传递给Class类中的静态方法forName即可):
public static Class forName(String className) 这个类名必须是全路径:包名.类名(可以在外面写,会提示是否出错,没错的话沾过去;也可以点击类名下的类名右击copy Qualified name )
Class c3 = Class.forName(“Person”);
第三种和前两种的区别 :前两种你必须明确Person类型;后面是一个字符串就行,而不需要是一个具体的类名.这种扩展更强.我不需要知道你的类.我只提供字符串,将这样的字符串配置到配置文件中,按照配置文件加载就可以了
所以开发中一般用第三种方法
Class类:
成员变量 Field
构造方法 Constructor
成员方法 Method
通过反射获取构造方法并使用:
获取构造方法:
getConstructor它获取的是类的public构造
getConstructors它获取的是类的所有的public构造
这两个方法不仅可以获取public,也可以获取其它权限的
获取单个构造方法
public Constructor
/*获取的单个构造方法实际上是一个对象,通过这个对象可以创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
public T newInstance(Object… initargs)
反射可以获取私有的构造方法,成员方法,成员变量:
获取的如果是私有的构造方法,要用public Constructor
我们得到构造器,就可以实例化对象
如果不是public,那么我们需要构造器对象的实例调用AccessibleObject中的
来取消语言检查
Reflect类继承于AccessibleObject类继承于Object类
通过反射获取成员变量并使用:Java.lang.reflect.Field它描述的属性对象
1.获取一个Field
获取所有成员
getFields 公共的
getDeclaredFields 所有的
获取单个成员
getField
getDeclaredField
2.Field的操作
(1)对Field进行赋值
赋值或修改成员的值
set(Object obj,Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。此处的obj需要通过构造方法创建一个obj对象,也可以直接通过Class的实例的newInstance方法直接获得obj对象(这个方法的底层就是使用了Class类额无参构造来实例化)
(2)对Field进行取值
通过反射获取成员方法并使用:Java.lang.reflect.Method它描述的是类或接口中的方法
获取所有方法
getMethods 获取自己的包括父亲的公共方法
getDeclaredMethods 获取自己的所有方法
获取单个方法
getMethod(String name,Class<?>… parameterTypes)
getDeclaredMethod(String name,Class<?>… parameterTypes)
第一个参数是方法的名称,第二个参数是一个可变参数Class[],它描述的是方法的参数Class
Method的使用:public Object invoke (Object obj , Object… args) 返回值是Object接收,第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
Method的invoke使用时注意事项?
\1. 如果方法是static,我们怎样调用?
如果方法是静态的,在通过invoke调用时不需要传递对象,传递null
\2. 如果方法的参数是一个数组类型,怎样处理?
为什么不能直接传args:因为数组也是object类型。在传递时,底层的invoke方法认为要调用的参数有四个。
为什么int[]直接传递args不报错,而Integer[]会报错:因为int[]数组中的每一个元素不是Object,只是基本数据类型,int[]被认为是一个对象。而Integer[]数组中的每一个元素都是Object
/*反射的调用和正常类的调用是恰好相反的。
正常类:p.show() p.show(参数)
反射:show.invoke(p) show.invoke(p,参数)
*/
案例:(这个案例可以帮助我们把集合中的值通过反射赋到一个类中)
并提供get/set方法
使用java中的反射技术将类中的属性与map中的key相同名称的,使用反射技术将key对应的value值赋值给属性.
采用两种方式完成操作:
1.直接操作属性 Field来完成操作(不常用)
2.通过属性对应的setXxx方法来完成操作
优点:
1、反射提高了程序的灵活性和扩展性。
2、降低耦合性,提高自适应能力。
3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
反射:需要配置文件配合使用。
配置文件是键值对。必须要有键,代码获取键;然后可以自己通过改变值,来使代码中的对象改变,从而获得不同的运行效果。
反射可以越过泛型检查。
真实的需求应该是在你调用方法后,每次操作前,都需要进行权限校验,在进行操作后,必须留下日志记录。
代理即Proxy Pattern,23种常用的面向对象软件的设计模式之一
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
组成:
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色(目标对象):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
分类:
静态代理:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
优点:
(1).职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
(3).高扩展性
动态代理
代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib(框架中学到,这个代理不仅仅是针对接口)
注意:在java中使用Proxy来完成动态代理对象的创建,只能为目标实现了接口的类创建代理对象。
Proxy类中的方法创建动态代理类对象:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
ClassLoader loader 是对象的字节码文件的类加载器:.getClass().getClassLoader()
Class<?>[] interfaces是对象的字节码文件的接口:.getClass().getInterfaces()
动态代理是在内存中直接生成代理对象。
通过这个方法可以直接创建一个代理对象。
//InvocationHandler应用了观察者模式,因为代理类没有实例,所以需要监听代理对象调用时,其底层实现的是目标行为
InvocationHandler h是一个接口,这里需要的是这个接口的实现类对象,所以需要创建一个类实现InvocationHandler接口。这个类中再重写InvocationHandler方法:public Object invoke(Object proxy,Method method,Object[] args)
Invoke方法,它是在代理对象调用行为时,会执行的方法,而invoke方法上有三个参数:
proxy:代表动态代理对象
method:要访问的目标行为,代表要调用的方法对象
args:目标行为,代表调用目标方法时传入的实参
这个方法的主要作用是:当我们通过代理对象调用行为时,来控制目标行为是否可以被调用(可以自己写代码控制,return null 不让调,return method.invoke(target,args)控制目标对象的方法执行)。
Proxy.newProxyInstance
创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
System.out.println(u.getClass().getName());
案例:测试addUser方法的运行时间
在开发中,在代理中通过目标来调用真实行为,我们使用动态代理可以完成性能监控(如上,进行时间的记录来监控性能),权限控制(在调用目标行为之前先判断是否有权限),日志记录(在调用方法之前,进行日志记录一些信息)等操作
注释(先写注释在写程序)
作用:用于解释说明程序的文字,提高程序的阅读性;可以帮助我们调试程序。
注释分类格式:
单行注释// 可以嵌套
多行注释/* */ 不能嵌套
文档注释/** */
对于单行和多行注释,被注释的文字是不会被JVM解释执行
对于文档注释 是java特有的注释,其中的注释内容可以被JDK提供的工具javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档。
/*
需求:
分析:
实现:
*/
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。
作用:如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
编写文档:通过代码里标识的元数据生成文档。
代码分析:通过代码里标识的元数据对代码进行分析。
编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查
在现在开发中使用注解,一般是用于将注解替换配置文件。(xml配置文件)
Java中基本内置注解:
1.@Override
它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
注意事项:
对于接口中的方法重写,在jdk1.5时@Override它是会报错.
在jdk1.6后的版本就可以描述接口与类之间的重写
2.@Deprecated
它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息
问题:什么时候方法是过时的?
当前版本中这个方法存在隐患,在后续版本中对其进行了补充,这时前一个版本中的方法就会标注成过时的。
3.@SuppressWarnings
它的作用是去掉程序中的警告.
其参数有:
deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
finally ,任何 finally 子句不能正常完成时的警告
all,关于以上所有情况的警告
可以在方法之前加@SuppressWarnings(“rawtype”)
当一个类中出现了很多,可以在类上加@SuppressWarnings(“all”),一劳永逸
自定义注解
1.注解声明:声明一个注解格式: 修饰符 @interface 注解名{}
2.注解本质分析
分析一下注解的本质:将其.class文件找到,反编译. (可以使用javap命令或反编译工具)
@interface MyAnnoation{}
反编译后的结果
interface MyAnnotation extends Annotation
{
}
结论:注解本质上就是一个接口。它扩展了java.lang.annotation.Annotation接口;
在java中所有注解都是Annotation接口的子接口。
3.注解成员
注解本质上就是一个接口,那么它也可以有属性和方法。
但是接口中的属性是 public static final的,在注解中注解没有什么意义。
在开发中注解中经常存在的是方法(这个方法的返回值类型有要求)。而在注解中接口的方法叫做注解的属性.
4.自定义注解-属性操作
注解属性类型:
1.基本类型 byte short int long float double char boolean
2.String
3.枚举类型:可以自己创建一个枚举类,然后类名 方法名();
4.注解类型
5.Class类型
6.以上类型的一维数组类型
注解属性的使用
1.如果一个注解有属性,那么在使用注解时,要对属性进行赋值操作.
例如:@MyAnnotation(st = “aaa”)
2.如果一个注解的属性有多个,都需要赋值,使用”,”分开属性.
@MyAnnotation(st = “aaa”,i=10)
3.也可以给属性赋默认值
double d() *default 1.23*;
如果属性有默认值,在使用注解时,就可以不用为属性赋值。
4.如果属性是数组类型
1.可以直接使用 属性名={值1,值2,。。。}方式,例如
@MyAnnotation(st = “aaa”,i=10,sts={“a”,”b”})
2.如果数组的值只有一个也可以写成下面方式
@MyAnnotation(st = “aaa”,i=10,sts=”a”)
注意sts属性它是数组类型,也就是说,只有一个值时,可以省略”{}”
5.对于属性名称 value的操作.
1.如果属性名称叫value,那么在使用时,可以省略属性名称
@MyAnnotation(“hello”)
2.如果有多个属性,都需要赋值,其中一个叫value,这时,必须写属性名称
@MyAnnotation(value=”hello”,i=10)
3.如果属性名称叫value,它的类型是数组类型.
1.只有这个value属性
可以直接赋值,不能写属性名称,但是,如果只有一个值
@MyAnnotation({“abc”})或 @MyAnnotation(“abc”)
但是如果有多个值
@MyAnnotation({“abc”,”def”})
2.如果有多个属性,属性名称叫value
所有属性都需要赋值,那么必须写属性名称.
5.自定义注解-元注解
元注解及其作用:用于修饰注解的注解,可以描述注解在什么范围及在什么阶段使用等
四个元注解介绍:
@Retention
指定注解信息在哪个阶段存在 Source Class Runtime
SOURCE它对应着编译阶段,可以帮助我们进行检查。
CLASS 它对应解析执行阶段
RUNTIME 它对应着在JVM中,在运行时结合反射技术,可以使注解具有一些功能
@Target
指定注解修饰目标对象的类型
TYPE 类、接口 FIELD 成员变量 METHOD 方法
在接口的定义中前面进行元注解@Target(ElementType.TYPE)标注后,这个自定义注解只能用在TYPE即类、接口上,不能用于方法上。
如果要求自定义的注解需要用在多个地方。可以@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
使用该元注解修饰,该注解的信息可以生成到javadoc 文档中
@Inherited
如果一个注解使用该元注解修饰,应用注解目标类的子类会自动继承该注解
@Retention @Target 是自定义注解必须使用两个元注解,并且,@Retention它的值应该是RUNTIME,因为我们会结合反射技术来使用。 @Target我们一般使用TYPE或METHOD
案例-获取Connection连接数据库
目的:让注解具有功能,必须结合反射技术来应用。
注解它可以替换配置文件。
第一步:创建注解
第二步:使用注解
在方法上进行注解标注:
@JdbcProperty(driverClass=””,url=””,user=””,password=””)
我们可以通过getAnnotation()方法(这个方法在Class,Method。Field中都有)来获取注解对象
通过jp.driverClass jp.url 等就可以得到注解的值,然后在数据库连接中使用,进行数据库连接
这里的操作实际上相当于是以前在配置文件中写这些属性值,在代码中从配置文件中取。
问题:使用注解可以替换配置文件,为什么要替换?
配置文件中的信息,它会随着程序的变大,配置信息越来越多,不利于开发。而将配置信息通过注解来替换,便于开发与阅读。
缺点:如果需要更改,需要在源代码上修改注解。