Java进阶03-反射,泛型

Java进阶知识点-反射和泛型

老规矩先放一张思维导图镇楼
在这里插入图片描述
Java中的进阶知识点有很多,这一篇主要学习反射和泛型。可以说这2个知识点我们自己平时的开始用的可能不多,但是系统源码和网上的第三方开源库中用到的是非常多。如果rxjava ,热修复,dagger2 ,等等都需要用到,还包括Hook点啊 动态代理 AOP APT 啊等等。所以掌握好反射 是学习这些框架的基础。

反射

  • 反射是什么
  • 反射怎么使用
  • 反射的优缺点
  • 反射的原理是什么
  • 反射使用时机

反射是什么?
反射(reflex)是指机体对内在或外在刺激有规律的反应。光沿着原路返回就叫反射,这个是物理的反射。java中的反射是指:程序在运行时能够获取自身的信息。

简而言之,你可以在运行状态中通过反射机制做到:
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
应该已经比较清楚了。就是非常规手段调用类中方法或者对象和字段。

反射怎么使用
我们先看正常的调用方法流程。如狗叫
1、定义一个接口有叫方法

public interface IAnimalCall {
	
	void call();

}

2、写一个类Gog实现接口,实现方法叫

public class Dog implements IAnimalCall {

	private String mName;

	public Dog() {
	}

	public Dog(String name) {
		this.mName = name;
	}

	@Override
	public void call() {
		System.out.println("狗:" + mName + "---汪汪汪");
	}

	@Override
	public String toString() {
		return "Dog [mName=" + mName + "]";
	}

}

3、new出对象,调用方法。

public static void main(String[] args) {
		IAnimalCall dog = new Dog("旺财");
		dog.call();

	}

4、运行结果
在这里插入图片描述
恩,结果不出我们所料(废话这么简单,大家都会)

下面想一想如果我把构造函数改为private私有的,这样才能调用到call方法呢?

public class Dog implements IAnimalCall {
	private String mName;
	private Dog() {
	}
	private Dog(String name) {
		this.mName = name;
	}
	@Override
	public void call() {
		System.out.println("狗:" + mName + "---汪汪汪");
	}
	@Override
	public String toString() {
		return "Dog [mName=" + mName + "]";
	}
}

私有的构造函数是不能直接用New创建对象的。
在这里插入图片描述
编译报错了,提示说构造方法是不可见的。这么办? 先不要杠 这样设计,就问你一句这么办,还怎么调用Call方法。5分钟考虑。。。

时间到了,想到办法没。哈哈 只要反射就可以了。
对于任意一个类,都能够知道这个类的所有属性和方法;
反射是Java写好提供给我使用的。其实每个类class都有一个Class类去描述它。它会记录这个类有什么东西比如构造函数,字段,方法等等是对类的解释和说明文档,它由Java提供的在类被加载的时候就会存储在方法区中。
平是我们接触多的就是:
public static final String TAG=Main.class.getSimpleName();
这个Main.class 就是拿到Class注意是大写,相当于:
Class main=Main.class;
String Tag=main.getSimpleName();

根据官方文档我们获取Class有3种方式:

  1. 通过 Object.getClass()
  2. 通过 .class 标识
  3. 通过 Class.forName() 方法
    大家可以测试一下。
public static void main(String[] args) {
		Dog dog=new Dog("旺财");
		Class<? extends Dog> dog1=dog.getClass();
		System.out.println("Object.getClass name="+dog1.getSimpleName());
		
		Class<Dog> dog2=Dog.class;
		System.out.println("Dog.class name="+dog2.getSimpleName());
		
		try {
			Class<?> dog3=Class.forName("com.zx.parse.annotation.Dog");
			System.out.println("Class.forName name="+dog3.getSimpleName());
		} catch (ClassNotFoundException e) {
			System.out.println("Dog.class name="+e.toString());
		}
	}

运行结果:
在这里插入图片描述
这里第三种方法就厉害了 你不需要有对象,甚至类都不需要知道,只要给全限定名称就好了 其实就是包名加类名。因为它要根据这个路径去找你的类(文件类 真实的文件)。
现在我们根据第三种方式获得了Dog类的 Class。然后就可以根据这个Class去获取构造函数了:有条件的可以点进Class类中看看到底有什么,它里面有很多的getxxx()方法
如果我们的Dog类构造函数不是private就用下面代码就可以了

