面试-java知识基础
下面是java的一些面试点,主要是基础知识。
1. 最常见的运行时异常
java运行时异常是可能在java虚拟机正常工作时抛出的异常。
java提供了两种异常机制。一种是运行时异常(RuntimeExepction),一种是检查式异常(checked execption)。
检查式异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,java编译器要求我们必须对出现的这些异常进行catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆catch来捕捉这些异常。
运行时异常:我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
RuntimeExecption在java.lang包下,
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常
2. String类是否可以被继承
不能,final修饰的。
3. 一个".java"源文件中是否可以包括多个类(不是内部类)
可以,但是一个文件中只能有一个public类,并且此public类必须与文件名相同。
4. 面向对象的编程方法具有四个基本特征:
抽象
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。
继承
类的重用。一个新类可以从现有的类中派生,这个过程称为类继承。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要,提高代码的重用行。
封装
封装是把数据和过程(对数据的操作)包围起来,隐藏内部的实现细节,对数据的访问只能通过已定义的方法 。
多态
多态性是指允许不同类的对象对同一消息作出响应。
5.spring的aop编程(aop Aspect Oriented Programming 面向切面编程)
AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP/OOP区分
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
换而言之,OOD/OOP面向名词领域,AOP面向动词领域。
IOC和AOP的区别
IoC就是Inversion of Control,控制反转。在Java开发中,IoC意味着将你设计好的类交给系统去控制,而不是在你的类内部控制。这称为控制反转。
将对象的创建和获取提取到外部。由外部容器提供需要的组件。
AOP 关注与主要的东西,也可以说让你只关注与业务,其他的东西就让AOP帮你完成。比如事务管理、持久化、资源池、系统统一的认证、权限管理等。允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。
6. 快速和归并算法
快速排序:
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成 的时候,此时令循环结束)。
排序演示
假设用户输入了如下数组:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
6 |
2 |
7 |
3 |
8 |
9 |
创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。
我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
7 |
6 |
8 |
9 |
i=0 j=3 k=6
接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
6 |
7 |
8 |
9 |
i=2 j=3 k=6
称上面两次比较为一个循环。
接着,再递减变量j,不断重复进行上面的循环比较。
在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:
下标 |
0 |
1 |
2 |
3 |
4 |
5 |
数据 |
3 |
2 |
6 |
7 |
8 |
9 |
如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。
然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。
注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。
代码:
public class QuickSort { private static int par(int[]arr,int left,int right){ int key=arr[left]; while(left < right){ //从最右边开始找比key小的数 while(left<right && arr[right]>=key) right--; //把找到的数移到左边 arr[left]=arr[right]; //从最左边开始找比key大的数 while(left<right && arr[left]<=key) left++; //把找到的数移到右边 arr[right]=arr[left]; } //最后,left等于right,将key至于中间,这样,key之前的数,比key小,key之后的数,比key大 arr[left]=key; return left; } private static void quickSort(int[]arr,int left,int right) { if(arr==null || arr.length==0 || left>=right){ return ; } int pos=par(arr,left,right); quickSort(arr,left,pos-1); quickSort(arr,left+1,right); } public static void main(String[]args) { int[] arr={22,11,23,32,45,32,21,33,67,12}; quickSort(arr,0,arr.length-1); for(int k:arr) System.out.print(k+" "); } }
归并排序
建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归 并过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二 个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中 从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左 区间和右区间用一次归并操作合并成有序的区间[s,t]。
归并操作
归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11,;
逆序数为14;
算法描述
归并操作的工作原理如下:
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
代码:
public class MergeSort { public static void mergeSort(int[] arr) { if(arr==null || arr.length<=1){ return; } mSort(arr, 0, arr.length-1); } public static void mSort(int[] arr, int left, int right) { if(left >= right) return ; int mid = (left + right) / 2; mSort(arr, left, mid); //递归排序左边 mSort(arr, mid+1, right); //递归排序右边 merge(arr, left, mid, right); //合并 } /** * 合并两个有序数组 * @param arr 待合并数组 * @param left 左指针 * @param mid 中间指针 * @param right 右指针 */ public static void merge(int[] arr, int left, int mid, int right) { //[left, mid] [mid+1, right] int[] temp = new int[right - left + 1]; //中间数组 int i = left; int j = mid + 1; int k = 0; while(i <= mid && j <= right) { if(arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while(i <= mid) { temp[k++] = arr[i++]; } while(j <= right) { temp[k++] = arr[j++]; } for(int p=0; p<temp.length; p++) { arr[left + p] = temp[p]; } } public static void main(String[] args) { int[] arr={1,98,33,31,35,2,55,78,44,3}; mergeSort(arr); for(int k:arr) System.out.print(k+" "); } }
7. 洗牌算法
所谓洗牌算法,就是给你一个1到n的序列,让你随机打乱,保证每个数出现在任意一个位置的概率相同,也就是说在n!个的排列中,每一个排列出现的概率相同。
我们考虑,当一个数被选之后,我们是没有必要在下一次随机的时候再考虑它的,因此,我们每次只从可选的数的集合中进行随机,也就不用考虑是否会碰到已经选过的数了,这样子直接将算法的复杂度降了一个数量级。
我们考虑,当一个数被选之后,我们是没有必要在下一次随机的时候再考虑它的,因此,我们每次只从可选的数的集合中进行随机,也就不用考虑是否会碰到已经选过的数了,这样子直接将算法的复杂度降了一个数量级。
void MySwap(int &x, int &y) { int temp = x; x = y; y = temp; } void Shuffle(int n) { for(int i=n-1; i>=1; i--) { MySwap(num[i], num[rand()%(i+1)]); } }
8. Java序列化
把对象转换为字节序列的过程—序列化。
把字节序列恢复为对象的过程—反序列化。
对象的序列化主要有两种用途:
1)
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2)
在网络上传送对象的字节序列。
JDK类库中的序列化API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以
采用默认的序列化方式 。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2) 通过对象输入流的readObject()方法读取对象。
9. String和StringBuffer的区别
StringBuffer对象的内容可以修改;而String对象一旦产生后就不可以被修改,重新赋值后生成的新对象。
10. b/s和c/s的区别
硬件环境不同:
C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网之间再通过专门服务器提供连接和数据交换服务. B/S 建立在广域网之上的, 不必是专门的网络硬件环境,例与电话上网, 租用设备. 信息自己管理. 有比C/S更强的适应范围, 一般只要有操作系统和浏览器就行
对安全要求不同 :
C/S 一般面向相对固定的用户群, 对信息安全的控制能力很强. 一般高度机密的信息系统采用C/S 结构适宜. 可以通过B/S发布部分可公开信息.
B/S 建立在广域网之上, 对安全的控制能力相对弱, 面向是不可知的用户群.
对程序架构不同:
C/S 程序可以更加注重流程, 可以对权限多层次校验, 对系统运行速度可以较少考虑.
B/S 对安全以及访问速度的多重的考虑, 建立在需要更加优化的基础之上. 比C/S有更高的要求 B/S结构的程序架构是发展的趋势, 从MS的.Net系列的BizTalk 2000 Exchange 2000等, 全面支持网络的构件搭建的系统. SUN 和IBM推的JavaBean 构件技术等,使 B/S更加成熟.
软件重用不同:
C/S 程序可以不可避免的整体性考虑, 构件的重用性不如在B/S要求下的构件的重用性好.
B/S 对的多重结构,要求构件相对独立的功能. 能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子
系统维护不同 :
系统维护是软件生存周期中,开销大, -------重要
C/S 程序由于整体性, 必须整体考察, 处理出现的问题以及系统升级. 升级难. 可能是再做一个全新的系统
B/S 构件组成,方面构件个别的更换,实现系统的无缝升级. 系统维护开销减到最小.用户从网上自己下载安装就可以实现升级.
处理问题不同:
C/S 程序可以处理用户面固定, 并且在相同区域, 安全要求高需求, 与操作系统相关. 应该都是相同的系统
B/S 建立在广域网上, 面向不同的用户群, 分散地域, 这是C/S无法作到的. 与操作系统平台关系最小.
用户接口不同
C/S 多是建立的Window平台上,表现方法有限,对程序员普遍要求较高
B/S 建立在浏览器上, 有更加丰富和生动的表现方式与用户交流. 并且大部分难度减低,减低开发成本.
11.private可以修饰方法里的变量吗?
不可以,private咳哟用来修饰类变量。方法内的变量作用域是从这个变量声明直到方法体结束,如果再使用private修饰的话两者就会冲突,所以不能在方法体内声明一个变量为private,而且在方法里 声明变量为private也没什么意义,方法内声明的变量的作用域本来就在方法体内,自然也不会被其它方法所访问。
12.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
可以进入其他非synchronized的方法,synchronized的方法不可以的!
Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示的将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。
如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。
13.对象的上转型,子类重写了基类的方法A,那么上转型对象操作的这个方法A,是基类的方法A?还是子类的方法A?
对象的上转型,操作的是子类的方法A。上转型对象只能操作基类的变量,子类继承的方法和子类重写的方法,不能使用子类新增的变量和方法。
14 for循环里可以有boolean的表达式吗?
可以。for( boolean1; boolean2&&条件表达式;boolean3)是正确的。
15switch()的条件语句类型:
byte、short、char、int、enum
在jdk1.5上,Byte、Short、Character、Integer也支持。
16 TreeMap是有序的map结构,HashMap不是,HashMap也是线程不稳定的。
17hibernate操作数据库的运行过程。
1 读取并解析配置文件
Configuration conf = new Configuration().configure();
2 读取并解析映射信息,创建SessionFactory
SessionFactory sf = conf.buildSessionFactoty();
3 打开Session
Session session = sf.openSession;//sf.getCurrentSession();
4 开始一个事务(增删改操作必须,查询操作可选)
Transaction tx = session.beginTransaction();
5 数据库操作
session.sava();
6 提交事务(回滚事务)
tx.commit();(tx.rollback();)
7 关闭session
session.close();
18 定义内部类,外部怎么调用?
一:static声明的内部类
假设定义了一个类Outer,在Outer里定义了一个内部类Inner。
class Outer{ // 定义外部类 private static String info = "hello world" ; // 定义外部类的私有属性 static class Inner{ // 使用static定义内部类为外部类 public void print(){ // 定义内部类的方法 System.out.println(info) ; // 直接访问外部类的私有属性 } }; public void fun(){ // 定义外部类的方法 new Inner().print() ; // 通过内部类的实例化对象调用方法 } }
那么编译后会生成Outer.class和Outer$Inner.class。
通过Outer.Inner in=new Outer.Inner(); 访问内部类。
二:不使用statc声明一个内部类
class Outer{ // 定义外部类 private static String info = "hello world" ; // 定义外部类的私有属性 class Inner{ // 使用static定义内部类为外部类 public void print(){ // 定义内部类的方法 System.out.println(info) ; // 直接访问外部类的私有属性 } }; public void fun(){ // 定义外部类的方法 new Inner().print() ; // 通过内部类的实例化对象调用方法 } }
通过 Outer o=new Outer(); Outer.Inner in=o.new Inner();访问内部类。
注意点:内部类不能含有static变量和方法,因为成员内部类的创建依赖于外部类对象。只有创建了外部类对象,才能创建内部类对象。
19 hibernate的inverse用法和含义
Inverse是hibernate双向关系中的基本概念。inverse的真正作用就是指定由哪一方来维护之间的关联关系。当一方中指定了“inverse=true”,那么那一方就有责任负责之间的关联关系。
在set节点里设置。
20 get和post的区别
①浏览器和服务器会对用get方式提交的数据进行限制,服务器会对用post方式提交的数据进行限制。
②post的请求数据放置在http请求包中。
③get的参数个数理论上没有限制,但是浏览器和服务器会对用get方式提交的数据进行限制。
另外,get方式的提交的url采用ASCII编码。
21 看看下面的运行结果
1. public static void main(String args[]){ {
String a;
System.out.println("a="+a);
}
2. public class InnerClassDemo03{
static String a;
public static void main(String args[]){
System.out.println("a="+a);
}
}
结果:第一个编译会有问题,第二个运行输出a=null。
分析:第一个很好理解,使用前没进行初始化,所以报错。第二个的a其实是成员变量,编译器有个默认初始化,初始化为null,所以有运行结果。
22 List、Set、Map的区别
java集合(可以存储和操作数目不固定的一组数据,只能存放引用类型的数据,不能存放基本类型的数据,位于java.util包中)主要分为三种类型,set、list、map。
List:可自动扩展的数组。
Set:没有重复的数组。
23 java文件经编译后,会生成字节码(class)文件。
24 Spring里集成的hibernate的DAO层继承的哪个类?hibernateDAOSupport
25 部署java应用的服务器有哪些?和ajax有关的框架有哪些?
部署java应用:Tomcat、Resin、Jboss、Weblogic
和ajax有关的框架:JQuery、DWR、Dojo、ExtJs、Mootools
26 通知垃圾回收的方法
System.gc()方法强制垃圾回收
27 抽象类和接口的区别:
① abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface,实现多重继承。接口还有标识(里面没有任何方法,如Remote接口)和数据共享(里面的变量全是常量)的作用。abstract class和interface所反映出的设计理念不同。其实abstract class表示的是"is-a"关系,interface表示的是"has-a"关系
② 接口是公开的,里面不能有私有的方法或变量,是用于让别人使用的,而抽象类是可以有私有方法或私有变量的。
③ 在abstract class 中可以有abstract的成员方法,也可以有非abstarct的成员方法,而在interface中所有的成员方法默认都是public abstract 类型的
④ 接口中定义的变量默认是public static final 型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值。抽象类中的变量默认是 friendly 型,其值可以在子类中重新定义,也可以在子类中重新赋值。
⑤ 实现接口的一定要实现接口里定义的所有方法,而实现抽象类可以有选择地重写需要用到的方法,一般的应用里,最顶级的是接口,然后是抽象类实现接口,最后才到具体类实现。抽象类中可以有非抽象方法。接口中则不能有实现方法。
28 transient使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
29 构造初始化顺序
对象的初始化顺序:(1)类加载之后,按从上到下(从父类到子类)执行被static修饰的语句;(2)当static语句执行完之后,再执行main方法;(3)如果有语句new了自身的对象,将从上到下执行构造代码块、构造器(两者可以说绑定在一起)。
在Java中,子类的构造过程中必须调用其父类的构造函数,是因为有继承关系存在时,子类要把父类的内容继承下来。但如果父类有多个构造函数时,该如何选择调用呢?第 一个规则:子类的构造过程中,必须调用其父类的构造方法。一个类,如果我们不写构造方法,那么编译器会帮我们加上一个默认的构造方法(就是没有参数的构造 方法),但是如果你自己写了构造方法,那么编译器就不会给你添加了,所以有时候当你new一个子类对象的时候,肯定调用了子类的构造方法,但是如果在子类构造方法中我们并没有显示的调用基类的构造方法,如:super(); 这样就会调用父类没有参数的构造方法。
第二个规则:如果子类的构造方法中既没有显示的调用基类构造方法,而基类中又没有无参的构造方法,则编译出错,所以,通常我们需要显示的:super(参数列表),来调用父类有参数的构造函数,此时无参的构造函数就不会被调用。
总之,一句话:子类没有显示调用父类构造函数,不管子类构造函数是否带参数都默认调用父类无参的构造函数,若父类没有则编译出错。