【effective java】45~56.通用程序设计

第45条:将局部变量的作用域最小化
1)在第一次使用某个局部变量的地方进行声明。不然可能出现变量未初始化等问题
2)几乎每个局部变量的声明都应该包含一个初始化表达式。
3)for循环,都允许声明循环变量,它们的总用域被限定在正好需要的范围之内。
4)使用另一个局部变量来限制一个局部变量的迭代范围。
5)使方法小而集中。就是一个方法尽量实现一个功能,两个功能合在一个方法里面会出现变量混用

第46条:for-each循环优先于传统的for循环
for-each循环通过完全隐藏迭代器或者索引变量,避免混乱和出错的可能,适用于集合和数组和任何实现Iterable接口的对象。
使用传统for循环,容易出错:

enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX }
Collection<Face> faces = Arrays.asList(Face.values());
for(Iterator<Face> i = faces.iterator(); i.hasNext();) 
    for(Iterator<Face> j = faces.iterator(); j.hasNext();) 
        System.out.println(i.next() + " " + j.next());//外层循环的变量放到内层执行太多次i.next()了,要剥离出来

无法使用for-each循环的情况:
1.过滤,需要遍历集合并删除选定的元素,需要显式的迭代器,以便调用它的remove方法
2.转换,需要遍历列表或者数组,并取代它部分或者全部元素值,需要列表迭代器或者数组索引,以便设定元素的值
3.平行迭代,并行地遍历多个集合,需要显式地控制迭代器或者索引变量,以便所有迭代器和索引变量都可以得到同步前移(像上述第一个代码 块那样)

第47条:了解和使用类库
1)你不必花时间重复制造这个轮子,拿来用就好
2)标准类库的性能随着时间推移,越来越好(标准类库被反复重写过很多次,性能有显著提升,而且一旦发现缺陷,会在下个版本修正)
3)使用标准类库可以是自己的代码融入主流
4)每个程序猿都应该熟悉 java.lang、 java.util,甚至 java.io,java.util.concurrent

第48条:如果需要精确的答案,请避免使用float和double
地址 https://blog.csdn.net/CharJay_Lin/article/details/81428864

第49条:基本类型优先于装箱的基本类型
地址 https://blog.csdn.net/CharJay_Lin/article/details/81428874

第50条:如果其他类型更适合,则尽量避免使用字符串
1.字符串不适合代替其他的值类型(如果有一段字符串数据从远程传输过来,那么最后对其进行解析保存)
2.字符串不适合替代枚举类型(枚举类型比字符串更加适合用来表示枚举类型的常量)
3.字符串不适合代替聚集类型(如果有一串复合的字符串数据,那么可以对其进行拆分解析为对象或者其他)
4.字符串不适合代替能力键(类型是安全的,String会使两个客户端用同一个值时,共享数据,不安全;用Object还需要转为它收集的值,类型不安全;而ThredLocal类泛型化就安全了)
设计一个提供线程局部变量的机制

public final class ThredLocal<T> {
  public ThredLocal(){};
  public void set(T value);
  public T get();
}

第51条:当心字符串连接的性能
不要使用字符串连接操作符来合并大规模字符串,应该使用StringBuilder的append方法

public static void main(String[] args) {
    long timestrap=System.currentTimeMillis();
    //由于String是final的,不可变,他内部每次拼接都会创建一个StringBuffer对象,这样他创建了5万次对象,性能就比较差了
    String result = "";
    for (int i = 0; i < 10000; i++) {
        result += (i);
    }
    System.out.println("consume:"+(System.currentTimeMillis()-timestrap));//consume:233

    timestrap=System.currentTimeMillis();
    //而StringBuilder只在外面创建了一个对象,其他直接append字符串即可
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++) {
        sb.append(i);
    }
    System.out.println("consume:"+(System.currentTimeMillis()-timestrap));//consume:1
}

第52条:通过接口引用对象
1)这样使用接口而不是类作为参数的类型

List< ? > list= new ArrayList< ? > 
Map< ?,? > map = new HashMap< ?,? > 

2)为什么一般都使用 List list = new ArrayList() ,而不用 ArrayList alist = new ArrayList()呢?
问题就在于List有多个实现类,如 LinkedList或者Vector等等,现在你用的是ArrayList,也许哪一天你需要换成其它的实现类呢?
这时你只要改变这一行就行了:List list = new LinkedList(); 其它使用了list地方的代码根本不需要改动。
假设你开始用 ArrayList alist = new ArrayList(), 这下你有的改了,特别是如果你使用了 ArrayList特有的方法和属性。 如果没有特别需求的话,最好使用List list = new LinkedList(); 便于程序代码的重构. 这就是面向接口编程的好处。

