《疯狂java-突破程序员基本功的16课 》笔记总结

  本人最近读完《疯狂java-突破程序员基本功的16课 》读完后,感觉对java基础又有了新的认识,在这里总结一下:
一、数组与内存控制
1.1 数组初始化
    java语言的数组是静态的,即数组初始化之后,长度不可以变(区别,JavaScript数组可变,是动态的)。
    初始化分两种:静态初始化,初始化时由程序员指定每个数组元素的初始值,系统决定长度。
                            动态初始化,初始化时由程序员指定数组的长度,由系统指定默认值(int为0、boolean为false)。
    初始化=长度+初始值;注意不能同时指定长度又指定初始值。
    数组是一种引用类型的变量(同C语言的指针):数组变量和数组对象组成,即数组变量指向数组对象。java引用类型变量包括数组和对象。
 
 
1.2 使用数组
    多维数组就是一位数组。
 
1.3 小结
 
二、对象与内存控制
java内存管理分为两部分:内存分配和内存回收。
虽然JVM内置了垃圾回收机制回收失去引用的java对象所占的内存;但仍存在内存泄露问题。
2.1 实例变量和类变量
    java变量分为:成员变量和局部变量;
    局部变量分为:形参、方法内的局部变量和代码块内的局部变量;(在方法的栈内存中,作用时间短)
    成员变量分为:实例变量(非静态变量)和类变量(静态变量)。
    (若使用static修饰,成员成为类本身,否则属于类的实例;所以static只能修饰类里的成员,不能修饰外部类、局部变量、局部内部类)
    定义成员变量时,必须采用向前引用。
 
    对实例变量初始化:    1定义实例时指定初始值;
                                       2非静态初始化块中对实例变量指定初始值;
                                       3构造器中对实例变量指定初始值。
    其中,1,2优先3执行,1和2根据代码中的前后顺序优先执行。
 
    类变量的初始化时机:     1 定义类变量时指定初始值;
                                            2 静态初始化块中对类变量指定初始值。
    其中,1,2执行顺序与排列顺序相同。(程序对类变量只初始化一次)    
    初始化实际过程是:1 系统为成员变量分配内存空间(默认值1、null、false);
                                    2 按顺序对成员变量进行初始化。
    (若分配内存的时候就在构造器里调用成员变量的初始值,值是默认值0,造成结果错误,因为还未来得及初始化变量)
 
2.2 父类构造器
    1)隐式和显式调用(隐式调用无参数的构造器)
            super调用用于显式调用父类的构造器;
            this用于显式调用本类中另一个重载的构造器;
            super和this均只能在构造器中使用,并且都必须作为第一行代码,只能使用其中之一并且最多调用一次。
    2)访问子类对象的实例变量
            问题:java对象由构造器创建的吗?
            答案:错误,构造器只负责对java对象实例变量执行初始化,在构造器之前就已经被分配内存,且初始值为0、null、false。
    3)调用被子类重写的方法
            导致子类的重写方法在子类构造器之前执行,从而访问不到子类的事例变量的正确初始值。          
 
2.3 父子实例的内存控制
    1)继承成员变量和继承方法的区别
            子类的对象中保存了所有父类的实例变量(包括同名实例变量,父类的实例变量被隐藏了);
                                  保存了所有父类的方法(不包括复写的父类同名的方法)。
            当变量编译时类型和运行时类型不同时,通过该变量  访问它引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定;
                                                                                               访问它引用的对象的事例方法时,该方法由它实际所引用的对象决定。
                (eg.   animal a = new dog();  假设animal和dog都含有同名的实例变量name和同名方法getname(),
                               编译时类型aniaml,访问a.name是animal的实例变量name;运行时类型是dog,调用a.getname();是dog的方法setname)
