第14章 类型信息
运行时类型信息可以让我们在程序运行时发现和使用类型信息。
在运行时识别类型信息的方式:RTTI(Run-Time Type Identification,运行时类型识别)和反射。
14.1 为什么需要RTTI
abstract class Shape{
public void drow(){
System.out.println(this+"在画");
}
// 抽象化,使所有的子类必须重写
@Override
abstract public String toString() ;
}
class Circle extends Shape{
@Override
public String toString() {
return "圆形";
}
}
public static void main(String[] args) {
ArrayList<Shape> list = new ArrayList<Shape>();
list.add(new Circle());
for(Shape shape:list){
shape.drow(); // 圆形在画
}
}
在上面的例子中,将Shape放入集合中时,它就丢失了具体的类型;当从容器中取出时(容器会将一切对象看作时Object),会自动转为Shape类型,这是RTTI的基本使用。
但是对于要求获得具体的类型信息,以上就不够用了。
14.2 Class对象
类是程序的一部分,每个类都有一个Class对象,该对象是由类加载器产生的。
类加载器实际上是一条类加载器链,但是只有一个原生类加载器,原生的加载可信类,一般是从本地磁盘加载的。
所有类都是在对其第一次使用时,动态加载到JVM中。当程序创建第一个对类的静态成员的引用时,就会加载该类,包括静态方法,创建对象。这证明了构造器是类的静态方法。
类加载验证
class A{
static{
System.out.println("加载A");
}
}
class B{
static{
System.out.println("加载B");
}
public static void f(){
System.out.println("B的f()方法");
}
}
class C{
static{
System.out.println("加载C");
}
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("未使用前");
new A();
B.f();
Class.forName("com.amarsoft.vo.C");
new A();
/*
未使用前
加载A
加载B
B的f()方法
加载C
*/
}
// 可以看到,在未使用前,并不会加载类;同时,类也只会加载一次,第二次调用静态方法时并不会再被加载。
Class中的一些方法
interface A{}
interface B{}
class P{
public P(){}
public P(int i){}
}
class PP extends P implements A,B{
public PP(){
super(1);
}
}
public static void printInfo(Class c){
System.out.println("----");
// 全限定类名
System.out.println(c.getName());
// 是否为接口
System.out.println(c.isInterface());
// 简单类名(不包含包名)
System.out.println(c.getSimpleName());
// 全限定类名
System.out.println(c.getCanonicalName());
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class c = Class.forName("com.amarsoft.vo.PP");
printInfo(c);
// 获得类实现的接口
Class[] interfaces = c.getInterfaces();
for(Class cc :interfaces){
printInfo(cc);
}
// 获得父类
c = c.getSuperclass();
printInfo(c);
// 创建类的一个实例,必须有默认构造器
Object o = c.newInstance();
}
/*
----
com.amarsoft.vo.PP
false
PP
com.amarsoft.vo.PP
----
com.amarsoft.vo.A
true
A
com.amarsoft.vo.A
----
com.amarsoft.vo.B
true
B
com.amarsoft.vo.B
----
com.amarsoft.vo.P
false
P
com.amarsoft.vo.P
*/
14.2.1 类字面常量
可以使用类字面常量来生成对Class对象的引用:A.class
。这样更简洁,安全,因为在编译时就会受到检查。类字面常量可以用于普通类/接口/数组/基本数据类型。包装类会有一个TYPE字段,指向基本数据类型Class对象。
当使用.class
的形式创建对象时,不会自动初始化该Class对象,而是延迟到对静态方法或者非常数静态域的首次引用。
class A {
public static final int b = 1;
public static final int c = new Random().nextInt();
static{
System.out.println("初始化A");
}
}
class B{
public static int s = 1;
static{
System.out.println("初始化B");
}
}
public static void main(String[] args) {
System.out.println("加载前");
Class c = A.class;
System.out.println("加载后");
System.out.println("引用静态最终编译期常量前");
int i = A.b;
System.out.println("引用静态最终编译期常量后");
System.out.println("引用静态最终非编译期常量前");
i = A.c;
System.out.println("引用静态最终非编译期常量后");
c = B.class;
System.out.println("引用静态常量前");
i = B.s;
System.out.println("引用静态常量后");
}
/*
加载前
加载后
引用静态最终编译期常量前
引用静态最终编译期常量后
引用静态最终非编译期常量前
初始化A
引用静态最终非编译期常量后
引用静态常量前
初始化B
引用静态常量后
*/
14.2.2 泛化的Class引用
通过使用泛型使类型变得更具体。
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class a = int.class;
Object o = a.newInstance();
Class<Integer> b = int.class;
// 当使用泛型时会得到具体的对象
Integer i = b.newInstance();
}
往下,为了放松限制,为什么如下不可以?
// Integer继承了Number
public final class Integer extends Number implements Comparable<Integer> {}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 下面的语法是不可以的
// Class<Number> c = Integer.class;
}
class A extends B {
}
class B {
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 该操作也是不可以的
// Class<B> c = A.class;
}
注意: 自动向上转型,是子类型向父类型转型,而Class<A>
并不是Class<B>
的子类型。
如下,为正确做法
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// ?:通配符,表示任意类
Class<?> c = Integer.class;
}
限定为某一类型的做法
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
// 表示该类和继承Number的类
Class<? extends Number> c = Integer.class;
c = Double.class;
}
返回实例类型及超类
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<? extends Number> a = Integer.class;
// 这里会返回基类型
Number number = a.newInstance();
// 声明目标类是A类的超类
Class<? super A> c = B.class;
// 这里并不会返回具体类型
Object newInstance = c.newInstance();
// 这里获得的并不是B类的超类,而是B类
c = c.getSuperclass();
}
14.2.3 新的转型语法
用的不多。
public static void main(String[] args) {
Class<Father> sClass = Father.class;
Son son = new Son();
// 强制转型
// 对于无法使用普通转型时,该方法会有用
Father f = sClass.cast(son);
}
14.3 类型转换前先做检查
instanceof
class C {
public void f() {
System.out.println("hello");
}
}
public static void main(String[] args) {
Object object = new C();
// 判断对象是否是某一具体类型
if (object instanceof C) {
((C)object).f();
}
}
使用instanceof计数需要的对象
所有对象
class O{}
// 技术S对象
class S{}
class A extends S{}
class AA extends A{}
class AAA extends AA{}
class B extends S{}
class BB extends B{}
class BBB extends BB{}
获得S对象数组类
// 创建S的工具类
abstract class Screator{
private Random random = new Random();
// 获得S类Class对象的集合,具体在子类中实现,这里运用的是模板方法设计模式
public abstract List<Class<? extends S>> types();
// 随机获得S
public S randomS() {
int i = random.nextInt(types().size());
S s = null;
try {
// 因为指定泛型为S及其子类型,因此,此处不需要转型
s = types().get(i).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return s;
}
// 获得S对象数组
public S[] sArray(int lenth){
S[]ss = new S[lenth];
for(int i = 0;i<lenth;i++){
ss[i] = randomS();
}
return ss;
}
// 获得S对象的List
public List<S> sList(int size){
/*
* 注意此处的写法,并没有创建List,然后一个个去add,
* 这样写,才符合程序的精神,代码要复用,不要重复。
*/
List<S> list = new ArrayList<S>();
Collections.addAll(list, sArray(size));
return list;
// 第二种方案
// return Arrays.asList(sArray(size));
}
}
// 具体实现
/*
* 为什么要这样做?
* 这样可以只加载一次,而不用每次调用都重新加载。
* 为什么要loder()方法?而不直接写在静态语句块中?
* 1.为了抑制警告;
* 2.这是一个完整的功能,应该封装在一块,不应和静态语句块中的其他代码耦合在一起
*/
class SubScreator extends Screator{
private static List<Class<? extends S>> myTypes = new ArrayList<Class<? extends S>>();
private static String strings[] = {
"com.amarsoft.vo.S",
"com.amarsoft.vo.A",
"com.amarsoft.vo.AA",
"com.amarsoft.vo.AAA",
"com.amarsoft.vo.B",
"com.amarsoft.vo.BB",
"com.amarsoft.vo.BBB"
};
// 强转会产生警告,增加注解,抑制警告
@SuppressWarnings("unchecked") // 为什么加在这?因为静态语句块上不能加!
private static void loder() {
for(String s:strings){
try {
myTypes.add((Class<? extends S>)Class.forName(s));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
static{
loder();
}
@Override
public List<Class<? extends S>> types() {
return myTypes;
}
}
计数工具类
// S计数类
class Scount{
/*
* 储存方案一:
// 使用map容器统计各S对象的个数
private static Map<String, Integer> map = new HashMap<String, Integer>();
// 将数据存入map中
private static void count(String s){
Integer i = map.get(s);
if (i == null) {
map.put(s, 0);
}else {
map.put(s, i+1);
}
}
// 问题:没办法统计多个
*/
/*
* 储存方案二:
* Scounts(Screator screator){
* Map<String, Integer> map = new HashMap<String, Integer>();
* }
* private static void count(Map<String, Integer> map,String s){}
*
* 问题:每次传参,需要传两个参数
*/
/*
* 储存方案三:
* 使用内部类,该类继承HashMap
* 问题:每次都需要对象名去点
*/
static class Counter extends HashMap<String, Integer>{
public void count(String s){
Integer i = get(s);
if (i == null) {
put(s, 1);
}else {
put(s, i+1);
}
}
}
public static void Scounts(Screator screator) {
Counter counter = new Counter();
for(S s:screator.sArray(10)){
if (s instanceof S) {
counter.count("S");
}
if (s instanceof A) {
counter.count("A");
}
if (s instanceof AA) {
counter.count("AA");
}
if (s instanceof AAA) {
counter.count("AAA");
}
if (s instanceof B) {
counter.count("B");
}
if (s instanceof BB) {
counter.count("BB");
}
if (s instanceof BBB) {
counter.count("BBB");
}
}
System.out.println(counter);
}
}
test
public static void main(String[] args) {
Scount.Scounts(new SubScreator());
}
/*
{BB=7, AA=1, A=1, B=9, S=10, BBB=6}
*/
14.3.1 使用类字面常量
class LiteralScreator extends Screator{
@SuppressWarnings("unchecked")
private static final List<Class<? extends S>> list = Arrays.asList(S.class,A.class,AA.class,AAA.class
,B.class,BB.class,BBB.class);
@Override
public List<Class<? extends S>> types() {
return list;
}
}
// 使用外观模式,对外提供统一的接口
class Ss{
public static final Screator screator = new LiteralScreator();
}
public static void main(String[] args) {
Scount.Scounts(Ss.screator);
}
14.3.2 动态的instanceof
class SCount2{
static class Counter extends HashMap<Class<? extends S>, Integer>{
// 先将所有类型填充进map
public Counter(){
for(Class<? extends S> c:Ss.screator.types()){
put(c, 0);
}
}
public void count(S s){
for(Map.Entry<Class<? extends S>, Integer> entry:entrySet()){
// c.isInstance(s):s是否是c类型
if(entry.getKey().isInstance(s)){
put(entry.getKey(), entry.getValue()+1);
}
}
}
// 因为map集合中放的是Class对象作为KEY,所以重写该方法
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("{");
for(Map.Entry<Class<? extends S>, Integer> entry:entrySet()){
sb.append(entry.getKey().getSimpleName()).append("=").append(entry.getValue()).append(",");
}
// 删除最后一个","
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append("}");
return sb.toString();
}
}
}
public static void main(String[] args) {
Screator screator = new LiteralScreator();
SCount2.Counter counter = new SCount2.Counter();
for(S s:screator.sList(10)){
counter.count(s);
}
System.out.println(counter);
}
// {A=4,B=6,BB=6,AAA=1,S=10,AA=1,BBB=2}
该处理方法的好处是不用写死instanceof,更具有灵活性,当有变化时,仅需修改list即可。
14.3.3 递归计数
class TypeCounter extends HashMap<Class<?>,Integer>{
private Class<?> baseType;
public TypeCounter(Class<?> baseType){
this.baseType = baseType;
}
// 为什么还需要这个方法,不将递归写在这里
public void count(Object obj){
Class<?> type = obj.getClass();
// type是否是baseType类型或子类型
// 和isInstance()的区别:一个参数是Object,一个Class
if (!baseType.isAssignableFrom(type)) {
throw new RuntimeException("该对象不是指定的类型");
}
countClass(type);
}
private void countClass(Class<?> type){
Integer i = get(type);
put(type, i == null?1:i+1);
Class<?> superClass = type.getSuperclass();
// 递归调用
if (baseType.isAssignableFrom(superClass)) {
countClass(superClass);
}
}
// 重写toString()
}
public static void main(String[] args) {
Screator screator = new LiteralScreator();
TypeCounter typeCounter = new TypeCounter(S.class);
for(S s:screator.sList(10)){
typeCounter.count(s);
}
System.out.println(typeCounter);
}
// {A=1,B=6,BB=4,S=10,AA=1,BBB=2}
14.4 注册工厂
使用工厂产生对象
// 工厂
interface Factory<T>{
T create();
}
class S{
private static List<Factory<? extends S>> factoryList = new ArrayList<>();
private static Random random = new Random();
static {
factoryList.add(new A.AFactory());
factoryList.add(new B.BFactory());
factoryList.add(new AA.AAFactory());
factoryList.add(new BB.BBFactory());
}
// 将产生对象的责任放在基类中
public static S random(){
int i = random.nextInt(factoryList.size());
return factoryList.get(i).create();
}
@Override
public String toString() {
// 获得具体类的名字
return getClass().getSimpleName();
}
}
class A extends S{
// 具体工厂方法
public static class AFactory implements Factory<A>{
@Override
public A create() {
return new A();
}
}
}
class AA extends A{
public static class AAFactory implements Factory<AA>{
@Override
public AA create() {
return new AA();
}
}
}
class B extends S{
public static class BFactory implements Factory<B>{
@Override
public B create() {
return new B();
}
}
}
class BB extends B{
public static class BBFactory implements Factory<BB>{
@Override
public BB create() {
return new BB();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(S.random());
}
}
/*
B
A
B
BB
AA
*/
14.5 instanceof与Class的等价性
public class Test {
public static void main(String[] args) {
B b = new B();
System.out.println(b instanceof A); // true
System.out.println(A.class.isInstance(b)); // true
System.out.println(A.class.equals(b)); // false
// System.out.println(A.class == b.getClass()); error:不可比较的类型
System.out.println(B.class == b.getClass()); // true
}
}
class A{}
class B extends A{}
equals和==比较的是Class对象本身,并不会因为继承关系而判定相等。
14.6 反射:运行时的类信息
Class类和java.lang.reflect类库一起提供了对反射的支持,该类库包含了Filed/Method/Constructor类。
反射并没有什么神奇之处,在它做事之前必须先加载.class,它要么在本地,要么在网络上;RTTI在编译时检查class文件,反射在运行时检查。
14.6.1 类方法提取器
public static void main(String[] args) throws Exception {
// 使用正则去掉修饰词
Pattern pattern = Pattern.compile("\\w+\\.");
Class c = Class.forName("java.lang.Object");
Constructor[] constructors = c.getConstructors();
System.out.println("构造方法:");
for (Constructor constructor : constructors) {
// 在类中,没有构造方法,但是在编译为class后会有默认的构造方法
System.out.println(pattern.matcher(constructor.toString()).replaceAll(""));
}
Method[] methods = c.getMethods();
System.out.println("方法:");
for (Method method : methods) {
System.out.println(pattern.matcher(method.toString()).replaceAll(""));
}
}
/*
构造方法:
public Object()
方法:
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
*/
14.7 动态代理
简单代理
interface Inter{
void doSomething();
}
class RealObj implements Inter{
@Override
public void doSomething() {
System.out.println("真正的处理");
}
}
class SimpleProxy implements Inter{
private Inter inter;
public SimpleProxy(Inter inter) {
this.inter = inter;
}
@Override
public void doSomething() {
System.out.println("代理中的处理");
inter.doSomething();
}
}
public class Test {
public static void consumer(Inter inter){
inter.doSomething();
}
public static void main(String[] args) throws Exception {
consumer(new SimpleProxy(new RealObj()));
}
}
/*
代理中的处理
真正的处理
*/
动态代理
// 调用处理器
class SimpleHandler implements InvocationHandler{
// 真正处理对象
private Inter inter;
public SimpleHandler(Inter inter) {
this.inter = inter;
}
@Override
public Object invoke(/*代理对象*/Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理对象:"+proxy.getClass());
System.out.println("方法:"+method);
System.out.println("参数:"+ Arrays.toString(args));
// 注意:这里传入的是真正的对象
Object reObj = method.invoke(inter, args);
return reObj;
}
}
public static void main(String[] args) throws Exception {
Inter inter = (Inter) Proxy
// 创建动态代理
.newProxyInstance(
// 类加载器
Inter.class.getClassLoader()
// 需要实现的接口
,new Class[]{Inter.class}
// 调用处理器
,new SimpleHandler(new RealObj()));
consumer(inter);
}
/*
代理对象:class com.$Proxy0
方法:public abstract void com.Inter.doSomething()
参数:null
真正的处理
*/
14.8 空对象
在很多时候,我们需要判断对象是否为null,书中提供了一种思路,创建单例的空对象,来使所用的变量都不会为null,这样可以避免空指针异常,然而,这并不是完美的,很多情况下,我们需要区别它是否是空对象,这也是变相的判断是否为null。
14.9 接口与类型信息
interface的一个重要目标就是隔离构件,降低耦合性,但实际上客户端程序员仍能通过instanceof找到具体类型;另外的方式,是通过访问权限来做隔离,但是这样的方式通过反射仍能破解;还有方式是通过编译后的class文件来实现,但是通过反编译,仍能达到目的。final修饰符可以做到一点,虽然不能避免被调用,但可以避免被修改。