首页 > 编程语言 >Effective-Java-Chapter9-通用程序设计

Effective-Java-Chapter9-通用程序设计

时间:2024-08-20 16:56:30浏览次数:16  
标签:舍入 Java BigDecimal Effective 准则 浮点数 使用 类型 Chapter9

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/Chapter-9/Chapter-9-Introduction.md
在这里插入图片描述

准则一 将局部变量的作用域最小化

  • 不要在变量使用之前就申明,在需要使用的时候进行申明。当然这条准则不是那么绝对,大部分时候遵守就好。
  • 每个局部变量声明都应该包含一个初始化表达式。 如果你还没有足够的信息来合理地初始化一个变量,你应该推迟声明,直到条件满足。这个规则的一个例外是 try-catch 语句。
  • 遍历集合的首选习惯用法是foreach
for (Element e : c) {
}

如果要访问迭代器,书中更推荐使用,传统的for循环:

for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
    Element e = i.next();
}

为什么呢?其实也是最小化局部变量的应用,可以避免bug发生:

Iterator<Element> i = c.iterator();
while (i.hasNext()) {
    doSomething(i.next());
}
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) { 
    doSomethingElse(i2.next());
}

我们可以看到第二个while循环引用了上面的变量I但是我们实际上的期望是引用i2所以不符合预期,产生bug,如果我们使用传统的for,我们引用不到i这个变量。

准则二 for-each 循环优于传统的 for 循环

书中之所以推荐使用for-each,是因为可以减少错误的发生,例如:

List<Card> deck = new ArrayList<>();
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
deck.add(new Card(i.next(), j.next()));

就比如上面的实例方法会抛出异常NoSuchElementException,原因就在于外层循环一次以后,里面的迭代器已经到了末尾。如果这样写是不是完全可以避免这个问题。

for (Suit i : suits) {
	for (Rank j : ranks) {
	}
}

for-each : 三个不易使用的场景

  • 破坏性过滤,如果需要遍历一个集合并删除选定元素,则需要使用显式的迭代器,以便调用其 remove 方法。
  • 转换,如果需要遍历一个 List 或数组并替换其中部分或全部元素的值,那么需要 List 迭代器或数组索引来替换元素的值。
  • 并行迭代,如果需要并行遍历多个集合,那么需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步执行。

准则三 通用程序设计

这个没什么好说的,我们尽量使用已有的库。首先来说已有的库经过大量人的验证很少有BUG,边界情况考虑的更加周全。其次是我们使用现成的库可以提高开发效率。

准则四 若需要精确答案就应避免使用 float 和 double 类型

浮点数在计算机中的表示不是完全精确的,因为它们使用了有限长度的二进制来表示数值。这意味着有些十进制小数无法被精确表示为二进制形式,从而导致精度损失。
这个其实在阿里巴巴开发手册也有提到如果是金融业务最好使用BigDecimal。

BigDecimal它与原始算术类型相比很不方便,而且速度要慢得多。但是比起钱的运算不能出错更重要。
对比

在 Java 中,float 和 double 类型用于表示浮点数,它们在计算机内部是以二进制形式存储的。使用 float 和 double 类型时,需要注意以下几点,尤其是当需要进行精确计算时:

  • 有限精度:
    浮点数在计算机中的表示不是完全精确的,因为它们使用了有限长度的二进制来表示数值。这意味着有些十进制小数无法被精确表示为二进制形式,从而导致精度损失。
  • 舍入误差:
    在进行浮点数运算时,由于浮点数的有限精度,可能会产生舍入误差。即使是简单的加减法也可能导致结果不精确。
  • 比较问题:
    直接比较两个浮点数是否相等通常是不可靠的,因为即使是非常接近的数值也可能由于舍入误差而被认为是不相等的。
  • 表示范围限制:
    float 和 double 类型虽然可以表示非常大的数值,但它们也有各自的表示范围限制。超出这个范围的数值会导致溢出或下溢。