2.4 final修饰符
    final修饰的变量赋初始值之后不能重新赋值;
    final修饰的方法不能被重写;
    final修饰的类不能派生子类(因此一个类不能既被声明为 abstract的,又被声明为final的);
    1)final修饰的变量
        final修饰的实例变量的初始化位置:
            1.定义实例变量时;
            2.非静态初始化块中;
            3.在构造器中(javap编译后发现,本质上均是在构造器中赋值)
        final修饰的类变量的初始化位置:
            1.定义类变量时;
            2.静态初始化块中。(编译后发现,本质是均在静态初始化块中被赋值)
        注:变量由final修饰并且定义时赋值,相当于宏替换,编译时直接把变量替换成初始值。
    2)执行宏替换的变量
     eg.(final String str1="疯狂";
             final String str1="java";
             str1 + str2 == "疯狂java"为true,执行了宏替换,str1指向了字符串池缓存中的串“疯狂”)
        final修饰变量只有在定义时初始化值,才被执行宏替换;在初始化块和构造器中均不执行宏替换。 
    3)final方法不能被重写
    4)内部类中的局部变量                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
        为什么内部类(匿名内部类)访问的局部变量必须使用final修饰?
        答:对于普通局部变量,它的作用域在方法内,方法执行结束,局部变量随之消失;
                但内部类则可能产生隐式的闭包,闭包将脱离它所在的方法继续存在(new thread();),所以内部类扩大了局部变量的作用域,
                若可以随意修改局部变量值,可能引起极大的混乱。
        eg  final String str=”java讲义“;
               new thread(){
                    public void run(){
                        println(str+1);
                    }
                }
2.5小结
    避免在父类构造器中访问子类实例变量、调用子类实例方法。
 
三、常见java集合的实现细节
 
3.1 set和map
    set的底层是由map实现的。
    1)set和map的关系
            map集合的key无序不重复就是set;
            map集合实际上是key和value的关联数组,把map集合的key和value捆绑在一起就是set集合。
    2)hashmap和hashset
            集合存储java对象时,只是保存了java的引用变量。            
            eg对于hashmap,当执行map.put("语文",80);时,系统调用"语文"的hashcode()方法得到hashcode值(每个java对象都有hashcode方法),
                    系统根据hashcode值决定元素的存储位置,系统会自动构建一个table数组保存hashmap的entry。(JDK安装目录下的src.zip中查看源                               文件)
            hashmap之所以能快速存取、取它所包含的entry,完全类似于生活中:不同东西放在不同位置,需要时才能快速找到它。
            hashset是基于hashmap实现的,两者本质是相同的。hashset的集合元素由hashmap的key保存,vlue保存一个静态的object类PRESENT.
            当对象存储在hashmap的key或hashset中时,一定要重写hashcode()和equals(),只有比较的两个对象的hashcode一样才会比较equls()。
    3)treemap和treeset
            treeset的底层使用的存储器就是treemap,treemap采用红黑树的排序二叉树保存map中每个entry(节点)。
            hashmap和hashset集合,像“妈妈放东西”,每个物品放在不同的地点。优点:查找快
            treemap和treeset集合, 像“体育课排队”,按大小个依次排成一队,每个人按身高大小插入。优点:有序    
 
3.2 map和list
    1)map的value()方法
 
    2)map和list的关系
            list相当于所有key都是int类型的map;map相当于索引是任意类型的list。
 
3.3 arraylist和linkedlist
    list由vector、arraylist和linkedlist构成
    1)vector和arraylist的区别
            vector是一个古老的版本,唯一的优点是线程安全的,arrarylist可以取代vector了。
    2)arraylist和linkedlist的实现差异
            list是线性表的数据结构,arraylist是顺序存储的线性表(采用数组存储)、linkedlist是链表存储的线性表(采用Deque双端队列实现,由队列和栈所有特征)。                   
            arraylist的总体性能比linkedlist好一些,尤其是查找某个元素的操作;但是插入和删除操作linkedlist比arratlist性能好,因为基于数组存储插入删除需要移动后面多有的元素。
 
3.4 iterator迭代器
    iterator是一个迭代器接口,java要求各种集合提供一个iterator()方法,用于遍历该集合中的元素。
    迭代器模式:java的iterator和enumeration两个接口都是迭代器模式的代表。目的是为了遍历多种数据列表、集合、容器,这些数据列表、集合、容器面向相同的接口编程。
 
 
