Java学习笔记——反射机制

反射机制简介

Java语言之所以会有如此众多的开源技术支撑,很大一部分是来自于Java最大的特征——反射机制。
所有的技术实现的目标只有一点:重用性。
对于反射技术首先考虑的是“反”与“正”的操作,所谓的“正”操作指的是当我们要使用一个类的时候,一定要导入程序所在的包,而后根据类进行对象的时候,并且依靠对象调用类中的方法。但是如果“反”,有对象根据实例化对象反推出其类型。

要想实现反的处理操作,首先要采用Object类中提供的操作方法:

  • 获取Class对象信息:public final Class<?>getClass();
    getClass()方法可以帮助对象找到类的根源

Class类对象的3种实例化模式

反射之中所有的核心操作都是通过Class类对象展开的, 可以说Class类是反射操作的根源所在,但是这个类如果要想获取他的实例化对象,可以采用三种方式完成。首先观察java.lang.Class类的定义:
public final class Class<T> extends Object implements Serializable,GenericDeclaration,Type,AnnotatedElement
三种实例化形式
1、【Object类支持】Object类可以通过实例化对象获取Class对象:public final Class<?>getClass()

  • 这种方法有种不是缺点的缺点:如果现在只是想获得Class类对象,则必须产生指定对象后才可以获得。
package demo;

class Person {}// 采用自定义的程序类

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Person per = new Person();// 已经存在有指定的实例化对象
		Class<? extends Person> cls = per.getClass();
		System.out.println(cls);
		System.out.println(cls.getName());//获取的是类的完整名称
	}
}
#class demo.Person
#demo.Person

2、【JVM直接支持】采用“类.class”的形式实例化

  • 特带你:如果要采用此种模式,则必须导入程序用的开发包;
package demo;

class Person {}// 采用自定义的程序类

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Class<? extends Person> cls = Person.class;
		System.out.println(cls.getName());//获取的是类的完整名称
	}
}

3、【Class类支持】在Class类里面提供有static 方法:

  • 加载类public static<?> forName(String className) throws ClassNotFoundException
package demo;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("demo.vo.Student");
		System.out.println(cls.getName());//获取的是类的完整名称
	}
}

这种模式最大的特点是可以直接采用字符串的形式定义要使用的类型,并且程序中不需要编写任何的import语句。如果此时要使用的程序不存在则会抛出“java.lang.ClassNotFoundException”异常。

反射实例化对象

获取Class对象之后最大的意义实际上并不是在于只是一个对象的实例化操作形式,更重要的是Class类里面提供有一个对象的反射实例化方法(代替了关键字new):

  • JDK1.9以前的实例化:public T newInnstance() throws InstantiationException, IllegalAccessException
  • jdk1.9之后:class.getDeclaredConstructor().newInstance();

【范例】通过newInstance()方法实例化Person类对象。
1、创建Person类

package demo.vo;

public class Person {
	//任何情况下如果要实例化类对象一定要调用类中的构造方法
	public Person() {
		System.out.println("******Person类构造方法*******");
	}
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "我是一个什么样的人";
	}
}

2、实例化对象

package demo;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("demo.vo.Person");
		Object obj = cls.newInstance();//实例化对象jdk1.9之后被废除了
		System.out.println(obj);
	}
}
******Person类构造方法*******
我是一个什么样的人

现在通过反射实现的对象实例化处理,依然要调用类中的无参构造方法,其本质等价于“类 对象=new 类”,相当于隐含了关键字new,而直接使用字符串进行了替代。

【范例】从JDK1.9之后newInstance()方法被替代了

package demo;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		Class<?> cls = Class.forName("demo.vo.Person");
		Object obj = cls.getDeclaredConstructor().newInstance();
		System.out.println(obj);
	}
}

因为默认的Class类中的newInstance()方法只能够调用无参构造,所以很多开发者会认为其描述的不准确,于是将其变换了形式。

反射与工厂设计模式

如果要想进行对象的实例化处理除了可以使用关键字new之外,还可以使用反射机制来完成。思考问题:为什么要提供有一个反射弧实例?那么到底是使用关键字new还是反射呢?
如果要想更好理解此类问题,最好结实方案就是通过工厂设计模式来解决。工厂设计模式的最大特点:客户端的程序类不直接牵扯到对象的实例化管理,只与接口发生关联,通过工厂类获取指定接口类实例化对象:
【范例】传统工厂设计类模式

package demo;
interface IMessage{
	public void send();//消息发送
}
class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送........");
	}
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		IMessage msg = new NetMessage();//如果直接实例化,则直接会有耦合问题
	}
}

