首页 > 编程语言 > Day16基础不牢地动山摇-Java基础
2022
05-20

Day16基础不牢地动山摇-Java基础

1、反射机制

反射机制如果只是针对普通开发者而言意义不大,一般都是作为一些系统的构架设计去使用的,包括以后学习的开源框架,那么几乎都是反射机制。

1.1 认识反射

反射指的是对象的反向处理操作,就首先观察以下“正”的操作,在默认情况下,必须先导入一个包才能产生类的实例化对象。

所谓的“反”根据对象来取得对象的来源信息,“反”的操作的本来来源就是Object的一个方法。

  • 取得class对象:public final 类<?> getClass() 该方法返回的一个Class类的对象,这个Class描述的就是类。
package com.day16.demo;

import java.util.Date;

import javafx.scene.chart.PieChart.Data;

public class FanShedemo {
	public static void main(String[] args) {
		Date date = new Date();
        //java.util.Date
		System.out.println(date.getClass().getName());
		
	}
}

此时通过对象的确对象的来源,就是“反”的本质。在反射的背后不在是一个对象,而是对象身后的来源。

而这个getClass()方法返回的对象是Class类对象,所以这个Class就是所有反射操作的源头,但是在讲解其真正使用之前还有一个需要先解释的问题,既然Class是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,java中定义了三种方式:

方式一:通过Object类的getClass()方法取得

package com.day16.demo;

import java.util.Date;

public class FanShedemo {
	public static void main(String[] args) {
		Date de = new Date();//正着操作
		Class<?> cla =de.getClass();//取得class对象
		System.out.println(cla.getName());//反着来		
	}
}

方式二:通过“类.Class”取得

package com.day16.demo;

import java.util.Date;


public class FanShedemo {
	public static void main(String[] args) {
		Date de = new Date();//正着操作
		Class<?> cla =Date.class;//取得class对象
		System.out.println(cla.getName());//反着来		
	}
}

方式三:使用Class内部定义的一个static方法

package com.day16.demo;

import java.util.Date;


public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Date de = new Date();//正着操作
		Class<?> cla =Class.forName("java.util.Date");//取得class对象
		System.out.println(cla.getName());//反着来		
	}
}

在以上给出的三个方法会发现一个神奇的地方,除了第一种形式会产生Date实例化对象,而第二种和第三种没有

实例化取得对象。于是取得Class类对象有一个最直接的好处:可以直接通过反射实例化对象,在Class类中有一个方法:

  • **通过反射实例化对象:**public T newInstance() throws InstantiationException IllegalAccessException

反射实例化对象

package com.day16.demo;

import java.util.Date;


public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Class<?> cla =Class.forName("java.util.Date");//取得class对象
		Object o = cla.newInstance();//取得Date对象
		System.out.println(o);
	}
}

image-20210821155437468

现在可以发现,对于对象的实例化操作,除了使用关键字new之外又多了一个反射机制操作,而且这个操作要比之前使用的new复杂一些,可是有什么用呢?

对于程序的开发模式之前一直强点:尽量减少耦合,而减少耦合的最好的做法是使用接口,但是就算使用了接口也逃不出关键字new,多以实际上new是耦合的关键元凶。

1.2 取得父类信息

反射可以做出一个对象所具备的所有操作行为,而且最关键的是这一切的操作都可以基于Object类型进行。

在Java里面任何的程序类实际上都一定会有一个父类,在Class类里面就可以通过此类方式来取得父类或者是实现的父接口,有如下两个方法提供:

public 软件包 getPackage() 取得类的包名称
public 类<? super T> getSuperclass() 取得父类的Class对象
public 类<?>[] getInterfaces() 取得父接口

取得类的相关信息

package com.day16.demo;

import java.util.Arrays;
interface IFruit{}
interface IMessage{}
class Person implements IFruit,IMessage{
	 
}
public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = Person.class;
		System.out.println(cls.getPackage().getName());
		System.out.println(cls.getSuperclass().getName());
		Class<?> itf[] = cls.getInterfaces();
		System.out.println(Arrays.toString(itf));
	}
}

