《Java编程思想第四版》学习笔记--12至14章

第十二章 通过异常处理错误


1.异常情形:阻止当前方法或作用域继续执行的问题。
  当抛出异常时:几件事会随之发生,1)使用new再堆上创建异常对象,2)当前执行路径被终止,并且从当前环境中弹出对异常对象的引用,3)异常处理机制接管程序,进入到异常处理程序,将程序从错误状态中恢复或换一种方式运行。
所有标准异常类都有两个构造器:一个是默认构造器,另一个是接收字符串作为参数。
2.捕获异常
try {
   普通程序代码块
}catch(Type id){
   异常处理程序
}
3.异常处理理论有两种基本模型
终止模型
恢复模型
Java支持终止模型,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行.一旦异常抛出,就表名错误无法挽回,也不能回来继续执行.
另一种是恢复模型,异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法.并认为第二次能成功。Java可以把try块放在while循环中,直到得到满意的结果.
4.创建自定义异常
要自己定义异常类,必须从已有的异常类继承。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器。
标准错误流:System.err。通常这比把错误信息输出到System.out要好,因为System.out也许会被重定向。如果把结果送到System.err,它就不会随System.out一起被重定向,这样更容易被用户注意。对于异常类来说,getMessage()方法类似于toString()方法。
5.异常说明
throw。以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。
关键字 throws,要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的异常正确性。
可以声明方法将抛出异常,实际上却不抛出。为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。
被检查的异常:被强制检查的异常。
6.捕获所有异常
通过捕获异常类型的基类Exception就可以处理所有类型的异常.最好将他放在处理程序列表的末尾。
Exception从基类Throwable继承的方法有
     String getMessage(): 详细信息
     String getLocalizedMessage(): 本地语言描述详细信息
     void printStackTrace(): 调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到标准输出
     void printStackTrace(PrintStream): 调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到要输出的流
     void printStackTrace(java.io.PrintWriter): 调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到要输出的流
     Throwable fillInStackTrace(): 用于在Throwable对象的内部记录栈帧的当前状态
7.栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法直接访问.
getStackTrace()方法返回一个由根轨迹中的元素所构成的数组,每一个元素都表示栈中的一帧.元素0是栈顶元素,并且是调用序列中的最后一个方法调用。数组中的最后一个元素和栈底是调用序列中的第一个方法调用.
8.重新抛出异常
重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch字句将忽略.
异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有消息.
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而非重新抛出点的信息.
想要更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的.
9.java标准异常
Throwable用来表示任何可以作为异常被抛出的类。分为两种类型:Error用来表示编译时和系统错误、Exception是可以被抛出的基本类型(经常关心的是这个)。
属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,这些异常都是从RuntimeException中继承而来的.
RuntimeException是 "不受检查异常" ,这种异常属于错误,将被自动捕获.
只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型的异常处理都是由编译器强制实施的.
RuntimeException代表的是编程错误:
1)无法预料的错误.比如你从控制范围之外传递进来的null引用.
2)作为程序员,应该在代码中进行检查的错误.
10.使用finally进行清理
1)Java中使用finally一般把除内存之外的资源恢复到它们的初始状态时.
     异常处理机制会在跳到更高一层的异常处理程序之前,执行finally子句.
2)在return中使用finally
     因为finally子句总是会执行,所以在一个方法中,可以从多个点返回,并且可以保证重要清理工作仍旧会执行.
     return语句返回之前会执行finally子句的代码块.
3)缺憾:异常丢失
     前一个异常还没处理就抛出下一个异常,没有catch捕获异常,使用finally抛出下一个异常.
     在finally中加入return语句,没有用catch语句捕获异常.
11.异常的限制
    不能基于异常说明来重载方法.派生类构造器的异常说明必须包含基类构造器的异常说明.子类抛出的异常要小于父类.
12.构造器
     除了内存的清理之外,所有的清理不会自动发生。
     在创建需要清理的对象之后,立即进入一个try-finally语句块。
13.异常匹配
     抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
    查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序。
14.其它可选方式
1)"被检查异常"强制你在还没准备好处理错误的时候被迫加上catch子句,这就导致了吞食则有害的问题.异常被吞食了.
2)把异常传递给控制台
     最简答而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台.
