java一些初级面试题
面向对象的特征有哪些方面?
原来学的时候说是三种特征,即封装、继承和多态。
现在一般说面向对象有四大特性,即抽象、封装、继承和多态。
1.抽象:将同类对象的共同特征提取出来构造类。
2.封装:将数据隐藏起来,对数据的访问只能通过特定接口。
3.继承:基于基类创建新类。
4.多态:不同子类型对象对相同消息做出不同响应。
访问修饰符public,private,protected以及不写(默认)的区别?
访问权限逐级递减,public>protected>default(不写)>private
public最高可以被其他包访问
protected最高可以被子类访问
default(默认,不写)最高可以被同包访问
private只能在当前类访问
String是基本数据类型吗?
不是。Java只有8个基本数据类型,分别是byte(字节类型)、short(短整型)、int(整型)、long(长整型)、float(浮点型)、double(双精度浮点型)、char(字符型)、boolean(布尔类型);除了基本类型(Primitive Type),剩下的都是引用类型(Reference Type),Java5以后引入的枚举类型也算是一种比较特殊的引用类型。
float f = 3.4;这个表达式是否正确?
不正确。在Java里面,没有小数点的默认是int类型,有小数点的默认是double类型。因此3.4是双精度数,将双精度浮点型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化),会造成精度损失,因此需要强制类型转换 float f = (float)3.4 或者写成 float f = 3.4f 。
short s1 = 1; s1 = s1 + 1; 这个表达式有错吗?short s1 = 1; s1 += 1; 这个表达式有错吗?
前面这个表达式,由于1是int类型,因此 s1 + 1 的运算结果也是int类型,再把int类型的结果赋值给short类型的s1是会报错的,需要对结果进行强制类型转换才能通过编译。
后面这个表达式, s1 += 1; 相当于 s1 = (short)(s1 + 1); 表达式,其中会有隐含的强制类型转换,可以正常通过编译。
Java中有没有goto?
goto是Java中的保留字,但是在目前版本的Java中没有使用,即没有goto语句。
int和Integer有什么区别?
Java是一个近乎纯洁的面向对象编程的语言,但是为了编程的方便还是引入了基本数据类型。为了能够将这些基本数据类型当作对象操作,Java为每一个基本数据类型都引入了相应的包装类型(Wrapper Class),int类型的包装类型就是Integer。从Java5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java为每个原始类型(基本数据类型)提供了相应的包装类型:
原始类型:byte,short,int,long,float,double,char,boolean
包装类型:Byte,Short,Integer,Long,Float,Double,Character,Boolean
Integer a = new Integer(3); Integer b = 3; // 将3自动装箱成Integer类型 int c = 3; System.out.println(a == b); // false 两个引用没有引用同一对象 System.out.println(a == c); // true a自动拆箱成int类型再和c比较
下面这段代码也和自动装箱/拆箱有关系:
Integer f1 = 100, f2 = 100, f3 = 128, f4 = 128; System.out.println(f1 == f2); // true System.out.println(f3 == f4); // false
为什么第一个运算的结果是true,第二个却是false呢?
首先注意,f1、f2、f3和f4都是Integer对象引用,所以下面的==运算比较的不是值而是引用。
装箱的本质是什么呢?是当我们给一个Integer对象赋一个int类型值的时候,会调用Integer类的静态方法valueOf(),看看valueOf()方法的源代码就知道为什么会产生这样的结果。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
IntegerCache是Interger类的内部类,其代码如下:(好好研究)
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
简单得说,如果整型字面量的值的范围在-128到127之间,那么就不会new一个新的Integer对象,而是直接引用常量池中的Integer对象。
&和&&的区别?
&运算符有两种用法,一是按位与,二是逻辑与。&&运算符是短路与。
逻辑与跟短语与的差别是非常大的,虽然二者都要求运算符左右两边的布尔值都是true整个表达式的值才是true,但是如果&&运算符左边的表达式是false的话,右边的表达式会直接被短路掉,不会进行运算,这就是&&运算符被称为短路运算的原因。
很多时候我们需要用到的可能都是&&而不是&,例如在验证用户登录的时候,判定用户名不是null而且不是空字符串,应当写为:userName != null && !userName.equals(""),二者的顺序不能交换,更不能使用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。
JavaScript中的短路运算更是强大,因为运算符两边不仅仅能是表达式,还能是值。
解释内存中的栈(Stack)、堆(Heap)和方法区(Method Area)的用法。
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;
而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;
方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(Literal),如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分。
栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。
String str = new String("hello");
上面的语句中,变量str放在栈区,用new关键字创建出来的字符串对象放在堆区,而"hello"这个字面量是放在方法区的。
较新版本的Java(Java6的某个更新开始)中,由于JIT编译器的发展和"逃逸分析"技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配再堆上这件事情已经变得不那么绝对了。
运行时常量池相当于Class文件,因此常量池具有动态性。即Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。
String s1 = new StringBuilder("go").append("od").toString(); System.out.println(s1.intern() == s1); // true String s2 = new StringBuilder("ja").append("va").toString(); System.out.println(s2.intern() == s2); // false
为什么会产生这样的结果,要好好思考一下。
Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的结果是12,Math.round(-11.5)的结果是-11。
四舍五入的原理是在参数上加0.5,然后进行向下取整。
那么Math.round(11.6)和Math.round(-11.6)的结果又是什么呢?
答案是12和-12。
11.6 + 0.5 = 12.1,然后向下取整为12;
-11.6 + 0.5 = -11.1,然后向下取整为-12。
要特别理解向下取整是什么意思。
switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?
在Java5以前,switch(expr)中,expr只能是byte、short、char、int。
从Java5开始,Java中引入了枚举类型,expr也可以是enum类型,从Java7开始,expr还可以是字符串类型(String)。
但是长整型(long)在目前的所有Java版本中都是不可以的。
用最有效率的方法计算2乘以8?
2<<3(左移三位相当于乘以2的三次方,右移三位相当于除以2的三次方)。
我们为编写的类重写hashCode()方法的时候,可能会看到如下所示的代码,其实我们不太理解为什么要使用这样的乘法运算来产生哈希码(散列码),而且为什么这个数是素数,为什么通常选择31这个数?
选择31,是因为可以用移位和减法运算来代替乘法,从而得到更好的性能。说到这里你可能已经想到了:31 * num等价于(num << 5) - num,左移5位相当于乘以2的5次方再减去自身就相当于乘以31,现在的VM都能自动完成这个优化。
public class PhoneNumber { private int areaCode; private String prefix; private String lineNumber; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + areaCode; result = prime * result + ((lineNumber == null) ? 0 : lineNumber.hashCode()); result = prime * result + ((prefix == null) ? 0 : prefix.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PhoneNumber other = (PhoneNumber) obj; if (areaCode != other.areaCode) return false; if (lineNumber == null) { if (other.lineNumber != null) return false; } else if (!lineNumber.equals(other.lineNumber)) return false; if (prefix == null) { if (other.prefix != null) return false; } else if (!prefix.equals(other.prefix)) return false; return true; } }
数组中有没有length()方法?String有没有length()方法?
数组没有length()方法,但是有length的属性。
String[] stringArr = {"1", "2", "3"}; System.out.println(stringArr.length); // 3
String有length()方法,没有length属性。
System.out.println("12345".length); // 5
在JavaScript中,获得字符串的长度是通过length属性得到的,这一点容易和Java混淆。
在Java中,如何跳出当前的多重嵌套循环?
在最外层循环前加一个标记如A,然后用break A;可以跳出多重循环。(Java中支持带标签的break和continue语句,作用有点类似于C和C++中的goto语句,但是就像要避免使用goto一样,应该避免使用带标签的break和continue,因为它不会让你的程序变得更优雅,很多时候甚至有相反的作用,所以这种语法非常不建议使用)。
构造器(Constructor)是否可以被重写(Override)?
构造器不能被继承,因此不能被重写(子类),但是可以被重载(同个类)。
两个对象值相同(x.equals(y) == true)但是却可有不同的hashCode,这句话对不对?
不对。如果两个对象x和y满足equals()结果为true,那么它们的哈希码(hashCode)应当相同。
Java对于equals)(方法和hashCode()方法是这样规定的:
1.如果两个对象相同(equals()方法返回true),那么它们的hashCode值一定要相同;
2.如果两个对象的hashCode相同,它们并不一定相同。
当然,你未必要按照要求去做,但是如果你违背了上述原则,就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁地冲突将会造成存取性能急剧下降)。
是否可以继承String类?
String类是final类,不可以被继承。
继承Stirng本身就是一个错误的行为,对String类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。
"如果你想去远方,就要先想办法离开你现在站的位置。"