资源描述
面试常见考题java基础全
J2SE基础
Ø 九种基本数据类型的大小,以及他们的封装类.
基本类型:boolean byte char short int long float double void
封装类型:Boolean Byte
基本类型
大小
范围
默认值
包装类型
boolean
1
True、false
False
Boolean
byte
8
—128 +127
0
Byte
char
16
Unicode0 unicode2^16 —1
’\u0000'
Character
short
16
-2^15 2^15—1
Short
int
32
-2^31 2^31—1
Integer
long
64
—2^63 2^63-1
Long
float
32
Float
double
64
Double
void
Void
l 基本数据类型及其对应的封装类由于本质的不同,具有一些区别:
基本数据类型只能按值传递,而封装类按引用传递。
基本类型在堆栈中创建;而对于对象类型,对象在堆中创建,对象的引用在堆栈中创建.基本类型由于在堆栈中,效率会比较高,但是可能会存在内存泄漏的问题。
Ø 2. Switch能否用string做参数?
在java7之前,switch只支持byte、short、char、int 或者其对应的封装类以及Eumn类型,
在java7中,String支持被加上了。
如在jdk 7 之前的版本使用, 会提示如下错误:
Cannot switch on a value of type String for source level below 1.7. Only convertible int values or enum variables are permitted
Ø 3。 equals及==的区别。
值类型是存储在内存的栈中,引用类型的变量在栈中存放其引用类型变量的地址,其值存储在堆中。
== 比较的是两个变量的值是否相等,对于引用变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。
Equals 比较的是两个变量是否是对同一个对象的引用,即堆中的内容是否相同。
public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println(”s1 != s2");
}
}
创建对象之前会在字符串缓冲池中查找是否有此字符串常量,如果存在直接返回其引用,s1、s2两者的引用都是相同的。
public class TestString {
public static void main(String[] args) {
String s1 = ”Monday”;
String s2 = new String("Monday");
s2 = s2。intern();
if (s1 == s2)
System。out。println(”s1 == s2");
else
System。out。println("s1 != s2");
if (s1.equals(s2))
System。out.println("s1 equals s2”);
else
System.out.println("s1 not equals s2”);
}
}
本程序中的s1指向字符串缓冲区中的常量的引用,s2根据字符串缓冲区中的常量作为构造函数的参数,新建了一个对象并存储在内存中的另一个位置.(如果常量不存在及字符串缓冲区,则先在字符串缓冲区中建立常量,然后newString,这样就创建了两个对象!)
附:构造方法 original即为字符串缓冲池中得到的
public String(String original) {
this.value = original。value;
this。hash = original。hash;
}
l : java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。
到底创建了几个对象?1,0,2,1,1
String a = "abc";
String b = "abc";
String c = new String(”xyz");
String d = new String(”xyz”);
String e="ab"+”cd”;
为什么相等?
public static void main(String[] args) {
String s1 = "Monday”;
String s2 = new String("Monday”);
s2 = s2。intern();
if (s1 == s2)
System。out。println(”s1 == s2”);
else
System。out。println(”s1 != s2”);
if (s1.equals(s2))
System。out.println(”s1 equals s2”);
else
System.out。println(”s1 not equals s2");
}
Intern()方法返回的是在字符串缓冲池中的对象引用!
对比:
public static void main(String[] args) {
String Monday = "Monday";
String Mon = ”Mon";
String day = "day";
System.out。println(Monday == "Mon" + ”day”);
System.out.println(Monday == "Mon” + day);
}
只有加号左右两边的都是字符串常量时,才能在编译阶段指向缓冲池的常量,day是一个变量,在运行时指向内存中某个地址
public static void main(String[] args) {
String Monday = "Monday”;
String Mon = "Mon";
final String day = "day”;
System.out.println(Monday == ”Mon" + "day”);
System.out.println(Monday == ”Mon” + day);
}
Final修饰的day变成一个常量,在编译阶段指向缓冲池。
Ø 4. Object有哪些公用方法?
ü clone
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
ü equals
在Object中及==是一样的,子类一般需要重写该方法
ü hashCode
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到
ü getClass
final方法,获得运行时类型
ü wait
使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁.wait()方法一直等待,直到获得锁或者被中断.wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
1. 其他线程调用了该对象的notify方法
2. 其他线程调用了该对象的notifyAll方法
3。 其他线程调用了interrupt中断该线程
4。 时间间隔到了
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常
ü notify
唤醒在该对象上等待的某个线程
ü notifyAll
唤醒在该对象上等待的所有线程
ü toString
转换成字符串,一般子类都有重写,否则打印句柄
l Java中的深复制和浅复制?
什么是影子clone?
class UnCloneA {
private int i;
public UnCloneA(int ii) {
i = ii;
}
public void doublevalue() {
i *= 2;
}
public String toString() {
return Integer.toString(i);
}
}
class CloneB implements Cloneable {
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone() {
CloneB o = null;
try {
o = (CloneB) super。clone();
} catch (CloneNotSupportedException e) {
e。printStackTrace();
}
return o;
}
}
public class ObjRef {
public static void main(String[] a) {
CloneB b1 = new CloneB();
b1.aInt = 11;
System。out。println(”before clone,b1.aInt = " + b1。aInt);
System。out。println("before clone,b1.unCA = ” + b1.unCA);
CloneB b2 = (CloneB) b1.clone();
b2.aInt = 22;
b2.unCA.doublevalue();
System.out.println(”=================================");
System。out.println("after clone,b1。aInt = ” + b1。aInt);
System.out。println(”after clone,b1.unCA = " + b1。unCA);
System。out。println("=================================");
System。out.println(”after clone,b2.aInt = ” + b2.aInt);
System.out.println(”after clone,b2.unCA = ” + b2.unCA);
}
}
原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
输出结果:
before clone,b1。aInt = 11
before clone,b1。unCA = 111
=================================
after clone,b1。aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2。aInt = 22
after clone,b2.unCA = 222
深拷贝的改进方法:改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA。clone();
深拷贝:
class UnCloneA implements Cloneable {
private int i;
public UnCloneA(int ii) {
i = ii;
}
public void doublevalue() {
i *= 2;
}
public String toString() {
return Integer。toString(i);
}
public Object clone() {
UnCloneA o = null;
try {
o = (UnCloneA) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
class CloneB implements Cloneable {
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone() {
CloneB o = null;
try {
o = (CloneB) super。clone();
} catch (CloneNotSupportedException e) {
e。printStackTrace();
}
o。unCA = (UnCloneA) unCA。clone();
return o;
}
}
l 注意:要知道不是所有的类都能实现深度clone的,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。
l 我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍为unCA): o。unCA = new StringBuffer(unCA。toString());
l String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
class CloneC implements Cloneable {
public String str;
public StringBuffer strBuff;
public Object clone() {
CloneC o = null;
try {
o = (CloneC) super。clone();
} catch (CloneNotSupportedException e) {
e。printStackTrace();
}
return o;
}
}
public class StrClone {
public static void main(String[] a) {
CloneC c1 = new CloneC();
c1。str = new String(”initializeStr”);
c1.strBuff = new StringBuffer("initializeStrBuff");
System.out.println(”before clone,c1。str = " + c1。str);
System。out。println(”before clone,c1.strBuff = ” + c1.strBuff);
CloneC c2 = (CloneC) c1.clone();
c2.str = c2。str.substring(0, 5);
c2。strBuff = c2.strBuff。append(” change strBuff clone");
System。out.println("=================================");
System.out。println("after clone,c1。str = " + c1.str);
System。out。println(”after clone,c1。strBuff = ” + c1.strBuff);
System.out.println(”=================================");
System.out.println("after clone,c2。str = ” + c2.str);
System.out。println(”after clone,c2。strBuff = ” + c2。strBuff);
}
}
秘密就在于c2。str = c2。str。substring(0,5)这一语句!实质上,在clone的时候c1.str及c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2。str = c2。str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2。str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。
Ø 5。 Java的四种引用,强弱软虚,用到的场景。
四种引用类型包括:强引用、弱引用、软引用、虚引用(幽灵引用),使程序能够更灵活的控制对象的生命周期。
1. 强引用(StrongReference):强引用不会被垃圾回收器回收,并且也没有实际的对应类型,平时接触最多的就是强引用。例如Object obj = new Object();这里obj即为强引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。当内存空间不足,java虚拟机宁愿抛出out of MemoryError错误,是程序异常终止,也不会靠回收具有强引用的对象来解决内存不足问题。
2. 软引用(SoftReference)如果一个对象只具有软引用,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足,就会回收这些对象的内存.只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,java虚拟机就会把这个软引用加入到及之关联的引用队列。
/** * 只有当内存不够的时候,才回收这类内存,因此在内存足够的时候,它们通常不被回收 * *
* 无论是否发送GC,执行结果都是: * java。lang.Object@f9f9d8 * null * java。lang。Object@f9f9d8 * null *
* * 可以看到:只有发送了GC,将对于从内存中释放的时候,JVM才会将reference假如引用队列 */
public static void soft() throws Exception {
Object obj = new Object();
ReferenceQueue refQueue = new ReferenceQueue();
SoftReference softRef = new SoftReference(obj, refQueue);
System.out.println(softRef.get()); //
System。out.println(refQueue。poll());
// null // 清除强引用,触发GC
obj = null;
System。gc();
System。out.println(softRef。get());
Thread.sleep(200);
System.out.println(refQueue.poll());
}
1 为什么需要使用软引用
首先,我们看一个雇员信息查询系统的实例。我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退"按钮)。
这时我们通常会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另一种是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。我们知道,访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。
2 如果使用软引用
SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收.也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用.另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
看下面代码:
MyObject aRef = new MyObject();
SoftReference aSoftRef = new SoftReference(aRef);
此时,对于这个MyObject对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量aReference的强引用,所以这个MyObject对象是强可及对象。
随即,我们可以结束aReference对这个MyObject实例的强引用:
aRef = null;
此后,这个MyObject对象成为了软可及对象.如果垃圾收集线程进行内存垃圾收集,并不会因为有一个SoftReference对该对象的引用而始终保留该对象。Java虚拟机的垃圾收集线程对软可及对象和其他一般Java对象进行了区别对待:软可及对象的清理是由垃圾收集线程根据其特定算法按照内存需求决定的。也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软可及对象,而且虚拟机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚使用过的“新”软可反对象会被虚拟机尽可能保留。在回收这些对象之前,我们可以通过:
MyObject anotherRef=(MyObject)aSoftRef。get();
重新获得对该实例的强引用。而回收之后,调用get()方法就只能得到null了。
3 使用ReferenceQueue清除失去了软引用对象的SoftReference
作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性.所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏.在java。lang.ref包里还提供了ReferenceQueue.如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,如:
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref = new
SoftReference(aMyObject, queue);
那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。常用的方式为:
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}
ReferenceQueue清除无用的引用
ReferenceQueue q = new ReferenceQueue();
// 获取数据并缓存
Object obj = new Object();
SoftReference sr = new SoftReference(obj, q);
// 下次使用时
Object obj = (Object)sr。get();
if (obj == null){
// 当软引用被回收后才重新获取
obj = new Object();
}
// 清理被收回后剩下来的软引用对象
SoftReference ref = null;
while((ref = q.poll()) != null){
// 清理工作
}
3. 弱引用:如果一个对象只具有弱引用,类似于可有可无的生活用品。弱引用和软引用的区别:只具有弱引用的对象拥有更短的生命周期。在垃圾回收器线程扫描他所管辖的内存区域过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够及否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象.弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,java虚拟机就会把这个弱引用加入到及之关联的引用队列中。
/** * 弱引用: 当发生GC的时候,Weak引用对象总是会内回收回收.因此Weak引用对象会更容易、更快被GC回收。 * Weak引用对象常常用于Map数据结构中,引用占用内存空间较大的对象 * *
* 如果不发生垃圾回收: * java.lang.Object@f9f9d8 * null * java.lang。Object@f9f9d8 * null * * 如果发生垃圾回收: * java.lang。Object@f9f9d8 * null * null * java。lang。ref.WeakReference@422ede * *
*/
public static void weak() throws Exception {
Object obj = new Object();
ReferenceQueue refQueue = new ReferenceQueue();
WeakReference weakRef = new WeakReference(obj, refQueue);
System。out。println(weakRef.get());
System.out.println(refQueue。poll());
// null // 清除强引用,触发GC
obj = null;
System.gc();
System.out。println(weakRef.get());
// 这里特别注意:poll是非阻塞的,remove是阻塞的.
// JVM将弱引用放入引用队列需要一定的时间,所以这里先睡眠一会儿
// System.out。println(refQueue。poll()); // 这里有可能是null
Thread.sleep(200);
System。out。println(refQueue。poll()); // System。out.println(refQueue。poll());
//这里一定是null,因为已经从队列中移除 // System.out。println(refQueue。remove());
}
什么是socket?
用于在两个的应用程序之间相互通信,scoket是属于TCP\IP的上一层.最早出现在UNIX系统中,是UNIX系统主要的信息传递方式。在WINDOWS系统中,SOCKET称为WINSOCK。
两个基本概念:客户方和服务方。当两个应用之间需要采用SOCKET通信时,首先需要在两个应用之间(可能位于同一台机器,也可能位于不同的机器)建立SOCKET连接,发起呼叫连接请求的一方为客户方,接受呼叫连接请求的一方成为服务方.客户方和服务方是相对的,同一个应用可以是客户方,也可以是服务方。在客户方呼叫连接请求之前,它必须知道服务方在哪里。所以需要知道服务方所在机器的IP地址或机器名称,如果客户方和服务方事前有一个约定就好了,这个约定就是PORT(端口号).也就是说,客户方可以通过服务方所在机器的IP地址或机器名称和端口号唯一的确定方式来呼叫服务方.在客户方呼叫之前,服务方必须处于侦听状态,侦听是否有客户要求建立连接.一旦接到连接请求,服务方可以根据情况建立或拒绝连接。
连接方式有两种,同步方式(Blocking)和(noBlocking).客户方发送的消息可以是文本,也可以是二进制信息流。当客户方的消息到达服务方端口时,会自动触发一个事件(event),服务方只要接管该事件,就可以接受来自客户方的消息了。
l 使用弱引用构建非敏感数据的缓存
a) 全局 Map 造成的内存泄漏
public class SocketManager {
private Map<Socket, User〉 m = new HashMap〈Socket, User>();
public void setUser(Socket s, User u) {
m.put(s, u);
}
public User getUser(Socket s) {
return m。get(s);
}
public void removeUser(Socket s) {
m.remove(s);
}
}
b) 用 WeakHashMap 堵住泄漏
public class SocketManager {
private Map<Socket,User〉 m = new WeakHashMap<Socket,User>();
public void setUser(Socket s, User u) {
m。put(s, u);
}
public
展开阅读全文