Java基础-反射学习和JDK新特性

 

类的加载

 

类的加载概述

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

加载:就是指将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包和类路径

 

反射

反射概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意一个方法和属性;

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。

而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

获取Class对象三种方式:

  1. Object类的getClass()方法
  2. 静态属性class
  3. Class类中静态方法forName()
package info.test;
public class Person {//Person类,用于下面的练习
    private String name;
    private int age;
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public static void show() {
        System.out.println("show 一下");
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

三种方式的代码:

 

	public static void main(String[] args) throws ClassNotFoundException {
		//第一种获取方式
		Person person = new Person();
		Class clazz1 =  person.getClass();
		//第二种获取方式
		Class clazz2 = Person.class;
		//第三种获取方式
		Class clazz3 = Class.forName("info.test.Person");
	}

反射的三个阶段:

 

上面的读取配置文件啥意思?看如下代码

 

interface Fruit {
	public void squeeze();
}
class Apple implements Fruit {
	@Override
	public void squeeze() {
		System.out.println("榨出一杯苹果汁");
	}
}
class Orange implements Fruit {
	@Override
	public void squeeze() {
		System.out.println("榨出一杯橘子汁");
	}
}
class Juicer {
	public void run(Fruit f) {
		f.squeeze();
	}
}


读取配置文件实现类的创建

 

 

public class Test {
	public static void main(String[] args) throws Exception {
		Juicer juicer = new Juicer();
		//普通方式
		juicer.run(new Apple());
		juicer.run(new Orange());
		System.out.println("----------");
		//配置文件读取方式
		InputStream is = Test.class.getClassLoader().getResourceAsStream("config.properties");
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		Class<?> clazz = Class.forName(br.readLine());
		Fruit fruit = (Fruit)clazz.newInstance();
		juicer.run(fruit);
	}
}

注:config.properties 文件在src目录下
      

 

虽然下面使用配置文件读取的方式比上面的普通代码要多,而且繁琐。虽然写的时候有点费事,但是其灵活性比上面的代码大大增多了,当我们需要一杯橘子汁时,普通方法需要在源文件中进行修改,而现在只需使用配置文件修改即可,配置文件的修改是符合开闭原则的,而前面则违反了开闭原则。

反射获取构造方法

首先,来看一下我们Class类中直接创建对象。

 

public T newInstance() throws InstantiationException,IllegalAccessException
创建此 Class 对象所表示的类的一个新实例。
如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。 
	public static void main(String[] args) throws Exception {
		Class clazz = Person.class;
		Person person = (Person) clazz.newInstance();
		person.setAge(23);
		person.setName("张三");
		System.out.println(person);
	}
	//outPut:Person [name=张三, age=23]

通过上面的API可以发现,该方法会调用空参构造,假设我们注释掉Person类中的构造会怎样?

 

 

Exception in thread "main" java.lang.InstantiationException: info.test.Person

那么它会给你一个异常。那就再来查看我们的API,肯定有其他的方式创建

 

 

public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。

以下是Constructor内方法
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,
											IllegalArgumentException, InvocationTargetException
//使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
	public static void main(String[] args) throws Exception {
		Class clazz = Person.class;
	//使用Constructor来实现clazz.newInstance()
		Constructor c = clazz.getConstructor();
		Person p = (Person) c.newInstance();
	//使用Constructor来实现有参构造创建对象
		c = clazz.getConstructor(String.class,int.class); //获取有参构造
		p = (Person) c.newInstance("张三",23);//通过有参构造创建对象
	}

这样就使用有参构造创建了一个对象,在getConstructor方法中,你可以一个都不给(无参构造),也可以给Class对象(Class 对象要按其形参声明顺序排列)。还有在时Constructor还是在反射阶段(操作字节码),所以传进去的也是需要其字节码。而到了下面的newInstance就到了创建对象的阶段,需传进去实际的参数值。

 

反射获取成员变量

 

public Field getField(String name) throws NoSuchFieldException, SecurityException
//返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段

以下是Field内方法
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
//将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
//如果底层字段的类型为基本类型,则对新值进行自动解包。 
	public static void main(String[] args) throws Exception {
		Class clazz = Person.class;
		Person p = new Person("李四",24);
		Field f = clazz.getField("name");
		f.set(p, "张三");
		System.out.println(p);
	}
	// Exception in thread "main" java.lang.NoSuchFieldException: name

可以发现,竟然报错了,明明我们有name字段的,为什么会找不到?仔细看看API的说明指定公共成员字段,再看看我们上面Person的字段(private)的。不过,在反射面前,一切都是赤裸裸的,我们可以暴力获取,但是单单暴力获取后直接修改值还是报错(java.lang.IllegalAccessException),我们还需给它去除权限才可正常使用。举个小例子:如果你想让某个人听令于你,那么你抓到他也没用,他不会情愿给你干活,还需给它进行洗脑后才可(哈~)

 

 

public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException
//返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段

以下为AccessibleObject内方法
	其子类有:Constructor, Field, Method 
public void setAccessible(boolean flag) throws SecurityException
//值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。(去除私有权限)

 

 

	public static void main(String[] args) throws Exception {
		Class clazz = Person.class;
		Person p = new Person("李四",24);
//		Field f = clazz.getField("name");获取姓名字段
		Field f = clazz.getDeclaredField("name");//暴力反射获取字段
		f.setAccessible(true);//去除私有权限
		f.set(p, "张三");//修改姓名的值
		System.out.println(p);
	}
	// Person [name=张三, age=24]

同样的,我们也可暴力获取构造或者方法。

 

反射获取方法

 

public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
//name为方法名,parameterTypes 参数是按其形参声明顺序排列的 Class 对象的一个数组
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法(暴力获取)

以下为Method内方法
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
//对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
//如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null
//如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null
	public static void main(String[] args) throws Exception {
		Class clazz = Person.class;
		Person p = new Person("李四",24);
		Method m = clazz.getMethod("setName",String.class);//获取setName方法
		m.invoke(p,"张三"); //由p调用,参数为 张三
		System.out.println(p);
	//静态方法的调用
		m = clazz.getMethod("show");
		m.invoke(null); //静态方法调用所以第一个参数为null,由于方法无参数,后面可省略也可写null
	}
	/*outPut:
	 * Person [name=张三, age=24]
	 * show 一下
	 */

由于上述方法都为公共的,可以直接获取,若是私有方法,需使用暴力获取并去除权限

 

反射小习一:

ArrayList<Integer>的一个对象,在这个集合中添加一个字符串数据,如何实现呢?

泛型其实是在set()处的类型检验,而在get()处的类型转换。由于泛型擦除(点此传送门),那么在运行过程中,一切都只是Object,所以此处我们可通过反射处理。

 

	public static void main(String[] args) throws Exception {
		ArrayList<Integer> list = new ArrayList<>();
		list.add(123);
		//list.add("234");  编译错误
		Class clazz = list.getClass();
		Method m = clazz.getMethod("add", Object.class); //获取 add 方法,实际字节码文件中其参数就是Object(泛型擦除)
		m.invoke(list, "234");//调用方法,插入字符串 "234"
		System.out.println(list);
	}
	/*outPut:
	 * [123, 234]
	 */

 

反射小习二:

使用反射写一个通用的方法来设置某个对象的某个属性为指定的值

 

	public static void setProperty(Object obj, String propertyName,Object value) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		Class clazz = obj.getClass();//获取字节码对象
		Field f = clazz.getDeclaredField(propertyName);//暴力反射获取字段
		f.setAccessible(true);//去除权限
		f.set(obj, value);//设置值
	}

 

反射小习三:

已知有如下该类:

 

package xxx.xxx.test;
public class Demo {
	public void run() {
		System.out.println("go! go! go!");
	}
}

使用读取配置文件(配置类的完整名称)的方式获取其类的完整名称并加载该类,使用反射运行run方法

 

 

