阅读《疯狂Java讲义》笔记

2018-04-22  本文已影响22人  疯震震

背景

古人云:温故而知新。
多回头看看基础总能学到新东西。

正文

第三章 数据类型和运算符

1. String字符串类型不是基本类型,而是引用类型。
2. 强制转换类型:表数范围大 -> 表数范围小 : 缩小转型
3. Java会确保每个字符串常量只有一个,不会产生多个副本:
String s0 = "hello";
String s1 = "hello";
String s2 = "he" + "llo";
System.out.println(s0 == s1);//true
System.out.println(s0 == s2);//true

但是

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); //false

当Java程序直接使用形如“hello”的字符串直接量(包括可以在编译时就计算出来的字符串)时,JVM将会使用常量池来管理这些字符串;当使用new Stirng(“hello”)时,JVM会先使用常量池来管理“hello”直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String(“hello”)一共产生了两个字符串对象。

4. 逻辑运算符:

&:不短路与,作用与&&相同,但不会短路;

|:不短路或,作用与||相同,但不会短路;

&与&&的区别:&总会计算前后两个操作数,而&&先计算左边的操作数,如果左边的操作数为false,则直接返回false,不会再去计算右边的操作数了。

|与||的区别同上。

5. 源代码就是一份文档,源代码的可读性比代码运行效率更重要。因此在这里提醒大家:
  1. 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成;
  2. 不要过多地依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用()来控制表达式的执行顺序。

第四章 流程控制与数组

1. switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种整数类型,枚举类型和java.lang.String类型(从Java7才允许),不能是boolean类型。
2.使用break直接结束外层循环:
public class BreadTest {

    public static void main(String[] args) {
        //外层循环,outer作为标志符
        outer:
        for(int i = 0; i < 5; i++) {
            //内层循环
            for(int j = 0; j < 3; j++) {
                if(j == 1) {
                    //跳出outer标签所标识的循环
                    break outer;
                }
            }
        }
    
    }

}
3. 使用continue直接跳过外层循环:
public class BreadTest {

    public static void main(String[] args) {
        //外层循环,outer作为标志符
        outer:
        for(int i = 0; i < 5; i++) {
            //内层循环
            for(int j = 0; j < 3; j++) {
                if(j == 1) {
                    //忽略outer标签所指定的循环中本次循环所剩下的语句
                    continue outer;
                }
            }
        }
    
    }

}
4. foreach循环注意事项:

使用foreach循环迭代数组元素时,并不能改变数组元素的值,请不要对foreach的循环变量进行赋值。

foreach中的循环变量相当于一个临时变量,系统会把数组元素依次赋给这个临时变量,而这个临时变量并不是数组元素,它只是保存了数组元素的值而已。

5. 看待一个数组时,一定要把数组看成两个部分:一部分是数组的引用,也就是在代码中定义的数组引用变量;还有一部分是实际的数组对象,这部分是在堆内存里运行的,通常无法直接访问它,只能通过数组引用变量来访问。
6. Java有一个让人极易“混淆”的语法,它允许使用对象来调用static修饰的成员变量、方法,但实际上这是不应该的。

static修饰的成员属于类本身,而不属于该类的实例,既然static修饰的成员完全不属于该类的实例,那么就不应该允许使用实例去调用static修饰的成员变量和方法!

所以请牢记一点:Java编程时不要使用对象去调用static修饰的成员变量、方法,而是应该使用类去调用static修饰的成员变量、方法。

7. Java里方法的参数传递方式只有一种:值传递。

所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。

注意容易混淆的:传入的如果是一个引用类型的参数。其实传入的只不过是该引用参数的地址值,只是复制了一个地址值传进去,他们都是指向同一块堆内存区域。

8. 即使在程序中使用局部变量,也应该尽可能地缩小局部变量的作用范围,局部变量的作用范围越小,它在内存里停留的时间就越短,程序运行性能就越好。因此,能用代码块局部变量的地方,就坚决不要使用方法局部变量。

第五章 面向对象

9. 方法重载与方法重写区别:

方法重载主要发生在同一个类的多个同名方法之间;方法重写发生在子类和父类的同名方法之间。

10. 多态

Java引用变量有两个类型:一个是编译时类型;一个是运行时类型。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

