zoukankan      html  css  js  c++  java
  • 《Java面向对象编程(第2版)》读后感

        《Java面向对象编程(第2版)》读完了,全书730+页,前后花了3个月左右才读完。

         这本书作者孙卫琴,我之前买过她的另一本书《Tomcat与Javaweb开发技术详解》,当时觉得讲的浅显易懂,很有收获(我读国外翻译过来的书比较吃力,总是不能很好理解其中的意思),后来考虑要系统学习下JAVA基础知识,就买了她出版的JAVA基础书,总体读下来感觉良好,值得一读。

    第22章:Annotation注解

         Java注解机制允许程序员自定义注解(如MarketTrace),然后在其他Java类中以“@MarketTrace”的形式来引用注解。在程序运用反射机制,可以读取一个Java类的注解。
         除了自定义的注解,JDK还提供了内置的注解:
    1.适用于自定义注解类型的注解:@Target、@Retention、@Documented和@Inherited。
    2.适用于所有Java类型的注解:@Override、@Deprecated和SuppressWarnings。
    以上内置的注解会影响到编译器的编译行为,例如@SuppressWarnings注解可以通知编译器关闭指定类型的警告信息。

    第21章:Java常用类

         本章对一些常用类的用法做了归纳,有以下要点:
    1.Object类是所有Java类的最终祖先,如果一个类在声明时没有包含extends关键字,则编译器创建一个从Ojbect派生的类。
    2.String类、StringBuffer类、StringBuilder类和包装类都是不可变类,但StringBuffer和StringBuilder通过扩展内部数组变量的长度更新存储的值。
    3.对于String类、StringBuffer类和StringBuilder类,字符在字符串中的索引都是从0开始计数。
    4.所有的包装类都可以用和它对应的基本类型数据为参数,来构造他们的实例。
    5.Byte、Short、Integer、Long、Float和Double类以一个字符串作为构造方法的参数时,字符串不能为null,并且该字符串必须可以解析为相应的基本类型的数据,否则虽然编译通过,但运行时会抛出NumberFormatExceptoin。
    6.String、StringBuffer和包装类都重写了toString()方法,返回对象包含内容的字符串表示。
    7.除了Character类和Boolean类以外,包装类都有parseXXX(String str)静态方法,把str字符串转变为相应的基本类型的数据(XXX表示基本数据类型的名称)。参数str不能为null,并且该字符串必须可以解析为相应的基本类型的数据,否则虽然编译通过,但运行时会抛出NumberFormatExceptoin。
    8.Math类是final的,因此没有子类,构造方法是private的,因此Math不能被实例化。
    9.Math类提供额方法都是静态的,可以通过类名直接调用。
    10.BigInteger类能够对任意大小的整数进行精确的数学运算。BigDecimal类能够进行浮点数的精确的加法、减法和乘法运算,对于浮点数的除法运算,可以满足用户指定的精度。
    11.Optional类:能够包装null或者各种类型的对象,提供了安全地判断包装内容是否为空的方法。

    第18章:输入和输出(I/O)

         Java I/O类库对各种常见的数据源、数据汇及处理过程进行了抽象处理。客户程序不必知道最终的数据源或数据汇是一个磁盘上的文件还是一个内存中的数组,都可以按照统一的接口来处理程序的输入和输出。

    第17章:Lambda表达式

         本章介绍了JDK8引入的Lambda表达式,它本质只是一颗让编程人员更加得心应手的“语法糖”,它只存在于Java源代码中,由编译器把它转换为常规的Java类代码。Lambda表达式有点类似于方法,由参数列表和一个使用这些参数的主体(可以是一个表达式或一个代码块)组成。
         Lambda表达式与Stream API联合使用,可以方便地操纵集合,完成对集合中元素的排序和过滤等操作。

    第16章:泛型

         泛型允许在定义类或方法时声明类型参数(例如<T>),当程序访问类或方法时,可以提供明确的类型参数(例如<String>)。泛型主要有两大作用:(1)编译器在编译时能根据类型参数来检查各种赋值操作是否类型兼容,从而避免ClassCastException运行时异常。(2)简化程序代码,不必使用强制类型转换。
    在声明类型参数时,可以通过extends关键字来设定上限,例如<T extends Number>,表示T必须是Number类或者其子类,也可以通过super关键字来设定下线,例如<T super ArrayList>,表示T必须是ArrayList类或者是其父类。
         如果定义了一个Set类型的变量s,它有可能引用TreeSet<String>类型的实例,也可能引用TreeSet<Integer>类型的实例,在这种情况下,可以使用通配符“?”:
    Set<?> set = new TreeSet<String>();
    set = new TreeSet<Integer>();

    第15章:Java集合

         本章介绍了几种常用Java集合类的特性和使用方法。为了保证集合正常工作,有些集合类对存放的对象有特殊的要求,归纳如下:
    1.HashSet:如果集合中对象所属的类重新定义了equals()方法,那么这个类也必须重新定义hashCode()方法,并且保证当两个对象用equals()方法比较的结果为true时,这两个对象的hashCode()方法的返回值相等。
    2.TreeSet:如果对集合中的对象进行自然排序,要求对象所属的类实现Comparable接口,并且保证这个类的compareTo()和equals()方法采用相同的比较规则来比较两个对象是否相等。
    3.HashMap:如果集合中键对象所属的类重新定义了equals()方法,那么这个类也必须重新定义hashCode()方法,并且保证当两个对象用equals()方法比较的结果为true时,这两个对象的hashCode()方法的返回值相等。
    4.TreeMap:如果对集合中的键对象进行自然排序,要求键对象所属的类实现Comparable接口,并且保证这个类的compareTo()和equals()方法采用相同的比较规则来比较两个键对象是否相等。
         HashSet和HashMap具有较好的性能,是Set和Map首选实现类,只有在需要排序的场合,才考虑用TreeSet和TreeMap。LinkedList和ArrayList各有优缺点,如果经常对元素执行插入和删除操作,那么可以用LinkedList,如果经常随机访问元素,那么可以用ArrayList。
         队列(Queue)不支持随机访问元素,只能在队列的末尾添加元素,在头部删除元素。双向队列(Deque)支持在队列的两端添加或删除元素。

    第14章:数组

         Java数组也是一种对象,必须通过new语句来创建。数组可以存放基本类型或引用类型的数据。同一个数组只能存放类相同的数据。用new语句创建了一个数组后,数组中的每个元素都会被自动赋予其数据类型的默认值。例如int类型的数组中所有元素的默认值是0,boolean类型的数组中所有元素的默认值为false,String类型的数组中所有元素的默认值为null。
         数组有一个length属性,表示数组中元素的数目,该属性可以被读取,但是不能被修改。数组中的每个元素都有唯一的索引,它表示元素在数组中的位置。第一个元素的索引为0,最后一个元素的索引为length-1。
         针对数组的常见操作包括排序和查找等。本章介绍了冒泡排序和二分查找算法。为了提高查找效率,本章最后还介绍了哈哈希表,它的元素的值和元素的位置存在固定的对应关系。可以按照特定的算法由元素的值推导出一个哈希码,在一般情况下,可以直接把哈希码作为元素的位置,如果该位置已经存放了其他元素,则需要采取必要的措施来解决哈希冲突,本章介绍的MyHashSet类利用链表来解决这种冲突。
         java.util.Arrays类提供了一系列操纵数组的实用方法,比如为数组填充数据的fill()方法、比较两个数组是否相等的equals()方法、为数组排序的sort()方法和查找数据的binarySearch()方法等。

    第13章:多线程

         本章对线程的运行机制、同步、通信与控制进行了详细的探讨。在面向对象的Java语言中,线程对象是线程向程序提供的接口,程序通过调用线程对象的各种方法来操作线程。所有的线程对象都是Thread类或者子类的实例。在Thread类中主要提供了以下方法:
    currentThread():静态方法,返回当前运行的线程对象的引用。
    start():启动一个线程。
    run():提供线程的执行代码。当run()方法返回时,线程运行结束。
    sleep(int n):静态方法,使当前运行的线程睡眠n毫秒,n毫秒后线程恢复运行。
    yield():静态方法,使当前运行的线程主动把CPU移交给其他处于就绪状态的线程。
    setPriority():设置线程优先级。
    getPriority():返回线程优先级。
    setName():设置线程的名字。
    getName():返回线程的名字。
    isAlive():判断线程是否活着,如果线程已被启动并且未被终止,那么isAlive()返回true。如果返回false,则该线程处于新建或死亡状态。
         当多个线程并发运行时,需要采取同步机制来解决共享资源的竞争问题,以及利用对象的wait()和notify()方法来进行线程之间的通信。
         java.util.concurrent并发包提供了许多实用的处理线程的接口和方法,在编写多线程代码时,可以用Lock外部锁和Condition条件来实现线程的同步和通信,以及利用线程池来提高并发执行多个任务的效率。Callable接口和Future接口支持异步运算,一个线程把运算的结果存放在Future实例中,其他线程从这个Future实例中读取运算结果。

    第12章:内部类

    比较方面 实例内部类 静态内部类 局部内部类
    主要特征  内部类的实例引用特定的外部类的实例  内部类的实例不与外部类的任何实例关联  可见范围是所在的方法
    可用的修饰符  访问控制修饰符,abstract,final  访问控制修饰符,static,abstract,final  abstract,final
    可以访问外部类的哪些成员  可以直接访问外部类的所有成员  只能直接访问外部类的静态成员  可以直接访问外部类的所有成员,并且能访问所在方法的最终或实际上的最终变量和参数
    拥有成员的类型  只能拥有实例成员  可以拥有静态成员和实例成员  只能拥有实例成员
    外部类如何访问内部类的成员  必须通过内部类的实例来访问  对于静态成员,可以通过内部类的完整类名来访问  必须通过内部类的实例来访问

    第11章:对象的生命周期

         对象是程序所处理数据的最主要的载体,数据以实例变量的形式存放在对象中,每个对象在生命周期的开始阶段,Java虚拟机都需要为它分配内存,然后对它的实例变量进行初始化。用new语句创建类的对象时,Java虚拟机会从最上层的父类开始,依次执行各个父类,以及当前类的构造方法,从而保证来自于对象本身,以及父类中继承的实例变量都被正确地初始化。
         当一个对象不被程序的任何引用变量引用时,对象就变成无用对象,它占用的内存就可以被垃圾回收器回收。每个对象都会占用一定的内存,而内存是有限的资源,为了合理地利用内存,在决定对象的生命周期时,应该遵循以下原则:
    1.重用已经存在的对象,尤其是需要经常访问的不可变的对象,可以通过类的静态工厂方法获得已存在的对象,而不是通过new来创建新对象。
    2.当程序不需要再使用一个对象时,应该及时清除对象的引用,以便可以被回收。
    在垃圾回收器的眼里,对象的生命周期开始于在内存中拥有立足之地,结束于它的内存被回收。
    从JDK1.2版开始,对象的引用可分为4个级别:强引用、软引用、弱引用和虚引用。如果一个对象不允许被垃圾回收器回收,则应该持有强引用;如果一个对象可以被垃圾回收器回收,但是在没有被回收之前仍然可以使用,则应该持有软引用或弱引用。一个仅持有虚引用的对象在任何时候都可能被垃圾回收器回收,虚引用与引用队列联合使用,可用来跟踪垃圾回收的过程。

    第10章:类的生命周期

         类的生命周期从类被加载、连接和初始化开始,到被卸载结束。只有当类处于生命周期中时,程序才能使用这个类,比如访问它的静态方法,或者创建它的实例。
    加载过程负责把类的二进制数据读入到Java虚拟机的方法区,并且在堆区创建一个描述这个类的Class对象。连接过程负责验证类的二进制数据,为静态变量分配内存并且初始化为默认值,把字节码流中的符号引用替换为直接引用。初始化过程负责执行类的初始化语句,为静态变量赋予初始值。
         只有当程序首次主动使用一个类,Java虚拟机才会对它初始化,假如类的父亲还没有初始化,那么先初始化父类。只有6种活动被看作是对类的主动使用:创建类的实例、调用类的静态方法、使用某个类或接口的静态变量(不包括编译时常量)、调用Java API中某些反射方法、初始化一个类的子类,以及把一个类表明为启动类。
         类的加载是由类加载器完成的。类的加载采用父亲委托机制,它能增强Java平台的安全,防止用户自定义的加载器加载非法类,去冒充本该由父加载器加载的合法类。每个类加载器都有各自的命名空间,Java虚拟机对不同命名空间中的类的相互可见性做了限制,从而保证不同命名空间中的类即使出现完整名字相同的情况,也不会发生冲突。此外,为了禁止用户自定义的类访问核心类库中的包可见(即默认访问级别)的成员,Java虚拟机还引入了运行时包的机制,通过它来增强对包可见成员的保护。
         对于普通的应用程序,只需要由系统类加载器从classpath中加载用户的Java类,用户一般不需要定制自己的类加载器。对于那些允许用户动态发布应用的服务器程序(比如Tomcat服务器允许用户发布自己的JavaWeb应用),则往往需要创建多个类加载器,从而为服务器程序本身的类库及用户发布的应用的类库提供不同的命名空间,并且便于管理这些类库的.class文件的存放路径。

    第9章:异常处理

         Java的异常处理涉及5个关键字try、catch、finally、throw和throws。异常处理流程由try、catch和finally等3个代码块组成。其中try代码块包含可能发生异常的程序代码;catch代码块紧跟在try代码块后面,用来捕获并处理异常;finally代码块用于释放被占用的相关资源。
         Exception类类表示程序中出现的异常,可分为受检查异常和运行时异常。受检查异常表示只要通过处理,就可能使程序恢复运行的异常。对于方法中可能出现的受检查异常,要么用try-catch语句捕获并处理它,要么用throws语句声明抛出它,Java编译器会对此做检查。运行时异常表示会导致程序终止的异常,Java编译器不会对此做检查。运行时异常通常是由程序代码中的错误造成的,因此要尽量避免它。
         本章最后还总结了一些异常处理的原则,包括:
    1.异常只能用于非正常情况。
    2.为异常提供说明文档。
    3.尽可能地避免异常,尤其是运行时异常。
    4.保持异常的原子性。
    5.避免过于庞大的try代码块。
    6.在catch子句中指定具体的异常类型。
    7.不要在catch代码块中忽略被捕获的异常。
    Java日志操作报用于记录程序运行中产生的日志,有助于调试和检测程序的运行。Logger类是日志记录器,负责生成各种级别的日志,Handler类负责向控制台或文件输出日志,Formatter类指定日志的输出格式,Level类有一系列的静态常量,分别表示各种日志级别。
    Java断言主要在程序调试阶段使用,有利于及时发现程序中的一些缺陷,当程序运行时出现AssertionError错误时,程序员可以根据该错误提示来修改程序代码,确保断言中指定的假设条件真正成立。

    第8章:接口

         接口是构建松耦合软件系统的重要法宝。由于接口用于描述系统对外提供的所有服务,因此接口中的成员变量和方法都是public类型,确保外部使用者能访问它们。接口仅仅描述系统能做什么,但不指明如何去做,所以接口中的方法都是抽象的。接口不涉及和任何具体实例相关的实现细节,因此接口没有构造方法,不能被实例化,没有实例变量。
         接口与抽象类都位于系统的抽象层,但两者有着不同的特点和用处。抽象类的优势在于可以为部分方法系统默认实现,避免子类重复实现它们,并且可以包含实例成员变量,从而提高代码的可重用性。但抽象类的这一优势会使得多继承变得错综复杂。为了简化Java虚拟机的绑定机制,Java语言不支持多继承,即不允许一个类有多个直接父类。
         接口的优势在于一个类可以实现多个接口,使得一个类可以身兼数职,拥有多种功能,提供多种服务。并且在JDK的高版本中,还允许为接口的一些方法提供默认实现,从而提高代码的可重用性。
         可以把接口作为系统中最高层次的抽象类型。站在外界使用者(另一个系统)的角度,接口向使用者承诺提供哪些服务;站在系统本身的角度,接口指定系统必须实现哪些服务。系统间通过接口来交互,实现系统间松耦合。
         抽象类可用来定制系统中的扩展点,可以把抽象类看作是介于“抽象”和“实现”之间的半成品,抽象类力所能及地完成了部分实现,但还有一些功能有待于它的子类来实现。

    第7章:Java语言中的修饰符

         本章介绍了public、protected、private、abstract、final和static修饰符的用法,下面总结了这些修饰符的主要特性。
    一、访问控制修饰符
    1.public的访问级别最高,其次是protected、默认和private。
    2.成员变量和成员方法可以处于4个访问级别中的一个:公开、受保护、默认或私有。
    3.顶层类可以处于公开或默认级别,顶层类不能被protected和private修饰。
    4.局部变量不能被访问控制修饰符修饰。
    二、abstract修饰符
    1.抽象类不能被实例化。
    2.抽象类中可以没有抽象方法,但包含了抽象方法的类必须被定义为抽象类。
    3.如果子类没有实现父类中的所有抽象方法,那么子类也必须定义为抽象类。
    4.抽象类不能被定义为final或static类型。
    5.抽象方法不能被定义为private、final和static方法。
    6.没有抽象构造方法。
    7.抽象方法没有方法体。
    三、final修饰符
    1.用final修饰的类不能被继承。
    2.用final修饰的方法不能被子类方法覆盖。
    3.private类型的方法都默认是final方法,因而不能被子类的方法覆盖。
    4.final类型的变量必须被显式初始化,并且只能被赋值一次。
    四、static修饰符
    1.静态变量在内存中只有一个备份,在类的所有实例中共享。
    2.在静态方法中不能直接访问实例方法和实例变量。
    3.静态方法中不能使用this和super关键字。
    4.静态方法不能被abstract修饰。
    5.静态方法和静态变量都可以通过类名直接访问。
    6.当类被加载时,静态代码块只被执行一次,类中不同的静态代码块按它们在类中出现的顺序被依次执行。
         作为普遍遵守的编程规范,通常把访问修饰符放在首位,其次是static或abstract修饰符,接着是其他修饰符。
    以下修饰符连用是无意义的,会导致编译错误:
    abstract与private;abstract与final;abstract与static。

    第6章:继承

        本章从继承的基本语法开始入手,逐步深入地介绍了方法重载、方法覆盖、多态和使用继承关系的原则。下面对本章的重点进行了归纳:
    一、重载方法必须满足以下条件:
    1.方法名必须相同。
    2.方法的参数签名必须不相同。
    3.方法的返回类型可以不相同。
    4.方法的修饰符可以不相同。
    二、方法覆盖必须满足以下条件:
    1.子类方法的名称及参数签名必须与所覆盖方法相同。
    2.子类方法的返回类型必须与所覆盖方法相同。
    3.子类方法不能缩小所覆盖方法的访问权限。
    4.子类方法不能抛出比所覆盖方法更多的异常。
    三、多态
    1.对于一个引用类型的变量,编译器按照它声明的类型处理。
    2.对于一个引用类型的变量,运行时Java虚拟机按照它实际引用的对象处理。
    3.在运行时环境中,通过引用类型变量来访问所引用对象的方法和属性时,Java虚拟机采用以下绑定规则:实例方法与引用变量实际引用的对象的方法绑定;静态方法与引用变量所声明的类型的方法绑定;成员变量(包括静态变量和实例变量)与引用变量所声明的类型的成员变量绑定。
    四、继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可为维护性。为了尽可能地克服继承的这一缺陷,应该遵循以下原则:
    1.精心设计专门用于被继承的类,继承树的抽象层应该比较稳定。
    2.对于父类中不允许覆盖的方法,采用final修饰符来禁止其被子类覆盖。
    3.对于不是专门用于被继承的类,禁止其被继承。
    4.优先考虑用组合关系来提高代码的可重用性。

    第5章:流程控制

        本章介绍了Java语言中各种流程控制语句的用法:
    1.if else语句:最常用的分支语句。
    2.switch语句:多路分支语句。
    3.while语句:最常用的循环语句,先检查循环条件,再执行循环体。
    4.do while语句:循环语句,先执行循环体,再检查循环条件,这样循环体至少会执行一次。
    5.for语句:先检查循环体,再执行循环语句,通常用于事先确定循环次数的场合。
        if else、while、do while和for语句的条件表达式都必须是布尔表达式,不能为数字类型,switch表达式和case表达式必须是与int类型兼容的基本类型(byte、short、char或int类型)、字符串类型或枚举类型,而且case表达式必须为常量。

    第4章:操作符

        本章介绍了Java语言中各种操作符的用法。
    算式操作符:+,++,-,--,*,/,%
    字符串操作符:+,+=
    位操作符:&(按位与),^(按位异或),|(按位或),~(求反),<<(左移),>>(右移),>>>(逻辑右移,将第一个操作元的二进制形式的每一位向右移位,所移位的数目由第二个操作元指定。忽略移出的位,左面的空位补零),
    逻辑操作符:&&,||,&,|,!
    比较操作符:==,!=,>,>=,<,<=
    赋值操作符:=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=,>>>=
    特殊操作符:?:(三元操作符),.(访问类的属性或方法的操作符,如dog.name),new(创建一个对象并返回对象的引用),instanceof(判断一个对象是否一个类或接口的实例)
        要掌握各种操作符的用法,需要了解以下内容:
    1.操作符的优先级。
    2.操作符的结合性。
    3.运算过程。
    4.操作元的类型。
    5.返回类型。
    6.类型的自动转换和强制转换。

    第3章:数据类型和变量

        本章介绍了Java数据类型的分类、取值范围,以及变量的生命周期等内容。当Java虚拟机开始运行一个Java程序时,它管辖的那块内存空间就是各种变量登台演出的大舞台。在这个舞台上,各种变量来也匆匆,去也匆匆,局部变量最短命,如昙花一现;实例变量附属于实例,与实例本身共存亡;静态变量寿命最长,只要所属的类没有被卸载,就会长居内存中,只到程序运行结束。内存是宝贵的有限资源,合理、有效地利用内存是提高程序运行性能的一个关键因素。每个变量占用多少内存空间、在内存中存在多久,这决定变量命运大权掌握在Java开发人员手中,而Java虚拟机只是一个按部就班的执行者。因此,编写程序时,Java开发人员要为变量确定合理的数据类型和生命周期,总的原则是在保证该变量能正常行使使命的前提下,使它的内存占用尽可能少。
        在内存中只能存放二进制数据,因此各种基本类型的数据在内存中表示为二进制序列
        作为Java编程人员,在操纵各种类型的数据时,多数情况下不必关心这些数据在内存中到底如何存储。但了解这些细节,有助于更好理解和处理以下问题:
    1.不同数据类型之间进行强制转换时导致的精度丢失问题。
    2.浮点数运算造成不精确问题。
    3.字符编码转换问题。

    第2章:第一个Java应用

        本章通过简单的应用实例,介绍了创建、编译、运行和发布Java应用的过程,此外还介绍了生成JavaDoc文档的步骤。应该要了解以下内容:
    1.Java源文件的结构。在一个Java源文件中可以包含一个package语句、多个import语句,以及多个类和接口的声明,只能有一个类或接口是public类型。
    2.Java应用的开发目录结构。目录结构中包含Java源文件根目录(src)、Java类文件根目录(classes)和帮助文档根目录(doc)等。其中Java源文件的存放位置应该和它的package语句声明的包名一致。
    3.JDK中的常用工具的用法。javac、java、jar命令分别用于编译、运行和打包java应用,javadoc命令能够解析Java源文件中的特定注释行,生成JavaDoc文档。

    第1章:面向对象开发方法概述

        本章结合实际例子,探讨了如何运用面向对象思维来构建可维护、可重用和可扩展的软件系统。面向对象的核心思想和概念包括:抽象、封装、接口、多态和继承,灵活运用这些理论武器,就会使得软件系统像用积木搭起来的系统一样,可以方便地进行组装、拆卸和重用。
        尽管本书主要是介绍面向对象的软件开发,在本章开头还对结构化开发做了简要介绍。通过两种开发方式的比较,能够帮助读者更深刻地理解面向对象开发方法所具有的魅力。

    (后期补充读后感)

  • 相关阅读:
    request、bs4爬虫
    1031 查验身份证
    1029 旧键盘
    1028 人口普查
    1027 打印沙漏
    1026 程序运行时间
    1025 反转链表
    1024 科学计数法
    1022 D进制的A+B
    1021 个位数统计
  • 原文地址:https://www.cnblogs.com/atai/p/14716765.html
Copyright © 2011-2022 走看看