IndentityHashMap的特殊性在于它的键值比较使用”key1==key2”,而Map实现的常规约束的键值比较使用”key1.equals(key2)”。这样设计的主要目的是适用引用对象比较级别的应用场景。
简单示例
其中一个应用场景就是使用IndentityHashMap维护代理对象,可以为每个对象创建一个关联对象,例如为每个对象创建一个日志实例。
Map<Teacher, Logger> loggers = new IdentityHashMap<>();
Teacher t1 = new Teacher("小李");//重名的小李老师
Teacher t2 = new Teacher("小李");
loggers.put(t1, new Logger("info"));
loggers.put(t2, new Logger("debug"));
//******
loggers.get(t1).log("吃饭");//不同对象使用不同Logger对象
loggers.get(t2).log("睡觉");
Public class Logger {
private String level;
public Logger(String level) {
this.level = level;
}
public void log(String msg) {
System.out.println(level + ":" + msg);
}
}
public class Teacher{
private String name;
public Teacher(String name) {
this.name = name;
}
******
}
实际应用
维护代理对这种场景的实际应用可以在netty源码中找到,netty中定义了一个参数类型匹配器(TypeParameterMatcher),用来验证某个对象是否创建自某个类。
我们验证对象类型的普通方法可能是instanceof关键字或获取对象的Class对象,调用Class对象的isAssignableFrom方法,再或者直接调用目标Class的isInstance方法:
Teacher o1 = new Teacher();
System.out.println(o1 instanceof Teacher);
System.out.println(o1.getClass().isAssignableFrom(Teacher.class));
System.out.println(Teacher.class.isInstance(o1));
//输出 true,true,true
但是如果考虑到类加载器,不同的类加载器加载的Class会创建不同的Class对象,类似下面的样例:
FileSystemClassLoader cl = new FileSystemClassLoader("D:\\bin\\test");
Class<Teacher> tClass = (Class<Teacher>)cl.findClass("map.Teacher");
Teacher o1 = new Teacher();
System.out.println(o1 instanceof Teacher);
System.out.println(o1.getClass().isAssignableFrom(tClass));
System.out.println(tClass.isInstance(o1));
//输出true, false, false
FileSystemClassLoader是自定义的一个类加载器,上面的输出结果并不难理解,因为o1这个Teacher对象并不是创建自tClass这个类对象。
Netty的InternalThreadLocalMap类中定义了一个IndentityHashMap类型的变量存储类与类型比较器的映射关系。
Map<Class<?>, TypeParameterMatcher> cache;
cache = new IdentityHashMap<Class<?>, TypeParameterMatcher>();
Netty中使用IndentityHashMap也并非那么有必要,因为Class类的equal方法继承自Object对象,默认使用的this==object等值比较,使用HashMap和使用IndentityHashMap效果一样。但是IndentityHashMap还有一个特定,就是对于基本操作,它的效率是恒定的(constant-time performance),因为其内部使用System.identityHashCode(Object)计算哈希值。
其他特点
IndentityHashMap对象被创建时可以指定maximum属性,空间不够的情况下会自动扩展,但扩展空间很影响效率,而最初指定的maximum过大可能会造成空间浪费,需要在实际应用中找到的合理的初始大小。
IndentityHashMap可以支持Null值的key和value。它也是非线程安全的,如果想在多线程中使用它,可以借助Collections 帮助类:Map m = Collections.synchronizedMap(new IdentityHashMap(...));
拓展思考
- 自定义类加载器
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);//class文件流转为byte数组
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try(InputStream ins = new FileInputStream(path)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
}
}
2.如何区分使用接口和泛型?
泛型的最大好处是让类实例可以处理未知类型的对象。一个对象被创建必定有它的职责,它的职责就是处理某些业务逻辑。对象履行自己的职责过程中可能需要其他对象的辅助,如果这些关联对象属于同一类型,可以抽象成接口类。
比如老师对象平时需要教导学生学习知识,同时也要向教导主任汇报工作,学生和主任都是他的关联对象,可以抽象Person接口,老师的关联人使用List<Person>定义。
如果老师类上定义了一个“审阅”方法,可以审阅试卷,也可以审阅日常作业,试卷和日常作业不能抽象同一类,可以借助泛型来定义。
public class Teacher {
public <T> T comment(T context) {
return context;
}
}
参考资料:
https://www.cnblogs.com/kakaxisir/p/5723260.html ClassLoader 学习笔记