Class<?> dogClass=Class.forName("com.zx.parse.annotation.Dog");
			Dog dog=(Dog) dogClass.newInstance();
			dog.call();

但是由于我们的构造函数是private所以这招行不通。我们再去看看官方文档怎么获取构造函数:
首先用Class点一下发现有个getConstructor方法,看名字就知道应该是获取构造函数的。好的 我们继续。

Constructor<?> constructor=dogClass.getConstructor(dogClass);

Constructor这个其实也是一个类和Class类似。Class是用来描述类的,那Constructor就是来描述构造函数。比如构造函数的修饰符,参数啊什么的。
测试用getConstructor获取不到构造函数,要用getDeclaredConstructors

Class<?> dogClass = Class.forName("com.zx.parse.annotation.Dog");
Constructor<?>[] constructor = dogClass.getDeclaredConstructors();
println(constructor[1].getParameterCount());
Dog dog=(Dog) constructor[1].newInstance("旺财");
dog.call();

直接调用会报错,检测access说你这个是私有的,要你确保权限
在这里插入图片描述
怎么确保,我们点击看这个错误是怎么产生的。
完,点不进去 eclipse就是这点不好,不要问我为什么还在用eclipse。这难不倒我们。我们可以去网上看在线源码,还是什么都看不见,
在这里插入图片描述
什么只要把override 设置为true就不会进来了。刚好就有一个方法
setAccessible
加上之后
在这里插入图片描述
其实只要你的是私有的就得加上这个,否则就会报使用异常,激动人心的时刻来了运行一下看看:

在这里插入图片描述
看到运行成功了。可以看到在构造函数是私有的情况下 我们也成功的获取了对象调用了方法。
接下来 我们把call方法也至为私有的。看怎么通过反射调用到。
猜测原理应该类似,经过测试:
getDeclaredMethods 是获取所有方法
getDeclaredMethod是获取指定的方法

Class<?> dogClass = Class.forName("com.zx.parse.annotation.Dog");
Constructor<?>[] constructor = dogClass.getDeclaredConstructors();
println(constructor[1].getParameterCount());
constructor[1].setAccessible(true);
Dog dog = (Dog) constructor[1].newInstance("旺财-反射");
Method methodEat = dogClass.getDeclaredMethod("eat", String.class);
println(methodEat.getName());
methodEat.setAccessible(true);
methodEat.invoke(dog, "饭");

运行结果
在这里插入图片描述
哈哈 又调到了。
这里之所以花这么多功夫 自己一个一个的摸索而不是 直接去网上看别人的。就是想检验一下自己的动手能力 有没有掉入
眼睛:我会了。大脑:我会了。 手:不,你不会。
果然知识点还是要自己动手实践实践的好,以前我看过几遍关于反射,但是这次动手来 花了几个小时,不过好在还弄出来了

接下来基本就是大同小异,就是熟练度了。修改变量等等,其实反射主要是为了获取注解,和调用方法。

反射的优缺点: 任何事物,都有两面性,反射的优点,也同是就是它的缺点。
优点:
(1)能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。
(2)与Java动态编译相结合,可以实现无比强大的功能
缺点:
(1)使用反射的性能较低
(2)使用反射相对来说不安全
(3)破坏了类的封装性,可以通过反射获取这个类的私有方法和属性
仔细想想也是,你要只要一个路径String就可以获取到对象 还可以调用任何方法太变态了。
其实反射是Java提供好给我们用,你发生全程我们就是调用系统准备好的反射相关的API。所以会用反射没什么值得高兴 满足的,是个人对着反射说明书(官方文档看两遍就会了)。既然如此 那我们怎么和别人区别开,那就要学习下一个问题了。
反射的实现原理是什么?
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
那这些描述的对象类 如Class Method Constructor 类在哪?
在这里插入图片描述
它们也是普通的类,只不过是JDK写好的,在java.lang.reflect包下面。
它们什么时候加载的?
类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例

