Class 类
文本通过李兴华 java 视频教学中跟进学习,做以下知识总结。分为上下两部分,本篇介绍获取类对象以及类的实例化。
知识导图
获取类对象
整个反射的开发模式之中,有一个最为重要的组成就是 java.lang.Class<T> 类,但是如果想要取得这个类的实例化对象,有三种方式可以完成:
-
Object 类之中存在一个
getClass()
方法:public final Class<?> getClass()
- 此方法不能被子类覆写,而且所有类的实例化对象都可以调用。
-
利用
包.类.class
的形式实例化 Class 类对象:- 例如:
java.util.Date.class
,在一些开源框架中大量使用到。
- 例如:
-
利用 Class 类中提供的
forName()
:public static Class<?> forName(String class) throws ClassNotFoundException
- 主要可用用在工厂类上。
范例:验证第一种方式
public class TestDemo {
public static void main(String[] args) {
String str = "Hello";// 是 String 类的实例化对象
Class<?> cls = str.getClass(); // 只要是实例化对象都具有
System.out.println(cls);
}
}
// 输出结果
class java.lang.String
以上的方式是不会使用到的,在所有的开发环境里面这种方式可以使用的几率是很低的。
范例:验证第二种方式
public class TestDemo {
public static void main(String[] args) {
Class<?> cls = java.lang.String.class;
System.out.println(cls);
}
}
此操作方式不需要得到指定操作类的实例化对象,而是通过类的名称就可以完成了,少了一个对象的空间。这种方式虽然严谨,那么却需要明确的结构,即:定义的时候类必须存在。
范例:验证第三种方式
public class TestDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
System.out.println(cls);
}
}
使用此方式最大特点是:程序没有很强的严谨性,操作的类可以不存在,只要你程序不运行,那么就不会出现任何的语法错误,如果要运行,那么就把指定的类设置上。
类的实例化
取得 Class 类对象最后,就可以实例化类对象了。在 Class 类里面提供了一个实例化对象的操作方法。
public T newInstance() throws
InstantiationException, // 没有无参构造,类名错误
IlleageAccessException // 构造方法私有化
范例:利用反射实例化对象
public class TestDemo {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.tina.moduler.Book");
Object obj = (Book)cls.newInstance(); // 表示实例化对象
System.out.println(obj); // 输出对象调用 toString()
}
}
public class Book {
public Book() {
System.out.println("Book 的无参构造方法");
}
@Override
public String toString() {
return "this is a book";
}
}
// 输出结果
Book 的无参构造方法
this is a book
需要注意的是,使用 newInstance()
方法只能调用类中的无参构造方法,这个时候的功能相当于使用关键字 new
进行对象的实例化操作。
Q:使用关键字
new
实例化对象,以及使用反射实例化对象有什么区别?如果只是单纯的对类进行对象的实例化,那么两者的区别不大,如果非要强调一个区别,那么就是反射实例化对象,它的灵活度更高,因为只需要传入
包.类
名称的字符串就可以取得实例化对象,比使用new
要宽松许多。如果现在是一个子类需要为父接口进行对象实例化,那么如果使用了关键字
new
,会造成接口对象的耦合性增加的问题,因为一个接口在使用中就与固定的一个子类进行绑定了,而最早的解耦合的方式是利用工厂设计模式,但是为了让一个类可以使用思所有接口子类的扩展要求,则可以利用反射完成。
范例:传统方式取得接口对象
public class TestDemo {
public static void main(String[] args) throws Exception {
Message msg = new Email();
msg.print("You have a new email message");
}
}
public interface Message {
public void print(String str);
}
class News implements Message {
@Override
public void print(String str) {
System.out.println("新闻消息: " + str);
}
}
class Email implements Message {
@Override
public void print(String str) {
System.out.println("邮件消息: " + str);
}
}
// 输出结果
邮件消息: You have a new email message
从上面的代码段可以看出,客户端代码需要负责具体的子类操作。
范例:利用工厂类
public class TestDemo {
public static void main(String[] args) throws Exception {
Message msg = Factory.getInstance("news");
msg.print("You have a new email message");
}
}
class Factory {
public static Message getInstance(String className){
if ("news".equalsIgnoreCase(className)){
return new News();
}else if ("email".equalsIgnoreCase(className)){
return new Email();
}else {
return null;
}
}
}
// 输出结果
新闻消息: You have a new email message
虽然此时实现了工厂设计模式,但是本代码却具备一个非常大缺点(根据子类口扩充来决定的):每当接口扩充新的子类的时候,都需要去修改工厂类,这有背于开闭原则。优秀的编码:一个类完成之后是可以适应情况变化的,所以这种严谨性的代码,就不适合于实际的编写。
范例:利用反射优化工厂模式
public class TestDemo {
public static void main(String[] args) throws Exception {
Message msg = Factory.getInstance("cn.tina.moduler.News");
msg.print("You have a new email message");
}
static class Factory {
public static Message getInstance(String className){
try {
return (Message) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
这个时候虽然解决了工厂类的固化问题,但是有个新的问题产生了,客户端代码还是固定的。客户端必须明确写出要操作的类名称,这样一来就会造成新的耦合问题。
最好的设计方案永远不是点对点直达(最快),通过 key 找到 value。如果一个普通用户,那么一定不能修改程序的逻辑代码,但是可以修改文件的内容,如果要解决这种耦合问题,可以定义文件来存储属性,在 java.utils 包中有一个 Properties 类,是 Hashtable 的子类,这个类可以定义key = value
的文件。在 Android 中也有默认的gradle.properties 文件,或者其他读取文件配置的方式来改善。
网友评论