在实际开发之中,接口的主要作用是为不同的层提供有一个操作的标准。但如果此时直接将一个子类设置为接口实例化操作,那么一定会有耦合问题,所以使用了工厂设计模式来解决此问题

【范例】利用工厂模式来解决此问题

package demo;
interface IMessage{
	public void send();//消息发送
}
class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送........");
	}
}
class Factory{
	private Factory() {}//没有产生实例化对象的意义,所以构造方法私有化
	public static IMessage getInstance(String className) {
		if("netmessage".equalsIgnoreCase(className)) {
			return new NetMessage();	
		}
		return null;
	}
	
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		IMessage msg = Factory.getInstance("netmessage");
		msg.send();
	}
}

此种工厂设计模式属于静态工厂设计模式,也就是说颗现在要追加一个子类,则意味着工程类一定要做出修改,如果不追加这种判断是无法获取指定接口对象的。
【范例】为IMessage追加一个子类

package demo;
interface IMessage{
	public void send();//消息发送
}
class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送........");
	}
}
class CloutMessage implements IMessage{
	@Override
	public void send() {
		System.out.println("【云消息】。。。。。。。");
	}
}
class Factory{
	private Factory() {}//没有产生实例化对象的意义,所以构造方法私有化
	public static IMessage getInstance(String className) {
		if("netmessage".equalsIgnoreCase(className)) {
			return new NetMessage();	
		}else if("cloudmessage".equalsIgnoreCase(className)) {
			return new CloutMessage();
		}
		return null;
	}
	
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		IMessage msg = Factory.getInstance("cloudmessage");
		msg.send();
	}
}

工厂设计模式最有效的解决的是子类与客户端的耦合问题,但是解决的核心思想是在于提供有一个工程类作为过渡段,可是随着项目的进行,你的IMessage接口可能会有更多的子类,随着时间的推移子类产生的可能越来越多,那么此时意味着,你的工程类永远就要进行修改,并且永无停止之日。

那么这个时候最好的解决方案就是不使用关键字new来完成,因为关键字new在使用的时候需要有一个明确的类存在。而newInstance()方法只需要有一个明确表示类名称的字符串即可应用。

package demo;

import java.lang.reflect.InvocationTargetException;

interface IMessage{
	public void send();//消息发送
}
class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送........");
	}
}
class CloutMessage implements IMessage{
	@Override
	public void send() {
		System.out.println("【云消息】。。。。。。。");
	}
}
class Factory{
	private Factory() {}//没有产生实例化对象的意义,所以构造方法私有化
	public static IMessage getInstance(String className) {
		IMessage instance = null;
		try {
			instance = (IMessage) Class.forName(className).getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return instance;
	}
	
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		IMessage msg = Factory.getInstance("demo.NetMessage");
		msg.send();
	}
}

这个时候可以发现,利用反射机制实现的工厂设计模式,最大的优势在于,对于接口子类的扩充将不再影响到工厂类的定义。

在实际的项目开发过程之中有可能会存在有大量的接口,并且这些接口都可能需要通过工程类实例化,所以此时的工厂设计模式不应该只为一个IMessage接口服务,而应该变为为所有的接口服务。

package demo;

