1.你所知道的集合类都有哪些?主要方法?
最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表,List 适用于按数值索引访问元素的情形。 Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其中每个键映射到一个值。 主要方法:对于set,大概的方法是add,remove, contains; 对于map,大概的方法就是put,remove,contains等。 我记住的一些思想就是List类会有get(int index)这样的方法,因为它可以按顺序取元素,而set类中没有get(int index)这样的方法。 List和set都可以迭代出所有元素,迭代时先要得到一个iterator对象,所以,set和list类都有一个iterator方法,用于返回那个iterator对象。 map可以返回三个集合,一个是返回所有的key的集合,另外一个返回的是所有value的集合,再一个返回的key和value组合成的EntrySet对象的集合, map也有get方法,参数是key,返回值是key对应的value。 2.介绍Collection框架的结构。
集合框架(Collection Framework)泛指java.util包的若干个类和接口。如Collection,List,ArrayList,LinkedList,Vector(自动增长数组),HashSet,HashMap等。 集合框架中的类主要封装的是典型的数据结构,如动态数组,链表,堆栈,集合,哈希表等。 集合框架类似编程中经常用到的工具类,使得编码这专注于业务层的实现,不需要从底层实现相关细节—“数据结构的封装”和”典型算法的实现”。 3.Comparable和Comparator接口是干什么的。
Java提供了只包含一个compareTo()方法的Comparable接口。 这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。 Java提供了包含compare()和equals()两个方法的Comparator接口。 compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。 equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。 只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时 候,这个方法才返回true。 4.Collection框架中实现比较要实现什么接口。
SortedSet和SortedMap接口对元素按指定规则排序。 SortedMap是对key列进行排序要实现 comparable 接口,把你的自定义类实现以上接口,实现 compareTo方法就OK了。 5.comparable/comparator区别。
用Comparable 简单,只要实现Comparable接口的对象直接就成为一个可以比较的对象但是需要修改源代码。
用Comparator 的好处是不需要修改源代码,而是另外实现一个比较器,当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。
6.List 和 Map 区别?
List:是存储单列数据的集合,List中存储的数据是有顺序,并且允许重复。 Map:是存储键和值这样的双列数据的集合;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。 7.List、Map、Set三个接口,存取元素时,各有什么特点?
List与Set都是单列元素的集合,它们有一个共同的父接口Collection。List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序,Map 保存key-value值,value可多值。
Set里面不允许有重复的元素。存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。 取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。 List表示有先后顺序的集合。存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。 取元素:方法1:Iterator接口取得所有,逐一遍历各个元素。 Map是双列的集合。Map是双列的集合,存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。 取元素:用get(Object key)方法根据key获得相应的value。也可以获得所有的key的集合,还可以获得所有的value的集合,还可以获得key和value组合成的Map.Entry对象的集合。 注意 :
HashSet按照hashcode值的某种运算方式进行存储,而不是直接按hashCode值的大小进行存储。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65在hashSet中的存储顺序不是62,65,78,LinkedHashSet按插入的顺序存储。 那被存储对象的hashcode方法还有什么作用呢?hashset集合比较两个对象是否相等,首先看hashcode方法是否相等,然后看equals方法是否相等。new 两个Student插入到HashSet中,看HashSet的size,实现hashcode和equals方法后再看size。 同一个对象可以在Vector中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往HashSet中却加不了多次的。 8.ArrayList和Vector的区别。
这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是HashSet之类的集合的最大不同处,HashSet之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。 ArrayList与Vector的区别,这主要包括两个方面。ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次增加多个存储单元,增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。 Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。 Vector是线程安全的,即它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 对于Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住Vector与Hashtable是旧的,是java一诞生就提供了的,它们是线程安全的,ArrayList与HashMap是java2时才提供的,它们是线程不安全的。 9.能创建 volatile 数组吗?
可以,但是创建的对象或数组的地址具有可见性,里面的数据是不可见的。 10.去掉一个Vector集合中重复的元素。
方法一:HashSet set = new HashSet(vector); 方法二:
Vector newVector = new Vector(); for (int i=0;i<vector.size();i++) { Object obj = vector.get(i); if(!newVector.contains(obj); newVector.add(obj); } 11.说出ArrayList,Vector, LinkedList的存储性能和特性。
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差。 而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用。 12.Collection 和 Collections的区别。
Collection是集合类的上级接口,继承于他的接口主要有Set 和List。 Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。 13.Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
Set里的元素是不能重复的,元素重复与否是使用equals()方法进行判断的。 equals()和==方法决定引用值是否指向同一对象,equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。 14.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
对。 如果对象要保存在HashSet或HashMap中,它们的equals相等,那么,它们的hashcode值就必须相等。 如果不是要保存在HashSet或HashMap,则与hashcode没有什么关系了,这时候hashcode不等是可以的。 例如arrayList存储的对象就不用实现hashcode,当然,我们没有理由不实现,通常都会去实现的。 15.TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常!
当前的add方法放入的是哪个对象,就调用哪个对象的compareTo方法,至于这个compareTo方法怎么做,就看当前这个对象的类中是如何编写这个方法的。
实验代码 public class Parent implements Comparable { private int age = 0; public Parent(int age){ this.age = age; } public int compareTo(Object o) { System.out.println("method of parent"); Parent o1 = (Parent)o; return age>o1.age?1:age<o1.age?-1:0; } } public class Child extends Parent { public Child(){ super(3); } public int compareTo(Object o) { System.out.println("method of child"); return 1;} } public class TreeSetTest { public static void main(String[] args) { TreeSet set = new TreeSet(); set.add(new Parent(3)); set.add(new Child()); set.add(new Parent(4)); System.out.println(set.size()); } } 16.快速失败(fail-.fast)和安全失败(fail-.safe)的区别是什么?
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。 java.util 包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。 快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。 17.HashMap和Hashtable的区别。
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。 HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。 就HashMap与Hashtable主要从三方面来说。历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。 同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的。 值:只有HashMap可以让你将空值作为一个表的条目的key或value。 18.HashMap的数据结构。
在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用)。 所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外。 Hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“)。 19.HashMap的工作原理是什么?
Java中的HashMap是以键值对(key-.value)的形式存储元素的。 HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。 当调用put() 方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。 如果key已经存在了,value会被更新成新值。 HashMap的一些重要的特性是它的容量 (capacity),负载因子(load factor)和扩容极限(threshold resizing)。 20.HashMap什么时候进行扩容呢?
java 当HshMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75, 也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍, 然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。 比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的, 因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。
21.HashSet的底层实现是什么?
通过看源码知道HashSet的实现是依赖于HashMap的,HashSet的值都是存储在HashMap中的。 在HashSet的构造法中会初始化一个HashMap对象,HashSet不允许值重复,因此,HashSet的值是作为HashMap的key存储在HashMap中的,当存储的值已经存在时返回false。 22.CorrentHashMap的工作原理?
在jdk 8中,ConcurrentHashMap不再使用Segment分离锁,而是采用一种乐观锁CAS算法来实现同步问题,但其底层还是“数组+链表->红黑树”的实现。 23.LinkedHashMap的实现原理?
LinkedHashMap也是基于HashMap实现的,不同的是它定义了一个Entry header,这个header不是放在Table里,它是额外独立出来的。 LinkedHashMap通过继承hashMap中的Entry,并添加两个属性Entry before,after,和header结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。 LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false。 一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。 24.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是 ArrayList?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。 Array大小是固定的,ArrayList的大小是动态变化的。 ArrayList处理固定大小的基本数据类型的时候,这种方式相对比较慢。 25.Java集合类框架的最佳实践有哪些?
假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置 初始容量来避免重新计算hash值或者是扩容。 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现 hashCode()和equals()方法。 编程的时候接口优于实现。 底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。 Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别。 Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。 equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。 26.为什么集合类没有实现Cloneable和Serializable接口?
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。 因此,应该 由集合类的具体实现来决定如何被克隆或者是序列化。 27.什么是迭代器(Iterator)?
Iterator接口提供了很多对集合元素进行迭代的方法。 每一个集合类都包含了可以返回迭代器实例的迭代方法。 迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的 remove(Object Obj)删除,可以通过迭代器的remove()方法删除。 28.Iterator和ListIterator的区别是什么?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。 Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。 29.说出一些常用的类,包,接口,请各举5个。
注意:要让人家感觉你对java ee开发很熟,java中的那些东西,要多列你在做ssh项目中涉及的那些东西。就写你最近写的那些程序中涉及的那些类。 常用的类:BufferedReader BufferedWriter FileReader FileWirter String Integer java.util.Date System Class List HashMap 常用的包:java.lang java.io java.util java.sql javax.servlet org.apache.strtuts.action org.hibernate 常用的接口:Remote List Map Document NodeList Servlet HttpServletRequest HttpServletResponse Transaction(Hibernate) Session(Hibernate) HttpSession 30.java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
字节流,字符流。 字节流继承于InputStream OutputStream。 OutputStream提供的方法:void write(int b):写入一个字节的数据。 void write(byte[] buffer):将数组buffer的数据写入流。 void write(byte[] buffer,int offset,int len):从buffer[offset]开始,写入len个字节的数据。 void flush():强制将buffer内的数据写入流。 void close():关闭流。 InputStream提供的方法:int read():读出一个字节的数据,如果已达文件的末端,返回值为-1。 int read(byte[] buffer):读出buffer大小的数据,返回值为实际所读出的字节数。 int read(byte[] buffer,int offset,int len)。 int available():返回流内可供读取的字节数目。 long skip(long n):跳过n个字节的数据,返回值为实际所跳过的数据数。 void close():关闭流。 字符流继承于InputStreamReader OutputStreamWriter。 在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。 31.字节流与字符流的区别。
要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为IO流,对应的抽象类为OutputStream和InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。 计算机中的一切最终都是二进制的字节形式存在,对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,java提供了字符流的包装类。 底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码的字节形式,读取也是反之的道理。
32.什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。
有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,(例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输),但是,jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java 帮我们做,要被传输的对象必须实现serializable接口,这样,javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。 需要被序列化的类必须实现Serializable接口,该接口是一个mini接口,其中没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。 例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输或通过rmi等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。 33.描述一下JVM加载class文件的原理机制?
Java语言是一种具有动态性的解释型语言,类(class)只有被加载到JVM后才能运行。当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序,这个加载过程是由类加载器完成。 具体来说,就是由ClassLoader和它的子类来实现的。类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。 类的加载方式分为隐式加载和显示加载。隐式加载指的是程序在使用new等方式创建对象时,会隐式地调用类的加载器把对应的类加载到JVM中。显示加载指的是通过直接调用class.forName()方法来把所需的类加载到JVM中。 任何一个工程项目都是由许多类组成的,当程序启动时,只把需要的类加载到JVM中,其他类只有被使用到的时候才会被加载,采用这种方法一方面可以加快加载速度,另一方面可以节约程序运行时对内存的开销。 此外,在Java语言中,每个类或接口都对应一个.class文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。 在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到JVM中,至于其他类,则在需要的时候才加载:下面是加载的步骤:初始化。对静态变量和静态代码块执行初始化工作。 装载。根据查找路径找到相应的class文件,然后导入。 链接。链接又可分为3个小步: 检查,检查待加载的class文件的正确性。 准备,给类中的静态变量分配存储空间。 解析,将符号引用转换为直接引用(这一步可选)。 34.stack和heap有什么区别。
java的内存分为两类,一类是栈内存,一类是堆内存。 栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。 堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。 栈内存(stack)和堆内存(heap)的区别
35.堆和栈的区别,有一个64k的字符串,是放到堆上,还是放到栈上,为什么?
堆栈都是内存的可用区域,但是堆的速度慢容量大,栈的速度快容量小。一个64K的字符串,自然放在堆。栈的内存是很宝贵的。 只有引用及基本数据类型是直接存在栈上。对象类型可能是在堆、方法区、常量池中;放到堆中还是放到栈中,jvm会根据你的数据类型决定。 栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆区(heap) :一般由程序员分配释放, 若程序员不释放,程序结束时可能由GC回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 36.GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。 Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。 37.垃圾回收的优点和原理。并考虑2种回收机制 。
Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。 优点 :垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。 垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。 回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。 38.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。 不过垃圾回收机制的回收是不确定的,不一定会马上回收内存。 可以主动通知虚拟机进行垃圾回收:程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。实际上GC是一个守护线程。 39.什么时候用assert?
assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。 在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,assert将给出警告或退出。 一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion检查通常是关闭的。
public class AssertTest { public static void main(String[] args) { int i = 0; for(i=0;i<5;i++) { System.out.println(i); } //假设程序不小心多了一句--i; --i; assert i==5; } } 40.java中会存在内存泄漏吗,请简单描述。
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。 由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的,例如下面的代码可以看到这种情况的内存回收: java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景(通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的)。 如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。 检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。 内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。 java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
import java.io.IOException; public class GarbageTest { public static void main(String[] args) throws IOException { try { gcTest(); } catch (IOException e) { e.printStackTrace(); } System.out.println("has exited gcTest!"); System.in.read(); System.in.read(); System.out.println("out begin gc!"); for(int i=0;i<100;i++) { System.gc(); System.in.read(); System.in.read(); } } private static void gcTest() throws IOException { System.in.read(); System.in.read(); Person p1 = new Person(); System.in.read(); System.in.read(); Person p2 = new Person(); p1.setMate(p2); p2.setMate(p1); System.out.println("before exit gctest!"); System.in.read(); System.in.read(); System.gc(); System.out.println("exit gctest!"); } private static class Person { byte[] data = new byte[20000000]; Person mate = null; public void setMate(Person other) { mate = other; } } } 41.能不能自己写个类,也叫java.lang.String?
可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去加载jre.jar包中的那个java.lang.String。 但是在实际应用中,这么干就会出很多潜在的问题,原来所有用了java.lang.String类的都将出现问题。 虽然java提供了endorsed技术,可以覆盖jdk中的某些类。但是,能够被覆盖的类是有限制范围,反正不包括java.lang这样的包中的类。
package java.lang; public class String { public static void main(String[] args) { System.out.println("string"); } } 报告的错误如下: java.lang.NoSuchMethodError: main Exception in thread "main" 这是因为加载了jre自带的java.lang.String,而该类中没有main方法。