通过我们反射可以取得类结构上的所有关键信息。

1.3 反射调用构造

一个类可以存在多个构造方法,如果我们要想取得类中构造的调用,我们就可以使用Class类提供的两个方法

public Constructor getConstructor(类<?>… parameterTypes)
throws NoSuchMethodException,
SecurityException
取得指定参数类型的构造方法
public Constructor<?>[] getConstructors()
throws SecurityException
取得类中的所有构造

以上两个方法的返回类型都是java.lang.reflect.Constructor类的实例化对象,这个类里面关注一个方法,实例化对象:public T newInstance(Object… initargs) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException

取得类中所有构造方法的信息—利用Constructor类中的toString()方法取得了构造方法的完整信息

package com.day16.demo;

import java.lang.reflect.Constructor;
import java.util.Arrays;

class Person {
	 public Person(){}
	 public Person(String name){}
	 public Person(String name , int age){}
}
public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = Person.class;
		Constructor<?> cst [] = cls.getConstructors();
		for (int i = 0; i < cst.length; i++) {
			System.out.println(cst[i]);
		}
	}
}

如果使用getName()方法就比较麻烦

自己拼凑构造方法操作

package com.day16.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;

class Person {
	 public Person() throws Exception,RuntimeException{}
	 public Person (String name) throws Exception,RuntimeException{}
	 public Person(String name , int age) throws Exception,RuntimeException{}
}
public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = Person.class;
		Constructor<?> cst [] = cls.getConstructors();
		for (int i = 0; i < cst.length; i++) {
			System.out.print(Modifier.toString(cst[i].getModifiers()) + " ");
			System.out.print(cst[i].getName() + "(");
			Class <?> params [] = cst[i].getParameterTypes();
			for (int j = 0; j < params.length; j++) {
				System.out.print(params[j].getName());
				if(j < params.length - 1){
					System.out.print(",");
				}
			}
			System.out.print(")");
			Class<?> exps [] = cst[i].getExceptionTypes();
			if(exps.length > 0){
				System.out.print(" throws ");
				for (int j = 0; j < exps.length; j++) {
					System.out.print(exps[j].getName());
					if(j < exps.length - 1){
						System.out.print(",");
					}
				}
			}
			System.out.println();
		}
	}
}

学习Constructor类目的并不是分析方法的组成,最需要的关注就是问题的结论:在定义简单的java类一定要保留一个无参构造。

观察没有无参构造的方法

package com.day16.demo;

import java.lang.reflect.Constructor;
class Per{
	private String name;
	private int age;
	public Per(String name,int age){
		this.name=name;
		this.age=age;
	}
	@Override
	public String toString() {
		return "Per [name=" + name + ", age=" + age + "]";
	}
}
public class FanShedemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = Per.class;//取得class对象
		Object obj=cls.newInstance();
	}
}
Exception in thread "main" java.lang.InstantiationException: com.day16.demo.Person
	at java.lang.Class.newInstance(Class.java:427)
	at com.day16.demo.FanShedemo.main(FanShedemo.java:19)
Caused by: java.lang.NoSuchMethodException: com.day16.demo.Person.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more

此时运行的时候出现了错误提示“java.lang.InstancetiationException”因为以上的方式使用反射实例化对象时需要的是类之中提供无参构造方法,但是现在既然没有了无参构造方法,那么就必须明确的找到一个构造方法。

通过Constructor类实例化对象

package com.day16.demo;

import java.lang.reflect.Constructor;
class Per{
	private String name;
	private int age;
	public Per(String name,int age){
		this.name=name;
		this.age=age;
	}
	@Override
	public String toString() {
		return "Per [name=" + name + ", age=" + age + "]";
	}
}
public class FanShedemo2 {
	public static void main(String[] args) throws Exception{
		Class<?> cls = Per.class;//取得class对象
        //现在明确表示取得指定参数类型的构造方法对象
		Constructor<?> cont = cls.getConstructor(String.class,int.class);
		System.out.println(cont.newInstance("张三",19));
		
	}
}