四、java的内存回收
4.1 java引用的种类
    1)对象在内存中的状态
            JVM是垃圾回收机制是否回收一个对象的标准在于:是否还有引用变量引用该对象,若没有引用垃圾回收机制就回收它。
            对象在堆内存中的状态分为:可达状态、可恢复状态(系统调用finalize方法进行清理资源,若恢复引用则可达状态,否则不可达状态)、和不可达状态。
            (区别final、finally(异常模块中清理操作)和finalize(清理资源))
    2)强引用
            前面提到的引用均是强引用,内存永远也不会被回收,即使异常内存泄漏。
    3)软引用(SoftReference)
  若内存不足,则被内存回收机制回收,赋值为null。
    4)弱引用(WeakReference)
            比软引用生存期更短,当系统回收机制运行时,不管内存是否足够,总是被回收释放。
            若大量的对象需要使用若引用,可以使用WeakHashMap来保存它们。(所以很少使用WeakReference,因为总是大量对象才会内存泄漏)
    5)虚引用(PhantomReference)
            虚引用不可以单独使用,因为它不能获取引用对象,必须结合ReferenceQueue类(用来保存回收后的对象),执行内存回收机制后,虚引用就会保存到引用队列中。
 
4.2 java的内存泄露
    JVM的垃圾回收机制会自动回收不可达状态对象的内存空间,因为垃圾回收机制实时的监控每个对象的运行状态
    (而JVM不会释放处于可达状态的无用对象的内存导致内存泄漏,所以删除对象后,需要把引用赋值空,让JVM自动回收它们);
    但是C++只能释放指针引用的内存空间,无法控制不可达状态对象,从而更容易发生内存泄漏问题。
 
4.3 垃圾回收机制
    综述:回收不可达状态的对象,清理内存分配、碎片。
    设计:串行回收和并行回收、并发执行和应用程序停止、压缩和不压缩(标记清除)和复制(标记压缩)。
    对内存的分代回收:Young代、Old代、Permanent代。
    Young代:采用复制算法,可达状态的对象数量少复制成本不大。由一个Eden区和两个Survivor区构成。
    Old代:采用标记压缩算法,避免复制大量对象,不会产生内存碎片,因为old代的对象不会很快死掉。Old代空间比Young代大。
    Permanent代:用于装载Class和方法等信息,回收机制不会回收该代中的内容。
    
4.4 内存管理的小技巧
    1尽量使用直接变量
        eg. String str = "hello";(而不是String str = new String("hello");)
    2使用StringBuilder和StringBuffer进行字符串连接
        因为多个字符串连接运算,会产生临时字符串。
    3尽早释放无用对象的引用
        Object obj = new Object();
        ... ...
        obj = null;//若后面存在耗时、耗内存操作,obj可能被回收 
    4尽量少用静态变量
        类和类的静态方法存在permanent永久代里,一旦创建常驻内存。
    5避免在经常调用的方法、循环中创建java对象
        这种不断创建、回收内存的操作,对系统性能影响很大。
    6缓存经常使用对对对象
        缓存器设计的关键是保留大部分已用过的对象。
    7尽量不要使用finalize方法
        垃圾回收机制准备回收该对象之前,会调用该类的finalize方法执行资源整理。
    8考虑使用SoftReference软引用
        当创建长度很大的数组时,使用SoftReference软引用,内存不足时,内存回收机制会回收软引用的对象内存;
        但是使用该引用时要注意,软引用的不确定性,应显示判断对象是否为null;若空则重新创建。
 
五、表达式中的陷阱
5.1 关于字符串的陷阱
    String str="java";字符串池中的字符串对象不会被垃圾回收,再次使用时,无需创建新的字符串。
    str1="hello java" == str2="hello"+"java"; true因为编译时就可以确定该表达式的值;但表达式含方法调用或变量时,编译时就不可以确定,false
    问题:str=“hello"+"java"+"rock"创建了几个字符串对象?答:一个,“hellojavarock”,因为编译时就确定了该表达式。
    String 类是不可变字符串类(str="java";str="hello java";实际上引用变量str一直没变,但引用对象有两个,str指向了"hellojava",这样会造成内存内存泄漏,因为"java"对象失去了引用而没有回收);
    StringBuilder和StringBuffer是可变字符串类(StringBuilder和StringBuffer唯一区别是StringBuffer是线程安全的,大多方法增加了synchronized修饰,但会降低执行效率,所以建议StringBuilder)。
    String 类的比较方法:==比较地址; 重写equals比较内容;compareTo比较大小(实现了Compareble接口),"ax">"abcd" 所以s1.compareTo(s2)>0。
 
