一、引言
反射和设计模式是Java编程中非常重要的两个概念,反射能够让我们在运行时动态地获取类的信息以及操作类的对象,而设计模式则是一种解决特定问题的经典解决方案。了解和掌握这两个概念对于我们成为一名优秀的Java开发者非常重要。
在本系列教程中,我们将逐步学习如何使用Java反射来获取类的信息、操作类的对象、动态地创建对象等,并且我们将探讨设计模式的不同类型以及如何应用它们来解决各种问题。
二、反射
是一种底层技术,通常用于底层框架的编写
1.类对象
- 类的对象: 是类实例化的结果,可以存在多个
- 类对象: 是类加载的产物, 通常只有一个, 内部存放类的所有信息(包,属性,方法,构造,父类信息,接口信息等)
获取类对象-Class
-
类名.class
Class<类名> 对象名=类名.class
-
对象名.getClass()
Class 对象名=对象名.getClass();
-
Class.forName
Class 对象名=Class.forName("类的全限定名");
public static void main(String[] args) throws Exception{
Class<Student> c1 = Student.class;
Student stu = new Student();
Class c2 = stu.getClass();
Class c3 = Class.forName("com.by.entity.Student");
}
常用方法
- newInstance(): 利用类对象中的无参构造构建一个类的实例对象
- Constructor<类名> getDeclaredConstructor(参数列表的类对象1,参数列表的类对象2,…): 获取指定参数列表的有参构造器对象
- newInstance(实参列表): 利用指定有参构造器对象构建类的实例对象
public static void main(String[] args) throws Exception{
Class<Student> c1 = Student.class;
Student stu1 = c1.newInstance();
Class c3 = Class.forName("com.by.entity.Student");
//获取的对像默认为Object类型,需要进行类型强转
Student stu2 = (Student) c3.newInstance();
stu1.setName("张三");
stu2.setName("李四");
System.out.println(stu1);
System.out.println(stu2);
//获取全属性的有参构造器对象
//Constructor<Student> con = c1.getDeclaredConstructor(String.class, int.class, double.class);
Constructor<Student> con = c3.getDeclaredConstructor(String.class, int.class, double.class);
//利用有参构造器对象构建类的实例
Student stu3 = con.newInstance("张三", 20, 99.5);
System.out.println(stu3);
}
2.反射的优缺点
优点
- 打破封装
- 提升代码扩展性
缺点
- 打破封装
- 降低对象属性的安全性
三、设计模式
是广大程序员们总结的编码套路
1.单例模式
一个类只能被实例化一个对象
(1)饿汉式
- 无论如何直接创建唯一实例
package com.by.entity;
/**
* 单例-饿汉式
*/
public class ClassA {
/*
private: 保证外界无法直接访问该属性
static: 1. 保证newClassA方法可以访问
2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次
* */
private static ClassA ca = new ClassA();
/**
* 供外界调用获取该类的唯一实例
* static: 方便外界通过类名直接调用该方法
* @return 当前类的唯一实例对象
*/
public static ClassA newClassA(){
return ca;
}
//构造私有化: 防止外界通过调用构造创建不同对象
private ClassA(){
}
}
缺点:有可能浪费对象空间
(2)懒汉式
- 需要对象时再实例化对象
package com.by.entity;
/**
* 单例-懒汉式
*/
public class ClassB {
/*
private: 保证外界无法直接访问该属性
static: 1. 保证newClassA方法可以访问
2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次
* */
private static ClassB cb = null;
/**
* 供外界调用获取该类的唯一实例
* static: 方便外界通过类名直接调用该方法
* @return 当前类的唯一实例对象
* synchronized: 同步方法,解决线程安全问题
*/
public static synchronized ClassB newClassB(){
if (cb==null) {//空值判断: 确定是第一次获取实例
cb = new ClassB();
}
return cb;
}
//构造私有化: 防止外界通过调用构造创建不同对象
private ClassB(){
}
}
缺点:线程效率慢
(3)懒汉式-进阶版
- 在懒汉式的基础上,利用二次校验+同步代码块尽可能提高线程效率
package com.by.entity;
/**
* 单例-懒汉式进阶版
*/
public class ClassB {
/*
private: 保证外界无法直接访问该属性
static: 1. 保证newClassB方法可以访问
2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次
* */
private static ClassB cb = null;
/**
* 供外界调用获取该类的唯一实例
* static: 方便外界通过类名直接调用该方法
* @return 当前类的唯一实例对象
*/
public static ClassB newClassB(){
//判断是否有可能出现线程安全问题,以此决定是否需要加锁
//二次校验:尽可能减少加锁的时机
if (cb==null) {
//同步代码块: 防止线程安全问题,临界资源对象:当前类的类对象
synchronized (ClassB.class) {
if (cb==null) {//空值判断: 确定是第一次获取实例
cb = new ClassB();
}
}
}
return cb;
}
//构造私有化: 防止外界通过调用构造创建不同对象
private ClassB(){
}
}
(4)懒汉式之懒加载
- 在懒汉式的基础上将内容交于一个静态内部类完成
package com.by.entity;
/**
* 单例-懒汉式-懒加载
*/
public class ClassB2 {
/*
private: 保证外界无法直接访问该属性
static: 1. 保证newClassB2方法可以访问
2. 使属性成为静态属性,内存中只会存在一份,如此就能保证实例化操作只会执行一次
* */
private static ClassB2 cb = null;
//private : 防止外界访问该内部类
private static class Inner{
/**
* 供外界调用获取该类的唯一实例
* static: 方便外界通过类名直接调用该方法
* @return 当前类的唯一实例对象
*/
public static ClassB2 get(){
//判断是否有可能出现线程安全问题,以此决定是否需要加锁
//二次校验:尽可能减少加锁的时机
if (cb==null) {
//同步代码块: 防止线程安全问题,临界资源对象:当前类的类对象
synchronized (ClassB2.class) {
if (cb==null) {//空值判断: 确定是第一次获取实例
cb = new ClassB2();
}
}
}
return cb;
}
}
public static ClassB2 newClassB(){
return Inner.get();
}
//构造私有化: 防止外界通过调用构造创建不同对象
private ClassB2(){
}
}
2.工厂模式
- 对象的创建和销毁工作交由工厂完成,以此实现在数据操作量较大的情况下分担数据操作的压力提高数据操作的效率,通常用于框架底层
案例演示
目的: 练习配置文件+IO流+Properties集合+反射的使用
案例: 利用工厂获取学生类的实例对象
步骤
-
在com.xxx.entity包下新建Student学生类
package com.by.entity; public class Student { private String name; private int age; private double score; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } 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 double getScore() { return score; } public void setScore(double score) { this.score = score; } public Student() { } public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } }
-
在当前项目下新建
xxx.properties
配置文件-
内容: 被工厂管理的类的全限定名
-
书写:
- 内容以
键=值
的方式存放 - 键值都不能添加双引号
- 语句末尾不加分号
- 语句之中不可存在多余字符
- 一行只能存在一个键值对
- 内容以
StudentClassName=com.by.entity.Student
-
-
新建工厂类.提供方法获取实例对象
package com.by.util; import com.by.entity.Student; import java.io.FileInputStream; import java.util.Properties; /** * 工厂类 */ public class MyFactory { /** * 获取学生类的实例对象 * @return 学生对象实例 */ public static Student newStudent(){ Student stu=null; try ( //创建字节输入流 FileInputStream fis=new FileInputStream("classNames.properties") ) { //将配置文件中的内容读取存放至Properties集合 Properties p = new Properties(); p.load(fis); //获取学生类的全限定名 String studentClassName = p.getProperty("StudentClassName"); //通过Class.forName获取类对象 Class c = Class.forName(studentClassName); //通过类对象构建学生实例 stu = (Student) c.newInstance(); } catch (Exception e) { System.out.println("未知异常"); e.printStackTrace(); } return stu; } }
-
测试类进行测试
package com.by.test; import com.by.entity.Student; import com.by.util.MyFactory; public class TestMyFactory { public static void main(String[] args) { //利用工厂获取学生实例对象 Student s = MyFactory.newStudent(); Student s2 = MyFactory.newStudent(); } }
四、结语
反射和设计模式是Java编程中非常重要的两个概念,我们可以通过反射来动态地获取类的信息进行操作,而设计模式则是一种经典的解决方案,能够帮助我们更好地组织和编写代码。通过理解和掌握这两个概念,我们可以提高我们的Java编程能力,编写出更高质量、更易维护的代码。