一行写简单Java类要写无参构造,以上内容就只需要了解就可以了。

1.4 反射调用方法

当取得了一个类之中的实例化对象之后,下面最需要调用的肯定是类之中的方法,所以可以继续使用Class类取得一个类中所定义的方法定义:

**public Method[] getMethods()
throws SecurityException
取得全部方法
public Method getMethod(String name,Class<?>… parameterTypes)
throws NoSuchMethodException, SecurityException
取得指定方法

发现以上的方法返回的都是java.lang.Method类的对象。

取得一个类之中全部定义的方法

package com.day16.demo;

import java.lang.reflect.Method;

class Student{
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}
public class FanShedemo3 {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("com.day16.demo.Student");
		Method met [] = cls.getMethods();
		for (int i = 0; i < met.length; i++) {
			System.out.println(met[i]);
		}
	}
}

但是取得类Method类对象最大的作用不在于方法的列出(方法的列出都在开发工具上使用了),但是对于取得了Method类对象之后还有一个最大的功能,就是可以利用反射调用类的方法:
调用方法:public Object invoke(Object obj,Object… args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException
之前调用类中的方法的时候使用的都是“对象.方法”,但是现在有了反射之后,可以直接利用Object类调用指定子类的操作方法。(同事解释一下,为什么setter,和getter方法的命名要求如此严格)。

利用反射调用Student类之中的setName(),getName()方法。

package com.day16.demo;

import java.lang.reflect.Method;

class Student{
   private String name;

   public String getName() {
   	return name;
   }

   public void setName(String name) {
   	this.name = name;
   }
}
public class FanShedemo3 {
   public static void main(String[] args) throws Exception {
   	Class<?> cls = Class.forName("com.day16.demo.Student");
   	Object obj = cls.newInstance();//实例化对象
   	Method met [] = cls.getMethods();
   	Method setM=cls.getMethod("setName",String.class);//通过反射构建方法
   	Method getM=cls.getMethod("getName");
   	setM.invoke(obj, "小张同学");//键值对的形式传递参数此过程相当于setM
   	Object result = getM.invoke(obj);
   	System.out.println(result);		
   }
}

在日后所有的技术开发中,简单Java类都是如此应用,多以必须按照标准进行。

1.5 反射调用成员

个组成部分就是成员(Field,也可以称为属性),如果要通过反射取得类的成员可以使用方法如下:
取得本类的全部成员:public Field[] getDeclaredFields() throws SecurityException
取得指定成员:public Field getDeclaredField(String name)throws NoSuchFieldException, SecurityException

取得本类全部成员

package com.day16.demo;

import java.lang.reflect.Field;

class Person4{
	private String name;
}
public class FanShedemo4 {
	public static void main(String[] args) throws Exception{
		Class<?> cls =  Class.forName("com.day16.demo.Person4");
		Object obj = cls.newInstance();
		Field[] fields = cls.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			//private java.lang.String com.day16.demo.Person4.name
			System.out.println(fields[i]);
		}
	}
}

但是找到了Field实际上就找到了一个很有意思的操作,在Field类之中提供了两个方法:

设置属性内容(类似于:对象.属性=内容):public void set(Object obj,Object value) throws IllegalArgumentException,IllegalAccessException
取得属性内容(类似于:对象.属性):public Object get(Object obj) throws IllegalArgumentException,IllegalAccessException

可是从类的开发要求而言,一直都强调类之中的属性必须封装,所以现在调用之前要想办法解除封装。

  • 解除封装(重点):public void setAccessible(boolean flag)throws SecurityException

利用反射操作类中的属性

package com.day16.demo;

import java.lang.reflect.Field;