5.2 表达式类的缺陷
    强引用类型:变量先声明后使用、只能赋该类型的值。
    表达式类型自动提升:byte->short->int->long->float->double
    复合赋值运算符(+=、-=、*=):short a=a+5 不等价 a+=5;因为a+=5 等价a=(int)a+5,包含隐含类型转换, a+5将表达式转为int类型,在把int型赋值short型出现异常,而a+=5隐式将表达式转为short,再赋值就正确。但                                   要注意超出范围的高位"截断"。
 
5.3 输入法导致的陷阱
    编译时提示"非法字符"是java代码中出现了全角字符(尤其注意全角空格)。
 
5.4 注释字符必须合法
 
5.5 转义字符的陷阱
 
5.6 泛型可能引起的错误
    1)当原始类型的变量赋值给一个带泛型信息的变量时,总是可以通过编译,但编译会把集合元素当成泛型类型处理,当发生类型冲突时会报错。
    eg  List list=new Arraylist();list.add("java");//原始类型就是List<String>
    2)具有泛型信息的对象付给一个没有泛型信息的变量时,多有<>内的类型信息都被抛弃,包括该对象方法中<>内的泛型信息。
    3)泛型数组:java不支持泛型数组。即:List<String>[] lsa=new List<String>[10];
 
5.7 正则表达式的陷阱
    String类支持正则表达式的方法:spilt()、replaceAll()、replaceFirst()和matches;
    使用支持正则表达式的方法,需要注意转义字符:str.split(//.);而不能str.split(.);
 
5.8 多线程的陷阱
    同步非静态方法的同步监视器是this;静态的同步方法监视器是类本身。
    注意静态初始化块启用新线程初始化时,和类本身初始化造成死锁现象,因为主线程和子线程相互等待初始化结束。所以子线程不能初始化变量。
    多线程环境存在危险,需要把访问共同资源的方法使用synchronized同步修饰。(eg pbulic synchronized double getbalance/graw(){})
 
六、流程控制的陷阱
6.1switch语句陷阱
    switch的表达式类型不能是String、long、double、float等基本类型,可以是int、char和enum枚举
 
6.2标签引起的陷阱
 
6.3if语句的陷阱
    小心空语句:  if(a>3);遇到第一个分号就结束执行体。
 
6.4循环体的花括号
    单独一条局部变量定义语句不能放在循环体里,如 for(int i=0;i<9;i++) Cat =new Cat();
    必须放在循环体里定义局部变量:如 for(int i=0;i<9;i++) { Cat =new Cat(); }
 
6.5for语句的陷阱
 
 
5.6foreach循环的循环计数器
 
 
七、面向对象的陷阱
7.1 instanceof运算符的陷阱
    Object obj = "hello";
    String str = (String)obj;//编译和运行时不会报错
    Int a = (Integer)obj;//编译时不会报错,但运行时报错。以内Int 是Object的子类,但String 类型不能强转Integer
    Math m = (Math)str;//编译会报错,因为Math 既不是Object的父类也不是它的子类
 
7.2构造器的陷阱
    构造器并不创建对象,只是进行初始化操作;
    clone方法和反序列化机制复制对象不需要调用构造器;实现clone方法需要两个要求:1)类实现了cloneable借口;2)类提供clone方法;
    eg  Dog a=new Dog("xian");  Dog b=a.clone();     a.equals(b);true    a==b;false
 
7.3持有当前类的事例
 
7.4到底调用哪个重载方法
 
7.5方法重写的陷阱
    子类无法重写父类的private方法(也就是说子类允许存在和父类同名的方法,但是两个方法)
    若不使用控制符(public private protected)修饰类或方法,则该类或方法是包访问控制权限(只能同一个包下的类可以访问)。
 