BigDecimal 能够避免精度问题的原因在于它的设计和实现方式。以下是几个关键点:

  • 可变精度:
    BigDecimal 允许用户指定任意精度来进行数学运算。这意味着用户可以控制小数点后保留多少位数字,从而避免了舍入误差。
    基于十进制表示:
    BigDecimal 使用十进制表示数值,这与人类习惯的计数方式一致。这意味着可以直接表示常见的分数,如 0.1、0.2 等,而不会出现二进制浮点数表示时的精度损失。
  • 内部结构:
    BigDecimal 类内部使用两个字段来表示一个数:unscaledValue 和 scale。
    unscaledValue 是一个 BigInteger 对象,表示数值的未缩放部分。
    scale 是一个整数,表示小数点的位置。例如,对于 0.123,unscaledValue 为 123,scale 为 3。
  • 精确的算术运算:
    BigDecimal 提供了一系列方法来进行精确的算术运算,包括加法、减法、乘法、除法等。这些方法在执行运算时会考虑到精度和舍入模式,以确保结果的准确性。
    舍入模式:
    BigDecimal 支持多种舍入模式,如 ROUND_HALF_UP、ROUND_HALF_DOWN、ROUND_HALF_EVEN 等,这允许用户根据需求选择合适的舍入策略。
  • 避免溢出:
    BigDecimal 使用 BigInteger 来存储数值,这意味着它可以表示非常大或非常小的数,而不会像 float 和 double 那样容易发生溢出或下溢。

准则五 基本数据类型优于包装类

基本类型和包装类型之间有三个主要区别。
首先,基本类型只有它们的值,而包装类型具有与其值不同的标识。换句话说,两个包装类型实例可以具有相同的值和不同的标识。
第二,基本类型只有全功能值,而每个包装类型除了对应的基本类型的所有功能值外,还有一个非功能值,即 null。
最后,基本类型比包装类型更节省时间和空间。

// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder =(i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

上面这段程序的问题在于在包装类型上使用了==应该使用equals方法。如果要修复这个问题:

Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
    int i = iBoxed, j = jBoxed; // Auto-unboxing
    return i < j ? -1 : (i == j ? 0 : 1);
};

再看下面这个程序:

public class Unbelievable {
static Integer i;
public static void main(String[] args) {
    if (i == 42)
        System.out.println("Unbelievable");
    }
}

当如果是IDE,上面的代码不会通过编译,如果通过编译呢,结果就是会空指针,为什么呢?看下面这个字节码:
在这里插入图片描述
可以看到,原因就是和数字比较时候自动拆箱了,相当于调用了null.initValue();
同样的在前面我们也提到了一段代码:

public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

这个程序比它预期的速度慢得多,因为它意外地声明了一个局部变量 (sum),它是包装类型 Long,而不是基本类型 long。程序在没有错误或警告的情况下编译,变量被反复装箱和拆箱,导致产生明显的性能下降。

什么时候使用包装类型呢?如果是使用泛型毫无疑问一定要使用包装类型。

准则六 其他类型更合适时应避免使用字符串

也就是说,数据类型我们最好是转换成对应的类型,不要都用字符串类替代。

准则七 当心字符串连接引起的性能问题

不要使用字符串连接操作符合并多个字符串,除非性能无关紧要。否则使用 StringBuilder 的 append 方法。其本质就是避免创建不必要的对象 。

准则八 通过接口引用对象

如果存在合适的接口类型,那么应该使用接口类型声明参数、返回值、变量和字段。
也就是依赖于更抽象,这样我们可以将多态这个特性发挥的更有力,提高代码的通用性和灵活性。

  • 如果没有合适的接口存在,那么用类引用对象是完全合适的。
  • 属于框架的对象,框架的基本类型是类而不是接口。但是这样的对象往往是一个抽象类。
  • 如果没有合适的接口,就使用类层次结构中提供所需功能的最底层的类,也就是引用最顶层的,因为里氏替换原则父类出现的地方子类一定可以出现。

准则九 接口优于反射

通过非常有限的形式使用反射,你可以获得反射的许多好处,同时花费的代价很少。 对于许多程序,它们必须用到在编译时无法获取的类,在编译时存在一个适当的接口或超类来引用该类(Item-64)。如果是这种情况,可以用反射方式创建实例,并通过它们的接口或超类正常地访问它们。

// Translate the class name into a Class object
Class<? extends Set<String>> cl = null;
try {
    cl = (Class<? extends Set<String>>) // Unchecked cast!
            Class.forName(args[0]);
} catch (ClassNotFoundException e) {
    fatalError("Class not found.");
}

// Get the constructor
Constructor<? extends Set<String>> cons = null;
try {
    cons = cl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
    fatalError("No parameterless constructor");
}

// Instantiate the set
Set<String> s = null;
try {
    s = cons.newInstance();
} catch (IllegalAccessException e) {
    fatalError("Constructor not accessible");
} catch (InstantiationException e) {
    fatalError("Class not instantiable.");
} catch (InvocationTargetException e) {
    fatalError("Constructor threw " + e.getCause());
} catch (ClassCastException e) {
    fatalError("Class doesn't implement Set");
}
// Exercise the set
s.addAll(Arrays.asList(args).subList(1, args.length));
System.out.println(s);