class Person4{
	private String name;
}
public class FanShedemo4 {
	public static void main(String[] args) throws Exception{
		Class<?> cls =  Class.forName("com.day16.demo.Person4");
		Object obj = cls.newInstance();
		Field field = cls.getDeclaredField("name");//得到Person4成员变量
		//由于Person4设置的属性是私有属性,所以是无法操作的
		field.setAccessible(true);///解决封装
		field.set(obj, "小张");
		System.out.println(field.get(obj));
	}
}

虽然反射机制运行直接操作类之中的属性,可是不会有任何一种程序直接操作属性,都会通过setter,getter方法。

image-20210821202523981

1.6 反射与简单Java类—单级VO操作原理

如果现在又一个简单Java类,那么这个简单Java类中的属性按照原始的做法一定要通过setter才可以设置,取得肯定继续使用getter(不关注此处)。

Emp.java

package com.day16.vo;

public class Emp {
	private String ename;
	private String job;
	public String getEname() {
		return ename;
	}
	public void setEname(String ename) {
		this.ename = ename;
	}
	public String getJob() {
		return job;
	}
	public void setJob(String job) {
		this.job = job;
	}
	@Override
	public String toString() {
		return "Emp [ename=" + ename + ", job=" + job + "]";
	}
}

EmpAction.java

package com.day16.action;

import com.day16.vo.Emp;

public class EmpAction {
	private Emp emp = new Emp();
	public void setValue(String val ){//设置属性内容
		this.emp.setEname("SMI");
		this.emp.setJob("STRACK");
	}
	public Emp getEmp(){
		return emp;
	}
}

EmpDemo.java

package com.day16.demo;

import com.day16.action.EmpAction;

public class EmpDemo {
	public static void main(String[] args) {
		String value="emp.ename:smi|emp.job:strack";
		EmpAction action = new EmpAction();
		action.setValue(value);
		System.out.println(action.getEmp());
	}
}

image-20210821211538647

1.7 单极自动VO设置实现

现在所有操作都是通过TestDemo类调用EmpAciton类实现的,而EmpAciton类的主要作用在于定位要操作属性的类型。同时该程序应该符合所有的简单Java类开发形式,也就意味着我们的设计必须有一个单独的类来实现。

由于Bean的处理操作肯定需要重复出去对象信息,所以我们还需要准备两个程序类:StringUtils,负责字符串的操作,毕竟属性的首字母需要大写处理,而后在写一个对象的具体操作(取得对象、设置对象内容)。

工具类—BeanOperation.java

package com.day16.util;

/**
 * @author 张晟睿
 *	本类主要负责实现自动VO匹配处理操作,本身不需要通过实例化对象完成,所以构造方法私有化
 */

public class BeanOperation {
	private BeanOperation(){}
	
	/**
	 * @param actionObject	表示当前发出设置请求的程序类的当前对象
	 * @param msg	所有属性的具体内容,格式“属性名称:内容|属性名称:内容”
	 * 
	 */
	public static void setBeanValue(Object actionObject,String msg) throws Exception{
		String result [] = msg.split("\\|");
		for (int i = 0; i < result.length; i++) {
			//需要针对于给定的属性名称和内容进行一次拆分
			String temp[] = result[i].split(":");		
			String attribute = temp[0];//属性名称,包括“XxxAction属性和具体的简单AJava类的属性”
			String value = temp[1];//接收具体的内容属性
			String fields [] = attribute.split("\\.");
			Object currentObject = ObjectUtils.getObject(actionObject,fields[0]);
			ObjectUtils.setObjectValue(currentObject, fields[1], value);
		}
	}
}

工具类—StringUtils.java

package com.day16.util;

/**
 * @author 张晟睿
 *	针对于字符串进行处理操作
 */
public class StringUtils {
	private StringUtils(){}
	/**
	 * @param str
	 * @return	返回首字母大写
	 * 首字母大写
	 */
	public static String initcap(String str){
		return str.substring(0,1).toUpperCase() + str.substring(1);
	}
}

