对于Java面试来说的本文只是对Java基础知识的一个小结,并不是什么武功秘籍或者什么速成法,更多的要求面试者自己脚踏实地的去学,多看书,多敲代码,多去思考,才是面试的王者之道。在这里作者提供了Java官方的API以供大家参考和学习[点我去官方API]
一个”.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?
一个“.java”源文件里面可以包含多个类,但是只允许有一个public类,并且类名必须和文件名一致。
每个编译单元只能有一个public 类。这么做的意思是,每个编译单元只能有一个公开的接口,而这个接口就由其public 类来表示。
你可以根据需要,往这个文件里面添加任意多个提供辅助功能的package 权限的类。但是如果这个编译单元里面有两个或两个以上的public 类的话,程序就不知道从哪里导入了,编译器就会报错。
&和&&的区别
&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null&& !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x==33 &++y>0) y会增长,If(x==33 && ++y>0)不会增长
&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。
下面的例子更清楚
y增长
y没有增长
在JAVA中如何跳出当前的多重嵌套循环?
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break语句,即可跳出外层循环。例如,
并不推荐这种方式,让外层的循环条件表达式的结果可以受到里层循环体代码的控制,跳出本层循环
switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量(更大字体),整数表达式可以是int基本类型或Integer包装类型,由于,byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的。显然,long符合switch的语法规定,并且不能被隐式转换成int类型,所以,它不能作用于swtich语句中。
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 = 1; s1 += 1;由于 +=是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
char型变量中能不能存贮一个中文汉字?为什么?
当然是可以了,char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字。补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
8、用最有效率的方法算出2乘以8等於几?
2<<3,位移三位就可以了,而位运算cpu直接支持的,效率最高。
int和Integer有什么区别?
Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
第二种情况
解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在静态区中。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,理论上整个内存没有被其他进程使用的空间甚至硬盘上的虚拟内存都可以被当成堆空间来使用。
String str = new String(“hello”);
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量放在静态区。
数组有没有length()方法?String有没有length()方法?
这个问题代码敲多了都应该知道,数组本身就有长度,String字符串则需要length方法来获取长度
构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
补充:关于equals和hashCode方法,很多Java程序都知道,但很多人也就是仅仅知道而已,在Joshua Bloch的大作《Effective Java》(很多软件公司,《Effective Java》、《Java编程思想》以及《重构:改善既有代码质量》是Java程序员必看书籍,如果你还没看过,那就赶紧去亚马逊买一本吧)中是这样介绍equals方法的:首先equals方法必须满足自反性(x.equals(x)必须返回true)、对称性(x.equals(y)返回true时,y.equals(x)也必须返回true)、传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)和一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值),而且对于任何非null值的引用x,x.equals(null)必须返回false。实现高质量的equals方法的诀窍包括:1. 使用==操作符检查”参数是否为这个对象的引用”;2. 使用instanceof操作符检查”参数是否为正确的类型”;3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;4. 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;5. 重写equals时总是要重写hashCode;6. 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。
是否可以继承String类?
String 类是final类,不可以被继承。
补充:继承String本身就是一个错误的行为,对String类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
String和StringBuilder、StringBuffer的区别?
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder对象的append方法连接字符串性能更好?
string旦赋值或实例化后就不可更改,如果赋予新值将会重新开辟内存地址进行存储。
而StringBuffer类使用append和insert等方法改变字符串值时只是在原有对象存储的内存地址上进行连续操作,减少了资源的开销
因此我们得出结论:当需要进行频繁修改字符串的操作时先建立StringBuffer类对象进行操作,
将最后结果转化成String类对象返回,这样效率会高很多。
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
面试题:华为的面试题中曾经问过这样一个问题 – “为什么不能根据返回类型来区分重载”,快说出你的答案吧!
因为调用时不能指定类型信息,编译器不知道你要调用哪个函数。float max(int a, int b); int max(int a, int b)
;
描述一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。
当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。
最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。
下面是关于几个类加载器的说明:
Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
抽象类(abstract class)和接口(interface)有什么异同?
抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。抽象类中的成员可以是private、默认、protected、public的,而接口中的成员全都是public的。抽象类中可以定义成员变量,而接口中定义的成员变量实际上都是常量。有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?
Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化,其语法看起来挺诡异的。
Java 中会存在内存泄漏吗,请简单描述。
理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。下面例子中的代码也会导致内存泄露。
抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。
阐述静态变量和实例变量的区别。
静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
静态变量是所有对象共有的,某一个对象将它的值改变了,其他对象再去获取它的值,得到的是改变后的值;
实例变量则是每一个对象私有的,某一个对象将它的值改变了,不影响其他对象取值的结果,其他对象仍会得到实例变量一开始就被赋予的值。
例:
1 |
|
GC是什么?为什么要有GC?
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
与垃圾回收相关的JVM参数:
1 |
|
String s = new String(“xyz”);创建了几个字符串对象?
两个对象,一个是静态区的”xyz”,一个是用new创建在堆上的对象。
接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?
接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。
Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
匿名的内部类不能extends(继承)其它类,但一个内部类可以作为一个接口,由另一个内部类实现。
1 |
|
指出下面程序的运行结果。
执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。
如果不能给出此题的正确答案,说明之前Java类加载机制还没有完全理解,赶紧再看看吧。
数据类型之间的转换:
- 如何将字符串转换为基本数据类型?
- 如何将基本数据类型转换为字符串?
调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型; - 一种方法是将基本数据类型与空字符串(””)连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf()方法返回相应字符串
Character ch2 = ‘8’; (char是基本数据类型,Character是其包装类型。)
int num2 = Integer.parseInt(ch2.toString());
如何实现字符串的反转及替换?
递归实现字符串反转
日期和时间:
- 如何取得年月日、小时分钟秒?
- 如何取得从1970年1月1日0时0分0秒到现在的毫秒数?
- 如何取得某月的最后一天?
- 如何格式化日期?
创建java.util.Calendar 实例,调用其get()方法传入不同的参数即可获得参数所对应的值。Java 8中可以使用java.time.LocalDateTimel来获取,代码如下所示。
以下方法均可获得该毫秒数。
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8
取得某月的最后一天
利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。Java 8中可以用java.time.format.DateTimeFormatter来格式化时间日期,代码如下所示。
打印昨天的当前时刻。
比较一下Java和JavaSciprt。
基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
- 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
- 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
- 代码格式不一样。
补充:上面列出的四点是网上流传的所谓的标准答案。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民,因此JavaScript支持函数式编程,可以使用Lambda函数和闭包(closure),当然Java 8也开始支持函数式编程,提供了对Lambda表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的语言回答会更加靠谱,不要背网上所谓的标准答案。
Error和Exception有什么区别?
Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
提示:用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候就不再继续递归)。
try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?
会执行,在方法返回调用者前执行。
注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。
Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?
Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。
运行时异常与受检异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:
- 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
- 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
- 优先使用标准的异常
- 每个方法抛出的异常都要有文档
- 保持异常的原子性
- 不要在catch中忽略掉捕获到的异常
列出一些你常见的运行时异常?
- ArithmeticException(算术异常)
- ClassCastException (类转换异常)
- IllegalArgumentException (非法参数异常)
- IndexOutOfBoundsException (下标越界异常)
- NullPointerException (空指针异常)
- SecurityException (安全异常)
阐述final、finally、finalize的区别。
- final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。
- finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
- finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
List,Set,Map是否继承自Collection接口?
List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。
阐述ArrayList、Vector、LinkedList的存储性能和特性。
ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。
Collection和Collections的区别?
Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
List、Map、Set三个接口存取元素时,各有什么特点?
List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。
TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。