3)把"被检查的异常"转换为"不检查的异常"
      当我们不知道该怎么处理这个异常,但是也不想把它"吞"了,或者打印一些无用的信息,可以使用异常链的思路解决.
      把"被检查的异常"包装进RuntimeException里面,避免异常被"吞".
15.异常使用指南
在恰当的级别处理问题.(在知道该如何处理的情况下才捕获异常)
解决问题并且重新调用产生异常的方法.
进行少许修补,然后绕过异常发生的地方继续执行.
用别的数据进行计算,以代替方法预计会返回的值.
把当前环境下能做的事情尽量做完,然后把相同的异常重抛到更高层.
把当前环境下能做的事情尽量做完,然后把不同的异常抛到更高层.
终止程序.
进行简化.
让类库和程序更加安全.


第十三章 字符串


1.不可变String
String对象是不可变的,字符串对象作为方法的参数传递时,实际传递的是引用的一个拷贝.该引用所指的对象其实一直待在单一的物理位置上,从未动过.
给String对象赋值本质上是改变该String对象引用的指向.
2.重载 “+” 与StringBuilder
String对象是不可变的,你可以给一个String对象加任意多的别名.因为String对象具有只读特性,所以指向它的任何引用都不可能改变它的值.
不可变性会带来一定的效率问题,为String对象重载的"+"操作符就是一个例子.
String的"+"操作经过编译器优化后是利用的StringBuilder对字符串进行拼接,性能不如直接使用StringBuilder拼接字符串要好.
多个String对象的"+"操作有可能会创建多个StringBuilder来拼接.
3.String,StringBuffer与StringBuilder的区别?
String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
速度:StringBuilder>StringBuffer>String
4.无意识的递归
想打印对象的内存地址,在toString()方法中不能使用this关键字的返回字符串,this关键字会转换为String对象,从而递归调用toString()方法,会抛出异常.
解决方案: 使用super.toString()方法打印内存地址.
5.String上的操作
当需要改变字符串的内容时,String类的方法都会返回一个新的String对象.
同时,如果内容没有发生改变,String的方法只是返回指向原对象的引用而已.这节省了存储空间以及避免了额外的花销。


常用的方法有length(),chatAt(),toCharArray(),contains(),indexof(),substring(),replace(),trim(),valueOf()。
6.System.out.format()
format()和printf()是等价的,都是格式化输出,和System.out.println()是不一样的。
7.Formatter类
格式化输出一些东西,其实主要的应用还是数字、日期、金额、类似超市购物单的小票等。
构造器经过重载可以接受多种输出目的地,比如PrintStream()、OutputStream()和File。
8.格式化说明符
要想控制空格和对齐,需要更加精细复杂的格式修饰符
width:一个域的最小尺寸
precision:最大尺寸
应用于String,代表输出字符的最大数量,
应用于浮点数,表示要显示出来的小数位数,位数过多舍入,太少会在尾部补零,
应用于整数,会触发异常。
9.Formatter转换
有要特殊注意的地方,将一个变量转换为boolean型或者Boolean对象,只要该参数不为null,即使是0,转换的结果依然还是true。
10.String.format()
这是一个静态方法,接受和Formatter.format()一样的参数,最终返回一个String对象
在String.format()内部也是创建一个Formatter对象,使用便捷的String.format()方法更好些
11.正则表达式
String还自带了一个非常有用的正则表达式工具——导入java.util.regex包,用Pattern.cimpile方法编译正则表达式,然后会生成一个Pattern对象,将要检索字符串传入matcher()方法,matcher()方法会生成一个Matcher()对象,它有很多功能可用。
//以-或+开头或二者皆没有,后面跟着一位或者多位数字
(-|\\+)?\\d+
在Java中使用正则表达式, \\ 的意思是要插入一个正则表达式的反斜线, \\\\ 是插入一个普通的反斜线.
量词:
X? X?? X?+    一个或零个X
X* X** X?*    零个或多个X
X+ X+? X++    一个或多个X
split()方法,将字符串从正则表达式匹配的地方切开。
replaceFirst(),replaceAll()替换操作。
start()与end()返回开始或结束的索引。


第十四章 类型信息