工具类—ObjectUtils.java

package com.day16.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author 张晟睿
 *	本类的主要功能是根据属性名称调用响应类中getter、setter方法
 */
public class ObjectUtils {
	private ObjectUtils(){}
	/**
	 * 根据指定的类对象,设置类中的属性
	 * @param wrapo 属性所在类的实例化对象
	 * @param attribute	属性名称
	 * @param value	属性内容
	 */
	public static void setObjectValue(Object wrapo,String attribute, String value) throws Exception{
		
		//调用指定属性的Field对象,母的是取得对象类型,如果没有属性也就是说该操作无法继续
		Field field = wrapo.getClass().getDeclaredField(attribute);//判断属性是否存在
		if(field == null){
			field = wrapo.getClass().getField(attribute);
		}
		if(field == null){//两次操作都无法取得对应的成员变量
			return ;//该属性一定不存在
		}
		String methodName = "set" + StringUtils.initcap(attribute);
		Method method = wrapo.getClass().getMethod(methodName,field.getType());
		method.invoke(wrapo, value);
	}
	/**
	 * 负责调用指定类中getter方法
	 * @param wrapo 表示要调用方法的所在对象
	 * @param attribute	表示属性名称
	 * @return	调用对象的结果
	 */
	public static Object getObject(Object wrapo,String attribute) throws Exception{
		String methodName = "get" + StringUtils.initcap(attribute);//定义getter方法
		//调用指定属性的Field对象,母的是取得对象类型,如果没有属性也就是说该操作无法继续
		Field field = wrapo.getClass().getDeclaredField(attribute);
		if(field == null){
			field = wrapo.getClass().getField(attribute);
		}
		if(field == null){//两次操作都无法取得对应的成员变量
			return null;//该属性一定不存在
		}
		Method method = wrapo.getClass().getMethod(methodName);
		return method.invoke(wrapo);
	}
}

EmpAction.java

package com.day16.action;

import com.day16.util.BeanOperation;
import com.day16.vo.Emp;

