Java基础知识面试题系列六:51~60题
- 51."=="、equals和hashCode有什么区别
- 52.String、StringBuffer、StringBuilder和StringTokenizer有什么区别
- 53.Java中数组是不是对象
- 54.数组的初始化方式有哪几种
- 55.length属性与length()方法有什么区别
- 56.异常处理的原理是什么
- 57.运行时异常和普通异常有什么区别
- 58.使用异常处理时,需要注意的问题
- 59.下面程序能否编译通过?如果把ArithmeticException换成IOException呢?
- 60.Java中有几种类型的流
51."=="、equals和hashCode有什么区别
==:
- 运算符用来比较两个变量的值是否相等。也就是说,该运算符用于比较变量对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能使用“==”运算符。
- 如果一个变量指向的数据是对象(引用类型),涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存。例如,对于赋值语句String s = new String(),变量s占用一块存储空间,而new String()则存储在另外一块存储空间里。此时,变量s所对应内存中存储的数值就是对象占用的那块内存的首地址。
- 对于指向对象类型的变量,要比较两个变量是否指向同一个对象,即要看这两个变量所对应内存中的数值是否相等(这两个对象是否指向同一块存储空间),这时候就可以用==运算符进行比较。如果要比较两个对象的内容是否相等,用==运算符就无法实现了。
equals:
- equals是Object类提供的方法之一。每个Java类都继承自Object类,所以每一个对象都具有equals这个方法。
- object类中定义的equals(Object)方法是直接使用==运算符比较的两个对象,所以没有覆盖equals(Object)方法的情况下,equals(Object)与==运算法一样,比较的是引用。
- equals(Object)方法的特殊之处就在于它可以被覆盖,所以可以通过覆盖的方法让它不是比较引用而是比较数据内容,例如:String 类的equals方法是用于比较两个独立对象的内容是否相同,即堆中内容是否相同。
示例:
String s1 = new String(“Hello”);
String s2 = new String(“Hello”);
两条new语句创建了两个对象,然后用s1、s2这两个变量分别指向一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不同的,所以表达式a=b将返回false,而这两个对象中的内容是相同的,所以表达式a.equals(b)将返回true。
如果一个类没有自己定义equals()方法,将继承Object类的equals()方法,Object类的equals()方法的实现代码如下:
boolean equals(Object o){
return this==o;
}
通过以上例子可以说明,如果一个类没有自己定义equals()方法,默认的equals()方法就是使用==运算符,也是在比较两个变量指向的对象是否是同一对象,此时使用equals()方法和使用==运算符会得到同样的结果。若比较两个独立的对象,则总返回false。
如果编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么必须覆盖equals()方法,由开发人员自己编写代码来决定在什么情况下即可认为两个对象的内容是相同的。
hashCode():
- hashCode()方法返回对象在内存中地址转换成的一个int值,如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。
- hashCode()方法的返回值和equals()方法的关系如下:如果x.equals(y)返回true,即两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode()方法都必须产生同样的整数结果,如果x.equals(y)返回false,即两个对象根据equals()方法比较是不相等的
,x和y的hashCode()方法的返回值有可能相等,也有可能不相等。
52.String、StringBuffer、StringBuilder和StringTokenizer有什么区别
- Java语言有4个类可以对字符或字符串进行操作,分别是Character、String、StringBuffer和StringTokenizer,其中Character用于单个字符,String用于字符串操作,属于不可变类,而StringBuffer也是用于字符串操作,不同之处是StringBuffer属于可变类。
String:
- String是不可变类,String一旦被创建,其值将不能改变,而StringBuffer是可变类,当对象被创建后仍然可以对其值进行修改。
- String是不可变类,适合在需要被共享的场合中使用,当一个字符串经常需要被修改时,最好使用StringBuffer来实现。
- String来保存一个经常被修改的字符串时,字符串被修改时会比StringBuffer多很多附加的操作,同时生成很多无用的对象,由于这些无用的对象会被垃圾回收器来回收,因此会影响程序的性能。
- String与StringBuffer的另外一个区别在于当实例化String时,可以利用构造函数的方式来对其进行初始化String s1=new String(“world”),也可以使用赋值的方式来初始化String s =“Hello”,而StringBuffer只能用构造函数的方式来进行初始化StringBuffer s = new StringBuffer(“Hello”)。
String字符串修改实现的原理如下:当用String类型来对字符串进行修改时,其实现方法是首先创建一个StringBuilder,其次调用StringBuilder的append()方法,最后调用StringBuilder的toString()方法把结果返回,示例如下:
String s = "Hello";
s += "world";
以上代码等价于下述代码:
StringBuilder sb = new StringBuilder(s);
sb.append("world");
由此可以看出,上述过程比使用StringBuilder多了一些附加的操作,同时也生成了一些临时的对象,从而导致程序的执行效率降低。
- StringBuilder也是可以被修改的字符串,它与StringBuffer类似,都是字符缓冲区,但StringBuilder不是线程安全的,如果只是在单线程中使用字符串缓冲区,那么StringBuilder的效率会更高些。
- 因此只有单线程访问时可以使用StringBuilder,当有多个线程访问时,最好使用线程安全的StringBuffer。因为StringBuffer必要时可以对这些方法进行同步,所以任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
- 在执行效率方面,StringBuilder最高,StringBuffer次之,String最低,一般而言,如果要操作的数据量比较小,应优先使用String类。如果是在单线程下操作大量数据,应优先使用StringBuilder类。如果是在多线程下操作大量数据,应优先考虑StringBuilder类。
StringTokenizer是用来分割字符串的工具类,示例如下:
import java.util.StringTokenizer;
public class StringSplit {
public static void main(String[] args) {
StringTokenizer st = new StringTokenizer("hello do you love me");
while (st.hasMoreTokens()){
String str = st.nextToken();
System.out.println(str);
}
}
}
运行结果如下所示:
hello
do
you
love
me
53.Java中数组是不是对象
- 数组是指具有相同类型的数据的集合,一般具有固定的长度,并且在内存中占据连续的空间。
- Java语言中,数组不仅有其自己的属性,也有一些方法可以被调用。
- 由于对象的特点是封装了一些数据,同时提供了一些属性和方法。
- 从这个角度讲,数组是对象。
- 每个数组类型都有其对应的类型,可以通过instanceof来判断数据的类型。
54.数组的初始化方式有哪几种
- 在Java语言中,一维数组的声明方式为
type arrayName [] 或type[] arrayName
- type即可以是基本的数据类型,也可以是类,arrayName表示数组的名字,[]用来表示这个变量的类型为一维数组。
- 在Java语言中,数组被创建后会根据数组存放的数据类型初始化成对应的初始值(int类型会初始化为0,对象会初始化为null)
- Java数组在定义时,并不会给数组元素分配存储空间,因此[]中不需要指定数组的长度
55.length属性与length()方法有什么区别
- Java语言中,数组提供了length属性来获取数组的长度。
- String提供了length()方法来计算字符串的长度
public class Test{
public static void testArray(int[] arr){
System.out.println("数组长度为:" + arr.length);
}
public static void testString(String s){
System.out.println("字符串长度为:" + s.length());
}
public static void main(String[] args){
int[] arr = {1,3,5,7};
String s = "1357";
testArray(arr);
testString(s);
}
}
程序运行结果为:
数组长度为:4
字符串长度为:4
- Java语言中还有一个计算对象大小的方法size()方法,该方法是针对泛型集合而言的,用于查看泛型中有多少个元素。
56.异常处理的原理是什么
异常是指程序运行时所发生的非正常情况或错误,当程序违反了语义规则时,JVM就会将出现的错误表示为一个异常并抛出。这个异常可以在catch程序块中进行捕获,然后进行处理。异常处理的目的则是为了提高程序的安全性与鲁棒性。
- Java语言把异常当作对象来处理,并定义了一个基类(java.lang.Throwable)作为所有异常的父类。
- 在Java API中,已经定义了许多异常类,这些异常类分为Error(错误)和Exception(异常)两大类。
违反语义规则包括两种情况:
- 一种是Java类库内置的语义检查,例如当数组下标越界时,会引发IndexOutOfBoundsException
- 当访问null的对象时,会引发NullPointerException
- 另一种情况是Java允许开发人员扩展这种语义检查,并自由选择在何时用throw关键字抛出异常
57.运行时异常和普通异常有什么区别
Java提供了两种错误的异常类,分别为Error和Exception,拥有共同的父类Throwable。
- Error表示程序在运行期间出现了非常严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,因此这种错误是会导致程序终止执行的。
- Exception表示可恢复的异常,是编译器可以捕捉到的,包含两种类型:检查异常和运行时异常。
- 检查异常:检查异常是在程序中最经常碰到的异常。所有继承自Exception并且不是运行时异常的异常都是检查异常,比如最常见的IO异常和SQL异常。
- 运行时异常不同于检查异常,编译器没有强制对其进行捕获并处理。如果不对这种异常进行处理,当出现这种异常时,会由JVM来处理,例如NullPointerException异常,就是运行异常。在Java语言中,最常见的运行异常包括NullPointerException(空指针异常)、ClassCastException(类型转换异常)、ArrayIndexOutOfBoundsException(数组越界异常)、Array-StoreException(数组存储异常)、BufferOverflowException(缓冲区存储异常)、ArithmeticException算术异常等。
出现异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。若没有处理块,则抛到最上层。如果是多线程就用Thread.run()方法抛出,如果是单线程,就用main()方法抛出。
58.使用异常处理时,需要注意的问题
- Java异常处理用到了多态的概念,如果在异常处理过程中,先捕获了基类,然后再捕获子类,那么捕获子类的代码块将永远不会被执行。因此,在进行异常捕获时,正确的写法是:先捕获子类,再捕获基类的异常信息。
正确的写法:
try{
//access db code
}catch(SQLException e1){
//deal with this exception
}catch(Exception e2){}
错误的写法
try
//access db code
}catch(Exception e1){
//deal with this exception
}catch(SQLException e2)
- 尽早抛出异常,同时对捕获的异常进行处理
- 可以根据实际的需求自定义异常类,这些自定义的异常类只要继承自Exception类即可
- 异常能处理就处理,不能处理就抛出。
59.下面程序能否编译通过?如果把ArithmeticException换成IOException呢?
public class ExceptionTypeTest{
public void doSomething() throws ArithmeticException{
System.out.println();
}
public static void main(){
ExceptionTypeTest ett = new ExceptionTypeTest();
ett.doSomething();
}
}
答:能。由于ArithmeticException属于运行时异常,编译器没有强制对其进行捕获并处理,因此编译可以通过。但是如果换成IOException后,由于IOException属于检查异常,编译器强制去捕获此类型的异常,因此如果不对异常进行捕获将会有编译错误。
60.Java中有几种类型的流
常见的流有两种,分别为字节流与字符流。
- 字节流继承于InputStream与OutputStream
- 字符流继承于Reader与Writer
- java.io包中还有许多其他的流,流的作用主要是为了改善程序性能并且使用方便