看看反射中最关键的一句:
Class.forName(“com.zx.parse.annotation.Dog”);
这个是怎么拿到Class对象的。
在这里插入图片描述

这里我们先插入一个知识点就是 类加载器 ClassLoader:这里先简单说明,后续学习虚拟机的时候会详细讲解。
看了这么多 原理都是和类的加载有关,看来虚拟机的学习要提前。
反射使用时机: 或者说反射的作用,对于我来说完全就是为了理解别人写的第三方框架。
1.能用正常方法就不要用反射
2.看别人的开源框架
3.需要动态代理的时候

泛型

  • 泛型是什么
  • 泛型怎么使用
  • 泛型的优缺点
  • 泛型的原理是什么
  • 泛型使用时机

泛型是什么
泛型这个东西大家可能用的不多,但是写SDK的人 应该就会经常使用,我们接触最多就是集合框架中 如ArrayList
在这里插入图片描述
我们是用的时候就必须先告诉它类型 否则会爆红
ArrayList list = new ArrayList();
如上,我告诉了ArrayList 使用String字符串类型,之后只能list添加String。
仔细想想,这个有什么用?
1.安全
2.减少重复代码。
3.通用性强
否则ArrayList 要针对每种数据类型 建立一个数组,如果只是8大基本数据还好,那些自定义数据 还没法玩了。
泛型官方定义:参数化类型。可以理解为把 类型当做参数 传给类,这一点明显符合依赖倒置原则 和依赖注入。要什么类型 客户端自己决定。
但是也有不好的,因为类型擦除的原因 类的行为就被抹除了,就是说泛型T不能使用原类里面自己的方法,只能用object中通用的方法。如:
在这里插入图片描述
上面我定义了一个泛型方法println打印,可以看到你传什么进来,这个msg只能用object中的方法。所以说泛型其实没有想象中的那么厉害,最大的作用就是存储了。因为 它调不了 你自己写的方法。

泛型怎么使用
泛型有3种:泛型类,泛型方法,泛型接口
泛型类:

public class Animal<T> {
	private T mT;

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

	public void call() {
		// mT.call();
	}

}

<>尖括号是关键。T 随意 你可以用任意大写字母,但是为了规范还是用T吧
T 代表一般的任何类。
E 代表 Element 的意思,或者 Exception 异常的意思。
K 代表 Key 的意思。
V 代表 Value 的意思,通常与 K 一起配合使用。
使用:

public static void main(String[] args) {
		Animal<Cat> animal=new Animal<>();
		Cat cat=new Cat();
		animal.setT(cat);
		animal.getT().call();
	}

比较简单,好像什么用都没有,只能存储一下,因为
在这里插入图片描述
必须强制转化为Cat才能调用call。这是为什么呢,我们传进来的明明就是Cat对象,这个我们反编译一下Animal看看 传进来的mT到底是什么
使用javap进行反编译:javap -c -l Animal.class
在这里插入图片描述
在这里插入图片描述
可以看到mT 类型是Object:所以不能当做Cat类,必须要先转化。泛型只有在编译器才有用,编译后类型都被擦出了。
那我们可不可以 不转化直接使用。这个可以是可以 后面讲解

泛型方法
之前面的那个println()方法就是泛型方法。

public static  <T> void println(T msg) {
		System.out.println("Main:" + msg.toString());
	}

使用

public static void main(String[] args) {
		println("泛型");
		println(true);
		println('H');
		println(122);
		println(12.12);
	}

结果:
在这里插入图片描述
这就是泛型的好处,不然就要重载println方法参数为String,int boolean 等。
泛型接口
泛型接口与泛型类基本一样。

public interface IAnimal<T> {

	public void setT(T t);

	public T getT();

	public void call();

}

下面我们想想有什么办法 可以不转化直接调用。这里就需要用到接口或者继承了。我们先定义一个接口。

public interface ICall {
	void call();
}

然后让猫Cat实现ICall

public class Cat implements ICall {
	private String name;

	public Cat(String name) {
		this.name = name;
	}

	public Cat() {
	}

	@Override
	public void call() {
		System.out.println(name + ":喵喵喵");
	}

}

然后改造一下泛型类Animal,让T继承ICall

