目录
- 引出
- 泛型和反射
- 建议93:Java的泛型是类型擦除的
- 建议94:不能初始化泛型参数和数组
- 建议95:强制声明泛型的实际类型
- 建议96:不同的场景使用不同的泛型通配符
- 建议97:警惕泛型是不能协变和逆变的
- 建议98:建议采用的顺序是List,List,List
- 建议99:严格限定泛型类型采用多重界限
- 建议100:数组的真实类型必须是泛型类型的子类型
- 建议101:注意Class类的特殊性
- 建议102:适时选择getDeclaredXXX和getXXX
- 建议103:反射访问属性或方法是将Accessible设置为true
- 建议104:使用forName动态加载类文件
- 建议105:动态加载不适合数组
- 建议106:动态代理可以使代理模式更加灵活
- 建议107:使用反射增加装饰模式的普适性
- 建议108:反射让模板方法模式更强大
- 建议109:不需要太多关注反射效率
- 深入认识JVM
- JVM内存分配,类加载
- 创建对象的4种方法总结
- 垃圾回收GC
- JVM调优,Arthas使用
- 认识多线程
- 创建多线程方法+了解线程池
- 多线程下-1非原子性问题即解决
- 再论线程,创建、生命周期
- 总结
引出
程序人生——Java泛型和反射的使用建议
泛型和反射
建议93:Java的泛型是类型擦除的
- 加入泛型优点:加强了参数类型的安全性,减少了类型的转换。Java的泛型在编译期有效,在运行期被删除,也就是说所有的泛型参数类型在编译后都会被清除掉。所以:1、泛型的class对象时是相同的;2、泛型数组初始化时不能声明泛型类型;3、instanceof不允许存在泛型参数
建议94:不能初始化泛型参数和数组
- 泛型类型在编译期被擦除,在类初始化时将无法获得泛型的具体参数,所以泛型参数和数组无法初始化,但是ArrayList却可以,因为ArrayList初始化是向上转型变成了Object类型;需要泛型数组解决办法:只声明,不再初始化,由构造函数完成初始化操作
建议95:强制声明泛型的实际类型
- 无法从代码中推断出泛型类型的情况下,即可强制声明泛型类型;方法:List list2 = ArrayUtils.asList();在输入前定义这是一个Integer类型的参数
建议96:不同的场景使用不同的泛型通配符
- Java泛型支持通配符(Wildcard),可以单独使用一个“?”表示任意类,也可以使用extends关键字表示某一个类(接口)的子类型,还可以使用super关键字表示某一个类(接口)的父类型。1、泛型结构只参与“读”操作则限定上界(extends关键字);2、泛型结构只参与“写”操作则限定下界(使用super关键字);3、如果一个泛型结构既用作“读”操作也用作“写”操作则使用确定的泛型类型即可,如List
建议97:警惕泛型是不能协变和逆变的
- Java的泛型是不支持协变和逆变的,只是能够实现协变和逆变)(协变和逆变是指宽类型和窄类型在某种情况下(如参数、泛型、返回值)替换或交换的特性。简单地说,协变是用一个窄类型替换宽类型,而逆变则是用宽类型覆盖窄类型。子类覆写父类返回值类型比父类型变窄,则是协变;子类覆写父类型的参数类型变宽,则是逆变。数组支持协变,泛型不支持协变
建议98:建议采用的顺序是List,List,List
- List是确定的某一个类型,编码者知道它是一个类型,只是在运行期才确定而已;
- List可以进行读写操作,List<?>是只读类型,因为编译器不知道List中容纳的是什么类型的元素,无法增加、修改,但是能删除
建议99:严格限定泛型类型采用多重界限
- 使用“&”符号连接多个泛型界限,如:
建议100:数组的真实类型必须是泛型类型的子类型
- 有可能会抛出ClassCastException异常,toArray方法返回后会进行一次类型转换,Object数组转换成了String数组。由于我们无法在运行期获得泛型类型的参数,因此就需要调用者主动传入T参数类型
建议101:注意Class类的特殊性
- Java处理的基本机制:先把Java源文件编译成后缀为class的字节码文件,然后再通过ClassLoader机制把这些类文件加载到内存中,最后生成实例执行。Java使用一个元类(MetaClass)来描述加载到内存中的类数据,这就是Class类,它是一个描述类的类对象。Class类是“类中类”,具有特殊性:1、无构造函数,不能实例化,Class对象是在加载类时由Java虚拟机通过调用类加载器中的defineClass方法自动构建的;2、可以描述基本类型,8个基本类型在JVM中并不是一个对象,一般存在于栈内存中,但是Class类仍然可以描述它们,例如可以使用int.class表示int类型的类对象;3、其对象都是单例模式,一个Class的实例对象描述一个类,并且只描述一个类,反过来也成立,一个类只有一个Class实例对象。Class类是Java的反射入口,只有在获得了一个类的描述对象后才能动态地加载、调用,一般获得一个Class对象有三种途径:1、类属性方式,如String.class;2、对象的getClass方法,如new String().getClass();3、forName方法重载,如Class.forName(“java.lang.String”)。获得了Class对象后,就可以通过getAnnotation()获得注解,通过个体Methods()获得方法,通过getConstructors()获得构造函数等
建议102:适时选择getDeclaredXXX和getXXX
- getMethod方法获得的是所有public访问级别的方法,包括从父类继承的方法,而getDeclaredMethod获得的是自身类的所有方法,包括公用方法、私有方法等,而且不受限于访问权限。Java之所以这样处理,是因为反射本意只是正常代码逻辑的一种补充,而不是让正常代码逻辑产生翻天覆地的改动,所以public的属性和方法最容易获取,私有属性和方法也可以获取,但要限定本类。如果需要列出所有继承自父类的方法,需要先获得父类,然后调用getDeclaredMethods方法,之后持续递归
建议103:反射访问属性或方法是将Accessible设置为true
- 通过反射方式执行方法时,必须在invoke之前检查Accessible属性。而Accessible属性并不是我们语法层级理解的访问权限,而是指是否更容易获得,是否进行安全检查。Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,这就可以大幅度地提升系统性能。经过测试,在大量的反射情况下,设置Accessible为true可以提升性能20倍以上
建议104:使用forName动态加载类文件
- forName只是加载类,并不执行任何代码)(动态加载(Dynamic Loading)是指在程序运行时加载需要的类库文件,一般情况下,一个类文件在启动时或首次初始化时会被加载到内存中,而反射则可以在运行时再决定是否要加载一个类,然后在JVM中加载并初始化。动态加载通常是通过Class.forName(String)实现。一个对象的生成必然会经过一下两个步骤:1、加载到内存中生成Class的实例对象;2、通过new关键字生成实例对象;**动态加载的意义:**加载一个类即表示要初始化该类的static变量,特别是static代码块,在这里我们可以做大量的工作,比如注册自己,初始化环境等,这才是我们重点关注的逻辑
建议105:动态加载不适合数组
- 通过反射操作数组使用Array类,不要采用通用的反射处理API)(如果forName要加载一个类,那它首先必须是一个类—8个基本类型排除在外,不是具体的类;其次,它必须具有可追索的类路径,否则会报ClassNotFoundException异常。在Java中,数组是一个非常特殊的类,虽然是一个类,但没有定义类路径。作为forName参数时会抛出ClassNotFoundException异常,原因是:数组虽然是一个类,在声明时可以定义为String[],但编译器编译后会为不同的数组类型生成不同的类,所以要想动态创建和访问数组,基本的反射是无法实现的
建议106:动态代理可以使代理模式更加灵活
- Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发。静态代理是通过代理主题角色(Proxy)和具体主题角色(Real Subject)共同实现抽象主题角色(Subject)的逻辑的,只是代理主题角色把相关的执行逻辑委托给了具体主题角色而已。动态代理需要实现InvocationHandler接口,必须要实现invoke方法,该方法完成了对真实方法的调用
建议107:使用反射增加装饰模式的普适性
- 装饰模式(Decorator Pattern)的定义是“动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比于生成子类更为灵活”。比较通用的装饰模式,只需要定义被装饰的类及装饰类即可,装饰行为由动态代理实现,实现了对装饰类和被装饰类的完全解耦,提供了系统的扩展性
建议108:反射让模板方法模式更强大
- 决定使用模板方法模式时,请尝试使用反射方式实现,它会让你的程序更灵活、更强大)(模板方法模式(Template Method Pattern)的定义是:定义一个操作中的算法骨架,将一些步骤延迟到子类中,使子类不改变一个算法的结构即可重定义该算法的某些特定步骤。简单说,就是父类定义抽象模板作为骨架,其中包括基本方法(是由子类实现的方法,并且在模板方法被调用)和模板方法(实现对基本方法的调度,完成固定的逻辑),它使用了简单的继承和覆写机制。使用反射后,不需要定义任何抽象方法,只需定义一个基本方法鉴别器即可加载复合规则的基本方法
建议109:不需要太多关注反射效率
- 反射效率低是个真命题,但因为这一点而不使用它就是个假命题)(反射效率相对于正常的代码执行确实低很多(经测试,相差15倍左右),但是它是一个非常有效的运行期工具类
深入认识JVM
JVM内存分配,类加载
Java进阶(1)——JVM的内存分配 & 反射Class类的类对象 & 创建对象的几种方式 & 类加载(何时进入内存JVM)& 注解 & 反射+注解的案例
创建对象的4种方法总结
Java进阶(4)——结合类加载JVM的过程理解创建对象的几种方式:new,反射Class,克隆clone(拷贝),序列化反序列化
垃圾回收GC
Java进阶(垃圾回收GC)——理论篇:JVM内存模型 & 垃圾回收定位清除算法 & JVM中的垃圾回收器
简介:本篇博客介绍JVM的内存模型,对比了1.7和1.8的内存模型的变化;介绍了垃圾回收的语言发展;阐述了定位垃圾的方法,引用计数法和可达性分析发以及垃圾清除算法;然后介绍了Java中的垃圾回收器,由串行、到并行再到并发,最后到G1的演变;最后给出了垃圾回收器的对比和使用指引。
JVM调优,Arthas使用
- Java进阶(JVM调优)——阿里云的Arthas的使用 & 安装和使用 & 死锁查找案例,重新加载案例,慢调用分析
- Java进阶(JVM调优)——JVM调优参数 & JDK自带工具使用 & 内存溢出和死锁问题案例 & GC垃圾回收
认识多线程
创建多线程方法+了解线程池
Java进阶(5)——创建多线程的方法extends Thread和implements Runnable的对比 & 线程池及常用的线程池
多线程下-1非原子性问题即解决
Java进阶(6)——抢购问题中的数据不安全(非原子性问题)& Java中的synchronize和ReentrantLock锁使用 & 死锁及其产生的条件
再论线程,创建、生命周期
Java进阶(再论线程)——线程的4种创建方式 & 线程的生命周期 & 线程的3大特性 & 集合中的线程安全问题
主要内容:
1.线程创建的方式,继承Thread类,实现Runable接口,实现Callable接口,采用线程池;
2.线程生命周期: join():运行结束再下一个, yield():暂时让出cpu的使用权,deamon():守护线程,最后结束,sleep():如果有锁,不会让出;
3.线程3大特性,原子性,可见性,有序性;
4.list集合中线程安全问题,hash算法问题;
总结
程序人生——Java泛型和反射的使用建议