1.RTTI:在运行时识别一个对象类型
JAVA在运行时 有时要 识别对象和类的信息这个机制叫RTTI。Java提供了两种机制去做这件事。传统的RTTI 和 反射。
传统的RTTI  假定编译时就已经知道了所有的类型。
反射   允许在运行时发现和使用类型信息
传统的RTTI 在编译期通过Class文件识别类型信息,反射在运行期通过Class文件识别类型信息。
2.Class对象
.类是程序的一部分,每个类都有一个Clas对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用时,动态加载到JVM中的。
Class类的静态方法forName()可以通过传入一个全限定类名(包含包名)返回一个该类的Class类对象引用,此时该类会被加载到内存。
运行时要获得一个类的信息(类型信息)可以通过一个该类的Class对象获得,使用Class.forName()就可以做到,而不必持有该类型的对象通过该对象获得。
如果有了一个实例对象可以调用getClass()方法来获得Class对象。
3.Class类的方法
getName() 获得全限定类名, getCanonicalName()也是获得全限定类名。对于普通类来说,二者没什么区别,只是对于特殊的类型上有点表示差异。
getSimpleName()只获得类名,没有包名。
isInterface()判断是否为接口。
getInterfaces() 返回一个Class对象数组,数组元素是该类实现的接口,元素顺序和实现顺序一致。
getSuperclass() 返回直接基类(不是接口)的Class对象,可以用来发现对象完整的类继续结构。
newInstance()创建该类实例对象并返回,但该类必须要有默认构造器,这个方法相当于一个虚拟构造器。
4.类字面常量
类名.class 就是字面常量,代表的就是该类的Class对象引用。常量需要赋值给变量
简单,安全。 编译期接受检查,不需要像forName一样至于try/catch块中。
加载后不会进行初始化,初始化被延迟到静态方法静态域首次使用时。
类字面常量可用于 类 接口 数组 基本数据类型
基本数据类型也有类字面常量如int.class, 对应的包装器的 Integer.TYPE。
        System.out.println(int.class); //int 
        System.out.println(Integer.TYPE); //int
        System.out.println(Integer.class); //class java.lang.Integer
Integer继承自Number 但 Integer Class 对象不是 Number Class对象的子对象,也就是说Class<Number> cn  = int.class; 是错误的。
5.泛化的Class引用
Class<?> 优于Class 因为Class在编译期不会产生警告,而Class<?>当指向一个非具体的类引用时会产生警告。
6.类型转换前先做检查
instanceof关键字(二元操作符) ,返回一个Boolean值,告诉我们对象是不是某个类或该类派生类的实例,他判断的是类型。
instanceof 不能比较Class对象,对于Class对象使用isAssignableFrom()判断。
动态的instanceof :Class对象的isInstance(Object o)方法判断该Class对象是不是o类的(如果o是class对象所在类则返回true,否则返回false哪怕o是所在类的父类)。
if (cs.isInstance(c)) //如果c是class对象所在类则返回true,否则返回false,哪怕c是所在类的父类
7.反射:用来检查可用方法,并返回方法名。
Class类和java.lang.reflect类库对反射提供了支持。
reflect包中有Field类,Method类,Constructor类,这些类对象由jvm在运行时创建,用来表示未知类里的字段,方法,构造器。
使用Constructor创建新对象,Filed的get() set()方法修改Filed对象关联的字段,Class类的invoke()调用Method关联的方法。
调用Class类的getFileds()返回表示字段的Filed数组,getMethods()返回表示方法的Method数组,getConstructor()返回表示构造器的Constructor数组。
通过以上方法一个匿名对象的类信息便可在运行时被确定下来,再在运行时通过.class文件获得相关信息构造该类。
没有任何方法可以阻止反射调用那些非公共访问权限的方法,哪怕是private方法或者private域。
8.动态代理
基本设计模式之一。其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
9.空对象
引人空对象可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假设所有的对象都是有效的,而不必浪费编程精力去检查null。
通常空对象是单例的,所以你不仅可以用instanceOf来比较,还可以用equals或==来比较。
注意:在某些地方仍然必须测试空对象,这与检查是否为null没有区别,但在很多地方就不必执行额外的测试了,可以直接假设所有对象都是有效的。
空对象的逻辑变体是模拟对象和桩。模拟对象和桩之间的差异在于程度不同。模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,它通常是重量级的,并且经常在测试之间被复用。
10.接口与类型信息
通过反射,可以到达并调用所有方法,甚至是private方法。但是,final域实际上在遭遇修改时依旧安全,运行时系统会在不抛异常的情况下接受任何修改尝试,可实际不会发生任何修改。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值