3)但是如果没有合适的接口存在,完全可以使用类而不是接口来引用对象。
(即可以使用父类,基类做引用对象,和接口做引用对象的本质是相同的,都是利用多态,达到方便扩展的目的)
1.值类,比如String和BigInteger.记住值类很少会用多个实现编写,它们通常是final,并且很少有对应的接口.使用值类做参数,变量,域或者返回值类型是再合适不过的。
2.不存在适当接口类型,对象属于框架,而框架的基本类型的类,不是接口.如果对象属于这种基于类的框架,那么就应该使用基类(通常是抽象类)来引用这个对象,而不是实现类。
3.类实现接口,但是它提供接口中不存在的额外方法,如果程序依赖这些额外方法,这种类应该只被用来引用它的实例

第53条:接口优先于反射机制
核心反射机制java.lang.reflect提供了“通过程序来访问关于已装载的类的信息”的能力,给定一个Class实例,可以获得Constructor、Method、Field实例,这些对象提供“通过程序来访问类的成员名称、域类型、方法签名等信息”的能力。

反射机制允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在,存在的代价:
1.失去编译时类型检查的好处,包括异常检查。
2.执行反射访问所需的代码很长。
3.性能上的损失。

反射功能只是在设计时被用到,通常,普通应用程序在运行时不应该以反射的方式访问对象。
有些复杂的应用程序需要使用反射机制,包括类浏览器、对象检测器、代码分析工具、解释型的内嵌式系统。在RPC中使用反射机制也是合适的,这样就不再需要存根编译器。
对于有些程序,必须用到在编译时无法获取的类,但是在编译时存在适当的接口或者超类,通过它们可以引用这个类,就可以以反射的方式创建实例,然后通过它们的接口或者超类,以正常的方式访问这些实例。

创建Set实例,吧命令行参数插入到集合中,然后打印该集合,其中第一个参数指定打印的结果,如果是HashSet以随机的方式打印出来,如果是TreeSet按照字母顺序打印出来的程序:

public static void main(String[] args) {
    String className = "java.util.HashSet";// [bb, ff, a, c, d, f, zs]
    // String className="java.util.TreeSet";//[a, bb, c, d, f, ff, zs]
    String[] dataArr = { "a", "c", "a", "f", "d", "bb", "zs", "ff" };
    Class<?> c = null;
    try {
        c = Class.forName(className);
    } catch (ClassNotFoundException e) {
        System.out.println("Class not found");
    }
    Set<String> s = null;
    try {
        s = (Set<String>) c.newInstance();
    } catch (IllegalAccessException e) {
        System.out.println("Class not accessible");
    } catch (InstantiationException e) {
        System.out.println("Class not instantiable");
    }
    s.addAll(Arrays.asList(dataArr));
    System.out.println(s);
}

反射机制是一种功能强大的机制,对于特定的复杂系统编程任务,它是非常必要的,但它也有一些缺点。如果你编写的程序必须要与编译时未知的类一起工作,如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或者超类。

第54条:谨慎地使用本地方法
本地方法,是指本地程序设计语言(c,或者c++)来编写的特殊方法.
本地方法在本地语言中可移植性任意的计算任务,并且返回到java程序语言.

为什么说谨慎使用本地方法?
1.如果调用了一个本地方法,那么要在主机上配置好运行这个本地方法的环境。(移植困难)
2.如果出现的bug,那么是本地方法出现问题,那么将无法调试(难于理解)
3.想利用本地方法来提高性能做法不提倡,原因是JVM也一直在进步,在变快。(有可能还会降低性能)
4.本地语言不一定是安全的,GC不能管理这部分内存

使用本地方法需要三思,极少情况会使用本地方法来提高性能,如果必须访问本地方法访问资源,那也要尽可能少用本地代码,并且要进行全面测试,只要本地代码出现个bug,对系统都有整体的破坏作用.

第55条:谨慎地进行优化
不要费力去编写快速的程序——应该努力编写好的程序,速度自然会随之而来。在设计系统的时候,特别是在设计API、线路层协议和永久数据格式的时候,一定要考虑性能的因素。当构建完系统之后,要测量它的性能。如果它足够快,你的任务就完成了。如果不够快,则可以在性能剖析器的帮助下,找到问题的根源,然后设法优化系统中相关的部分。第一个步骤是检查所选择的算法:再多的低层优化也无法弥补算法的选择不当。必要时重复这个过程,在每次改变之后都要测量性能,直到满意为止。

第56条:遵守普遍接受的命名惯例
命名规范:参考java开发手册 https://blog.csdn.net/CharJay_Lin/article/details/81269587

猜你喜欢

转载自blog.csdn.net/CharJay_Lin/article/details/81428843
今日推荐