interface IService{
	public void service();
}
class HouseService implements IService{
	@Override
	public void service() {
		System.out.println("【服务】提供住宿服务。");
	}
}
interface IMessage{
	public void send();//消息发送
}
class NetMessage implements IMessage{
	public void send() {
		System.out.println("网络消息发送........");
	}
}
class CloutMessage implements IMessage{
	@Override
	public void send() {
		System.out.println("【云消息】。。。。。。。");
	}
}
class Factory{
	private Factory() {}//没有产生实例化对象的意义,所以构造方法私有化
	/**
	 * 获取接口实例化对象
	 * @param <T>
	 * @param className 接口的子类
	 * @param clazz 描述的是一个接口的类型
	 * @return	如果子类存在则返回指定接口实例化对象
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getInstance(String className,Class<T> clazz) {
		T instance = null;
		try {
			instance = (T) Class.forName(className).getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return instance;
	}
	
}
public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		IMessage msg = Factory.getInstance("demo.NetMessage",IMessage.class);
		msg.send();
		IService service = Factory.getInstance("demo.HouseService", IService.class);
		service.service();
	}
}

此时的工厂设计模式将不再受限于指定的接口,将为所有的接口实现实例化,达到了高的可重用性。

反射与单例设计模式

单例设计模式的本质在于:类内部的构造方法私有化,在类的内部产生实例化对象之后通过static方法获取实例化对象进行类中的结果调用,单例设计模式一共有两种设计模式:懒汉式、饿汉式,饿汉式的单例不在本次讨论范围之类,主要讨论懒汉式的单例设计模式。
【范例】懒汉式单例设计模式问题

package demo;

public class JavaAPIDemo {
	public static void main(String[] args) throws Exception {
		for(int x = 0 ; x<3; x++) {
			new Thread(()->{
				Singleton.getInstance().print();
			},"单例消费端-" + x).start();
		}
	}
}
class Singleton{
	private static Singleton instance = null;
	private Singleton() {
		System.out.println("【"+Thread.currentThread().getName()+"】"+"********实例化Singleton类对象************");
	}
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
	public void print() {
		System.out.println("Singleton 打印。。。。");
	}
}

【单例消费端-0】********实例化Singleton类对象************
【单例消费端-2】********实例化Singleton类对象************
Singleton 打印。。。。
【单例消费端-1】********实例化Singleton类对象************
Singleton 打印。。。。
Singleton 打印。。。。

单例设计模式的最大特点是在整体的运行过程之中只允许产生一个实例化对象,这个时候会发现当有了若干个线程之后实际上当前程序就可以产生多个实例化对象了,此时就不是单例设计模式了。 此时问题造成的关键在于代码出现不同步的情况,解决问题的关键核心就在于需要进行同步处理,同步自然就想到synchronized关键字。
在这里插入图片描述
【范例】修改getInstance()方法进行同步处理

	public static synchronized Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}

这个时候进行了同步处理,但是这个同步代价有点大,因为效率会低。因为整体代码里面只有一块代码需要进行同步处理,instance对象的实例化处理部分,那么在这样的情况下会发现同步加的有些草率了。
【范例】】更加合理的同步处理。

class Singleton{
	private static volatile Singleton instance = null;
	private Singleton() {
		System.out.println("【"+Thread.currentThread().getName()+"】"+"********实例化Singleton类对象************");
	}
	public static Singleton getInstance() {
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
	public void print() {
		System.out.println("Singleton 打印。。。。");
	}
}

面试题:请编写单例设计模式

  • 编写一个饿汉式的单例设计模式,并且实现构造方法私有化;
  • 在Java中哪里使用到了单例设计模式?Runtime类、Pattern、Spring框架;
  • 懒汉式单例设计模式的问题?

参考资料:https://edu.aliyun.com/lesson_1012_9047#_9047

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文件上传是Web开发中常见的功能之一,Java中也提供了多种方式来实现文件上传。其中,一种常用的方式是通过Apache的commons-fileupload组件来实现文件上传。 以下是实现文件上传的步骤: 1.在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> ``` 2.在前端页面中添加文件上传表单: ```html <form method="post" enctype="multipart/form-data" action="upload"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> ``` 3.在后台Java代码中处理上传文件: ```java // 创建一个DiskFileItemFactory对象,用于解析上传的文件 DiskFileItemFactory factory = new DiskFileItemFactory(); // 设置缓冲区大小,如果上传的文件大于缓冲区大小,则先将文件保存到临时文件中,再进行处理 factory.setSizeThreshold(1024 * 1024); // 创建一个ServletFileUpload对象,用于解析上传的文件 ServletFileUpload upload = new ServletFileUpload(factory); // 设置上传文件的大小限制,这里设置为10MB upload.setFileSizeMax(10 * 1024 * 1024); // 解析上传的文件,得到一个FileItem的List集合 List<FileItem> items = upload.parseRequest(request); // 遍历FileItem的List集合,处理上传的文件 for (FileItem item : items) { // 判断当前FileItem是否为上传的文件 if (!item.isFormField()) { // 获取上传文件的文件名 String fileName = item.getName(); // 创建一个File对象,用于保存上传的文件 File file = new File("D:/uploads/" + fileName); // 将上传的文件保存到指定的目录中 item.write(file); } } ``` 以上代码中,首先创建了一个DiskFileItemFactory对象,用于解析上传的文件。然后设置了缓冲区大小和上传文件的大小限制。接着创建一个ServletFileUpload对象,用于解析上传的文件。最后遍历FileItem的List集合,判断当前FileItem是否为上传的文件,如果是,则获取文件名,创建一个File对象,将上传的文件保存到指定的目录中。 4.文件上传完成后,可以给用户一个提示信息,例如: ```java response.getWriter().write("File uploaded successfully!"); ``` 以上就是使用Apache的commons-fileupload组件实现文件上传的步骤。需要注意的是,文件上传可能会带来安全隐患,因此在处理上传的文件时,需要进行严格的校验和过滤。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值