7.6非静态内部类的陷阱
    非静态内部类限制多;
    静态内部类建议使用,外部类相当于一个包,易于使用
 
7.7static关键字
    Animal a=new Animal(); Animal a2=new Dog(); a.info(); a2.info();注:info方法都是静态的
    结果输出的都是Animal 的info方法,因为静态方法是属于类的,声明类都是Animal。
    限制:静态内部类不能访问外部类的非静态成员;
 
7.8naive方法的陷阱
 
八、异常捕捉的陷阱
 
1)粉红色的是受检查的异常(checked exceptions):必须在编译时必须用try、catch捕捉,否则编译器会报异常;但若try块中没有抛出checked类型的异常,则编译会报错。
2)绿色的异常是运行时异常(runtime exceptions):运行时出现的异常,需要程序员自己决定是否捕捉,如空指针、除0等;若try块中没有抛出runtime类型的异常,编译却不会报错。
3)错误(error):严重的错误,不需要捕捉。
注:处理checked异常的两种方法:1catch中修复该异常;2抛出该异常。
8.1正确关闭资源的方式
    1)使用finally块来关闭物理资源,保证关闭操作总是被执行的;
    2)关闭每个资源前先保证引用该资源的引用变量不为null;  
    3)每个资源使用单独的try..catch块关闭资源,保证关闭资源时引发的异常不会影响其它资源的关闭。
    eg    
finally{
    if(oos != null){
        try{oos.close}catch(Exception){ex.printStackTrace();}
    }
}
 
8.2finally块的陷阱
    1)不管try块是否正常结束(exit(0)),都会执行finally块。
    2)若try块、catch块遇到return语句,不会立即结束该方法,而是会去执行finally块,然后返回该方法处结束;若finally块中也使用了return,则系统会直接在finally块中结束该方法。
    3)若try块、catch块遇到throw语句(同使用return),先去执行finally块,若finally块使用return,则不返回try块、catch块抛出异常,否则返回try块、catch块处抛出异常
 
8.3catch块的用法
   catch执行顺序:先处理小异常,再处理大异常,否则将出现编译错误。
   
8.4实际的修复
    不要在finally或catch块中调用任何可能引起异常的方法,否则不能抛出异常或循环异常,造成StackOverflowError。
 
九、线性表
十、栈和队列
 
十一、树和二叉树
 
十二、常用的内部排序
 
十三、程序开发
 
十四、程序调试
 
十五、使用IDE工具
 
十六、软件测试
16.1概述
    1软件测试分类:
        1)按总体把握分类:
        静态测试:代码审阅、代码分析、文档检测;
        动态测试:结构测试(白盒)、功能测试(黑盒);
        2)按测试工程大小分类:
        单元测试、集成测试、系统测试、用户测试、平行测试;
 
    2常用的bug管理工具:Bugzilla、BugFree、TestDirector、JIRA、ClearQuest;
 
16.2单元测试(属于难度较大的白盒测试)
    1)测试对象:函数;接口、类、对象;
    测试任务:接口测试、局部数据结构测试、边界条件测试、所有独立执行路径测试、各条错误处理路径测试;
    2)单元的逻辑覆盖:
        语句覆盖:设计的测试用例,把每条语句均执行一遍;
        判定覆盖:每个判断去真取假至少执行一次;eg  if(a>10 && b==0){取真}else{取假};
        条件覆盖:使判定条件的每个可能取值至少满足一次;eg:a>10,取真T1;取假F1; b==0取真T2;取假F2;
        判定-条件覆盖:
        路径覆盖:几乎不能覆盖所有路径,一般测试满足判定覆盖即可。
    3)Junit的使用
        annotation注释符:@Test、@Before(初始化方法)、@After(回收资源)
        Assert类是系列断言的集合,提供一些断言方法用于比较是否与期望值匹配。
 
16.3系统测试和自动化测试(利用系统测试和自动化测试工具)
 
16.4性能测试(由性能测试工具完成)
 
posted @ 2016-04-17 19:24  rongyux  阅读(360)  评论(0编辑  收藏  举报