Don’t let complexity stop you. Be activists. Take on the big inequities. It will be one of the great experiences of your lives.
不要让这个世界的复杂性阻碍你的前进。要成为一个行动主义者,将解决人类的不平等视为己任。它将成为你生命中最重要的经历之一。
——比尔·盖茨在哈佛大学的演讲
泛型可以减少强制类型的转换,可以规范集合的元素类型,还可以提高代码的安全性和可读性,正式因为这些优点,自从Java引入泛型后,项目的编码规则上便多了一条:优先使用泛型。
反射可以“看透”程序的运行情况,可以让我们在运行期知晓一个类或实例的运行状况,可以动态地加载和调用,虽然有一定的性能忧患,但它带给我们的便利远远大于其性能缺陷。
Java的泛型在编译期有效,在运行期被删除,也就是说所有的泛型参数类型在编译后都会被清除掉。转换规则如下:
1、List<String>、List<Integer>、List<T>擦除后的类型为List。
2、List<String>[]擦除后的类型为List[]。
3、List<? extends E>、List<? super E>擦除后的类型为List<E>。
4、List<T extends Serializable & Cloneable>擦除后为List<Serializable>。
泛型的class对象时相同的,每个类都有一个class属性,泛型化不会改变class属性的返回值。
泛型数组初始化时不能声明泛型类型,可以声明一个带有泛型参数的数组,但是不能初始化该数组。
instanceof不允许存在泛型参数。
类的成员变量是在类初始化前初始化的,所以要求在初始化前它必须具有明确的类型,否则就只能声明,不能初始化。
无法从代码中推断出泛类型的情况下,即可强制声明泛型类型。
不同场景使用不同的泛型通配符:
1、泛型结构只参与“读”操作则限定上界(使用extends关键字)
2、泛型结构只参与“写”操作则限定下界(使用super关键字)
3、泛型结构同时参与“读”“写”操作,则使用确定的泛型类型即可
协变是用一个窄类型替换宽类型,逆变是用宽类型覆盖窄类型。
Java的泛型是不支持协变和逆变的,只是能够实现协变(使用extends)和逆变(使用super),数组支持协变。
建议采用的顺序是List<T>、List<?>、List<Object>。List<T>表示的是List集合中的元素都为T类型,具体类型在运行期决定;List<?>表示的是任意类型,与List<T>类似,而List<Object>则表示List集合中所有元素为Object类型。
List<T>可以进行读写操作,List<?>是只读类型,不能进行增加、修改操作,同时List<?>读取的元素都是Object类型,需要主动转型,所以它经常用于泛型方法的返回值。List<Object>也可以进行读写操作,但是它执行写入操作时需要向上转型,在读取数据后需要向下转型。
使用“&”符号设定多重边界。它可以关联多个上界并实现多个边界限定。
当一个泛型类(特别是泛型集合)转变为泛型数组时,泛型数组的真实类型不能是泛型类型的父类型(比如顶层类Object),只能是泛型类型的子类型(当然包括自身类型),否则就会出现类型转换异常。
Java语言是先把Java源文件编译成后缀为class的字节码文件,然后通过ClassLoader机制把这些类文件加载到内存中,最后生成实例执行的,这是Java处理的基本机制。
Class类是“类中类”,有很多特殊的地方:
1、无构造函数。Class对象是在加载类时由Java虚拟机通过调用类加载器中的defineClass方法自动构造的。
2、可以描述基本类型。
3、其对象都是单例模式。
获得一个Class对象有三种途径:
1、类属性加载,如String.class。
2、对象的getClass方法,如new String().getClass()。
3、forName方法加载,如Class.forName(“java.lang.String”)
获得了Class对象后,就可以通过getAnnotations()获得注解,通过getMethod()获得方法,通过getConstructors()获得构造函数等。
getMethod方法获得的是所有public访问级别的方法,包括从父类继承的方法,而getDeclaredMethod方法获得自身类的所有方法,包括公用方法、私有方法等,而不受限于访问权限。其他的getDeclaredConstructors和getConstructors、getDeclaredFields和getFields等与此相似。
反射访问属性或方法时将Accessible设置为true。AccessibleObject是Field、Method、Constructor的父类,通过反射获取后,访问其属性与方法时,AccessibleObject类中通过override成员变量保存Accessible值,默认是false。但是在Method类中的invoke方法中通过override来判断的,如果是false,还有要进行安全检查,如果是true可以直接执行方法。这也是为了提高系统性能。
动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,对Java程序来说,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否要加载一个类。
一个对象的生成必然会经过两个步骤:
步骤一:加载到内存中生成Class的实例对象。
步骤二:通过new关键字生成实例对象。
通过forName方法动态加载类文件,是因为一般的动态加载我们不知道生成的实例对象是什么类型,而且方法和属性都不可访问。
forName只是把一个类加载到内存中,并不保证由此产生一个实例对象,也不会执行任何方法,之所以会初始化static代码,那是由类加载机制所决定的,而不是forName方法决定的。也就是说,如果没有static属性或static代码块,forName就只是加载类,没有任何的执行行为。
数组编译对应的关系表
元素类型 | 编译后的类型 |
byte[] | [B |
char[] | [C |
Double[] | [D |
Float[] | [F |
int[] | [I |
Long[] | [J |
Short[] | [S |
Boolean[] | [Z |
引用类型(如String[]) | [L引用类型(如:[Ljava.lang.String) |
通过反射操作数组使用Array类,不要采用通用的反射处理API。
静态代理:通过代理主体角色(Proxy)和具体主体角色(Real Subject)共同实现抽象主体角色(Subject)的逻辑的,只是代理主体角色把相关的执行逻辑委托给了具体主体角色。
动态代理很容易实现通用的代理类,只要在InvocationHandler的invoke方法中读取持久化数据即可实现,而且还能实现动态切入的效果,这也是AOP编程理念。
public class Client {
public static void main(String[] args) {
// 具体主体角色,也就是被代理类
Subject subject = new RealSubject();
// 代理实例的处理Handler
InvocationHandler handler = new SubjectHandler(subject);
// 当前加载器
ClassLoader cl = subject.getClass().getClassLoader();
// 动态代理
Subject proxy = (Subject) java.lang.reflect.Proxy.newProxyInstance(cl, subject.getClass().getInterfaces(), handler);
// 执行具体主体角色方法
proxy.request();
}
}
// 抽象主体角色
interface Subject {
// 定义一个方法
public void request();
}
// 具体主体角色
class RealSubject implements Subject {
// 实现方法
@Override
public void request() {
// 业务逻辑处理
}
}
// 代理主体角色(静态代理)
class Proxy implements Subject {
// 要代理哪个实现类
private Subject subject = null;
// 默认被代理者
public Proxy() {
this.subject = new RealSubject();
}
// 通过构造函数传递被代理者
public Proxy(Subject subject) {
this.subject = subject;
}
// 实现接口中定义的方法
@Override
public void request() {
before();
subject.request();
after();
}
// 预处理
private void before() {
//do something
}
// 善后处理
private void after() {
//do something
}
}
// 动态代理
class SubjectHandler implements InvocationHandler {
// 被代理的对象
private Subject subject;
public SubjectHandler(Subject subject) {
this.subject = subject;
}
// 委托处理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
// 预处理
System.out.println("预处理");
// 直接调用被代理类的方法
Object obj = method.invoke(subject, args);
// 后处理
System.out.println("后处理");
return obj;
}
}
装饰模式(Decorator Pattern)的定义是“动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比于生成子类更为灵活”,不过,使用Java的动态代理也可以实现装饰模式的效果,而且其灵活性、适应性都会很强。
public class Client {
public static void main(String[] args) {
// 定义Jerry这只家喻户晓的老鼠
Animal Jerry = new Rat();
// 为Jerry增加飞行能力
Jerry = new DecorateAnimal(Jerry, FlyFeature.class);
// 为Jerry增加钻地能力
Jerry = new DecorateAnimal(Jerry, DigFeature.class);
// Jerry开始耍猫了
Jerry.doStuff();
}
}
interface Animal {
public void doStuff();
}
// 老鼠是一种动物
class Rat implements Animal {
@Override
public void doStuff() {
System.out.println("Jerry will play with Tom");
}
}
// 定义某种能力
interface Feature {
// 加载特性
public void load();
}
// 飞行能力
class FlyFeature implements Feature {
@Override
public void load() {
System.out.println("增加一只翅膀......");
}
}
// 钻地能力
class DigFeature implements Feature {
@Override
public void load() {
System.out.println("增加钻地能力......");
}
}
class DecorateAnimal implements Animal {
// 被包装的动物
private Animal animal;
// 使用哪一个包装器
private Class<? extends Feature> clz;
public DecorateAnimal(Animal animal, Class<? extends Feature> clz) {
this.animal = animal;
this.clz = clz;
}
@Override
public void doStuff() {
InvocationHandler handler = new InvocationHandler() {
// 具体包装行为
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
Object obj = null;
// 设置包装条件
if (Modifier.isPublic(method.getModifiers())) {
obj = method.invoke(clz.newInstance(), args);
}
animal.doStuff();
return obj;
}
};
// 当前加载器
ClassLoader cl = getClass().getClassLoader();
// 动态代理,由Handler决定如何包装
Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
proxy.load();
}
}
模板方法模式(Template Method Pattern)的定义是:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可重定义该算法的某些特定步骤。简单地说,就是父类定义抽象模板作为骨架,其中包括基本方法(是由子类实现的方法,并且在模板方法被调用)和模板方法(实现对基本方法的调度,完成固定的逻辑),它使用了简单的继承和覆写机制。
决定使用模板方法模式时,请尝试使用反射方式实现,它会让你的程序更灵活、更强大。
abstract class AbsPopulator {
// 模板方法
public final void dataInitialing() throws Exception {
// 获得所有的public方法
Method[] methods = getClass().getMethods();
for (Method m : methods) {
if (isInitDataMethod(m)) {
m.invoke(this);
}
}
}
// 判断是否是数据初始化方法,基本方法鉴别器
private boolean isInitDataMethod(Method m) {
return m.getName().startsWith("init") // init开始
&& Modifier.isPublic(m.getModifiers()) // 公开方法
&& m.getReturnType().equals(Void.TYPE) // 返回值是void
&& !m.isVarArgs() // 输入参数为空
&& !Modifier.isAbstract(m.getModifiers()); // 不能是抽象方法
}
}
class UserPopulator extends AbsPopulator {
public void initUser() {
// 初始化用户表,如创建、加载数据等
}
public void initPassword() {
// 初始化密码
}
public void initJobs() {
// 初始化工作任务
}
}
反射效率低是个真命题,但因为这一点而不使用它就是个假命题。
class Utils {
// 获得一个泛型类的实际泛型类型
public static <T> Class<T> getGenricClassType(Class clz) {
Type type = clz.getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Type[] types = pt.getActualTypeArguments();
if (types.length > 0 && types[0] instanceof Class) {
// 若有多个泛型参数,依据位置索引返回
return (Class) types[0];
}
}
return (Class) Object.class;
}
}
abstract class BaseDao<T> {
// 获得T的运行期类型
private Class<T> clz = Utils.getGenricClassType(getClass());
// 根据主键获得一条记录
public void get(long id) {
session.get(clz, id);
}
}
// 操作user类
class UserDao extends BaseDao<String> {
}