JavaSE基础——(27)反射&JDK新特性

目录

一、反射

1.1类的加载概述和加载时机

1.2类加载器的概述和分类

1.3反射概述

1.4反射实现——使用Class.forName()读取配置文件

1.5反射获取带参构造方法

1.6反射获取成员变量

1.7反射获取方法

1.8反射练习

1.9反射之动态代理

二、Template设计模式

三、JDK新特性

3.1JDK5新特性

3.1.1枚举类

3.1.2enum实现枚举类

3.1.3枚举的注意事项

3.1.4枚举类的常用方法

3.2JDK7新特性

3.3JDK8新特性

3.3.1接口

3.3.2局部内部类


 

一、反射

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!");
    }
}

要求:

  1. 写一个properties格式的配置文件,配置类的完整名称
  2. 写一个程序,读取这个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();
    }
}

运行结果:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值