public class EmpAction {
	private Emp emp = new Emp();
	public void setValue(String val ){//设置属性内容
		//之所以传递this,主要将EmpAction的类对象传递方法里面
		//因为给定的标记:emp.ename:smith,而emp应该对象的是getEmp()方法
		try {
			BeanOperation.setBeanValue(this, val);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public Emp getEmp(){
		return emp;
	}
}

image-20210822083531155

1.8 反射与简单Java类—多级VO设置实现

image-20210822084050435

现在假设一个雇员属于一个部门,一个部门属于一个公司,一个公司属于一个城市,一个城市属于一个省份,一个省份属于一个国家,这种类似关系都可以通过字符串实现多级配置。

修改Dept.java类

public class Dept {
	private String dname;
	private String loc;
	private Company company = new Company();
}

修改Emp.java类

public class Emp {
	private String ename;
	private String job;
	private Dept dept = new Dept();
}

此时所有的引用关系上都自动进行了对象实例化。而现在程序希望可以满足于单级和多级。

image-20210822122022038

修改BeanOperation.java

package com.day16.util;

/**
 * @author 张晟睿
 *	本类主要负责实现自动VO匹配处理操作,本身不需要通过实例化对象完成,所以构造方法私有化
 */

public class BeanOperation {
	private BeanOperation(){}
	
	/**
	 * @param actionObject	表示当前发出设置请求的程序类的当前对象
	 * @param msg	所有属性的具体内容,格式“属性名称:内容|属性名称:内容”
	 * 
	 */
	public static void setBeanValue(Object actionObject,String msg) throws Exception{
		String result [] = msg.split("\\|");
		for (int i = 0; i < result.length; i++) {
			//需要针对于给定的属性名称和内容进行一次拆分
			String temp[] = result[i].split(":");		
			String attribute = temp[0];//属性名称,包括“XxxAction属性和具体的简单AJava类的属性”
			String value = temp[1];//接收具体的内容属性
			String fields [] = attribute.split("\\.");//拆分出属性信息
			if(fields.length > 2){//多级配置
				//如果要想多级确定出属性的操作对象,那么应该一层找出每一个getter方法返回的内容
				Object currentObject = actionObject;//确定当前要操作的对象
				for (int j = 0; j < fields.length - 1; j++) {//对应getter返回对象
					currentObject = ObjectUtils.getObject(currentObject, fields[j]);
				}
				ObjectUtils.setObjectValue(currentObject, fields[fields.length - 1], value);
			}else{//单级配置
				Object currentObject = ObjectUtils.getObject(actionObject,fields[0]);
			ObjectUtils.setObjectValue(currentObject, fields[1], value);
			}
			
		}
	}
}

定义TestEmpDemo.java

package com.day16.demo;

import com.day16.action.EmpAction;

public class TestEmpDemo {
	public static void main(String[] args) {
		String value="emp.ename:smi|emp.job:strack|emp.dept.dname:财务部|emp.dept.company.name:zsr|emp.dept.company.address:北京";
		EmpAction action = new EmpAction();
		action.setValue(value);
		System.out.println(action.getEmp());
	}
}

这样的程序才可以正常使用,属于无限级配置。

2、ClassLoader类加载器

Class类描述的是类的整个信息,在Class类中提供的forName()方法它所能处理的只是通过CLASSPATH配置的路径进行加载,而我们的类加载的路径可能是网络、文件、数据库。这是ClassLoader类主要作用。

2.1 认识类加载器

首先Class观察一个方法:public ClassLoader getClassLoader();

编写简单的反射程序,观察ClassLoader的存在

package com.day17.demo;
class Member{//自定义类一定在CLASSPATH之中
	
}
public class TestDemo1 {
	public static void main(String[] args) {
		Class<?> cls = Member.class;
		System.out.println(cls.getClassLoader());
		System.out.println(cls.getClassLoader().getParent());
		System.out.println(cls.getClassLoader().getParent().getParent());
	}
}

/*
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
*/

出现两个加载器AppClassLoader(应用程序类加载器)、ExtClassLoader(扩展类加载器)。

image-20210822132640087

对于第三方程序类库除了CLASSPATH之外,实际上在java里面还有一个加载目录:C:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext

image-20210822133615839

观察类我们发现ClassLoader里有一个方法:public 类<?> loadClass(String name) throws ClassNotFoundException,进行类的加载操作处理。

2.2 自定义ClassLoader

**实现文件的类加载器 **

package com.day17.demo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

class MyClassLoader extends ClassLoader{
	/**
	 * 实现一个自定义的类加载器,传入类名称后,通过指定文件路径加载
	 * @param className
	 * @return
	 * @throws Exception
	 */
	public Class<?> loadData(String className) throws Exception{
		byte classDate [] = this.loadClassData();
		return super.defineClass(className, classDate, 0, classDate.length);
		
	}
	/**
	 * 通过指定文件路径进行类的文件加载,进行二进制读取
	 * @return
	 * @throws Exception
	 */
	private byte [] loadClassData() throws Exception{
		InputStream input = new FileInputStream("f:" + File.separator + "java" + File.separator + "Member.class");
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte [] data = new byte [20];//定义读取的缓冲区
		int temp = 0;
		while((temp = input.read(data)) != -1){
			bos.write(data,0,temp);
		}
		byte ret [] = bos.toByteArray();
		input.close();
		bos.close();
		return ret;
	}
}
public class ClassLoaderDemo {
	public static void main(String[] args) throws Exception{
		Class<?> cls = new MyClassLoader().loadData("com.day17.test.Member");
		System.out.println(cls.newInstance());
	}
}

类加载器给我们用户最大的帮助就是在于可以通过动态的路径实现类的加载处理操作。

到此这篇关于Day16基础不牢地动山摇-Java基础的文章就介绍到这了,更多相关Java基础内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