目录
1.4反射实现——使用Class.forName()读取配置文件
一、反射
1.1类的加载概述和加载时机
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化,
- 加载:就是将class文件(字节码文件)读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象
- 连接:连接分为验证,准备和解析三步
- 验证:是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的静态成员分配内存,并设置默认初始化值
- 解析:将类的二进制数据中的符号引用替换为直接引用
- 初始化:包括默认初始化,显示初始化,构造方法初始化
加载时机的几种情况,
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
以上都会进行类的加载。
1.2类加载器的概述和分类
类加载器时负责将字节码文件即.class文件加载到内存中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
类加载器的分类如下:
- Bootstrap ClassLoader 跟类加载器
- Extension ClassLoader 扩展类加载器
- System ClassLoader 系统类加载器
类加载器的作用有以下几点:
- Bootstrap ClassLoader 跟类加载器
- 也被称为引导类加载器,负责Java核心类的加载
- 比如System,String等。再JDK中JRE的lib目录下rt.jar文件中
- Extension ClassLoader 扩展类加载器
- 负责JRE的扩展目录中jar包的加载
- 在JDK中JRE的lib目录下ext目录
- System ClassLoader 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
1.3反射概述
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,
对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象,
而解剖使用的就是Class类中的方法,所以先要获取到每个字节码文件对应的Class类型的对象。
获取字节码文件对象一共有三种方式:
- Obeject类的getClass()方法,一般用于判断两个对象是否是同一个字节码文件
- 静态属性class,一般用作锁对象
- Class类中的静态方法forName(),用于读取配置文件
我们用程序检测一下三种方法获得的字节码对象是否相等,
public class reflection {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz1=Class.forName("Person");
Class clazz2=Person.class;
Person p=new Person();
Class clazz3=p.getClass();
System.out.println(clazz1==clazz2);
System.out.println(clazz2==clazz3);
}
}
class Person{
public int age;
public String name;
}
结果显示相等:
1.4反射实现——使用Class.forName()读取配置文件
我们以榨水果为例,如果不使用反射,我们会使用到多态的方法:
public class reflection {
public static void main(String[] args) throws ClassNotFoundException {
Juicer j=new Juicer();
j.run(new Apple());
j.run(new Orange());
}
}
interface Fruit{
public void squeeze();
}
class Apple implements Fruit{
public void squeeze(){
System.out.println("榨出一杯苹果汁");
}
}
class Orange implements Fruit{
public void squeeze(){
System.out.println("榨出一杯橘子汁");
}
}
class Juicer{
public void run(Fruit f){
f.squeeze();
}
}
我们使用读取配置文件的方法,获取类的字节码文件创建对象,这样就不用对源码进行修改,
import java.io.BufferedReader;
import java.io.FileReader;
public class reflection {
public static void main(String[] args) throws Exception{
BufferedReader bf=new BufferedReader(new FileReader("config.properties"));//读取配置文件中的类名
Class clazz=Class.forName(bf.readLine());//获得类的字节码文件
Fruit f=(Fruit)clazz.newInstance();//通过字节码创建对象
Juicer j=new Juicer();
j.run(f);
}
}
interface Fruit{
public void squeeze();
}
class Apple implements Fruit{
public void squeeze(){
System.out.println("榨出一杯苹果汁");
}
}
class Orange implements Fruit{
public void squeeze(){
System.out.println("榨出一杯橘子汁");
}
}
class Juicer{
public void run(Fruit f){
f.squeeze();
}
}
配置文件中写的是类的名字,如果要榨苹果汁就写Apple,橘子汁就写Orange,
1.5反射获取带参构造方法
Class类的newInstance()方法是使用该类无参的构造函数创建对象,如果一个类没有无参的构造函数,就不能这样创建对象了,
就可以调用Class类的getConstructor(参数1类型.class, 参数2类型.class, ...)方法获取一个指定的构造函数,
然后再调用Constructor类的newInstance(参数1, 参数2, ...)方法创建对象,
下面我们通过实际程序来看看如何使用,
import java.lang.reflect.Constructor;
public class reflection {
public static void main(String[] args) throws Exception{
Class clazz=Class.forName("Student");
Constructor c=clazz.getConstructor(String.class,int.class);
Student s=(Student)c.newInstance("测试",20);
System.out.println(s);
}
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
运行结果如下:
1.6反射获取成员变量
Class.getField(String name)方法可以获取类中的指定名称的字段,如果是私有的可以用getDeclaedField("name")方法获取,
通过set(obj,"李四")方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(true)设置访问权限,
用获取的指定的字段调用get(obj)获取指定对象中该字段的值,
下面我们通过一个程序理解,
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class reflection {
public static void main(String[] args) throws Exception{
Class clazz=Class.forName("Student");//获取学生类的字节码对象
Constructor c=clazz.getConstructor(String.class,int.class);//获取有参构造方法
Student s=(Student)c.newInstance("测试",20);//通过有参构造创建学生s
Field f=clazz.getDeclaredField("age");//获取学生类的年龄字段
f.setAccessible(true);//将私有字段访问权限打开
f.set(s,25);//修改学生s的年龄为25
System.out.println(s);
}
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
下面是更改的结果:
1.7反射获取方法
Class.getMethod(String, Class ...)和Class.getDeclaredMethod(String, Class ...)方法可以获取类中的指定方法,
调用invoke(Object, Object ...)可以调用该方法,
下面我们通过一个例子进行学习,
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class reflection {
public static void main(String[] args) throws Exception{
Class clazz=Class.forName("Student");//获取学生类的字节码对象
Constructor c=clazz.getConstructor(String.class,int.class);//获取有参构造方法
Student s=(Student)c.newInstance("测试",20);//通过有参构造创建学生s
Method m=clazz.getMethod("study",int.class);//获取study方法,后面为参数类型.class
m.invoke(s,365);//执行对象s的study方法,并给定参数
}
}
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void study(int num){
System.out.println("已经学习了"+num+"天");
}
}
运行结果如下:
1.8反射练习
已知一个类定义如下:
class reflectionRun{
public void run(){
System.out.println("Hello reflection!");
}
}
要求:
- 写一个properties格式的配置文件,配置类的完整名称
- 写一个程序,读取这个properties配置文件,获得类的完整名称并加载这个类,用反射的方式运行run方法
配置文件如下:
代码如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.lang.reflect.Method;
public class reflection {
public static void main(String[] args) throws Exception{
BufferedReader br=new BufferedReader(new FileReader("config.properties"));//读取配置文件
Class clazz=Class.forName(br.readLine());//获取reflectionRun类的字节码对象
reflectionRun r=(reflectionRun)clazz.newInstance();//利用无参构造创建对象
Method m=clazz.getMethod("run");//获取reflectionRun类中的run方法
m.invoke(r);//执行对象s的run方法
}
}
class reflectionRun{
public void run(){
System.out.println("Hello reflection!");
}
}
运行结果:
1.9反射之动态代理
代理是指本来应该自己做的事情,让别人帮忙来做,帮忙的人就是代理对象。
动态代理是在程序运行过程中产生代理对象,而程序运行过程中产生对象其实就是反射,所以动态代理其实就是通过反射来生成一个代理。
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
JDK提供的代理只能针对接口做代理,我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象,
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)//loader为被代理对象的类加载器,interfaces为被代理对象的所有接口,h为代理处理
最终会调用 InvocationHandler的invoke方法,
InvocationHandler Object invoke(Object proxy, Method method, Object[] args)//对proxy对象进行代理,执行method方法,参数为args
假设有一个用户的接口,有添加和删除的功能接口,我们在UserImp类中实现了这两个方法,
在添加和删除之前一般有权限校验操作,进行操作后有日志记录,权限校验和日志记录操作一般我们用动态代理来实现,
下面是代码实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class reflection {
public static void main(String[] args) throws Exception{
UserImp ui=new UserImp();
System.out.println("动态代理前:");
ui.add();
ui.delele();
System.out.println();
myInvocationHandler m=new myInvocationHandler(ui);//生成代理对象的代理处理程序
User u=(User) Proxy.newProxyInstance(ui.getClass().getClassLoader(),ui.getClass().getInterfaces(),m);//返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序
System.out.println("动态代理后:");
u.add();
u.delele();
}
}
interface User{
public void add();
public void delele();
}
class UserImp implements User{
public void add() {
System.out.println("添加数据");
}
public void delele() {
System.out.println("删除数据");
}
}
class myInvocationHandler implements InvocationHandler{
private Object target;//被代理的对象
public myInvocationHandler(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("权限校验");
method.invoke(target,args);//对target对象代理,执行method方法
System.out.println("日志记录");
return null;
}
}
然后是程序运行截图,可以看到代理成功了:
二、Template设计模式
Template模板方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现,
其优缺点为:
- 优点:使用模板方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求
- 缺点:如果算法骨架有修改的话,则需要修改抽象类
我们用代码来理解一下这个设计模式如何进行使用,
public class Template {
public static void main(String[] args) {
forTest f=new forTest();
System.out.println(f.getTime());
}
}
abstract class GetTime{
public final long getTime(){//添加final关键字,不允许子类重写该方法
long start=System.currentTimeMillis();//获取程序运行开始时间
Code();
long end=System.currentTimeMillis();//获取结束时间
return end-start;//返回程序运行时间
}
public abstract void Code();//定义抽象方法,让子类去实现具体的算法
}
class forTest extends GetTime{
@Override
public void Code() {
for (int i = 0; i < 100000; i++) {
System.out.println("*");
}
}
}
下面是运行截图:
三、JDK新特性
3.1JDK5新特性
3.1.1枚举类
枚举就是将变量的值逐个列出来,变量的值只限于列举出来的值的范围内。
比如一周只有七天,一年只有十二个月等等。
3.1.2enum实现枚举类
enum实现枚举类一共有三种格式,
public class EnumTest {
public static void main(String[] args) {
Week mon=Week.MON;
System.out.println(mon);
Week2 tue=Week2.TUE;
System.out.println(tue.getName());
Week3 wed=Week3.WED;
wed.show();
}
}
enum Week{//无参枚举
MON,TUE,WED;//枚举项
}
enum Week2{//有参枚举
MON("星期一"),TUE("星期二"),WED("星期三");
private String name;
private Week2(String name){//有参构造方法
this.name=name;
}
public String getName(){
return name;
}
}
enum Week3{//有参并有抽象方法的枚举
MON("星期一"){
@Override
public void show() {
System.out.println("星期一");
}
},TUE("星期二"){
@Override
public void show() {
System.out.println("星期二");
}
},WED("星期三"){
@Override
public void show() {
System.out.println("星期三");
}
};
private String name;
private Week3(String name){
this.name=name;
}
public String getName(){
return name;
}
public abstract void show();//抽象方法,枚举项必须重写
}
运行结果如下:
3.1.3枚举的注意事项
- 定义枚举类要用关键字enum
- 所有枚举类都是Enum的子类
- 枚举类的第一行必须是枚举项,最后一个枚举项后面的分号可以省略,但是如果枚举类还有其他东西则不可以省略
- 枚举类可以有构造器,但必须是私有private的,不写默认也是private
- 枚举类可以有抽相仿啊,但是枚举项必须重写抽象方法
- 枚举可以在switch语句中使用
3.1.4枚举类的常用方法
枚举类主要有以下几种常用的方法,
int ordinal()//返回枚举常量的下标位置,第一个位置从0开始
int compareTo(E o)//比较和枚举常量o的下标是否相同,相同则返回0
String name()//获取枚举实例名称
String toString()//默认也是返回枚举实例名称,可以对该方法进行重写
<T> T valueOf(Class<T> type, String name)//静态方法,通过字节码对象获取到枚举项
values()//返回枚举类对象数组,常用于遍历枚举类的所有枚举值
3.2JDK7新特性
- 二进制字面量用"0b"开头。例如0b010输出显示为2
- 数字字面量可以出现下划线,方便数位数,没有实际含义。例如10_000_000代表一亿
- switch语句可以用字符串
- 泛型简化,菱形泛型
- 异常的多个catch合并,每个异常用或符号隔开"|"
- try-with-resources语句,1.7版本标准的异常处理代码(自动关流)
3.3JDK8新特性
3.3.1接口
接口中可以定义有方法体的方法。如果是非静态,则必须用default修饰,如果是静态则不用,
public class Test {
public static void main(String[] args) {
Demo d=new Demo();
d.print();//调用接口的非静态方法
Inter.run();//调用接口的静态方法
}
}
interface Inter{
public default void print(){
System.out.println("non-static method");
}
public static void run(){
System.out.println("static method");
}
}
class Demo implements Inter{
}
运行结果:
3.3.2局部内部类
在JDK1.8版本之前,局部内部类用到方法中的局部变量时,局部变量必须用final修饰,
在1.8版本之后,不用final显示修饰也可以直接使用,系统会自动帮你加上final关键字修饰,
换句话说,在局部内部类中如果对局部变量进行修改时,程序会报错,因为是final隐式修饰的,
public class EnumTest {
public static void main(String[] args) {
Outer out=new Outer();
out.method();
}
}
class Outer{
public void method(){
int num=10;//1.8版本之前局部内部类用到方法中的局部变量时必须用final修饰
class Inner{
public void test(){
System.out.println(num);
}
}
Inner i=new Inner();
i.test();
}
}
运行结果: