一、笔试题目:
1. 简述类与对象的区别,Java 虚函数的作用。
类是对象的抽象,对象是类的具体实例。
类是抽象的,不占用内存,而对象是具体的,占有内存空间。
java中没有虚函数的概念,普通函数就相当于C++中的虚函数,不过可以在函数前加final使函数不能被重写。虚函数的作用是允许在派生类中重新定义与基类同名的函数,是多态性的一种体现。
2. Database table 写SQL语句去掉重复的记录,保留其中ID最小的一条。
delete from tablename where id not in (select min(id) from tablename group by col1,col2,...)
3. 分析两个for循环的优缺点:
(1)
1 for(i=0; i<N; i++) 2 { 3 if(condition) 4 DoSomething(); 5 else 6 DoOtherthing(); 7 }
(2)
1 if(condition) 2 { 3 for(i=0; i<N; i++) 4 DoSomething(); 5 } 6 else 7 { 8 for(i=0; i<N; i++) 9 DoOtherthing(); 10 }
程序(1)(前者):
优点:程序简洁
条件判断出现在for里面,意味着,即使我在DoSomething()或DoOtherthing()这2个函数中改变了condition的值,for循环也能正确执行我的意图,因为它在每次循环中都会重新检测conditon的值并针对condition的值做不同动作,所谓以不变应万变,这是难能可贵的.
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
如果condition一直未曾改变,我们可怜的if必须每次循环都判断一下condition的真假.牺牲了运行时效率.
程序(2)(后者):
优点:循环的效率高。只进行一次判断,运行时效率高.适合那种condition的值不会改变的情况.
缺点:由于只在一开始进行一次判断,所以失去了改变condition的值的机会,也就是说,即使我在DoSomething()中改变了condition的值为false,这个程序也不会改变它的判断,它依然执行着DoSomething()的循环.我们不能随时更换我们需要进行的动作。这是牺牲了弹性。
N较大时,建议采用后面这种写法,由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
4. 1-100的自然数,放到长度为99的数组a[]中,设计一个好的方法,能够方便地找出未被放入的数。
1 int total = 0; 2 for (int i=0; i<99; i++) { 3 total += a[i]; 4 } 5 System.out.println("未被放入的数是:"+(5050-total));
其他方法:
1).先将数组中的99个数字进行排序,然后将1-100个数字依次在数组中折半查找,可以借用java中已有的
Arrays.binarySearch
2).其实如果数组可以换成集合的话,可以直接判断集合中是否contain这1-100个数字就可以了
3).用HashMap实现,把数字1-100都放入HashMap中,key为数字,value随便,比如为1,for循环遍历长度为99的数组,对HashMap移除当前数字的key,最后剩下的1个数即是所求。
5. 用递归实现100!。
1 import java.math.BigInteger; 2 3 public class factorial { 4 5 public static BigInteger callFactorial(int n) { 6 if (n < 1) 7 throw new IllegalArgumentException(); 8 9 if (n == 1) 10 return BigInteger.ONE; 11 12 return callFactorial(n - 1).multiply(BigInteger.valueOf(n)); 13 } 14 15 public static void main(String[] args) { 16 System.out.println(callFactorial(100)); 17 } 18 }
6. 设计模式:Singleton、Factory(代码)
第一种(懒汉,线程不安全):
1 public class Singleton { 2 private static Singleton instance; 3 4 public static Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 }
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
第二种(懒汉,线程安全):
1 public class Singleton { 2 private static Singleton instance; 3 4 public static synchronized Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 }
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
第三种(饿汉):
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
第四种(饿汉,变种):
1 public class Singleton { 2 private Singleton instance = null; 3 static { 4 instance = new Singleton(); 5 } 6 7 public static Singleton getInstance() { 8 return this.instance; 9 } 10 }
表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。
第五种(静态内部类):
1 public class Singleton { 2 private static class SingletonHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 6 public static final Singleton getInstance() { 7 return SingletonHolder.INSTANCE; 8 } 9 }
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
第六种(枚举):
1 public enum Singleton { 2 INSTANCE; 3 public void whateverMethod() { 4 } 5 }
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
第七种(双重校验锁):
1 public class Singleton { 2 private volatile static Singleton singleton; 3 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
这个是第二种方式的升级版,俗称双重检查锁定,详细介绍请查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5之后,双重检查锁定才能够正常达到单例效果。
总结
有两个问题需要注意:
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
1 private static Class getClass(String classname) 2 throws ClassNotFoundException { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 5 if(classLoader == null) 6 classLoader = Singleton.class.getClassLoader(); 7 8 return (classLoader.loadClass(classname)); 9 } 10 }
对第二个问题修复的办法是:
1 public class Singleton implements java.io.Serializable { 2 public static Singleton INSTANCE = new Singleton(); 3 4 protected Singleton() { 5 6 } 7 private Object readResolve() { 8 return INSTANCE; 9 } 10 }
对我来说,我比较喜欢第三种和第五种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。
Factory模式:待续。