public class Animal<T extends ICall> {
	private T mT;

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

	public void call() {
		mT.call();
	}

}

然后运行:

public static void main(String[] args) {
		Animal<Cat> mAnimal = new Animal<Cat>();
		mAnimal.setT(new Cat("白嫖"));
		mAnimal.call();
	}

结果:
在这里插入图片描述
哈哈,我们没有经过转化也调用了call方法。我们再去看看此时mT编译后是什么类型-用javap反编译:
在这里插入图片描述
看到mT不再是Object类型而是变成了ICall类型,所以可以直接调用接口中的call()方法了。是不是瞬间感觉 好厉害了。其实也没有多大用。
这就叫做 泛型的通配符 吗?
通配符通常有2种 <? extends T> 和<? super T>
我们 一个一个讲解,先看<? extends T>

public <T extends ICall> void call(T t) {
		t.call();
		System.out.println(t.getClass().getName());
	}

其实这个还不算通配符,也和简单就是 你传进来的类型必须是 继承了ICall的子类,本来如果不加extends ICall 你可以传任意类型,但是加了之后 就只能传ICall子类。这个好像没什么用,失去了泛型的意义了。
我还不如直接写成:

public  void call(ICall t) {
		t.call();
		System.out.println(t.getClass().getName());
	}

其实通配符这个东西是 给集合类使用的。举个栗子:
其实还是网上的那个水果例子。那个例子就已经说的很好了。
首先我们有盘子放什么东西不知道,所以是泛型:

public class Plate<T> {

	private T mT;

	public Plate(T t) {
		this.mT = t;
	}

	public Plate() {
	}

	public void setT(T t) {
		this.mT = t;
	}

	public T getT() {
		return mT;
	}

}

很简单就是能够set 和get 。接着定义2个类 水果和苹果,继承关系

public class Fruit {
	public void describe() {
		System.out.println("我是水果");
	}
}
public class Apple extends Fruit {
	public void describe() {
		System.out.println("我是苹果");
	}
}

接着测试:

	public static void main(String[] args) {
		Plate<Fruit> plateFruit = new Plate<>(new Fruit());
		plateFruit.getT().describe();
/////////////////////////////////////////////////
		Plate<Apple> plateApple = new Plate<>(new Apple());
		plateApple.getT().describe();
	}

运行结果:
在这里插入图片描述
一切都没什么问题。但是如果我在放水果的盘子 放苹果 可以吗,试试看
Plate plateFruit = new Plate<>(new Apple());
这一句编译不过,提示不能转化。 那怎么才能转化呢?答案就是通配符
我们改造一下改为:
Plate<? extends Fruit> plateFruit = new Plate<>(new Apple());
哈哈 可以编译过去 也可以运行出结果:我是苹果。
现在不管是苹果还是橘子只要是 继承于Fruit类都可以了。
别高兴太早了,当加了通配符之后你会发现它不能调用set方法了
在这里插入图片描述
怎么办? 没办法 就是不能set了 原因就是 编译器不知道 你要放什么进来。我们反编译看看Plate:看不出什么

泛型的优缺点
优点:1.适配,简洁,消除重复代码
2.复用
3.类型安全
缺点:其实它的优点没那么强大,有点鸡肋其实,缺点的话 由于优点不明显,相应的缺点也不大,硬要说的话就是 不好看 不好理解。还有就是 类型擦除导致 原来定义的方法都不能用了。
泛型的原理
原理就是类型擦除了。而且泛型它只是在编译器有用,在JVM运行时 类型都变成了Object。java 有编译时 和运行时 Runtime 运行时。先放着,复习虚拟机时再来看看这个类是干什么的。
泛型使用时机
综合来看,和容器相关的时候可以考虑使用,如果Adapter 集合框架 等等,这些不需要 类的具体方法 就可以使用了。其次就是看别人的框架 因为框架就是一种广泛适配的东西 就很适合使用泛型。

总结:泛型并不难,唯一难理解的地方就那个 通配符了。我们平时在框架中接触的很多,多思考一下就好了。记住一点 泛型=参数化类型+类型擦除 类型擦除怎么实现 那是编译器实现的,定义好的语法规则。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章