正常的反射实例化一个类很容易,但是当涉及内部类的情况就有点不一样了。本文关于内部类的语法是最核心的部分,更多内容可参考语法书。
本文所有的异常都用Exception,不会区分何种异常。具体情况要根据实际情况。
Test001代码
Test001.java代码如下,在其中包含一个私有的内部类,其构造函数是带有参数的。
package java_code;
public class Test001 {
public Test001(){
System.out.println("burning");
}
private class Test001001 {
public Test001001(String str){
System.out.println(str);
}
}
public class Test001002 {
public Test001002(){
System.out.println("burning002");
}
}
static class Test001003 {
public Test001003(){
System.out.println("burning003");
}
}
private static class Test001004 {
public Test001004(){
System.out.println("burning004");
}
}
}
外部类实例化
公用类的反射实例化:
下面的代码给出了两种方式,不过第一种方式是Deprecated的。
try{
//Class.forName("java_code.Test001").newInstance();
Test001.class.getDeclaredConstructor().newInstance();
} catch (Exception e){
e.printStackTrace();
}
内部类的实例化
内部类的出现是简化了Java那,还是把Java搞复杂了那。笔者更倾向于后者,可能也是我对Java的理解不够。尤其是匿名内部类和lambda表达式的出现,虽然能够在一定程度上减少代码量,但是极大的增加了代码的阅读难度。
内部类
内部类分为如下三种:
- 静态类
- 非静态类
- 局部内部类,具体可参考lambda表达式
内部类的共有4个作用域: 同一个类、同一个包、父子类和任意位置。
静态内部类
静态内部类是外部类的类相关的,而不是外部类的对象相关的。静态内部类只是持有外部类的类引用,没有持有外部类对象的引用,这也就是为什么静态内部类的实例方法不能外部类的实例属性的原因。
非静态内部类
在非静态内部类的对象里,保存了一个它所寄生的外部类的对象引用,所以可以访问外部类的私有成员。
反射
有了反射,Java的灵活性上升了一个层级,不过安全性就. . .
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
Android中的很多hook技术,利用的就是反射+代理来实现的,后面的文章会专门写Android的hook机制,包括Binder的HOOK。
API
没有什么比官方文档更权威的解释,如果有,那就是源代码了。相应的API均来自官方文档。
Modifier and Type | Method | Description | Comment |
---|---|---|---|
Class<?>[] | getDeclaredClasses() | Returns an array of Class objects reflecting all the classes and interfaces declared as members of the class represented by this Class object. | 获取到所有的类,包括内部类和接口。 |
Constructor | getConstructor(Class<?>… parameterTypes) | Returns a Constructor object that reflects the specified public constructor of the class represented by this Class object. | 如果是非静态内部类的话,第一个参数需要是其寄生的外部类 |
Constructor<?>[] | getConstructors() | Returns an array containing Constructor objects reflecting all the public constructors of the class represented by this Class object. | 当实在搞不明白参数该怎么传的时候,可以考虑把所有的构造器都打印出来 |
T | newInstance(Object… initargs) | Uses the constructor represented by this Constructor object to create and initialize a new instance of the constructor’s declaring class, with the specified initialization parameters. | 如果是非静态内部类的话,第一个参数需要是其寄生的外部类实例 |
实例化私有内部类
Test.java代码如下,关键地方给出了注释。
package java_code;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
Test001 test001 = new Test001();
Object object = getInnerInstance(test001);
try{
Class.forName("java_code.Test001").newInstance();
//Test001.class.getDeclaredConstructor().newInstance();
} catch (Exception e){
e.printStackTrace();
}
}
private static Object getInnerInstance(Test001 test001) {
Object object = null;
try {
Class[] innnerClass = test001.getClass().getDeclaredClasses();
for (Class c: innnerClass){
if (c.getName().indexOf("Test001001") != -1){//有参数
//Constructor cc = c.getConstructors()[0];
Constructor cc = c.getConstructor(test001.getClass(), String.class);
object = cc.newInstance(test001, "欢迎关注我的微信公众号:无情剑客");
} else if (c.getName().indexOf("Test001002") != -1){//无参数
Constructor cc = c.getConstructor(test001.getClass());
object = cc.newInstance(test001);
} else {//静态内部类
Constructor cc = c.getConstructor();
object = cc.newInstance();
}
}
return object;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
最终运行结果:
参考
https://docs.oracle.com/javase/9/javadoc/javadoc.html
写在最后
网上很多写反射的,但是对于内部类的反射涉及的非常少,尤其是这种私有的内部类。也许有人认为私有内部类是不能被外部实例化的,但是强大的反射功能突破了Java本身的一些规则。
2024/12/21 今天考研 周六 冬至 龙年
这段话,是当时对代码的理解不够。修改为匿名内部类和lambda表达式的出现,极大的增强了程序的灵活性和可扩展性。
内部类的实例化
内部类的出现是简化了Java那,还是把Java搞复杂了那。笔者更倾向于后者,可能也是我对Java的理解不够。尤其是匿名内部类和lambda表达式的出现,虽然能够在一定程度上减少代码量,但是极大的增加了代码的阅读难度。
内部类 匿名内部类 lambda表达式是一脉相承的。
Runnable t1 = new Runnable() {
@Override
public void run() {
System.out.println("欢迎关注我的微信公众号:半夏之夜的无情剑客");
}
};
t1.run();
可以看看Runnable的定义:
@FunctionalInterface
public interface Runnable {
/**
* Runs this operation.
*/
void run();
}
乍一看,那我直接写一个run()方法,不是更简单吗?为啥还要这个函数式接口?
事实上,很多时候,我们不会单纯的调用run这个方法,可能会在他前面执行一些方法,然后在其后面也会执行一些操作。比如调用start之后,设置某个标志,然后调用run,并且run可自定义。这个时候可能会这样写
public class ClientStarter{
private boolean _mFlag = false;
public void start(){
_mFlag = true;
run();
}
public void run(){
System.out.println("欢迎关注我的微信公众号:半夏之夜的无情剑客");
}
public static void main(String[] args){
new ClientStarter().start();
}
}
这时候,可能会有多个地方(Client)都需要调用ClientStarter的start方法,进而执行run。为了代码复用,我们需要一个单独的类来把这个功能封装起来,同时我们还要让这个run方法可自定义,这句话用专业的话说就是,算法骨架固定,怎么实现可扩展。
模板模式正是为这样的功能诞生的。
public class ClientStarter{
private boolean _clmFlag = false;
public void start(Runnable run){
_mFlag = true;
run.run();
}
public static void main(String[] args){
Runnable t1 = new Runnable() {
@Override
public void run() {
System.err.println("欢迎关注我的微信公众号:半夏之夜的无情剑客");
}
};
new ClientStarter().start(t1);
}
}
此时ClientStarter可完美实现上面的需求。完全可以在另一个客户端(Cleint)调用start,进而执行run方法,并且可自定义run方法。这就是就函数式接口的魅力。当然了,接口中可以定义很多方法,通过匿名内部类自定义其中的方法就可以了。极大的增加了程序的灵活性。
在Java中Thread类的实现思路与次类似,只不过,没有使用匿名内部类。通过继承重写run方法,异曲同工。
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
@Override
public void run() {
Runnable task = holder.task;
if (task != null) {
Object bindings = scopedValueBindings();
runWith(bindings, task);
}
}
你要写自定义run方法,只需要继承就好,这再模板模式中称为hook。
至于静态内部类,可以通过静态内部类很好的实现单例。