    public static void main(String[] args) throws  Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(
                new FileInputStream("config.properties")));
        String name = br.readLine();//读取配置文件
        Class clazz = Class.forName(name);//获取字节码对象
        Demo demo = (Demo) clazz.newInstance();//创建新实例
        Method m = clazz.getMethod("run");//获取run方法
        m.invoke(demo);//调用
    }

注:config.properties 文件在项目根目录下。

 

 

动态代理

动态代理概述:

代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。(春节回家买票让人代买)

动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理

在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象。

 

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

最终会调用InvocationHandler的方法

 

 

InvocationHandler Object invoke(Object proxy,Method method,Object[] args)

 

 

public interface IStudent {
	public void login();
	public void submit();
}
public class Student implements IStudent {
	@Override
	public void login() {
		System.out.println("登录");
	}
	@Override
	public void submit() {
		System.out.println("提交");
	}
}
public 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对象的方法
        System.out.println("日志记录");//方法执行后
        return null;
    }
}
public class Test {
	public static void main(String[] args) {
		Student stu = new Student();//未代理
		stu.login();
		stu.submit();
		System.out.println("-----------");
		MyInvocationHandler m = new MyInvocationHandler(stu);//使用代理
		IStudent istu = (IStudent)Proxy.newProxyInstance(stu.getClass().getClassLoader(), stu.getClass().getInterfaces(), m);
		istu.login();
		istu.submit();
	}
}


