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

  本人最近读完《疯狂java-突破程序员基本功的16课 》读完后,感受对java基础又有了新的认识,在这里总结一下:
1、数组与内存控制
1.1 数组初始化
    java语言的数组是静态的,即数组初始化以后,长度不能够变( 区别,JavaScript数组可变,是动态的)。
    初始化分两种:静态初始化,初始化时由程序员指定每一个数组元素的初始值,系统决定长度。
                            动态初始化,初始化时由程序员指定数组的长度,由系统指定默认值(int为0、boolean为false)。
    初始化=长度+初始值;注意不能同时指定长度又指定初始值。
    数组是一种引用类型的变量(同C语言的指针):数组变量和数组对象组成,即数组变量指向数组对象。java引用类型变量包括数组和对象。
 
 
1.2 使用数组
    多维数组就是一位数组。
 
1.3 小结
 
2、对象与内存控制
java内存管理分为两部分:内存分配和内存回收。
虽然JVM内置了垃圾回收机制回收失去引用的java对象所占的内存;但仍存在内存泄露问题。
2.1 实例变量和类变量
    java变量分为:成员变量和局部变量;
    局部变量分为:形参、方法内的局部变量和代码块内的局部变量;(在方法的栈内存中,做用时间短)
    成员变量分为:实例变量(非静态变量)和类变量(静态变量)。
    (若使用static修饰,成员成为类自己,不然属于类的实例;因此static只能修饰类里的成员,不能修饰外部类、局部变量、局部内部类)
    定义成员变量时,必须采用向前引用。
 
    对实例变量初始化:    1定义实例时指定初始值;
                                       2非静态初始化块中对实例变量指定初始值;
                                       3构造器中对实例变量指定初始值。
    其中,1,2优先3执行,1和2根据代码中的先后顺序优先执行。
 
    类变量的初始化时机:     1 定义类变量时指定初始值;
                                            2 静态初始化块中对类变量指定初始值。
    其中,1,2执行顺序与排列顺序相同。(程序对类变量只初始化一次)    
    初始化实际过程是: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小结
    避免在父类构造器中访问子类实例变量、调用子类实例方法。
 
3、常见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两个接口都是迭代器模式的表明。目的是为了遍历多种数据列表、集合、容器,这些数据列表、集合、容器面向相同的接口编程。
 
 
4、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、表达式中的陷阱
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、流程控制的陷阱
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、面向对象的陷阱
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方法的陷阱
 
8、异常捕捉的陷阱
 
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。
 
9、线性表
10、栈和队列
 
11、树和二叉树
 
12、 经常使用的内部排序
 
十3、程序开发
 
十4、程序调试
 
十5、使用IDE工具
 
十6、软件测试
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性能测试(由性能测试工具完成)
相关文章
相关标签/搜索