javajava 面试问题。

深入探讨JAVA中的异常与错误处理
字体:[ ] 类型:转载 时间:
这篇文章详细介绍了JAVA中的异常与错误处理,有需要的朋友可以参考一下
异常与错误:  异常:  在Java中程序的错误主要是语法错误和语义错误,一个程序在编译和运行时出现的错误我们统一称之为异常,它是VM(虚拟机)通知你的一种方式,通过这种方式,VM让你知道,你(开发人员)已经犯了个错误,现在有一个机会来修改它。Java中使用异常类来表示异常,不同的异常类代表了不同的异常。但是在Java中所有的异常都有一个基类,叫做Exception。  错误:  它指的是一个合理的应用程序不能截获的严重的问题。大多数都是反常的情况。错误是VM的一个故障(虽然它可以是任何系统级的服务)。所以,错误是很难处理的,一般的开发人员(当然不是你)是无法处理这些错误的,比如内存溢出。 和异常一样,在Java中用错误类来表示错误,不同的错误类代表了不同的错误。 但是在Java中所有的错误都有一个基类,叫做Error。  综上,我们可以知道异常和错误最本质的区别就是异常能被开发人员处理而错误时系统本来自带的,一般无法处理也不需要我们程序员来处理。  1.一个异常是在一个程序执行过程中出现的一个事件,它中断了正常指令的运行  2.错误,偏离了可接受的代码行为的一个动作或实例  异常的结构分类:  1、运行时异常(未检查异常)  2、编译时异常(已检查异常)  运行异常即是RuntimeE其余的全部为编译异常  在Java中异常Exception和错误Error有个共同的父类Throwable。  Error Exception  runtimeException几个子类  1、 java.lang.ArrayIndexOutOfBoundsException  数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。  2、java.lang.ArithmeticException  算术条件异常。譬如:整数除零等。  3、java.lang.NullPointerException  空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的  属性、计算null对象的长度、使用throw语句抛出null等等  4、java.lang.ClassNotFoundException  找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出  该异常。  对异常的处理:  try{}catch{}  try{}catch{}finally{}无论有无异常finally代码块都会被执行  try{}finally{}也是可以组合使用的但是catch{}finally{}不可以  注意:在继承关系中,子类覆盖父类的方法,抛出异常的范围不能比父类更宽泛  异常的使用  在异常的使用这一部分主要是演示代码,都是我们平常写代码的过程中会遇到的(当然只是一小部分),抛砖引玉吗!  例1. 这个例子主要通过两个方法对比来演示一下有了异常以后代码的执行流程。 代码如下:public static void testException1() {int[] ints = new int[] { 1, 2, 3, 4 };System.out.println("异常出现前");try {System.out.println(ints[4]);System.out.println("我还有幸执行到吗");// 发生异常以后,后面的代码不能被执行} catch (IndexOutOfBoundsException e) {System.out.println("数组越界错误");}System.out.println("异常出现后");}/*output:  异常出现前  数组越界错误  常出现后  */ 代码如下:public static void testException2() {int[] ints = new int[] { 1, 2, 3, 4 };System.out.println("异常出现前");System.out.println(ints[4]);System.out.println("我还有幸执行到吗");// 发生异常以后,他后面的代码不能被执行}  首先指出例子中的不足之处,IndexOutofBoundsException是一个非受检异常,所以不用try...catch...显示捕捉,但是我的目的是对同一个异常用不同的处理方式,看它会有什么不同的而结果(这里也就只能用它将就一下了)。异常出现时第一个方法只是跳出了try块,但是它后面的代码会照样执行的。但是第二种就不一样了直接跳出了方法,比较强硬。从第一个方法中我们看到,try...catch...是一种"事务性"的保障,它的目的是保证程序在异常的情况下运行完毕,同时它还会告知程序员程序中出错的详细信息(这种详细信息有时要依赖于程序员设计)。  例2. 重新抛出异常 代码如下:public class Rethrow {public static void readFile(String file) throws FileNotFoundException {try {BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));} catch (FileNotFoundException e) {e.printStackTrace();System.err.println("不知道如何处理该异常或者根本不想处理它,但是不做处理又不合适,这是重新抛出异常交给上一级处理");//重新抛出异常}}public static void printFile(String file) {try {readFile(file);} catch (FileNotFoundException e) {e.printStackTrace();}}public static void main(String[] args) {printFile("D:/file");}}  异常的本意是好的,让我们试图修复程序,但是现实中我们修复的几率很小,我们很多时候就是用它来记录出错的信息。如果你厌倦了不停的处理异常,重新抛出异常对你来说可能是一个很好的解脱。原封不动的把这个异常抛给上一级,抛给调用这个方法的人,让他来费脑筋吧。这样看来,java异常(当然指的是受检异常)又给我们平添很多麻烦,尽管它的出发点是好的。  例3. 异常链的使用及异常丢失 代码如下:ExceptionA,ExceptionB,ExceptionCpublic class ExceptionA extends Exception {public ExceptionA(String str) {super();}}public class ExceptionB extends ExceptionA {public ExceptionB(String str) {super(str);}}public class ExceptionC extends ExceptionA {public ExceptionC(String str) {super(str);}}  异常丢失的情况: 代码如下:public class NeverCaught {static void f() throws ExceptionB{throw new ExceptionB("exception b");}static void g() throws ExceptionC {try {f();} catch (ExceptionB e) {ExceptionC c = new ExceptionC("exception a");}}public static void main(String[] args) {try {g();} catch (ExceptionC e) {e.printStackTrace();}}}/*exception.ExceptionCat exception.NeverCaught.g(NeverCaught.java:12)at exception.NeverCaught.main(NeverCaught.java:19)*/为什么只是打印出来了ExceptionC而没有打印出ExceptionB呢?这个还是自己分析一下吧!  上面的情况相当于少了一种异常,这在我们排错的过程中非常的不利。那我们遇到上面的情况应该怎么办呢?这就是异常链的用武之地:保存异常信息,在抛出另外一个异常的同时不丢失原来的异常。 代码如下:public class NeverCaught {static void f() throws ExceptionB{throw new ExceptionB("exception b");}static void g() throws ExceptionC {try {f();} catch (ExceptionB e) {ExceptionC c = new ExceptionC("exception a");//异常连c.initCause(e);}}public static void main(String[] args) {try {g();} catch (ExceptionC e) {e.printStackTrace();}}}/*exception.ExceptionCat exception.NeverCaught.g(NeverCaught.java:12)at exception.NeverCaught.main(NeverCaught.java:21)Caused by: exception.ExceptionBat exception.NeverCaught.f(NeverCaught.java:5)at exception.NeverCaught.g(NeverCaught.java:10)... 1 more*/  这个异常链的特性是所有异常均具备的,因为这个initCause()方法是从Throwable继承的。  例4. 清理工作  清理工作对于我们来说是必不可少的,因为如果一些消耗资源的操作,比如IO,JDBC。如果我们用完以后没有及时正确的关闭,那后果会很严重,这意味着内存泄露。异常的出现要求我们必须设计一种机制不论什么情况下,资源都能及时正确的清理。这就是finally。 代码如下:public void readFile(String file) {BufferedReader reader =try {reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));// do some other work} catch (FileNotFoundException e) {e.printStackTrace();} finally {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}例子非常的简单,是一个读取文件的例子。这样的例子在JDBC操作中也非常的常见。(所以,我觉得对于资源的及时正确清理是一个程序员的基本素质之一。)  Try...finally结构也是保证资源正确关闭的一个手段。如果你不清楚代码执行过程中会发生什么异常情况会导致资源不能得到清理,那么你就用try对这段"可疑"代码进行包装,然后在finally中进行资源的清理。举一个例子: 代码如下:public void readFile() {BufferedReader reader =try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work//close readerreader.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}我们注意一下这个方法和上一个方法的区别,下一个人可能习惯更好一点,及早的关闭reader。但是往往事与愿违,因为在reader.close()以前异常随时可能发生,这样的代码结构不能预防任何异常的出现。因为程序会在异常出现的地方跳出,后面的代码不能执行(这在上面应经用实例证明过)。这时我们就可以用try...finally来改造: 代码如下:public void readFile() {BufferedReader reader =try {try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work// close reader} finally {reader.close();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}及早的关闭资源是一种良好的行为,因为时间越长你忘记关闭的可能性越大。这样在配合上try...finally就保证万无一失了(不要嫌麻烦,java就是这么中规中矩)。  再说一种情况,假如我想在构造方法中打开一个文件或者创建一个JDBC连接,因为我们要在其他的方法中使用这个资源,所以不能在构造方法中及早的将这个资源关闭。那我们是不是就没辙了呢?答案是否定的。看一下下面的例子: 代码如下:public class ResourceInConstructor {BufferedReader reader =public ResourceInConstructor() {try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("")));} catch (FileNotFoundException e) {e.printStackTrace();}}public void readFile() {try {while(reader.readLine()!=null) {//do some work}} catch (IOException e) {e.printStackTrace();}}public void dispose() {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}  这一部分讲的多了一点,但是异常确实是看起来容易用起来难的东西呀,java中还是有好多的东西需要深挖的
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具中国领先的IT技术网站
51CTO旗下网站
Java初学者的30个常见问题
本文回答了30个Java入门级初学者的常见问题。 我可以用%除以一个小数吗? a += b 和 a = a + b 的效果有区别吗? 声明一个数组为什么需要花费大量时间? 为什么Java库不用随机pivot方式的快速排序?
作者:爷爷泡的茶来源:博客园| 10:13
本文回答了30个Java入门级初学者的常见问题。 我可以用%除以一个小数吗? a += b 和 a = a + b 的效果有区别吗? 声明一个数组为什么需要花费大量时间? 为什么Java库不用随机pivot方式的快速排序?
1.2 基本数据类型
Q. 为什么 -0/3 结果是 0,而 -0.0/3.0 结果是 -0.0?(注意后边的结果0带负号)
A. 在Java里,整数是用补码表示的。在补码中0只有一种表示方法。另一方面,浮点数则是用 IEEE 标准表示的, 对于0有两种表示方法, 0 和 -0。
Q. 我可以用 % 除以一个小数吗?
A. 当然可以。比如,如果 angle 是一个非负数,那么 angle % (2 * Math.PI) 就会把 angle 转换到 0 到 2 & 之间。
Q. 当 a b 都是基本类型变量时,a += b 和 a = a + b 的效果有区别吗?
A. 当 a 和 b 的类型不同时,那两条语句的效果就可能有区别。 a += b 等同于 a = (int) (a + b),这种情况下可以是 a是int型,b是float型。但是同等情况下 a = a + b 就会编译报错。
1.3 条件语句和循环语句
Q. 为什么判断字符串相等不能使用 == ?
A. 这反映了基础类型(int, double, boolean)和引用类型(String)的区别。
Q. 有没有在什么情况下,一条语句块的花括号不能省略的?
A. 在下面的例子中,第一段代码是合法的,第二段代码会引发编译错误。从技术角度说,那一条语句是一个变量声明,而不是语句,所以会报错。
&for&(int&i&=&0;&i&&=&N;&i++)&{ &&&&int&x&=&5; &} &&&for&(int&i&=&0;&i&&=&N;&i++) &&&&int&x&=&5;&
Q. 在下面的两段代码里,有没有情况,它们的效果不一样?
for&(&init&stmnt&&&boolean&expr&;&&incr&stmnt&)&{ &&&&&body&statements& &} &&&init&stmnt&; &while&(&boolean&expr&)&{ &&&&&body&statements& &&&&&incr&stmnt& &}&
A. 有的。如果在循环块里使用 continue 语句。在for的代码里,计数器会加一;而在while的代码里,因为被continue略过了,计数器不加一。
Q. 某些Java开发人员使用 int a[] 而不是 int[] a 去声明一个数组。这两者有什么区别?
A. 在Java中这两种用法都是合法的,他们的作用都是一样的。前者是在C中的定义数组的方法。后者是JAVA推荐的方法,因为它的写法 int[] 更能表明这是一个 int 的数组。
Q. 为什么数组下标从0 开始 而不是从 1 开始?
A. 这种传统起源于机器语言的编程方法。在机器语言中,数组下标被用来计算元素位置与第一个元素之间的偏移量。如果从1开始的话,计算偏移时还需要做一次减法运算,那是种浪费。
Q. 如果我用 负数 作为数组下标会发生什么事?
A. 下标小于0 或者 大于等于数组长度,JAVA运行时会抛出 ArrayIndexOutOfBoundsException 异常,并且中止程序运行。
Q. 使用数组时还有其他需要注意的陷阱吗?
A. 需要记住,JAVA在你创建一个数组时会去初始化它,所以声明一个数组需要 O(N)的时间。
Q. 既然 a[] 是一个数组,为什么 System.out.println(a) 会打印出一个16进制的数,就像 @f62373 这样,而不是打印出数组的元素?
A. 好问题。这条语句打印出的是 数组在内存中的地址,不幸的是,在绝大多数情况下,这不是你需要的。
1.5 输入输出语句
Q. 我可以从标准input中重新读一次数据吗?
A. 不可以,你只能读一次。
Q. 怎样输入 end-of-file (eof) 符号?
A. 操作系统自动包括它了。
Q. 使用 printf() 时还有哪些用法?
A. 对于整数来说,使用 o 输出八进制,使用 x 输出十六进制。对于浮点数来说,使用 e 或者 g 输出科学计数法形式。
Q. 行结束的符号是什么?
A. 不同的文件系统使用了不同的符号。在 Unix 系统上,新行的符号是 '\n' ;在 Windows 系统上,每一行都有两个字符组成的字符串终结 &\r\n& ;在 Macs 系统上,终结符号是 &\n\r& 。如果要打印行号,可以使用 System.out.println() ,或者使用下面的语句得到当前操作系统下的行结束符:
String&NEWLINE&=&System.getProperty(&line.separator&);&
Q. 下面两种写法,哪一种更有效率?
String&s;&&&&&&&&&&&&&&&&&&&&&&&&& &while&(!StdIn.isEmpty())&{&&&&&&&&while&(!StdIn.isEmpty())&{ &&&&&s&=&StdIn.readString();&&&&&&&&&&&String&s&=&StdIn.readString(); &&&&&...&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&... &}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}&
A. 从效率角度说,两者没有区别。 但是第二种写法更好,因为它限制了变量的作用域。
2.1 函数调用
Q. 当把数组当作函数调用时的参数时,我常常感到疑惑?
A. 是的。你需要牢记传值参数(参数是基本变量类型)和传引用参数(比如数组)之间的区别。
Q. 那为什么不把所有的参数都使用传值的方式,包括对待数组?
A. 但数组很大时,复制数组需要大量的性能开销。因为这个原因,绝大多数变成语言支持把数组传入函数但不复制一个副本&&MATLAB语言除外。
2.3 递归调用
Q. 有没有只能用循环而不能用递归的情况?
A. 不可能,所有的循环都可以用递归替代,虽然大多数情况下,递归需要额外的内存。
Q. 有没有只能用递归而不能用循环的情况?
A. 不肯能,所有的递归调用都可以用循环来表示。比如你可以用while的方式来实现栈。
Q. 那我应该选择哪个,递归的方式 还是 循环的方式?
A. 根据代码的可读性和效率性之间做权衡。
Q. 我担心使用递归代码时的空间开销和重复计算(例如用递归解Fibonacci)的问题。有没有其他需要担心的?
A. 在递归代码中创建大数据类型(比如数组)时需要额外注意,随着递归的推进,内存使用将会迅速增加,由于内存使用增加,操作系统管理内存的时间开销也会增加。
4.2 排序与查找
Q. 为什么我们要花大篇幅来证明一个程序是正确的?
A. 为了防止错误的结果。二分查找就是一个例子。现在,你懂得了二分查找的原理,你就能把递归形式的二分查找改写成循环形式的二分查找。Knuth 教授在 1946年就发表了二分查找的论文,但是第一个正确的二分查找的程序在 1962年在出现。
Q. 在JAVA内建库中有没有排序和查找的函数?
A. 有的。在 java.util.Arrays 中包含了 Arrays.sort() 和 Arrays.binarySearch() 方法。对于Comparable 类型它使用了 归并排序,对于基本数据类型,它使用了快速排序。因为基本类型是值传递,快速排序比归并排序更快而且不需要额外的空间。
Q. 为什么JAVA库不用 随机pivot方式的快速排序?
A. 好问题。 因为某些程序员在调试代码时,可能需要确定性的代码实现。使用随机pivot违背了这个原则。
4.3 栈和队列
Q. 在Java库中有对stacks 和 queues 的实现吗?
A. Java库中内建 java.util.Stack,但是你应该避免使用它如果你需要一个真正的栈的话。因为它是实现了额外的功能,比如访问第N个元素。另外,它也支持从栈底部插入元素,所以它看上去更像是一个队列。尽管实现了这些额外的功能对编程人员是一个加分,可是我们使用数据结构并不只是想使用所有功能,而是需要我们正好需要的那种结构。JAVA对于栈的实现就是一个典型的宽接口的例子。
Q. 我想使用数组来表示一个包含泛型的栈,但是以下代码编译报错。为什么?
private&Item[]&a&=&new&Item[max];& &oldfirst&=&&&
A. 不错的尝试。不幸的是,创建一个泛型数组在 Java 1.5里不支持。你可以使用cast,比如下面的写法:
private&Item[]&a&=&(Item[])&new&Object[max];& &oldfirst&=&&&
根本的原因是JAVA中的数组是&协变的(covariant)&,但是泛型并不是。比如, String[] 是 Object[]的一种子类型,但是 Stack&String&并不是 Stack&Object& 的一种子类型。 许多程序员认为&协变的&数组是JAVA在数据类型方面的一个缺点。但是,如果我们不考虑泛型,&协变的&数组是有用的,比如实现 Arrays.sort(Comparable[]) 方法,然后当参数是 String[]时它也可以被正常调用。
Q. 可不可以在数组上使用 foreach 方式?
A. 可以的(虽然 数组并没有实现 Iterator 接口)。请参考下面的代码:
public&static&void&main(String[]&args)&{ &&&&for&(String&s&:&args) &&&&&&&StdOut.println(s); &}&&
Q. 在 linked list 上使用 iterator 是不是比循环或者递归更有效率?
A. 编译器在翻译时,可能把那种&尾递归&形式翻译成等价的循环形式。所以可能并没有可以被观测到的性能提升。
尾部递归是一种编程技巧。如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归。尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比如f(n, sum) = f(n-1) + value(n) + 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可,之前的可优化删去。
Q. 自动装箱机制会怎么处理下面的情况?
Integer&a&=&null; &int&b&=&a;&
A. 它将返回一个运行时错误。基础类型不允许它对应的装箱类型里的值是null。
Q. 为什么第一组打印的是 true,但是后面两组打印的是 false?
Integer&a1&=&100; &Integer&a2&=&100; &System.out.println(a1&==&a2);&&&&&Integer&b1&=&new&Integer(100); &Integer&b2&=&new&Integer(100); &System.out.println(b1&==&b2);&&&&&Integer&c1&=&150; &Integer&c2&=&150; &System.out.println(c1&==&c2);&&&&
A. 第二组代码打印 false 是因为 b1 和 b2 指向不同的 Integer 对象引用。第一组和第三组依赖于自动装箱机制。 令人意外的第一组打印了 true 是因为在 -128 和 127 之间的值会自动转换成同样的immutable型的Integer 对象。对于超出那个范围的数,Java会对于每一个数创建一个新的Integer对象。
本文翻译自《Introduction to Programming in Java》一书中部分章节的 Q&A 部分。原书地址&
译文链接:【编辑推荐】【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点热点头条头条头条
24H热文一周话题本月最赞
讲师:132001人学习过
讲师:125359人学习过
讲师:91360人学习过
精选博文论坛热帖下载排行
本书是一本非常全面地讲述黑客入侵主动防御技术的网络安全工具书。本书的重点是介绍黑客的攻击手段和提供相应的主动防御保护措施,在组织结...
订阅51CTO邮刊推荐这篇日记的豆列
&&&&&&&&&&&&中国领先的IT技术网站
51CTO旗下网站
Java六大必须理解的问题
对于这个系列里的问题,每个学Java的人都应该搞懂。当然,如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。
作者:Mr_Hannibal来源:Mr_Hannibal的博客| 23:59
对于这个系列里的问题,每个学Java的人都应该搞懂。当然,如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。
问题一:我声明了什么!
String&s&=&&Hello&world!&;&
许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是&Hello world!&。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。
这个语句声明的是一个指向对象的引用,名为&s&,可以指向类型为String的任何对象,目前指向&Hello world!&这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,如果再运行一句:
String&string&=&s;&
我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。
问题二:&==&和equals方法究竟有什么区别?
==操作符专门用来比较变量的值是否相等。比较好理解的一点是:
int&a=10; &int&b=10; &则a==b将是true。 &但不好理解的地方是: &String&a=new&String(&foo&); &String&b=new&String(&foo&); &则a==b将返回false。&
根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为&foo&的字符串,既然是&两个&,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用&==&操作符,结果会是false。诚然,a和b所指的对象,它们的内容都是&foo&,应该是&相等&,但是==操作符并不涉及到对象内容的比较。
对象内容的比较,正是equals方法做的事。
看一下Object对象的equals方法是如何实现的:
boolean&equals(Object&o){ &&return&this==o; &&} &
Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把这个任务留给了类的创建者。
看一下一个极端的类:
Class&Monster{ &private&String& &... &boolean&equals(Object&another){&return&true;} &&} &&
我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返回true。
所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。
问题三:String到底变了没有?
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:
String&s&=&&Hello&; &s&=&s&+&&&world!&;&
s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是&Hello&,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为&Hello world!&,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public&class&Demo&{ &private&String&s; &... &public&Demo&{ &s&=&&Initial&Value&; &} &... &}&
s&=&new&String(&Initial&Value&); &
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。
问题四:final关键字到底修饰了什么?
final使得被修饰的变量&不变&,但是由于对象型变量的本质是&引用&,使得&不变&也有了两种含义:引用本身的不变,和引用指向的对象不变。
引用本身的不变:
final&StringBuffer&a=new&StringBuffer(&immutable&); &final&StringBuffer&b=new&StringBuffer(&not&immutable&); &a=b;&
引用指向的对象不变:
final&StringBuffer&a=new&StringBuffer(&immutable&); &a.append(&&broken!&);&&
可见,final只对引用的&值&(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的&值&相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。
理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它&永远不变&。其实那是徒劳的。
问题五:到底要怎么样初始化!
本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。
1.&类的属性,或者叫值域 &2.&方法里的局部变量 &3.&方法的参数&
对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。
int类型变量默认初始值为0 &float类型变量默认初始值为0.0f &double类型变量默认初始值为0.0 &boolean类型变量默认初始值为false &char类型变量默认初始值为0(ASCII码) &long类型变量默认初始值为0 &所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null。&
对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。
对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!
其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。
问题六:instanceof是什么东东?
instanceof是Java的一个二元操作符,和==,&,&是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:
String&s&=&&I&AM&an&Object!&; &boolean&isObject&=&s&instanceof&O&
我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:
public&class&Bill&{&public&class&PhoneBill&extends&Bill&{&public&class&GasBill&extends&Bill&{&
在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:
public&double&calculate(Bill&bill)&{ &if&(bill&instanceof&PhoneBill)&{ &&} &if&(bill&instanceof&GasBill)&{ &&} &... &}&
这样就可以用一个方法处理两种子类。
然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:
public&double&calculate(PhoneBill&bill)&{ &&}&
public&double&calculate(GasBill&bill)&{ &&}&
所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。
java方向及学习方法
java分成J2ME(移动应用开发),J2SE(桌面应用开发),J2EE(Web企业级应用),所以java并不是单机版的,只是面向对象语言。建议如果学习java体系的话可以这样去学习:
*第一阶段:Java基础,包括java语法,面向对象特征,常见API,集合框架;
*第二阶段:java界面编程,包括AWT,事件机制,SWING,这个部分也可以跳过,用的时候再看都能来及;
*第三阶段:java API:输入输出,多线程,网络编程,反射注解等,java的精华部分;
*第四阶段:数据库SQL基础,包括增删改查操作以及多表查询;
*第五阶段:JDBC编程:包括JDBC原理,JDBC连接库,JDBC API,虽然现在Hibernate比JDBC要方便许多,但是JDBC技术仍然在使用,JDBC思想尤为重要;
*第六阶段:JDBC深入理解高级特性:包括数据库连接池,存储过程,触发器,CRM思想;
*第七阶段:HTML语言学习,包括HTML标签,表单标签以及CSS,这是Web应用开发的基础;
*第八阶段:JavaScript脚本语言,包括javaScript语法和对象,就这两个方面的内容;
*第九阶段:DOM编程,包括DOM原理,常用的DOM元素以及比较重要的DOM编程思想;
*第十阶段:Servlet开发,从此开始踏入java开发的重要一步,包括XML,Tomcat服务器的安装使用操作,HTTP协议简单理解,Servlet API等,这个是java web开发的基础。
*第十一阶段:JSP开发:JSP语法和标签,自定义标签,EL,JSTL库了解以及MVC三层架构的设计模式理念;
*第十二阶段:AJAX开发:AJAX原理,请求响应处理,AJAX开发库;
*第十三阶段:轻量级框架,三大框架之一Struts框架的学习,自此踏入java web开发的精华部分,包括Struts体系架构,各种组件,标签库和扩展性的学习;
*第十四阶段:Hibernate框架学习,三大框架之一,包括检索映射技术,多表查询技术,缓存技术以及性能方面的优化;
*第十五阶段:Spring框架的学习,三大框架之一,包括了IOC,AOP,DataSource,事务,SSH集成以及JPA集成;
*最后呢,还有些java的技术,包括EJB3.0等,可以选择学习,与三大轻量级框架相比,EJB就是当之无愧的重量级了。
原文链接:【编辑推荐】【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点热点头条头条头条
24H热文一周话题本月最赞
讲师:244824人学习过
讲师:218565人学习过
讲师:83035人学习过
精选博文论坛热帖下载排行
Java的出现,实现了跨操作系统平台的程序开发,以Java为基础的J2EE技术已经成为因特网服务技术的主流。然而,以J2EE为基础的SOA架构技术必...
订阅51CTO邮刊

我要回帖

更多关于 八皇后问题java 的文章

 

随机推荐