书中这个例子,是一个接口,但是具体的实例却是由反射进行创建的,并且类的名称是通过参数类确定了,这就是反射结合接口的最佳实践。同时这个实践是我们不确定类型的时候,也就是说如果我们确定了类型,最好是使用接口直接引用,因为反射有自己的缺陷。

准则十 明智地使用本地方法

本地方法之前要三思。一般很少需要使用它们来提高性能。如果必须使用本地方法来访问底层资源或本地库,请尽可能少地使用本地代码,并对其进行彻底的测试。本地代码中的一个错误就可以破坏整个应用程序。

准则十一 明智地进行优化

规则 1:不要进行优化。

规则 2 (仅针对专家):还是不要进行优化,也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化。

努力编写 好的程序,而不是快速的程序。为了获得良好的性能而改变 API 是一个非常糟糕的想法。

准则十二 遵守被广泛认可的命名约定

在这里插入图片描述
这个准则其实就是说命名规范,这个可以多看源码,参考源码去命名。

标签:舍入,Java,BigDecimal,Effective,准则,浮点数,使用,类型,Chapter9
From: https://blog.csdn.net/qq_43259860/article/details/141360219

相关文章

  • Java中stream流的filter机制理解
    一开始我并不理解,为什么filter接受一个实现了Predicate接口的对象后,就能通过重写的test方法来筛选元素,这个将筛选后的元素放到新的流中,我并不知道如何实现的。后来我发现ReferencePipeline抽象类中重写了filter方法,并且Collection中的stream方法返回的是StreamSupport.stream方法......
  • java_数组
    1.数组概念:指的是一种容器,可以同来存储同种数据类型的多个值。但是数组容器在存储数据的时候,需要结合隐式转换考虑。比如:定义了一个int类型的数组。那么boolean。double类型的数据是不能存到这个数组中的,但是byte类型,short类型,int类型的数据是可以存到这个数组里面的。建议......
  • 【原创】java+swing+mysql网吧管理系统设计与实现
    个人主页:程序员杨工个人简介:从事软件开发多年,前后端均有涉猎,具有丰富的开发经验博客内容:全栈开发,分享Java、Python、Php、小程序、前后端、数据库经验和实战文末有本人名片,希望和大家一起共同努力,一起进步,顶峰相见。开发背景:随着互联网技术的飞速发展和普及,网络已成为人......
  • JavaScript 中的 闭包
    闭包在JavaScript中,你可以在其他函数内部声明并返回函数。内部函数可以访问在其上方声明的任何变量。functioncreateAdder(a){returnfunctionadd(b){constsum=a+b;returnsum;}}constaddTo2=createAdder(2);addTo2(5)//7addTo2(0)//2......
  • C# Deflate压缩字节数组(和java压缩结果一样)利用zlib.net对文件进行压缩
    今天在对接一个三方接口时,需要传一个附件数据,附件需要使用Deflate压缩算法压缩后的文件二进制流,再进行base64编码后的字符串。刚开始使用的官方自带的System.IO.Compression.DeflateStream方式,结果不管怎么改变CompressionLevel级别,压缩结果始终和java的压缩结果不一致,结果不一致......
  • [Java基础]int的表示范围
    在java基础类型中,int类型占四个字节,而每个字节在内存中占8位(8byte),所以可以使用共4X8=32个位数来存储该类型,也就是四个八位的二进制数,所以有了以下说法在计算机中,它的二级制表示为四个长度为8的二进制数,00000000000000000000000000000000,不了解的同学可以补一下原码,反码和补......
  • java+vue计算机毕设健身信息管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着现代生活节奏的加快,人们对健康生活的追求日益增强,健身已成为许多人日常生活不可或缺的一部分。传统的健身管理方式往往存在效率低下、信息不透明......
  • java+vue计算机毕设紧急救援物资分配系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着自然灾害与突发事件的频发,紧急救援行动的高效性与准确性成为了衡量社会应急管理体系成熟度的重要标志。在这些紧急情况下,救援物资的及时分配直接......
  • java+vue计算机毕设考研论坛网站【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景:在当今社会,随着高等教育的普及和就业竞争的日益激烈,考研已成为众多学子提升自我、追求更高学术成就的重要途径。然而,考研信息的繁杂与分散给考生带来......
  • java+vue计算机毕设科研文献管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着科研活动的日益频繁与深入,科研文献的数量呈爆炸性增长,如何高效地管理、检索和利用这些文献成为科研人员面临的重要挑战。传统的文献管理方式,如纸......