一、单项选择题 5. 有如下代码:
public class Test
{
public static void main(String[]args)
{
String s;
System.out.println("s="+s);
}
}
上述代码的输出结果是______。
A.代码得到编译,并输出“s=null” B.代码得到编译,并输出“s=” C.由于String s没有初始化,代码不能编译通过 D.代码得到编译,但捕获到NullPointException异常
A B C D
C
[解析] 在Java语言中,任何变量只有被初始化后才能被使用,如果没有被初始化就直接使用,是无法编译通过的。本题中,由于String s没有初始化,代码不能编译通过。所以,选项C正确。
6. 有如下代码:
public class Test
{
public static void stringReplace(String text)
{
text=text+"c";
}
public static void bufferReplace(StringBuffer text)
{
text=text.append("c");
}
public static void main(String args[])
{
String textString=new String("ab");
StringBuffer textBuffer=new StringBuffer("ab");
stringReplace(textString);
bufferReplace(textBuffer);
System.out.pIintln(textString+textBuffer);
}
}
当编译并运行上面程序时,输出结果是______。
A.编译并运行输出ab ab B.编译并运行输出abc abc C.编译并运行输出ab abc D.编译并运行输出abc ab
A B C D
C
[解析] String和StringBuffer都是类,在方法调用的时候,二者传递的都是引用值(可以理解为传递的是它们的地址),对于String而言,由于String是不可变量,一旦赋值后,它的内容就不能被修改了。 从本质上来讲,引用传递是通过值传递实现的(传递了引用的值,或者可以理解为传递的是对象地址的值),对于本题而言,在调用stringReplace方法的时候,首先把实参textString的值复制给形参text(textString是字符串“ab”的引用,或者可以理解为是“ab”的地址),当在方法stringReplace内执行语句text=text+"c"的时候,相当于创建了一个新的字符串对象“abc”,然后text指向这个字符串对象,这并没有改变实参textString的值,因此,在调用结束后,textString指向的字符串的值还是“ab”。StringBuffer不是不可变量,在调用bufferReplace方法的时候,先把实参的值textBuffer赋值给形参text(字符串“ab”的引用,或可以理解为地址),在调用text.append("c")的时候,会直接对text指向的字符串后面拼接一个字符串“c”,由于text与textBuffer指向同一个字符串,因此,这个对形参的修改也会影响到实参的值,调用结束后textBuffer的值为“abc”。所以,选项C正确。
7. 有如下代码:
public class Test
{
public static void changeStr(String str) { str="world"; }
public static void main(String[]args)
{
String str="hello";
changeStr(str);
System.out.println(str);
}
}
程序的输出是______。
A.hello B.world C.hello world D.world hello
A B C D
A
[解析] 本题中,方法调用的执行过程如图所示。
方法调用执行过程
在调用方法changeStr时,会把实参的值(main方法中的str)赋值给形参(changeStr方法中的str),这个赋值可以理解为把字符串“hello”的引用(或地址)赋值给了形参。在方法changeStr中执行语句str="world"的结果是使得changeStr中的临时变量str指向了另外一个新的字符串,但是这个赋值对实参的值是没有影响的。当方法调用结束后,changeStr方法中的str变量的作用域将结束,因此,也就不存在了,此时,main方法中的str变量的值没有改变,因此,输出的结果为字符串“hello”。所以,选项A正确。
9. 有如下代码:
public class Example
{
String Str=new String("good");
char[]ch={'a','b','c'};
public static void main(String args[])
{
Example ex=new Example();
Ex.change(ex.str,ex.ch);
System.out.print(ex.str+"and");
System.out.print(ex.ch);
}
public void change(String str,char ch[])
{
str="test ok";
ch[0]='0';
}
}
程序的运行结果为______。
A.good and abc B.good and gbc C.test ok and abc D.test ok and gbc
A B C D
11. 以下关于异常的描述中,正确的是______。
A.如果一个方法声明将抛出某个异常,它就必须真的抛出那个异常 B.一旦出现异常,程序运行就终止了 C.在catch子句中匹配异常是一种精确匹配 D.可能抛出系统异常的方法是不需要声明异常的
A B C D
D
[解析] 异常是指程序运行时(非编译时)所发生的非正常情况或错误,当程序违反了语义规则时,JVM就会将出现的错误表示为一个异常并抛出。这个异常可以在catch程序块中进行捕获,然后进行处理。而异常处理的目的则是为了提高程序的安全性与健壮性。
Java语言提供了两种错误的处理类,分别为Error(错误)和Exception(异常),且它们拥有共同的父类:Throwable。
Error表示程序在运行期间出现了非常严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,所以,这种错误是会导致程序终止执行的。此外,编译器不会检查Error是否被处理,因此,在程序中不推荐去捕获Error类型的异常,主要原因是运行时异常多是由于逻辑错误导致的,属于应该解决的错误,也就是说,一个正确的程序中是不应该存在Error的。OutOfMemoryErTor、ThreadDeath等都属于错误。当这些异常发生时,JVM一般会选择将线程终止。
Exception表示可恢复的异常,是编译器可以捕捉到的。它包含两种类型:运行时异常(RuntimeException)和检查异常(Checked Exception)。
1)检查异常是在程序中最经常碰到的异常,所有继承自Exception并且不是运行时异常的异常都是检查异常,比如最常见的IO异常和SQL异常。对于这种异常,都发生在编译阶段,Java编译器强制程序去捕获此类型的异常,即把可能会出现这些异常的代码放到try块中,把对异常的处理的代码放到catch块中。这种异常一般在如下几种情况中使用:
①异常的发生并不会导致程序出错,进行处理后可以继续执行后续的操作。例如,当连接数据库失败后,可以重新连接后进行后续操作。
②程序依赖于不可靠的外部条件,例如系统IO。
2)对于运行时异常,编译器没有强制对其进行捕获并处理。如果不对这种异常进行处理,当出现这种异常时,会由JVM来处理。在Java语言中,最常见的运行时异常有如下几种:NullPointerException(空指针异常)、ArrayStoreException(数据存储异常)、ClassCastException(类型转换异常)、InexOutOfBoundException(数组越界异常)、BufferOverflowException(缓冲区溢出异常)和ArithmeticException(算术异常)等。
出现运行时异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。如果没有处理块,则抛到最上层,如果是多线程就由Thread.run()方法抛出,如果是单线程,就被main()方法抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么整个程序也就退出了。所以,如果不对运行时异常进行处理,后果是非常严重的,一旦发生,要么是线程中止,要么是主程序终止。
在使用异常处理时,还需要注意以下几个问题:
1)Java异常处理用到了多态的概念,如果在异常处理过程中,首先捕获了基类,然后再捕获子类,那么捕获子类的代码块将永远不会被执行。因此,在进行异常捕获的时候,正确的写法是:首先捕获子类,然后再捕获基类的异常信息。如下例所示:
正确的写法
错误的写法
try { //access db code } catch(SQLException e1) { //deal with this exception } catch(Exceptioil e2){}
try { //access db code } catch(Exception e1) { //deal witll this exception } catch(SQLException e2){}
2)尽早抛出异常,同时对捕获的异常进行处理,或者从错误中恢复,或者让程序继续执行。对捕获的异常不进行任何处理是一个非常不好的习惯,这样的代码将非常不利于调试。当然,也不是抛出异常越多越好,对于有些异常类型,例如运行时异常,实际上根本不必处理。
3)可以根据实际的需求自定义异常类,这些自定义的异常类只要继承自Exception类即可。
4)异常能处理就处理,不能处理就抛出。对于一般异常,如果不能进行行之有效地处理,最好转换为运行时异常抛出。对于没有处理的异常,最终JVM会进行处理。
本题中,对于选项A,一个方法声明了抛出一个异常只表明这个方法有可能会抛出这个异常,而不是一定会抛出这个异常。因此,选项A错误。
对于选项B,如果出现的异常被捕获到,并进行相应的处理后,程序可以继续运行,而不会终止。因此,选项B错误。
对于选项C,异常匹配不是一种精确的匹配,使用了多态的概念。假如异常A是异常B的子类,如果有异常A抛出,在捕获异常的代码中,不仅可以匹配异常A,而且也可以匹配异常B。因此,选项C错误。
对于选项D,对于可能抛出的运行时异常,编译器没有强制对其进行声明,只有检查异常(例如IOException),编译器才会强制要求在方法中声明。因此,选项D正确。
12. f()方法定义如下所示,try中可以捕获三种类型的异常,如果在该方法运行中产生了一个IOException,那么,此时的输出结果是______。
public void f(){
try{
//method that may cause an Exception
}
catch(java.io.FileNotFoundException ex){
System.out.print("FileNotFoundException!");
}
catch(java.io.IOException ex){
System.out.print("IOException!");
}
catch(java.lang.Exception ex){
System.out.print("Exception!");
}
}
A.IOException! B.FileNotFoundException!IOException! C.IOException!Exception! D.FileNotFoundException!IOException!Exception!
A B C D
A
[解析] 在Java语言中,通常是通过try/catch来处理异常的,当try块中的代码出现异常后,将会匹配catch块中的异常,一旦匹配成功,就会执行catch块的代码进行异常处理。 通常,可以有多个catch语句,也就是说,可以用来匹配多个异常,但是当每次执行的时候,最多只会匹配一个异常,每当匹配到其中一个异常后,仅会执行catch块中匹配上的那个异常,而其他的catch块将不会被执行。对于本题而言,如果抛出IOException异常后,FileNotFoundException不是IOException的父类,接着异常IOException匹配成功,进入异常处理块输出IOException,然后程序运行结束。所以,选项A正确。
14. 有如下代码:
public class Test
{
public static void main(String[]args)
{
int a[]={0,1,2,3,4};
int sum=0;
try
{
for(int i=0;i<6;i++)
{
sum+=a[i];
}
System.out.println("sum="+sum);
}
catch(java.lang.ArrayIndexOutOfBoundsException e)
{
System.out.println("数组下标越界");
}
finally
{
System.out.println("程序结束")
}
}
}
以上程序的运行结果为______。
A.10程序结束 B.10数组下标越界程序结束 C.数组下标越界程序结束 D.程序结束
A B C D
C
[解析] 本题中,首先定义了长度为5的数组(数组下标范围为0~4),在接下来访问数组的时候,当遍历到下标为5的数组元素时,会抛出ArraylndexOutOfBoundsException异常,从而执行catch块的代码输出:数组下标越界,接着会运行finally块的代码输出:程序结束。所以,选项C正确。
18. 给定一个Java程序的main方法的代码片段如下:
try
{
PrintWfiter out=new PrintWriter(new FileOutputStream("d:/a.txt"));
String name="chen";
out.print(name);
}
catch(Exception e)
{
System.out.println("文件没有发现!");
}
假如d目录下不存在a.txt文件,现运行该程序,下面的结果正确的是______。
A.将在控制台上打印:“文件没有发现!” B.运行后生成abc.txt,该文件内容为chen C.运行后生成abc.txt,但该文件中可能无内容 D.正常运行,但没有生成文件abc.txt
A B C D
A
[解析] 由于文件不存在,因此,在调用new FileOutputStream("d:/abc.txt")时,会抛出FileNotFoundException异常,这个异常是Exception的子类,能匹配Exception从而执行catch块的代码输出“文件没有发现!”。所以,选项A正确。
20. 以下代码是SuperClass和Sub两个类的定义。在序列化一个Sub的对象sub到文件时,下面会被保存到文件中的字段是______。
class SuperClass
{
public String name;
}
class Sub extends SuperClass implements Serializable
{
private float radius;
transient int color;
public static String type="Sub";
}
A.name B.radius C.color D.type
A B C D
B
[解析] 在分布式环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。序列化是一种将对象转换成字节序列的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要的时候把该流读取出来重新构造成一个相同的对象。 如何实现序列化呢?其实,所有要实现序列化的类都必须实现Serializable接口,Serializable接口位于java.lang包中,它里面没有包含任何方法。使用一个输出流(例如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,紧接着,使用该对象的writeObject(Object obj)方法就可以将obj对象写出(即保存其状态),要恢复的时候可以使用其对应的输入流。 具体而言,序列化有如下几个特点: 1)如果一个类能被序列化,那么它的子类也能够被序列化。 2)由于static(静态)代表类的成员,transient(Java语言关键字,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持)代表对象的临时数据,因此,被声明为这两种类型的数据成员是不能够被序列化的。 3)子类实现了Serializable接口,父类没有,父类中的属性不能序列化,但是子类中的属性仍能正确序列化。 通过以上分析可知,只有选项B是正确的。
23. 有以下一个对象:
import java.io.Serializable;
public class DataObject implements Serializable
{
private static int i=0;
private String word=" ";
public void setWord(String word)
{
this.word=word;
}
public void setI(int i)
{
DataObject.i=i;
}
}
创建一个如下方式的DataObject:
DataObject object=new DataObject();
object.setWord("123");
object.setI(2);
将此对象序列化,并在另一个JVM中读取文件,进行反序列化,此时读出的DataObject对象中的word和i的值分别是______。
A.“”,2 B.“”,0 C.“123”,2 D.“123”,0
A B C D
D
[解析] Java序列化指的是把Java对象转换为字节序列的过程,而Java反序列化指的是把字节序列恢复为Java对象的过程。由于Java语言在序列化的时候不会序列化static变量,因此,上述代码只实例化了变量word,而没有实例化变量i。在反序列化的时候,只能读取到变量word的值,而变量i的值仍然为默认值,该默认值为0。所以,word的值为“123”,i的值为0,选项D正确。
三、填空题 1. 有如下代码:
public class Test
{
public static void main(String[]args)
{
String a="hello";
change(a);
System. out. println(a);
}
public static void change(String name) {name="world";}
}
程序的运行结果是______。
“hello”。
[解析] 本题中,在调用change方法的时候,传递的是字符串a的引用(或地址),此时,name与a指向同一个字符串,也就是说,对于字符串a的地址而言,这个方法调用是值传递。而在方法change内部对这个传递的地址(值)进行修改,也就是修改了name的指向,这个修改对实参是没有影响的,因此,程序的运行结果为“hello”。
2. 下面程序的运行结果是______。
public class Test
{
public static void main(String args[])
{
String str1="Hello";
String str2="world";
System.out.println(new Str(str1,str2));
System.out.println(str2);
}
}
class Str
{
String s1;
String s2;
Str(String str1,String str2)
{
S1=str1;
s2=str2;
str2+=str1;
}
public String toString() { return s1+s2; }
}
Hello world world
[解析] 本题中,在调用new Str(str1,str2)的时候,初始化了一个临时的Str的实例(s1="Hello!",s2="world"),当调用System.out.println方法输出这个对象的时候,会调用这个对象的toString方法,返回s1+s2,显然,返回值为Hello world。在这个过程中,str2的值被初始化以后就没有被修改过,因此,接下来输出str2的值:world。
四、论述题 1. switch是否能作用在byte上?是否能作用在long上?是否能作用在String上?
switch能作用在byte上,不能作用在long上,从Java7开始可以作用在String上。 switch语句用于多分支选择,在使用switch(expr)的时候,expr只能是一个枚举常量(内部也是由整型或字符类型实现)或一个整数表达式,其中,整数表达式可以是基本数据类型int或其对应的包装类Integer,当然也包括不同的长度整型,例如short。由于byte、short和char都能够被隐式地转换为int类型,因此,这些类型以及它们对应的包装类型都可以作为switch的表达式。但是,long、float、double和String类型由于不能够隐式地转换为int类型,因此,它们不能被用作switch的表达式。如果一定要使用long、float或double作为switch的参数,必须将其强制转换为int型才可以。 例如,以下对switch中参数的使用就是非法的。 float a=0.123; switch(a)//错误!a不是整型或字符类型变量 { ... } 另外,与switch对应的是case语句,case语句之后可以是直接的常量数值,例如1、2,也可以是一个常量计算式,例如1+2等,还可以是final型的变量(fina1变量必须是编译时的常量),例如fina1 int a=0,但不能是变量或带有变量的表达式,例如i*2等。当然,更不能是浮点型数,例如1.1,或者1.2/2等。 switch(form Way) { case 2-1: //正确 ... case a-2: //错误 ... case2.0: //错误 ... } 随着Java语言的发展,在Java7中,switch开始支持String类型了。
2. 下面程序是否存在问题?如果存在,请指出问题所在,如果不存在,说明输出结果。
public class Test
{
public static void main(String[]args)
{
String str=new String("good");
char[]ch={'a', 'b', 'c'};
Test ex=new Test();
ex. change(str,ch);
System.out.print(str+"and");
System.out.print(ch);
}
public void change(String str,char ch[])
{
str="testok";
ch[0]='g';
}
}
不存在问题,输出结果为goodandgbc。
[解析] 在调用change方法时,str和cb传递的都是引用,在方法中修改了ch指向对象的内容,由于形参与实参指向相同的对象,因此,通过形参对对象内容的修改对实参是可见的。对于str来说,修改的是引用本身,也就是说,修改的是引用的值,而不是修改了引用指向的内容。形参引用的值是实参引用值的一个拷贝;从这个角度来讲,引用传递是通过传递引用的值来实现的,可以理解为值传递,对引用本身的修改对实参是不可见的。
3. 对于一些敏感的数据(例如密码),为什么使用字符数组存储比使用String更安全?
在Java语言中,String是不可变类,它被存储在常量字符串池中,从而实现了字符串的共享,减少了内存的开支。正因为如此,一旦一个String类型的字符串被创建出来,这个字符串就会存在于常量池中直到被垃圾回收器回收为止。因此,即使这个字符串(比如密码)不再被使用,它仍然会在内存中存在一段时间(只有垃圾回收器才会回收这块内容,程序员没有办法直接回收字符串)。此时有权限访问memory dump(存储器转储)的程序都可能会访问到这个字符串,从而把敏感的数据暴露出去,这是一个非常大的安全隐患。如果使用字符数组,一旦程序不再使用这个数据,程序员就可以把字符数组的内容设置为空,此时这个数据在内存中就不存在了。从以上分析可以看出,与使用String相比,使用字符数组,程序员对数据的生命周期有更好的控制,从而可以增强安全性。
4. 语句String s="Hello world!"声明了什么?
也许大部分人都会认为这个语句只是创建了一个字符串而己,这种回答不是非常准确。 其实,对于String s="Hello world!"这条语句,要分以下两种情况来讨论: (1)在此之前没有定义过字符串常量“Hello world!” 对于这种情况,这行代码其实做了两件事:一是在常量区创建了字符串“Hello world!”,二是声明了一个字符串对象的引用s,这个变量s引用的是常量区中的字符串常量“Hello world!”。这行代码可以等价为以下两行代码: String s; //声明一个字符串对象的引用 s="Hello world!"; //让s指向字符串常量“Hello world!” (2)在此之前已经定义过字符串常量“Hello world!” 如果在执行这行代码之前,已经定义过字符串常量“Hello world!”,那么这行代码不会创建新的字符串,只创建一个字符串对象的引用s,让s指向常量区中已经存在的字符串常量“Hello world!”。
5. 至少写出String中的10个常用方法。
String中的常用方法见下表。
String中的常用方法
返回值
方法名
方法描述
boolean
startsWith(String prefix)
判断此字符串是否以指定的前缀开始
String
substring(int beginlndex)
返回一个新的字符串,它是此字符串的一个子字符串
String
trim()
返回字符串的副本,忽略前导空白和尾部空白
static String
valueOf(char[]data)
返回char数组参数的字符串表示形式
char
charAt(int index)
返回字符串在位置index处的字符
String
concat(String str)
将指定字符串连到此字符串的结尾
boolean
contains(CharSequence s)
当且仅当此字符串包含char值的指定序列时,才返回true
boolean
endsWith(String suffix)
判断此字符串是否以指定的后缀结束
byte[]
getBytes()
使用平台默认的字符集将此String解码为字节序列,并将结果存储到一个新 的字节数组中
int
indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引
mt
length()
返回此字符串的长度
String[]
split(String regex)
根据给定的正则表达式的匹配来拆分此字符串
6. 怎样实现将GB2312编码的字符串转换为ISO8859-1编码?
编码是一个非常重要的概念,尤其在网络传输中。通过网络传输一个字符串的流程如下:发送方把字符串转换成byte序列进行传输,接收方接收到byte序列后,需要把byte序列转换成字符串。也许有人认为这个工作非常简单,假如要传输字符串string s="中国";,只需要调用s.getBytes()方法就可以把字符串转换为byte数组进行发送,接收方接收到这个byte数组b后,再调用new String(b)就可以转换为字符串。getBytes()方法采用系统默认的编码方式把字符串转换为byte数组,new String(b)采用系统默认的编码方式把byte数组转换为字符串。采用这种方法,当发送方与接收方所在系统默认的编码方式不同时,就会出现乱码。因此,在这种情况下需要使用另外一套接口来显式地指定编码方式,假设字符串采用utf-8的编码方式,可以采用下面的写法保证在任何情况下都能正确运行: 发送方通过调用s.getBytes("utf-8")方法使用utf-8编码方式把字符串编码为字节流,接收方接收到字节流后,使用new String(b, "utf-8")方法进行解码,转换为字符串。 当然,各个编码之间可以相互转换,只不过以A方式编码的字符串转换为以B方式编码的内容后可能会有乱码,如下代码的功能是把GB2312编码的字符串转换为ISO8859-1编码: public class Test { public static void main(String[]args)throws Exception { //这是一个用GB2312编码的字符串 String gb=new String("中国".gecBytes(),"gb2312"); System.out.println(gb); //把GB2312编码的字符串改为用ISO8859-1编码 //需要先用GB2312解码,然后用ISO8859-1编码,采用新的编码可能会有乱码 String ios=new String(gb.getBytes("gb2312"),"ISO-8859-1"); System.out.println(ios); //再转换回用GB2312编码,不会有乱码 gb=new String(ios.getBytes("ISO-8859-1"),"gb2312"); System.out.println(gb); } } 程序的运行结果为: 中国 ???..2 中国
7. 为什么要把String设计为不变量?
在Java编程中,String是一个非常重要的类,几乎在所有的项目中都会被用到,因此,String的性能就非常重要。鉴于此,String被设计为一个不可变量。具体而言,String被设计为不可变量有如下几个重要的原因: 1)节省空间:在Java语言中,为了提供效率与空间使用率,把字符串常量存储在Stringpool(池)中,这些字符串可以被共享,为了保证一个用户对字符串的修改不会影响到其他用户的使用,String被设计为不可变量。 2)提高效率:正是由于String会被不同的用户共享,在多线程编程时,String可能会被不同的线程共享,如果把String设计为不可变量,那么它就是线程安全的,就不需要对String进行同步,可以显著提高多线程的效率。此外,String会经常被当作HashMap的key进行存储,也就需要计算String的hash值,如果String被设计为不可变量,它的hash值也会保持不变,就可以把它的hash值缓存起来,而不需要每次都计算hash值,可以显著地提高效率。 3)安全因素:由于String会经常被用作参数来使用(例如连接数据库时使用的用户名、密码,读写文件时使用的文件名等),如果String是可变量,黑客可以通过某种特定的手段对这些参数进行修改,从而修改文件的内容或属性,造成安全隐患。为了安全,String被设计为不可变量,那么黑客就无法对字符串参数的内容进行修改了。
8. 请给出Java异常类的继承体系结构,以及Java异常的分类,且为每种类型的异常各举几个例子。
Java语言提供了两种错误的处理类,分别为Error和Exception,且它们拥有共同的父类:Throwable。
它们的继承关系如图所示。
Java异常的继承关系
由于有很多种检查异常,这里只画了IOException作为代表。
9. 如何捕获一个线程抛出的异常?
可以通过设置线程的UncaughtExceptionHandler(异常捕获处理方法)来捕获线程抛出的异常,如下例所示: class MyThread extends Thread { public void run() { System.out.println("thread will throw exception"); throw new RuntimeException("My own exception from thread"); } } public class Test { public static void main(String[]args) { Thread.UncaughtExceptionHandler handler= new Thread.UncaughtExceptionHandler(){ public void uncaughtException(Thread th,Throwable ex){ System.out.println("Uncaught exception:"+ex); } }; Thread myThread=new MyThread(); //设置捕获异常的handler myThread.setUncaughtExceptionHandler(handler); myThread.start(); } }程序的运行结果为:thread will throw exception Uncaught exception:java.1ang.RuntimeException:My own exception from thread
10. throw和throws有什么区别?
在Java语言中,异常处理是对可能出现的异常进行处理,以防止程序遇到异常时直接退出(对于需要长时间持续运行的程序来说是不可接受的)或者得到无法预知的结果。用户程序自定义的异常和应用程序特定的异常,必须借助于throws和throw语句来定义抛出异常。 throw语句用来抛出一个异常。其语法如下: throw(异常对象); throw e; throws是方法可能抛出异常的声明。其语法如下: [(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{......} public void doA(int a)throws Exception1,Exception3{......} 当需要显式地抛出一个异常(通常情况下是用户自定义的异常)时,需要使用关键字throw来抛出异常,而关键字throws用来列出一个方法可能会抛出的异常类型。 使用方法如下例所示: class MyException extends Exception{} public class Test{ //告诉方法的调用者这个方法可能会抛出MyException异常 public void f(int i)throws MyException { if(i==1) //当满足条件的时候抛出自定义的异常 throw new MyException(); } } 除了以上强调的一个区别以外,二者还有以下几点异同之处: 1)throws通常出现在函数头;而throw则通常出现在函数体。 2)throws表示出现异常的一种可能性,并不一定会发生这些异常;而throw则是抛出了异常,即如果执行throw,则一定抛出了某种异常。 3)两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
11. 请给出三种打印异常信息的方法。
示例代码如下: public class Test { public static void main(String args[]) { try { int result=1/0; } catch(Exception e) { System.out.println(e); //方法1 System.out.pfintln(e.getMessage()); //方法2 e.pfintStackTrace(); //方法3 } } }
12. FilelnputStream和FileReader有什么区别?
在介绍这两个流的区别之前,首先介绍InputStream和Reader的区别。 InputStream和Reader都可以用来读数据(从文件中读取数据或从Socket中读取数据),最主要的区别如下:InputStream用来读取二进制数(字节流),而Reader用来读取文本数据,即Unicode字符。那么二进制数与文本数据有什么区别呢?从本质上来讲,所有读取的内容都是字节,要想把字节转换为文本,需要指定一个编码方法。而Reader就可以把字节流进行编码从而转换为文本。当然,这个转换过程就涉及编码方式的问题,它默认采用系统默认的编码方式对字节流进行编码,也可以显式地指定一个编码方式,例如“UTF-8”。尽管这个概念非常简单,但是Java程序员经常会犯一些编码的错误,最常见的错误就是不指定编码方式。在读文件或从Socket读取数据的时候,如果没有指定正确的编码方式,读取到的数据可能就会有乱码,进而导致数据丢失。 FileInputStream和FileReader有着类似的区别,它们都用于从文件中读取数据,但是FilelnputStream用于从文件中读取二进制数据(字节流),而FileReader用于从文件中读取字符数据。FileReader继承自InputStreamReader,它要么使用系统默认的编码方式,要么使用InputStreamReader所使用的编码方式。需要注意的是,InputStreamReader缓存了字符编码,因此,在创建InputStreamReader对象以后,如果再对字符编码进行修改将没有任何作用。下面给出一个使用FileInputStream和FileReader的例子: import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; public class Test{ public static void mare(String args[])throws Exception{ //读取字节流 FiMnputStream fis=null; try{ fis=new FilelnputStream("testInput.txt"); intdata=fis.read(); while(data!=-1){ System.out.print(Integer.toHexString(data)); data=fis.read(); } }catch(IOException e){ e.printStackTrace(); }finally{ if(fis!=null) try{ fis.close(); }catch(IOException e){ } } System.out.println(); //用FileReader读取字符 FileReader reader=null; try{ reader=new FileReader("testInput.txt"); intcharacter=reader.read(); while(character!=-1){ System.out.print((char)character); character=reader.read(); } }catch(IOException io){ System.out.println("Failed to read character data from File"); io.printStackTrace(); }finally{ if(reader!=null) try{ reader.close(); }catch(IOException e){ } } } } 程序的运行结果为: 7465737420726561642066696c65 test readfile 从上面的代码可以看出,FileInputStream读取数据的方式是一个字节一个字节地读取,因此,读取速度会比较慢,同时,read方法是一个阻塞方法,它要么读取到一个字节,要么阻塞(等待可被读取的数据),这个方法的返回值为读取到的字节数,当读取到文件结尾的时候,会返回-1。在使用FilelnputStream的例子中,每个循环读取一个字节,然后转换为十六进制字符串输出。FileReader中的read方法每次读取一个字符,直到读取到文件结尾时,这个方法返回-1。
13. 简要介绍一下什么是FileInputStream和FileOutputStream,并给出一个使用的例子。
FilelnputStream用来从文件中读取字节流。FileOutputStream用来向文件中写字节流。使用例子如下: import java.io.*; public class Test { public static void main(String[]args)throws IOException { FileInputStream inputStream=new FileInputStream("Input.txt"); FileOutputStream outputStream=new FileOutputStream("Output.txt",true); byte[]buffer=new byte[2048]; int bytesRead; while((bytesRead=inputStream.read(buffer))!=-1) outputStream.write(buffer,0,bytesRead); inputStream.close(); outputStream.close(); } }
14. 在Java语言中,Socket的连接和建立的原理是什么?
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。在Java语言中,Socket可以分为两种类型:面向连接的Socket (Transmission Control Protocol,TCP,传输控制协议)通信协议和面向无连接的Socket (User Datagram Protocol,UDP,用户数据报协议)通信协议。任何一个Socket都是由IP地址和端口号唯一确定的,如图所示。
Socket通信
基于TCP协议的通信过程如下:首先,Server端Listen(监听)指定的某个端口(建议使用大于1024的端口)是否有连接请求,然后,Client端向Server端发出Connect(连接)请求,紧接着,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了,会话随即产生。Server端和Client端都可以通过Send、Write等方法与对方通信。
Socket的生命周期可以分为三个阶段:打开Socket、使用Socket收发数据和关闭Socket。在Java语言中,可以使用ServerSocket来作为服务端,Socket作为客户端来实现网络通信。
下面给出一个例子,用Socket通信写出客户端和服务器端的通信,要求客户发送数据后能够回显相同的数据。
首先,创建一个名为Server.java的服务端程序代码,如下所示:
import java.net.*;
import java.io.*;
class Server
{
public static void main(String[]args)
{
BufferedReader br=null;
PrintWriter pw=null;
ServerSocket server=null;
try
{
server=new ServerSocket(2000);
Socket socket=server.accept();
//获取输入流
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取输出流
pw=new PrintWfiter(socket.getOutputStream(),true);
String s=br.readLine();//获取接收的数据
pw.println(s);//发送相同的数据给客户端
}
catch(Exception e)
{
e.printStackTrace();
}finally
{
try
{
if(br!=null)br.close();
if(pw!=null)pw.close();
if(server!=null)server.close();
}
catch(Exception e){
}
}
}
}
然后,创建一个Client.java的客户端程序代码,如下所示:
import java.net.*;
import java.io.*;
class Client
{
public static void main(String[]args)
{
BufferedReader br=null;
PrintWriter pw=null;
Socket socket=null;
try
{
socket=new Socket("localhost",2000);
//获取输入流与输出流
br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw=new PrintWriter(socket.getOutputStream(),true);
//向服务器发送数据
pw.println("Hello");
String s=null;
while(true){
s=br.readLine();
if(s!=null)
break;
}
System.out.pfimha(s);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
if(br!=null)br.close();
if(pw!=null)pw.close();
if(socket!=null)socket.close();
}catch(Exception e)
{
}
}
}
}
最后启动服务端程序,然后运行客户端程序,客户端将会把从服务器端转发过来的“Hello”打印出来。
15. JDK和JRE的区别是什么?
JVM(Java Virtual Machine,Java虚拟机)是实现Java跨平台的核心,负责解释执行class文件。 JRE(Java Runtime Environment,Java运行环境)是运行Java程序所必须的环境的集合,包括JVM标准实现以及Java核心类库。在编写Java程序的时候,经常会用到系统的类库,JVM在解释执行class文件的时候,也会用到这些类库。在Java的安装目录下,通常会有bin目录和lib目录(在配置环境变量的时候,也需要把bin目录配置到path中,lib目录配置到classpath中),这里的lib目录下就存放了编写代码或运行代码时需要用到的类库。可以认为bin目录就是JVM,而JVM+lib=JRE。 JDK(Java Development Kit,Java开发工具包)是整个Java的核心,包括Java运行环境(Java Runtime Environment)、许多开发与调试Java工具(包括javac、java、appletviewer、javadoc、jdb、javah、javap等)和Java基础的类库(即Java API,包括rt.jar)。也就是说,JDK=JRE+Java开发工具。