当把一个子类对象直接赋给父类引用变量时,运行时调用该引用变量的方法时,其方法行为总是表现出子类的方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。

11. 引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里面包含的方法。例如,通过Object p = new Person()代码定义了一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里面定义的方法。

通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。

12. StringBuilder 和 StringBuffer基本相似,两个类的构造器和方法也基本相同。

不同的是,StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。

因此在通常情况下,如果需要创建一个内容可变的字符串对象,则应该优先考虑使用StringBuilder类。

13. 创建BigDecimal对象时,不要直接使用double浮点数作为构造器参数来调用BigDecimal构造器,否则同样会发生精度丢失的问题

第八章 Java集合

14. 当向HashSet中添加可变对象时,必须十分小心。如果修改HashSet集合中的对象,有可能导致该对象与集合中的其他对象相等(hasCode跟equals都相等),从而导致HashSet混乱,无法准确访问该对象。
15. HashSet子类LinkedHashSet除了根据hashCode值来决定存储位置外,同时使用链表维护元素的次序(按添加顺序)。

LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时讲有很好的性能,因为它以链表来维护内部顺序。

16. 与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。
17. 如果希望TreeSet能正常工作,TreeSet只能添加同一种类型的对象(因为TreeSet需要调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小)。
18. 如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中。对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过CompareTo(Object obj)方法比较是否返回0.
19. 各个Set性能分析:

HashSet的性能总是比TreeSet好(特别是最常用的添加、查询等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序
只有当需要一个保护排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。

HashSet还有一个子类:LinkedHashSet。对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是由于维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。

EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。

Set的三个实现类HashSet、TreeSet和EnumSet都是线程不安全的。通常可以通过Collections工具类的synchronizedSortedSet方法来“包装”该Set集合。

20. 当程序中需要使用到“栈”这种数据结构时,推荐使用ArrayDeque,尽量避免使用Stack--因为Stack是古老的集合,性能比较差。
21. Deque接口和ArrayDeque实现类是双端队列,所以可以当“栈”和“队列”使用。
22. LinkedList同时实现了List接口和Deque接口。
23. ArrayDeque、ArrayList内部以数组的形式来保存集合中的元素,因此随机访问性能比较好;LinkedList内部以链表的形式来保存集合中的元素,因此随机访问性能比较差,但在插入、删除元素时性能比较出色。
24. 再次强调:Set和Map的关系十分密切,Java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
25. 各个Map性能分析:

虽然HashMap和Hastable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HashMap通常比Hashtable要快。

TreeMap通常比HashMap、Hashtable要慢(尤其是插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对(红黑树的每一个节点就是一个key-value对)

使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。
当TreeMap被填充后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。

对于一般的应用场景,程序应该多考虑使用HashMap,因为HashMap正是为快速查询设计的(HashMap底层其实是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。

LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的添加顺序。

IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本相似的实现,只是它使用==而不是equals()方法来判断元素相等。

EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。

26. 各个线性表的性能分析:

Java提供的List就是一个线性表接口,而ArrayList、LinkedList又是线性表的两种典型实现:基于数组的线性表和基于链表的线性表。

Queue代表了队列,Deque代表了双端队列(既可作为队列使用,也可作为栈使用)。

一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作(只需改变指针所指的地址即可)时有较好的性能。

但总体来说,ArrayList的性能比LinkedList的性能要好,因此大部分时候都应该考虑使用ArrayList。

关于使用List集合有如下建议:

  1. 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法get()来遍历集合元素,这样性能更好;对于LinkedList集合,则应该采用迭代器(Iterator)来遍历集合元素。
  2. 如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList、Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。
  3. 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collections将集合包装成线程安全的集合。
27. Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap和TreeMap都是线程不安全的。

Collections类中提供了多个synchronizedXxx()方法可以把它们包装成线程同步的集合。如:synchronizedList()、synchronizedSet()、synchronizedMap()等。

28. 如果Foo是Bar的一个子类型(子类或子接口),而G是一个具有泛型声明的类或接口,那么G<Foo>并不是G<Bar>的子类型。这一点需要注意。
29. 数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或子接口),那么Foo[]依然是G[]的子类型;但G<Foo>不是G<Bar>的子类型。

结尾

持续温习中。。。。。。未完结。。

上一篇下一篇

猜你喜欢

热点阅读