日期:2014-05-20 浏览次数:21026 次
第一章:Java基础篇
1、谈谈你对java的认识
这个问题很大,很抽象,要回答好确实不容易。宏观上面来说,从C语言面向过程到C++面向对象到java语言纯面向对象这一发展过程都是为了提高公用性、重用性、可读性,降低耦合性。java程序是对象的集合,是一系列带有方法的对象组合,这些方法以其他对象为参数,并发送消息给其他对象。这样由于java中的对象是由状态、行为和标识组成。状态可以认为是对象存在的具体值;行为可认为是对象所能做的操作;而标识是对象在内存中的唯一地址。通常在程序中只能看到对象的前两个属性。在java中所有的代码都必须写在class里,而每个对象都是某个类(class)的一个实例(instance),也就是说java中的所有操作都是由对象完成的。
java有许多优良的特性,使得Java应用具有无比的健壮性和可靠性,这也减少了应用系统的维护费用。Java对对象技术的全面支持和Java平台内嵌的API能缩短应用系统的开发时间并降低成本。Java的编译一次,到处可运行的特性使得它能够提供一个随处可用的开放结构和在多平台之间传递信息的低成本方式。java的优良特性说明如下:
1)简单。Java丢弃了C++ 中很少使用的、很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别是,Java语言不使用指针,并提供了自动的垃圾回收机制,使得程序员不必为内存管理而担忧。
2)面向对象。Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制。Java语言全面支持动态绑定,而C++ 语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。
3)分布式。Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java.net),它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、 ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。
4)健壮。Java的强类型机制、异常处理、废料的自动收集等是Java程序健壮性的重要保证,Java的安全检查机制使得Java更具健壮性。
5)安全。Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让Java应用设置安全哨兵。
6)体系结构中立。Java程序在Java平台上被编译为体系结构中立的字节码格式,
然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。
7)可移植性。任意一个JAVA程序,不论它运行在何种CPU、操作系统或JAVA编译器上,都将产生同样的结果。人们使用C、C++也可以产生同样的效果。但C或C++在许多细节上它都没有严格定义,如:未初始化变量的值、对已释放的内存的存取、浮点运算的尾数值等等。所以除非你一开始就严格按照系统无关的概念来进行设计,否则这种可移植性只能是一种理论上的设想而不能形成实践。而JAVA定义了严密的语意结构,它的特性能够减小在不同平台上运行的JAVA程序之间的差异,也使得JAVA具有即使没有JAVA虚拟机的存在的情况下比C和C++更好的平台无关性。
8)解释型。Java程序在Java平台上被编译为字节码格式,
然后可以在实现这个Java平台的任何系统中运行。在运行时,Java平台中的Java解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。
9)高性能。与那些解释型的高级脚本语言相比,Java的确是高性能的。事实上,Java的运行速度随着JIT(Just-In-Time)编译器技术的发展越来越接近于C++。
10)多态。Java语言的设计目标之一是适应于动态变化的环境。Java程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java中的类有一个运行时刻的表示,能进行运行时刻的类型检查。
注:在回答这个问题时,应该先从宏观方面说出java面向对象的特点,然后从java特性展开说明。其中1)、2)、3)、4)、5)能说出来最好,其他几点相对来说无关紧要。
2、抽象类与接口
抽象类:抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象,我们不能把它们实例化(拿不出一个具体的东西)所以称之为抽象。在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为这个抽象类的所有派生类。
接口:Java中的接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
区别:1)从设计理念层面上:abstract class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a"关系,即父类和派生类在概念本质上应该是相同的。对于interface来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已,接口与实现类 之间的关系是"like-a"的关系。(举例:class AlarmDoor extends Door implements Alarm);2)从语法定义层面上:在abstract class方式中,抽象类可以有自己的数据成员,也可以有非 abstract的成员方法,而在interface方式的实现中,只能有有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的 abstract class。abstract class 在 Java 语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑。在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为。
3、overload和override的区别
overload是重载,是同一个类中有相同的方法名,但参数类型或个数彼此不同。1)参数类型、个数、顺序至少有一个不相同。2)不能重载只有返回值不同的方法名。3)存在于父类和子类、同类中。
override是重写,是在子类与父类中,子类中的方法的方法名,参数个数、类型都与父类中的完全一样,在子类中覆盖掉了父类的改方法 。1)方法名、参数、返回值相同。2)子类方法不能缩小父类方法的访问权限。3)子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。4)存在于父类和子类之间。5)方法被定义为final不能被重写。
4、String与StringBuffer的区别
JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。另外,String实现了equals方法,new
String(“abc”).equals(new String(“abc”)的结果为true,而StringBuffer没有实现equals方法,所以,new StringBuffer(“abc”).equals(newStringBuffer(“abc”)的结果为false。
接着要举一个具体的例子来说明,我们要把1到100的所有数字拼起来,组成一个串。
StringBuffer sbf = new StringBuffer(); for(int i=0;i<100;i++) { sbf.append(i); }上面的代码效率很高,因为只创建了一个StringBuffer对象,而下面的代码效率很低,因为创建了101个对象。
String str = new String(); for(int i=0;i<100;i++) { str = str + i; }String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。
5、java集合框架结构(Map,List,Set......)
1)类结构图
简化图
详细图
2)ArrayList,Vector容量相关问题
ArrayList:如果以new ArrayList()方式创建时,初始容量为10个;如果以new ArrayList(Collection c)初始化时,容量为c.size()*1.1,即增加10%的容量;当向ArrayList中添加一个元素时,先进行容器的容量调整,如果容量不够时,则增加至原来的1.5倍加1,再然后把元素加入到容器中,即以原始容量的0.5倍比率增加。
Vector:初始化时容量可以设定,如果以new
Vector()方式创建时,则初始容量为10,超过容量时以2倍容量增加。如果以new Vector(Collection c)方式创建时,初始容量为c.size()*1.1,超过时以2倍容量增加。如果以new Vector(int initialCapacity, int capacityIncrement),则以capacityIncrement容量增加。
3)集合特点
4)HashMap与HashTable的区别
int hash = key.hashCode();//直接使用键的hashCode方法求哈希值 //哈希地址转hash数组索引,先使用最大正int数与,这样将负转正数,再与数组长度求模得到存入的hash数组索引位置 int index = (hash & 0x7FFFFFFF) % tab.length;而HashMap重新计算hash值,而且用位运算&代替求模:
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) { //以键本身的hash码为基础求哈希地址,但看不懂是什么意思 int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } static int indexFor(int h, int length) { return h & (length-1);//将哈希地址转换成哈希数组中存入的索引号 }HashMap实现图:
5) 集合中键值是否允许null小结
6) 对List的选择
7) 对Set的选择
8) 对Map的选择
9) Stack,Vector:Stack基于线程安全,Stack类是用Vector来实现的(public class Stack extends Vector),但最好不要用集合API里的这个实现栈,因为它继承于Vector,本就是一个错误的设计,应该是一个组合的设计关系。
10) Iterator对ArrayList(LinkedList)的操作限制:
11) hashCode(),equals()方法:当以自己的对象做为HashMap、HashTable、LinkedHashMap、HashSet 、LinkedHashSet 的键时,一定要重写hashCode ()与equals ()方法,因为Object的hashCode()是返回内存地址,且equals()方法也是比较内存地址,所以当要在这些hash集合中查找时,如果是另外new出的新对象是查不到的,除非重写这两个方法。因为AbstractMap类的containsKey(Object key)方法实现如下:
if (e.hash == hash && eq(k, e.key))//先比对hashcode,再使用equals return true; static boolean eq(Object x, Object y) { return x == y || x.equals(y); }String对象是可以准确做为键的,因为已重写了这两个方法。因此,Java中的集合框架中的哈希是以一个对象查找另外一个对象,所以重写hasCode与equals方法很重要。 重写hashCode()与equals()这两个方法是针对哈希类,至于其它集合,如果要用public boolean contains(Object o)或containsValue(Object value)查找时,只需要实现equals()方法即可,他们都只使用对象的 equals方法进行比对,没有使用 hashCode方法。
12) TreeMap,TreeSet:放入其中的元素一定要具有自然比较能力(即要实现java.lang.Comparable接口)或者在构造TreeMap/TreeSet时传入一个比较器(实现java.util.Comparator接口),如果在创建时没有传入比较器,而放入的元素也没有自然比较能力时,会出现类型转换错误(因为在没有较器时,会试着转成Comparable型)。
//自然比较器 public interface java.lang.Comparable { public int compareTo(Object o); } public interface java.util.Comparator { int compare(Object o1, Object o2); boolean equals(Object obj); }13) TreeMap,TreeSet:Collection或Map的同步控制:可以使用Collections类的相应静态方法来包装相应的集合类,使他们具线程安全,如public static Collection synchronizedCollection (Collection c)方法实质返回的是包装后的SynchronizedCollection子类,当然你也可以使用Collections的synchronizedList、synchronizedMap、synchronizedSet方法来获取不同的经过包装了的同步集合,其代码片断:
public class Collections { static Collection synchronizedCollection(Collection c, Object mutex) { return new SynchronizedCollection(c, mutex); } public static List synchronizedList(List list) { } static Set synchronizedSet(Set s, Object mutex) { } public static Map synchronizedMap(Map m) { return new SynchronizedMap(m); } static class SynchronizedCollection implements Collection, Serializable { Collection c; // 对哪个集合进行同步(包装) Object mutex; // 对象锁,可以自己设置 SynchronizedCollection(Collection c, Object mutex) { this.c = c; this.mutex = mutex; } public int size() { synchronized (mutex) { return c.size(); } } public boolean isEmpty() { synchronized (mutex) { return c.isEmpty(); } } } static class SynchronizedList extends SynchronizedCollection implements List { List list; SynchronizedList(List list, Object mutex) { super(list, mutex); this.list = list; } public Object get(int index) { synchronized (mutex) { return list.get(index); } } } static class SynchronizedSet extends SynchronizedCollection implements Set { SynchronizedSet(Set s) { super(s); } } }由包装的代码可以看出只是把原集合的相应方法放在同步块里调用罢了。
14) 通过迭代器修改集合结构:通过迭代器修改集合结构在使用迭代器遍历集合时,我们不能通过集合本身来修改集合的结构(添加、删除),只能通过迭代器来操作,下面是拿对HashMap删除操作的测试,其它集合也是这样:
public static void main(String[] args) { Map map = new HashMap(); map.put(1, 1); map.put(2, 3); Set entrySet = map.entrySet(); Iterator it = entrySet.iterator(); while (it.hasNext()) { Entry entry = (Entry) it.next(); /* * 可以通过迭代器来修改集合结构,但前提是要在已执行过 next 或 * 前移操作,否则会抛异常:IllegalStateException */ // it.remove(); /* * 抛异常:ConcurrentModificationException 因为通过 迭代 器操 * 作时,不能使用集合本身来修 * 改集合的结构 */ // map.remove(entry.getKey()); } System.out.println(map); }