1.货币计算推荐BigDecimal
ps:其实这一内容原标题为:需要精确计算,请避免使用float和double.
书中特别注明的一点:float和double类型不适合用于货币计算。
为什么? 因为让float和double精确地表示0.1(或者10的任何负数次方值)是不可能的。
使用float和double造成的结果就是会出现0.6000000000000001 , 0.9999999999999999这样的情况
所以这时我们需要通过BigDecimal、int或long来进行货币计算
int和long就不多说了,都是整型计算
BigDecimal则是可自定义的,进行四舍五入计算。
BigDecimal A = new BigDecimal(1.1);
使用.add() 、.subtract()、.multiply()、divide() 可进行加减乘除。
由于可以设置保留几位小数,而且可以轻松转类型,所以在使用上比float、double更适合。
2.基本类型优先于装箱基本类型
之前简要讲过装箱类型和基本类型的转换了,这里则讲明了两者区别
1.基本类型只有值,装箱基本类型有值,且一样的值对象不一样
2.基本类型只有函数值,而每个装箱基本类型则都有一个非函数值,除对应的基本类型的函数值外。还有null
3.基本类型一般都比装箱基本类型更节省空间和时间。
所以在这里需要注意==的使用:
之前也讲过==比较的是内存地址。同时基本类型只有值,所以值相同就相等,
而装箱类型不一样,它地址和值是分开来的,所以对于2个相同值的装箱类型使用==,返回的是true。这里要相等则需要使用equals
注意:当一项操作混合使用基本类型和装箱基本类型是,装箱基本类型会自动拆箱(就是它会自动提取值,变成基本类型来使用)。其中如果装箱基本类型的对象是null的话,则会抛出NullPointerException异常。
3.其它类型更适合则避免使用字符串
因为普遍都使用字符串作为一个通用类型,而书中也指出其实有其它类型更适合的话,则不要用字符串类型。
这里讲了几个不适合使用字符串的:
1.字符串不适合代替枚举类型:这也很好理解,枚举类型做常量更加方便。
2.字符串不适合代替聚合类型:一个实体有多个组件,则用字符串表示该实体是不合适的,单独写个类可能更好。
3.字符串不适合代替key-value类型:相比字符串查找,使用key查找value更不容易被伪造,变化。
4.了解字符串连接的性能
字符串连接顾名思义:就是将多个字符串连接起来,拼成一个新的字符串。
而将n个字符串使用+号连接起来,所需要的时间是n的平方级的时间!是特别消耗性能的;
其消耗性能的原因就是,使用连接操作符(+号之类的)连接,它们的内容连一次就得复制一次两边的字符串,将内容都拷贝再生成。
所以为了节省性能,书中是推荐使用StringBuilder或者Stringbuffer来代替String的.
它们两者都代表可变字符串对象,但有一点不同,就是StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能。
所以StringBuilder性能比StringBuffer高一点。
而与String相比:它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串,更节省了性能。
5.接口优先于反射机制
先讲反射:
什么是反射机制?
JAVA反射机制是在JVM运行状态中,间接操作目标对象(类)的机制(注意重点:在运行时加载。)
然后正常加载是通过类来找到对象,而反射正是相反的,它通过对象来找到类。
其中反射主要是将要使用的类,但初始化时并未加载的类加载到jvm的内存里面去。
举例参考:那些中间件-数据库驱动:只有使用时才会加载调用,其它时候不会加载。
正常加载:编译已经有new实例化时,jvm运行时会自动找到java文件中匹配的(类名.class文件),并将其加载进内存里面产生(类名对象),然后在内存里面由(类名对象)生成class对象,通过class对象进行实例化。
顺序:类名.class文件 ->通过jvm加载到内存->生成类名对象->通过类名对象生成class对象
反射:前面机制也是一样到生成class对象,但不同的是它是从class对象反向读取(类名对象),再通过类名对象实例化来使用包含的方法。
注意:这里内存里的对象只会存在一份。
反射机制的具体使用:
public class testclass {
public void myMethod(String name) {
System.out.println("你好!");
System.out.println("我是:"+name);
}
}
public class ReflectionStudy {
public static void main(String[] args) throws Exception {
// 1、初始化实例
Object obj = Class.forName("cn.beyond.study.testclass").newInstance();
// 2、获取方法
Method method = testclass.class.getDeclaredMethod("myMethod", String.class);
// 3、调用
method.invoke(obj ,"沙丁鱼flat");
}
}
这就是个反射过程的一个简单的使用:
1.通过.forName()来找到class对象
2.通过.newInstance()来实例化对象
3.通过 类的class对象.getDeclaredMethod(“方法名”,参数的class对象)来获取方法
PS:getDeclaredField获取参数,getDeclaredConstructors获取所有构造方法等等。
4.通过 方法.invoke(实例化的对象,实际参数) ,来使用方法。
总结:
- Class.forName 找到类;
- newInstance 开辟内存空间,构造实例;
- getDeclaredMethod 根据方法签名搜索方法体;
- method.invoke 执行方法调用。
反射调用为什么开销大?
这里参考知乎大佬的回答:https://www.zhihu.com/question/24304289?sort=created
根据上面四个阶段,我们来一步一步拆解为什么性能开销大。
- Class.forName 会尝试先去方法区(1.8以后就是 Metaspace) 中寻找有没有对应的类,如果没有则在 classpath 中找到对应的class文件, 通过类加载器将其加载到内存里。注意这个方法涉及本地方法调用,这里本地方法调用的切换、类的搜索以及类的加载都存在开销;
- newInstance ,开辟内存空间,实例化已经找到的 Class;
- getDeclaredMethod/getMethod 遍历 Class 的方法,以方法签名匹配出所需要的 Method 实例,也是比较重的开销所在;虽然 java.lang.Class 内部维护了一套名为 reflectionData 的数据结构,其本身是 SoftReference 的。Class 会将匹配成功的 method 缓存到这里,以供下次访问命中,这样一来会减轻 method 遍历的开销,但是会增加额外的堆空间消耗(以空间置换时间);
- method.invoke ,反射调用执行的部分,会有jvm直接调用也会有本地,所以存在开销
为什么接口优于反射机制?
1.反射机制损失了编译时的类型检查的优势
2.反射访问的代码较为冗长
3.反射性质性能开销较大