【前言】别人都在你看不到的地方暗自努力,在你看得到的地方,他们也和你一样显得游手好闲,和你一样会抱怨,而只有你自己相信这些都是真的,最后,也只有你一个人继续不思进取 ……
【下载】本人刚学习Java时总结的一些JavaSE常见面试题,偶尔在电脑中翻出,重新整理一下分享给需要的人,主要针对初级程序员。想要PDF完整版下载的,评论里留言留下你的邮箱!
61.同步代码块与同步函数的区别?
答:(1)同步代码块:位置比较灵活,锁对象可以任意对象,但必须是同一对象。格式:
synchronized(对象) { //任意对象都可以。这个对象就是锁。
需要被同步的代码;
}
(2)同步函数:声明方法时加synchronized关键字,同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
(3)在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
62.Java中如何实现序列化,有什么意义?
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。
例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输或通过rmi等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
63.Java中有几种类型的流?
答 (1)字节流 InputStream/OutputStream
①FileInputStream/FileOutputStream:文件字节流,用于文件的读写操作
②BufferedInputStream/BufferedFileOutputStream:加缓冲区的字节流,用于提高效率
(2)字符流 Reader/Writer
①FileReader/FileWriter:文件字符流,用于文本文件的读写操作
②BufferedReader/BufferedWriter:加缓冲区的字符流,用于提高效率
(3)转换流 InputStreamReader/OutputStreamWriter
64.Statement和PreparedStatement有什么区别?哪个性能更好?
答:与Statement相比:
①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);
②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;
③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
补充:为了提供对存储过程的调用,JDBC API中还提供了CallableStatement接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的SQL语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。
65.使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?
答:要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
66.在进行数据库编程时,连接池有什么作用?
答:由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行TCP的三次握手,释放连接需要进行TCP四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于Java的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。
补充:在计算机系统中时间和空间是不可调和的矛盾,理解这一点对设计满足性能要求的算法是至关重要的。大型网站性能优化的一个关键就是使用缓存,而缓存跟上面讲的连接池道理非常类似,也是使用空间换时间的策略。可以将热点数据置于缓存中,当用户查询这些数据时可以直接从缓存中得到,这无论如何也快过去数据库中查询。当然,缓存的置换策略等也会对系统性能产生重要影响,对于这个问题的讨论已经超出了这里要阐述的范围。
67.什么是DAO模式?
答:DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO模式实际上包含了两个模式,一是Data Accessor(数据访问器),二是Data Object(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。
68.JDBC中如何进行事务处理?
答:Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外,从JDBC 3.0中还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。
69.获得一个类的类对象有哪些方式?
答:(1)Class.forName(classname):用于做类加载
//根据给定的类名来获得,用于类加载
String classname = "cn.itcast.reflect.Person"; //来自配置文件
Class clazz1 = Class.forName(classname); //此对象代表Person.class
(2)对象.getClass():用于获得对象的类型
//如果拿到了对象,不知道是什么类型,用于获得对象的类型
Object obj = new Person();
Class clazz2 = obj.getClass(); //获得对象具体的类型
(3)类名.class:用于获得指定的类型,传参用
//如果是明确地获得某个类的Class对象,主要用于传参
Class clazz3 = Person.class;
70.如何通过反射创建对象?
答:(1)调用空参数的构造函数:使用了Class类中的newInstance()方法。
//第一步:获取字节码文件 //①通过 Class.forName(“类名”)来获取字节码文件 Class clazz = Class.forName("cn.itcast.bean.Person"); //②通过 类.class 通过加载器来获取字节码文件 clazz = Person.class; //③通过 对象.getClass() 来获取字节码文件 Object obj = new Person("zhangsan", 19); clazz = obj.getClass(); //第二步:通过节码文件,创建对象 Object obj = clazz.newInstance(); //该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。 |
(2)调用带参数的构造函数:先要获取指定参数列表的构造器,然后通过该构造函数的对象的newInstance(实际参数)进行对象的初始化。
public static void method_2() throws Exception { //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 Class clazz = Class.forName("cn.itcast.bean.Person"); //获取一个带参数的构造器。 Constructor constructor = clazz.getConstructor(String.class,int.class); //想要对对象进行初始化,使用构造器的方法newInstance(); Object obj = constructor.newInstance("zhagnsan",30); //获取所有构造器。 Constructor[ ] constructors = clazz.getConstructors(); //只包含公共的 //constructors = clazz.getDeclaredConstructors(); //本类公有,包含私有的 for(Constructor con : constructors) { System.out.println(con); } } |
71.简述一下面向对象的"六原则一法则"?
答: (1)单一职责原则:一个类只做它该做的事情。其核心就是我们常说的"高内聚",写代码最终极的原则只有六个字"高内聚、低耦合",就像葵花宝典的八字中心思想一样"欲练此功必先自宫"一样重要。我们都知道一句话叫"因为专注,所以专业",一个对象如果承担太多的职责,那么注定它什么都做不好。这个世界上任何好的东西都有两个特征,一个是功能单一,好的相机绝对不是电视购物里面卖的那种一个机器有一百多种功能的,它基本上只能照相;另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,这从我们的框架演化的过程中也可以开出来:。。。。)
(2)开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:
①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;
②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。)
(3)依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。)
(4)里氏替换原则:任何时候都可以用子类型替换掉父类型。(关于里氏替换原则的描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)
(5)接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的)
(6)聚合复用原则:优先使用聚合关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码。例如Properties类继承了Hashtable类,API中原话:因为 Properties 继承于 Hashtable,所以可对Properties 对象应用put 和putAll 方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是String 的项。相反,应该使用setProperty 方法。这个继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据。)
(7)迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到"低耦合",门面模式和调停者模式就是对迪米特法则的践行。对于门面模式可以举一个简单的例子,你去一家公司洽谈业务,你不需要了解这个公司内部是如何运作的,你甚至可以对这个公司一无所知,去的时候只需要找到公司入口处的前台美女,告诉她们你要做什么,她们会找到合适的人跟你接洽,前台的美女就是公司这个系统的门面。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗。调停者就是中间人,中间件dubbo为例)
72.简述一下你了解的设计模式?
答:所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。
在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任和算法的抽象化])共23种设计模式,包括:Abstract Factory(抽象工厂模式),Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(责任链模式)。
面试被问到关于设计模式的知识时,可以拣最常用的作答,例如:
①工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
②代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不同,代理可以分为:远程代理、虚拟代理、保护代理、Cache代理、防火墙代理、同步化代理、智能引用代理。
③适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
- 模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。
④除此之外,还可以讲讲上面提到的门面模式、桥梁模式、单例模式、装潢模式(Collections工具类和I/O系统中都使用装潢模式)等,反正基本原则就是拣自己最熟悉的、用得最多的作答,以免言多必失。
73.用Java写一个单例类?
答:
①饿汉式单例
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
②懒汉式单例
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance(){
if (instance == null) instance = new Singleton();
return instance;
}
}
注意:实现一个单例有两点注意事项:①将构造器私有,不允许外界通过构造器创建对象;②通过公开的静态方法向外界返回类的唯一实例。这里有一个问题可以思考:Spring的IoC容器可以为普通的类创建单例,它是怎么做到的呢?
74.线程如何同步和通讯?
答:(1)同步有两种方法实现:一种是利用synchronized标示,另外一种是加锁。生成锁的对象的方法是:private static Lock lock = new ReentrantLock();Lock是一个接口,而Reentrantlock是一个实现的类。构造方法有:ReentrantLock()和ReentrantLock(fair:boolean)两种。其中第二种传递的是一个boolean值的参数,当设置为true时系统会按照等待的先后时间让线程活得锁,但是效率和性能不如默认值的好,但是同时也可以避免资源匮乏。synchronized可以声明方法,也可以用来标记某段代码。用来标记某段代码时的用法是:synchronized(){}。
(2)通讯方面有两种方法,第一种是利用锁生成Condition对象,然后调用await()和signal()或者signalall(),另外一种是利用继承的object类中的wait()和notify()或者notifyall()唤醒方法。个人觉得利用锁生成的condition对象的方法会比较灵活。
75.hashCode方法的作用?
答:hashcode这个方法是用来鉴定2个对象是否相等的。与equals方法的区别:
①一般来讲,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等了。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。举个例子,有个学生类,属性只有姓名和性别,那么我们可以认为只要姓名和性别相等,那么就说这2个对象是相等的。
②hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了!所以简单来讲,hashcode相当于是一个对象的编码,就好像文件中的md5,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要覆盖hashcode,让他们的逻辑一致。举个例子,还是刚刚的例子,如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。
③要从物理上判断2个对象是否相等,用==就可以了。
76.什么是SOA,谈谈你的SOA的理解?
答:(1)SOA的概念
SOA(Service-oriented archITecture,面向服务架构),而 WebService是实现 SOA 的其中一种方式,所以两者并不是一个概念。 WebService基于 SOAP 协议的,本质就是基于XML的,正是因为基于 XML,所以SOA才能整合不同的平台,不同的应用。
(2)SOA的作用:
①整合异构系统。准确点说,整合企业的遗留系统、正在开发的系统和即将开发的新系统。不同的系统可能采用不同的平台,如操作系统的选择;不同的开发语言;不同的系统架构。
②及时的响应用户的业务变化。大量的SOA组件,通过整合和拆散,然后打包成一个个的粒度合适的SOA组件,再经过流程管理工具的处理,实现业务的不断变化。就像变形金刚那样,零件还是那些零件,但是经过一定的变化,也就是对SOA组件的拆和并, 就可以玩不同的花样。当然这是理想情况,至于到底实际情况如何,笔者也没有搞过,所以不清楚。
77.java中实现多态的机制是什么?
答:靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
78..super.getClass()方法调用?
下面程序的输出结果是多少?
import java.util.Date;
public class Test extends Date{
public static void main(String[] args) { new Test().test(); }
public void test(){ System.out.println( super.getClass().getName()); }
}
答:结果是Test。
原因:在test方法中,直接调用getClass().getName()方法,返回的是Test类名
由于getClass()在Object类中定义成了final,子类不能覆盖该方法,所以,在test方法中调用getClass().getName()方法,其实就是在调用从父类继承的getClass()方法,等效于调用super.getClass().getName()方法,所以,super.getClass().getName()方法返回的也应该是Test。
如果想得到父类的名称,调用:getClass().getSuperClass().getName();
79.请写出你最常见到的5个runtime exception?
答:(1)Object x = new Integer(0);
System.out.println((String)x);
当试图将对象强制转换为不是实例的子类时,抛出该异常(ClassCastException)
(2)int a=5/0;
一个整数“除以零”时,抛出ArithmeticException异常。
(3)String s=null;
int size=s.size();
当应用程序试图在需要对象的地方使用 null时,抛出NullPointerException异常
(4)"hello".indexOf(-1);
指示索引或者为负,或者超出字符串的大小,抛出StringIndexOutOfBoundsException异常
(5)String[] ss=new String[-1];
如果应用程序试图创建大小为负的数组,则抛出NegativeArraySizeException异常。
80.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
答:分几种情况:
(1)其他方法前是否加了synchronized关键字,如果没加,则能。
(2)如果这个方法内部调用了wait,则可以进入其他synchronized方法。
(3)如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
(4)如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
81.简述synchronized和java.util.concurrent.locks.Lock的异同 ?
答:(1)主要相同点:Lock能完成synchronized所实现的所有功能
(2)主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
82.HashMap和Hashtable的区别?
答:(1)相同点:HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,
(2)主要区别在于
①HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
②Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,
83.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
答:对。
①如果对象要保存在HashSet或HashMap中,它们的equals相等,那么,它们的hashcode值就必须相等。因为HashSet和HashMap在保证值唯一的时候,会先调用hashcode(),如果相同,再调用equals()方法,如果不同,则“类似于桶结构一样”挂在一个hashcode值上
②如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode不等是可以的,例如arrayList存储的对象就不用实现hashcode,当然,我们没有理由不实现,通常都会去实现的。
84.描述一下JVM加载class文件的原理机制?
答:JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
85.heap和stack有什么区别?
答:java的内存分为两类,一类是栈内存,一类是堆内存。
栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。
86.传统for循环、增强for循环、及迭代器的区别?
答:(1)传统for循环:可以定义角标,通过角标操作元素。
(2)增强for循环:对集合进行遍历,集合不能为空。只能获取集合元素,无法获取角标,不能对集合进行操作。增强for循环有一个局限性,必须要有被遍历的目标。
(3)迭代器(Iterator):除了遍历,还可以进行remove集合中元素的动作。如果是用ListIterator,还可以在遍历过程中进行增删改的动作。