EffectiveJava学习笔记26:通用编程(中)-货币计算推荐BigDecimal、基本类型优先于装箱类型、字符串注意事项、接口优先于反射

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(实例化的对象,实际参数) ,来使用方法。

总结:

  1. Class.forName 找到类;
  2. newInstance 开辟内存空间,构造实例;
  3. getDeclaredMethod 根据方法签名搜索方法体;
  4. 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.反射性质性能开销较大

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值