看完上面的例子,你一定对动态代理产生了极大的疑惑。其实如果只需会用动态代理,你发现上面的代码其实就是模板一样,很多东西都是定死的,照葫芦画瓢即可。但若想深入了解动态代理,我这里转载了一片关于动态代理的,可以帮助我们解决更多的疑惑,至少我自己看完感觉明了多了(点此传送门

 

 

JDK5新特性

1,自动拆装箱
2,泛型
3,可变参数
4,静态导入
5,增强for循环
6,互斥锁
7,枚举

自己实现枚举类

枚举概述

是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。举例:一周只有7天,一年只有12个月等。

单例类是一个类只有一个实例。那么多例类就是一个类有多个实例,但不是无限个数的实例,而是有限个数的实例。这才能是枚举类。

 

//自定义枚举实现一
class Week_1 {
	public static final Week_1 MON = new Week_1();
	public static final Week_1 TUE = new Week_1();
	public static final Week_1 WED = new Week_1();
	private Week_1() {} //私有构造,不让其他类创建本类对象
}
//自定义枚举实现二
class Week_2 {
	public static final Week_2 MON = new Week_2("星期一");
	public static final Week_2 TUE = new Week_2("星期二");
	public static final Week_2 WED = new Week_2("星期三");
	private String name;
	private Week_2(String name) {//同样还是需要私有构造
		this.name = name;
	}
	public String getName() {
		return name;
	}
}
//自定义枚举实现三
abstract class Week_3 {
	public static final Week_3 MON = new Week_3("星期一") {
		@Override//使用匿名内部类方式创建
		public void show() {
			System.out.println("今天是星期一");
		}
	};
	public static final Week_3 TUE = new Week_3("星期二") {
		@Override
		public void show() {
			System.out.println("今天是星期二");
		}
	};
	public static final Week_3 WED = new Week_3("星期三") {
		@Override
		public void show() {
			System.out.println("今天是星期三");
		}
	};
	private String name;
	private Week_3(String name) {//私有构造
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public abstract void show();//抽象方法
}

测试

 

 

	public static void main(String[] args) throws  Exception {
		Week_1 mon = Week_1.MON;
		System.out.println(mon); //info.test.Week_1@15db9742
		Week_2 tue = Week_2.TUE;
		System.out.println(tue.getName());//星期二
		Week_3 wed = Week_3.WED;
		wed.show();//今天是星期三
	}

 

enum实现枚举类

简单概述:

定义枚举类要用关键字enum,所有枚举类都是Enum的子类

枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略。

枚举类可以有构造器,但必须是private的,它默认的也是private的。

枚举类也可以有抽象方法,但是枚举项必须重写该方法

枚举在switch语句中的使用

 

//枚举实现一
public enum Week_1 {
	MON,TUE,WED;
}
//枚举实现二
public enum Week_2 {
	MON("星期一"),TUE("星期二"),WED("星期三");
	private String name;
	private Week_2(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
}
//枚举实现三
public enum Week_3 {
	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 Week_3(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public abstract void show();
}

测试:

 

 

	public static void main(String[] args) throws  Exception {
		Week_1 mon = Week_1.MON;
		System.out.println(mon); //MON
		Week_2 tue = Week_2.TUE;
		System.out.println(tue.getName());//星期二
		Week_3 wed = Week_3.WED;
		wed.show();//今天是星期三
		switch(tue) {
			case MON :
				System.out.println("星期一啦~");
				break;
			case TUE :
				System.out.println("星期二啦~");
				break;
		}//星期二啦~
	}


上面说了,所有枚举类都是Enum的子类。那么我们来看看该类中有什么常见方法。

 

 

//返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 相当于编号
public final int ordinal()
//比较此枚举与指定对象的顺序
public final int compareTo(E o)
//返回此枚举常量的名称,在其枚举声明中对其进行声明
public final String name()
//返回枚举常量的名称,它包含在声明中。可以重写此方法
public String toString()
//返回带指定名称的指定枚举类型的枚举常量
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
//此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便
values() 方法
	public static void main(String[] args) throws  Exception {
		Week_1 mon = Week_1.MON;
		Week_1 tue = Week_1.TUE;
		System.out.println(mon.ordinal());//枚举项都是有编号的
		System.out.println(tue.ordinal());
		System.out.println("---------");
		System.out.println(mon.compareTo(tue));//比较的是编号
		System.out.println("---------");
		System.out.println(mon.name());//获取实例名称
		System.out.println(mon.toString());//调用toString方法
		System.out.println("---------");
		Week_2 wed = Week_2.valueOf(Week_2.class, "WED");//通过字节码对象获取枚举项
		System.out.println(wed);
		System.out.println("---------");
		Week_3[] arr = Week_3.values();
		for (Week_3 week : arr) {
			System.out.println(week);
		}
	}

结果:

 

 

0
1
---------
-1
---------
MON
MON
---------
WED
---------
MON
TUE
WED

 

 

 

JDK7新特性

A:二进制字面量
B:数字字面量可以出现下划线
C:switch 语句可以用字符串
D:泛型简化,菱形泛型
E:异常的多个catch合并,每个异常用或|
F:try-with-resources 语句

 

	public static void main(String[] args) throws  Exception {
		//二进制字面量
		System.out.println(0b111); //结果为 7
		//数字字面量可以出现下划线,该特性只为方便观察
		System.out.println(100_000);//结果为 1000000
		//剩余特性不在罗列,之前的学习都碰到过
	}

 

 

 

JDK8新特性

1.接口中可以定义有方法体的方法,如果是非静态,必须用default修饰。如果是静态的就不用了

 

interface test {
	public default void run() {
		System.out.println("gogogo!");
	}
	public static void show() {
		System.out.println("show!");
	}
}

2.局部内部类在访问他所在方法中的局部变量必须用final修饰,而JDK8中可不使用final(其默认是final)

 

 

	public void run() {
		int x = 10;//编译未报错
		class Inner {
			public void method() {
				System.out.println(x);
			}
		}
		Inner inner = new Inner();
		inner.method();
	}

为啥JDK7以及以前版本需要final修饰呢?

 

因为当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,就没有了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也可以继续使用。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值