疯狂Java面试题大全(Android版)
Java核心技术部分
Java核心技术部分的面试题,可能覆盖Java基本语法、面向对象(包括类定义、方法、构造器、递归、继承、抽象类、接口、枚举以及final、static等关键字)、Java常用API、Java集合框架(需要重点掌握)、注解(Annotation)、泛型、输入/输出、多线程、网络通信、反射、内存管理等相关内容,这些知识基本都可通过《疯狂Java讲义》一书找到详细解答。
这部分面试题大部分从网络收集、整理,也有部分题目来自疯狂软件学员面试之后的反馈。
1、面向对象的特征有哪些?
面向对象的三大特征:
继承:通过继承允许复用已有的类,继承关系是一种“一般到特殊”的关系,比如苹果类继承水果类,这个过程称为类继承。
派生出来的新类称为原有类的子类(派生类),而原有类称为新类的父类(基类)。
子类可以从父类那里继承得到方法和成员变量,而且子类类可以修改或增加新的方法使之适合子类的需要。
封装:封装是把对象的状态数据隐藏起来,再通过暴露合适的方法来允许外部程序修改对象的状态数据。Java的封装主要通过private、protected、public等访问控制符来实现。
多态性:多态指的是当同一个类型的引用类型的变量在执行相同的方法时,实际上会呈现出多种不同的行为特征。比如程序有Animal a1 = new Animal (); Animal a2 = new Wolf();虽然a1、a2两个引用变量的类型都是Animal,但当它们调用同一个run()方法时,如果Wolf()类重写过Animal的run()方法,这就会导致a1、a2两个变量执行run()方法时呈现出不同的行为特征,这就是多态。多态增加了编程的灵活性,实际上大量设计模式都是基于多态类实现的。
除此之外,抽象也是一个重要的特征,抽象就是忽略与当前目标无关的相关方面,以便更充分地突出与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。
2、Java中实现多态的机制是什么?
Java允许父类或接口定义的引用变量指向子类或具体实现类的实例对象,而程序调用的方法在运行时才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
正是由于这种机制,两个相同类型的变量,但由于它们实际引用了不同的
3、一个”.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?
可以有多个类,但只能有一个public的类,并且public的类名必须与文件的主文件名相同。
包含多个类的Java源文件编译之后会生成多个.class文件。
4、String是基本数据类型吗?
基本数据类型包括byte、short、int、long、char、float、double和boolean。String不是基本类型。String是引用类型。
而且java.lang.String类是final类型的,因此不可以继承这个类。
并且它是一个不可变类,因此如果程序需要使用的字符串所包含的字符序列需要经常改变,建议使用StringBuffer(线程安全、性能略差)类或StringBuilder类。
5、int 和 Integer 有什么区别
Java 提供两种不同的类型:引用类型和基本数据类型。
int是基本数据类型,Integer是java为int提供的包装类。
Java为每个原始类型提供了包装类。
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
基本类型的变量只能当成简单的直接量、参与表达式运算,不具备面向对对象的特征,基本类型的变量不能被赋为null;但包装类的变量则完全可以当成对象使用,它具有面向对象的特征,包装类的变量可以被赋为null。
因为Integer具有面向对象的特征,因此Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用EL输出为null的Integer时,将会显示为空白字符串,而int默认的默认值为0,用EL输出为将显示0。所以,int不适合作为Web层的表单数据的类型。
从Java 5开始,Java提供了自动装箱、自动拆箱功能,因此包装类也可以直接参与表达式运算,因此使用起来十分方便。
另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。
6、Java有没有goto?
goto是Java中的保留字,暂时还不是Java的关键字。
7、String 和StringBuffer、StringBuilder的区别
Java提供了:String、StringBuffer和StringBuilder,它们都是CharSequence的实现类,都可以作为字符串使用。
String代表了字符序列不可变的字符串;而StringBuffer、StringBuilder都代表了字符序列可变的字符串。
StringBuffer、StringBuilder的区别是StringBuffer是线程安全的、性能略低,而StringBuilder是线程不安全的,适合单线程环境使用,性能较好。
8、Collection 和 Collections的区别。
Collection是集合类(List、Set、Queue)的根接口。
Collections是针对集合类的一个工具类,它提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
9、说说&和&&的区别。
&和&&都可以用作逻辑与的运算符,当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(a >8 && b > 5),当a小于等于8时,由于&&之前的表达式已经为false了,因此&&之后的表达式根本不会执行;
再例如if(str !=null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,因此不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。
再例如if(x > 8& ++y)与if(x > 8&& ++y ),当a小于等于8时,前一个表达式中y的值会增长;后一个表达式中y的值不会增加。
除此之外,&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
10、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
Overload是方法的重载
Override是方法的重写,也叫覆盖。
Overload要求两个方法具有方法名相同、形参列表不同的要求,返回值类型不能作为重载的条件。
Override要求子类方法与父类方法具有“两同两小一大”的要求。两同指:即父类方法、子类方法的方法名相同、形参列表相同;两小指:子类方法返回值类型要么是父类方法返回值类型的子类、要么与父类方法返回值类型相同;子类方法声明抛出的异常类型要么是父类方法声明抛出的异常类型的子类、要么与父类声明抛出的异常类型相同;一大指:子类方法的访问权限要么与父类方法的访问权限相同,要么比父类方法的访问权限更大。
Overloaded的方法是可以改变返回值的类型。
11、Java如何跳出当前的多重嵌套循环?
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如,
outer:
for(inti=0;i<10;i++)
{
for(intj=0;j<10;j++)
{
System.out.println(“i=”+ i + “,j=” + j);
if(j== 5) break ouer;
}
}
12、switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
在Java 7以前,在switch(expr1)中,expr1只能是一个整数表达式(但不包括long和Long)或者枚举常量,整数表达式可以是int基本类型或Integer包装类型,byte、short、char都可以自动转换为int,它们都可作为switch表达式。
从Java 7开始,switch表达式的可以使用String。
13、String s = new String(“xyz”);创建了几个String Object?
两个。一个是直接量的xyz对象;另一个是通过new Sting()构造器创建出来的String对象。
通常来说,应该尽量使用直接量的String对象,这样具有更好的性能。
14、数组有没有length()这个方法? String有没有length()这个方法?
数组没有length()这个方法,有length的属性。String有length()方法。
15、short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 =1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
对于short s1 =1; s1 += 1;由于 +=运算符里已经包括了一个隐式的强制类型转换,因此Java会把s1+=1计算后的结果进行隐式的强制类型转换,因此它不会有任何错误。
16、char型变量中能不能存储一个中文字符?为什么?
char型变量是用来存储Unicode编码的字符的,Unicode编码字符集中包含了汉字,因此char型变量中可以存储汉字。不过,如果某个特殊的汉字没有被包含在Unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
char类型的变量占两个字节,而Unicode编码中每个字符也占两个字节,因此char类型类型的变量可以存储任何一个Unicode字符。
17、用最有效率的方法算出2乘以8等于几?
2 << 3
因为将一个数左移n位,就相当于乘以了2的n次方,那么,一个数乘以8只要将其左移3位即可,而位运算CPU直接支持的,效率最高,所以,2乘以8等于几的最效率的方法是2 << 3。
但需要注意的是,如果这个数字本身已经很大,比如本身已经是2的30次方了,此时再用这种位移运算就可能导致“溢出”,这样就得不到正确结果了。
18、使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。例如,对于如下语句:
final StringBuilder a=new StringBuilder (“immutable”);
执行如下语句将报告编译错误:
a = new StringBuilder (“”);
但如下语句则是完全正确的
a.append(“fkjava.org”);
有人希望在定义方法的形参时,通过final修饰符来阻止方法内部修改传进来的实参:
public void method(final StringBuilder param)
{
}
实际上这没有用,在该方法内部仍然可以增加如下代码来修改实参对象:
param.append(“fkjava.org”);
19、”==”和equals方法究竟有什么区别?
==操作符的功能有两个:
A.如果==的两边都是基本类型变量、包装类对象所组成的表达式,==用于比较两边的表达式的值是否相等——只要两边的表达式的值相等,即使数据类不同,该运算符也会返回true。
B.如果==的两边是引用类型的变量,==用于判断这两个引用类型的变量是否引用同一块内存,只有当它们引用同一块内存时,==才会返回true。
而equals()则是一个java.lang.Object类的一个方法,因此任何Java对象都可调用该方法与其他对象进行比较。java.lang.Object类的equals方法的实现代码如下:
boolean equals(Object o)
{
returnthis==o;
}
从上面代码可以看出,如果一个类没有重写java.lang.Object的equals()方法时,此时equals()方法的比较结果与==的比较结果是相同的。
但Java允许任何类重写equals()方法,重写该方法就是让程序员来自己决定两个对象相等的标准——极端的情况下,我们完全可以设计出Person对象与Dog对象equals()比较返回true的情况——当然一般不会这么设计。
实际上重写equals()方法时通常会按如下格式:
publicboolean equals(Object obj)
{
if(this == obj)
returntrue;
if(obj == null)
returnfalse;
if(getClass() != obj.getClass())
returnfalse;
Personother = (Person) obj;
if(name == null)
{
if(other.name != null)
returnfalse;
}
elseif (!name.equals(other.name))
returnfalse;
if(pass == null)
{
if(other.pass != null)
returnfalse;
}
elseif (!pass.equals(other.pass))
returnfalse;
returntrue;
}
上面重写equals()方法用于判断两个Person对象是否“相等”,程序只要两个Person对象的name、pass相等,程序就可以把这两个Person对象当成相等——这是系统业务决定的。如果业务需要,我们也可以增加更多的参与判断的Field,当然也可以只根据name进行判断——只要两个Person的name相等,就认为两个Person相等,这都是由系统的业务决定。
总结起来就是一句话:开发者重写equals()方法就可以根据业务要求来决定两个对象是否“相等”。
20、静态变量和实例变量的区别?
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:实例变量属于一个对象,必须先创建实例对象,它的实例变量才会被分配空间,才能使用这个实例变量。静态变量则属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
例如,对于下面的程序:
public class VarTest
{
publicstatic int staticVar = 0;
publicint instanceVar = 0;
publicVarTest ()
{
staticVar++;
instanceVar++;
System.out.println(“staticVar=”+ staticVar + ”,instanceVar=” + instanceVar);
}
}
上面程序中的staticVar变量随VarTest类初始化而分配内存、执行初始化的,以后无论创建多少个实例对象,不会再分配staticVar变量,因此用永远只有一个staticVar变量。
但instanceVar变量则是随着VarTest对象初始化而分配内存、执行初始化的,因此每创建一个实例对象,就会分配一个instanceVar,即可以分配多个instanceVar。因此上面程序中每创建一个VarTest对象,staticVar的值就会自加一,但每个VarTest对象的instanceVar最多只自加1。
21、是否可以从一个static方法内部调用非static方法?
不可以。静态成员不能调用非静态成员。
非static方法属于对象,必须创建一个对象后,才可以在通过该对象来调用static方法。而static方法调用时不需要创建对象,通过类就可以调用该方法。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果允许从一个static方法中调用非static方法的调用,那个非static方法是没有调用对象的。因此Java不允许static方法内部调用非static方法。
22、Math.round(11.5)等於多少? Math.round(-11.5)等於多少?
Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应,例如,ceil的英文意义是天花板,该方法就表示向上取整,所以,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;floor的英文意义是地板,该方法就表示向下取整,所以,Math.floor(11.6)的结果为11,Math.floor(-11.6)的结果是-12;最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。
23、请说出作用域public,private,protected,以及不写时的区别
这四个作用域的可见范围如下表所示。
作用域 当前类 同一package 子类 全局
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×
说明:如果在修饰的元素上面没有写任何访问修饰符,则表示default。
只要记住访问权限由小到大依次是private → default → protected → public,然后再记住Java存在的4个访问范围,就很容易画出上面的表格了。
24、外部类能用private、protected修饰吗?内部类可以用private、protected修饰吗?
外部类不能用private、protected修饰不能。内部类能用private、protected修饰不能。
外部类的上一级程序单位是包,因此它只有两个使用范围:包内和包外,因此它只能用public(表示可以在全局位置使用)和默认修饰符(default,表示只能被同一个包的其他类使用)修饰。
25、一个类定义多个重载方法,参数分别是int ,char,和double,然后将double x =2,传递进去,会选择哪个方法?
选择参数类型为double的方法。
26、说说has a与is a的区别。
is a是典型的“一般到特殊”的关系,也就是典型的继承关系。例如Apple is a Fruit。那么Apple是一种特殊的Fruit,也就是说Apple继承了Fruit。
has a是典型的“组合”关系。比如Wolf has a Leg,也就是Leg组合成了Wolf。
需要指出的是:由于继承会造成了对父类的破坏,因此有时候可以通过组合来代替的继承。使用继承的好处:程序语义更好理解。坏处是:子类可能重写父类方法,不利于父类封装;使用组合则造成语义的混淆,但组合类不会重写被组合类的方法,因此更利于被复合类的封装。
27、ClassLoader如何加载class 。
jvm里有多个类加载,每个类加载可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时用的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的目录或jar中的类。除了bootstrap之外,其他的类加载器本身也都是java类,它们的父类是ClassLoader。
4.抽象类的作用
28、GC是什么? 为什么要有GC?
GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。
Java的System类和Runtime类都提供了“通知”程序进行垃圾回收的方法,例如如下代码:
Systme.gc();
或
Runtime.getInstance().gc();
但这两个方法只是“通知”Java进行垃圾回收,但实际上JVM何时进行垃圾回收,还是由JVM自己决定。
29、垃圾回收的优点和原理。并考虑2种回收机制。
传统的C/C++等编程语言,需要程序员负责回收已经分配的内存。显式进行垃圾回收是一件比较困难的事情,因为程序员并不总是知道内存应该何时被释放。如果一些分配出去的内存得不到及时回收,就会引起系统运行速度下降,甚至导致程序瘫痪,这种现象被称为内存泄漏。总体而言,显式进行垃圾回收主要有如下两个缺点:
A.程序忘记及时回收无用内存,从而导致内存泄漏,降低系统性能。
B.程序错误地回收程序核心类库的内存,从而导致系统崩溃。
与C/C++程序不同,Java语言不需要程序员直接控制内存回收,Java程序的内存分配和回收都是由JRE在后台自动进行的。JRE会负责回收那些不再使用的内存,这种机制被称为垃圾回收(GarbageCollection,也被称为GC)。通常JRE会提供一条后台线程来进行检测和控制,一般都是在CPU空闲或内存不足时自动进行垃圾回收,而程序员无法精确控制垃圾回收的时间和顺序等。
实际上,垃圾回收机制不可能实时检测到每个Java对象的状态,当一个对象失去引用后,它也不会被立即回收,只有等接下来垃圾回收器运行时才会被回收。
对于一个垃圾回收器的设计算法来说,大致有如下可供选择的设计:
A.串行回收(Serial)和并行回收(Parallel):串行回收就是不管系统有多少个CPU,始终只用一个CPU来执行垃圾回收操作;而并行回收就是把整个回收工作拆分成多部分,每个部分由一个CPU负责,从而让多个CPU并行回收,并行回收的执行效率很高,但复杂度增加,另外也有其他一些副作用,比如内存碎片会增加。
B.并发执行(Concurrent)和应用程序停止(Stop-the-world):。Stop-the-world的垃圾回收方式在执行垃圾回收的同时会导致应用程序的暂停。并发执行的垃圾回收虽然不会导致应用程序的暂停,但由于并发执行垃圾回收需要解决和应用程序的执行冲突(应用程序可能会在垃圾回收的过称中修改对象),因此并发执行垃圾回收的系统开销比Stop-the-world更好,而且执行时也需要更多的堆内存。
C.压缩(Compacting)和不压缩(Non-compacting)和复制(Copying):为了减少内存碎片,支持压缩的垃圾回收器会把所有的活对象搬迁到一起,然后将之前占用的内存全部回收。不压缩式的垃圾回收器只是回收内存,这样回收回来的内存不可能是连续的,因此将会有较多的内存碎片。较之压缩式的垃圾回收,不压缩式的垃圾回收回收内存快了,而分配内存时就会更慢,而且无法解决内存碎片的问题。复制式的垃圾回收会将所有可达对象复制到另一块相同的内存中,这种方式的优点是垃圾及回收过程不会产生内存碎片,但缺点也很明显,需要拷贝数据和额外的内存。
30、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于Java程序中对象而言,如果这个对象没有任何引用变量引用它,那么这个对象将不可能被程序访问,因此可认为它是垃圾;只要有一个以上的引用变量引用该对象,该对象就不会被垃圾回收。
对于Java的垃圾回收器来说,它使用有向图来记录和管理堆内存中的所有对象,通过这个有向图就可以识别哪些对象是“可达的”(有引用变量引用它就是可达的),哪些对象是“不可达的”(没有引用变量引用它就是不可达的),所有“不可达”对象都是可被垃圾回收的。
但对于如下程序:
class A
{
Bb;
}
class B
{
Aa;
}
public class Test
{
publicstatic void main(String[] args)
{
Aa = new A();
a.b= new B();
a.b.a= a;
a= null;
}
}
上面程序中A对象、B对象,它们都“相互”引用,A对象的b属性引用B对象,而B对象的a属性引用A对象,但实际上没有引用变量引用A对象、B对象,因此它们在有向图中依然是不可达的,因此也会被当成垃圾处理。
程序员可以手动执行System.gc(),通知GC运行,但这只是一个通知,而JVM依然有权决定何时进行垃圾回收。
31、什么时候用assert。
assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,assert将给出警告或退出。
Java的assert是关键字。
public class TestAssert
{
public static void main(String[] args)
{
int a = 5;
//断言a>3
assert a > 3;
//断言a<3,否则显示a不小于3,且a的值为:” + a
assert a < 3 : “a不小于3,且a的值为:” + a;
}
}
从上面代码可以看出,assert的两个基本用法如下:
assert logicExp;
asert logicExp : expr;
A.第一个直接进行断言,
B.第二个也是进行断言,但当断言失败失败时显示特定信息。
最后要指出:
虽然assert是JDK1.4新增的关键字,但有一点非常重要:
java命令默认不启动断言,
为了启动用户断言,应该在运行java命令时增加-ea(Enable Assert)选项。
为了启动系统断言,应该在运行java命令时增加-esa(Enable System Assert)选项。
32、Java中会存在内存泄漏吗,请简单描述。
为了搞清楚Java程序是否有内存泄露存在,我们首先了解一下什么是内存泄露:程序运行过程中会不断地分配内存空间;那些不再使用的内存空间应该即时回收它们,从而保证系统可以再次使用这些内存。如果存在无用的内存没有被回收回来,那就是内存泄露。
对于Java程序而言,只要Java对象一直处于可达状态,垃圾回收机制就不会回收它们——即使它们对于程序来说已经变成了垃圾(程序再也不需要它们了);但对于垃圾回收机制来说,它们还不是垃圾(还处于可达状态),因此不能回收。
看ArrayList中remove(int index)方法的源代码,程序如下:
public E remove(int index)
{
//检查index索引是否越界
RangeCheck(index);
//使修改次数加1
modCount++;
//获取被删除的元素
EoldValue = (E)elementData[index];
intnumMoved = size - index - 1;
//整体搬家
if(numMoved > 0)
System.arraycopy(elementData,index+1
,elementData, index, numMoved);
//将ArrayList的size减1,
//并将最后一个数组赋为null,让垃圾回收机制回收最后一个元素
elementData[–size]= null;
returnoldValue;
}
上面程序中粗体字代码elementData[–size]= null;就是为了避免垃圾回收机制而书写的代码,如果没有这行代码,这个方法就会产生内存泄露——每删除一个对象,但该对象所占用的内存空间却不会释放。
33、能不能自己写个类,也叫java.lang.String?
可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去加载jre.jar包中的那个java.lang.String。
但在Tomcat的web应用程序中,都是由webapp自己的类加载器先自己加载WEB-INF/classess目录中的类,然后才委托上级的类加载器加载,如果我们在Tomcat的web应用程序中写一个java.lang.String,这时候Servlet程序加载的就是我们自己写的java.lang.String,但是这么干就会出很多潜在的问题,原来所有用了java.lang.String类的都将出现问题。
34、ArrayList如何实现插入的数据按自定义的方式有序存放
编程思路是:实现一个类对ArrayList进行包装,当程序试图向ArrayList中放入数据时,程序将先检查该元素与ArrayList集合中其他元素的大小,然后将该元素插入到指定位置。
class MyBean implements Comparable{
publicint compareTo(Object obj){
if(!obj instanceof MyBean)
thrownew ClassCastException()。
MyBeanother = (MyBean) obj;
returnage > other.age?1:age== other.age?0:-1;
}
}
class MyTreeSet {
privateArrayList datas = new ArrayList();
publicvoid add(Object obj){
for(inti=0;i<datas.size();i++){
if(obj.compareTo(datas.get(i)!= 1){
datas.add(i,obj);
}
}
}
}
35.序列化接口的id有什么用?
反序列化Java对象时必须提供该对象的class文件,现在的问题是随着项目的升级,系统的class文件也会升级,Java如何保证两个class文件的兼容性?
Java序列化机制允许为序列化类提供一个private static final的serialVersionUID值,该Field值用于标识该Java类的序列化版本,也就是说如果一个类升级后,只要它的serialVersionUID Field值保持不变,序列化机制也会把它们当成同一个序列化版本。
36、hashCode()方法的作用?
hashCode()方法与equals()方法相似,都是来自java.lang.Object类的方法,都允许用户定义的子类重写这两个方法。
一般来说,equals这个方法是给用户调用的,如果你想根据自己的业务规则来判断2个对象是否相等,你可以重写equals()方法。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。
而hashCode()方法通常是给其他类来调用的,比如当我们要把两个对象放入HashSet时,由于HashSet要求两个对象不能相等,而HashSet判断两个对象是否相等的标准是通过equals()比较返回false、或两个对象的hashCode()方法返回值不相等——只要满足任意一个条件都可会认为两个对象不相等。
从这个角度来看,我们可以把hashCode()方法的返回值当成这个对象的“标识符”,如果两个对象的hashCode()相等,即可认为这两个对象是相等的。因此当我们重写一个类的equals()方法时,也应该重写它的hashCode()方法,而且这两个方法判断两个对象相等的标准也应该是一样的。
)
37、编写一个函数将一个十六进制数的字符串参数转换成整数返回。
Stringstr = “13abf”;
intlen = str.length;
intsum = 0;
for(inti=0;i<len;i++){
charc = str.charAt(len-1-i);
intn = Character.digit(c,16);
sum+= n * (1<<(4*i));
}
其实,也可以用Integer.parseInt(str,16),但面试官很可能是想考我们的编码基本功。
38、银行还款问题
银行贷款的还款方式中最常用的是一种叫“等额本息”,还款法,即借款人在约定还款期限内的每一期(月)归还的金额(产生的利息+部分本金)都是相等的,现有一笔总额为T元的N年期住房贷款,年利率为R,要求算出每一期的还款的本金和利息总额,请写出解决思路和任意一种编程语言实现的主要代码。
思路:既然是按月还款,那我就要将N年按月来计算,即要还N*12个月,这样就可以求出每月要还的本金。由于每月要还的那部分本金所欠的时间不同,所以,它们所产生的利息是不同的,该部分本金的利息为:部分本金额*所欠月数*月利率。应该是这么个算法,如果利息还计利息,如果月还款不按年利率来算,老百姓算不明白的。
intmonthMoney = T/N/12;
floatmonthRate = R/12;
inttotalMonth = N * 12;
floattotalRate = 0;
for(inti=1;i<=totalMonth;i++){
totalRate+= monthMoney * monthRate * i;
}
intresult = monthMoney + totalRate/N/12;
39. *任意数字序列“123456”之类,输出它们所有的排列组合
Stringstr = “fkjav”;
char[]arr1 = str.toCharArray();
char[]arr2 = java.util.Arrays.copyOf(arr1,arr1.length);
for(inti=0;i<arr1.length-1;i++)
{
for(intj = i+1;j<arr2.length;j++){
System.out.println(arr1[i]+ “,” + arr2[j]);
}
}
40、构造器Constructor是否可被override?
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
41、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)? 抽象类中是否可以有静态的main方法?
接口可以继承接口。抽象类可以实现(implements)接口,抽象类也可以继承具体类。抽象类中可以有静态的main方法。
只要记住《疯狂Java讲义》中的归法:抽象类的特征是有得有失,得到的功能是抽象类可以拥有抽象方法(当然也可以没有);失去的功能的是抽象类不能创建实例了。至于其他的,抽象类与普通类在语法上大致是一样的。
42、写clone()方法时,通常都有一行代码,是什么?
clone 有缺省行为,super.clone();因为首先要把父类中的成员复制到位,然后才是复制自己的成员。
43、abstract class和interface有什么区别?
含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected和默认访问权限。但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。即使你不写修饰符,默认也是public的。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
下面接着再说说两者在应用上的区别:
接口更多的是在系统架构设计方法发挥作用,接口体现的是一种规范。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板设计模式是抽象类的一个典型应用,假设项目中需要使用大量的DAO组件,这些DAO组件通常都具有增、删、改、查等基本方法,因此我们就可以定义一个抽象的DAO基类,然后让其他DAO组件来继承这个DAO基类,把这个DAO基类当成模板使用。
44、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?
abstract的method 不可以是static的,因为抽象的方法是要被子类实现的,而static与子类扯不上关系!
native方法表示该方法要用另外一种依赖平台的编程语言实现的,不存在着被子类实现的问题,所以,它也不能是抽象的,不能与abstract混用。
关于synchronized与abstract不能同时使用。因为synchronized修饰一个方法时,表明将会使用该方法的调用者作为同步监视器,但对于一个abstract方法而言,它所在类是一个抽象类,抽象类也无法创建实例,因此也就无法确定synchronized修饰方法时的同步监视器了,因此synchronized与abstract不能同时使用。
45、什么是内部类? Static Nested Class 和 Inner Class的不同。
内部类就是在一个类的内部定义的类,内部类中不能定义静态成员。
内部类作为其外部类的一个成员,因此内部类可以直接访问外部类的成员。但有一点需要指出:静态成员不能访问非静态成员,因此静态内部类不能访问外部类的非静态成员。
如果内部类使用了static修饰,那这个内部类就是静态内部类,也就是所谓的static NestedClass;如果内部类没有使用修饰,它就是Inner Class。除此之外,还有一种局部内部类:在方法中定义的内部类就是局部内部类,局部内部类只在方法中有效。
对于StaticNested Class来说,它使用了static修饰,因此它属于类成员,Static Nested Class的实例只要寄生在外部类中即可。因此使用Static NestedClass十分方便,开发者可以把外部类当成Static Nested Class的一个包即可。
对于Inner Class而言,它是属于实例成员,因此Inner Class的实例必须寄生在外部类的实例中,因此程序在创建Inner Class实例之前,必须先获得一个它所寄生的外部类的实例。否则程序无法创建InnerClass的实例。例如如下代码:
class Outer
{
classInner
{
}
}
public class Test
{
publicstatic void main(String[] args)
{
Outer.Innerinner;
Outerouter = new Outer();
//必须先获得外部类的实例,然后才能调用构造器。
inner= outer.new Inner();
}
}
46、内部类可以引用它的外部类的成员吗?有没有什么限制?
内部类可以访问所在外部类的成员。
但有一点需要注意:静态成员不能访问非静态成员,因此静态内部类(属于静态成员)就不能访问外部类的非静态成员。
47、Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?
可以继承其他类或实现其他接口。而且由于匿名内部类特殊的语法:
new 父类|父接口()
{
类体实现部分
}
从上面语法不难看出,匿名内部类必须继承其他类或实现其他接口。
48、super.getClass()方法调用
下面程序的输出结果是多少?
import java.util.Date;
public class Test extends Date{
publicstatic void main(String[] args) {
newTest().test();
}
publicvoid test(){
System.out.println(super.getClass().getName());
}
}
程序输出的是Test。
这很简单,《疯狂Java讲义》(第2版)中有关于super关键字很透彻的解释:super它只是一个限定词,当用super引用时,它也是引用当前对象本身,只是super只是限定了访问当前对象从父类那里继承得到成员变量或方法。
如果需要访问父类的类名,应该使用如下语法:
super.getClass().getSuperclass().getName()
49. JDK中哪些类是不能继承的?
不能继承的是类是那些用final关键字修饰的类。
实际上即使我们自己开发的类,也可以通过使用final修饰来阻止被继承。通过使用final修饰一个类,可以阻止该类被继承,这样该类就被完全地封闭起来了,不会有子类来重写它的方法,因此更加安全。
50、String s = “Hello”;s = s + ” world!”;这两行代码执行后,原始的String对象中的内容到底变了没有?
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 “Hello”,然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为”Hello world!”,原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
实际上,当我们需要一个字符串对象时,应该使用如下语法来创建String对象:
Sring s = “fkjava.org”;
也就是直接使用字符串直接量的语法。而不是:
String s = new String(“fkjava.org”);
对于第二种语法而言,每次都会调用构造器生成新的String对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。
基于这样一种想法,Java提供了字符串缓存池来管理字符串直接量,当程序多次用到同一个字符串直接量时,系统会让它们都引用字符串缓存池中的同一个String对象。因此使用在程序中使用字符串直接量可以充分利用这个特性来降低系统内存开销,提高程序性能。
51、是否可以继承String类?
String类是final类,不可以被继承。
52、如何把一段逗号分割的字符串转换成一个数组?
A. 在以前的时候,Java提供了一个StingTokenizer工具类来处理字符串分割的问题。比如使用如下语法:
StringTokenizer st = new StringTokenizer(“this,is,a,test”, “,”);
while (st.hasMoreTokens())
{
System.out.println(st.nextToken());
}
这样程序将会输出
this
is
a
test
B. 后来Java为String类增加了正则表达式支持,StingTokenizer基本上没用了。因此上面代码可以简写为:
String [] result = “this,is,a,test”.split(“,”);
其中result数组中就存放了this、is、a、test等字符串元素。
53、下面这条语句一共创建了多少个对象:String s=”a”+”b”+”c”+”d”;
答:对于如下代码:
String s1 = “a”;
String s2 = s1 + “b”;
String s3 = “a” + “b”;
System.out.println(s2 == “ab”);
System.out.println(s3 == “ab”);
第一条语句打印的结果为false,第二条语句打印的结果为true。
Java会在编译时对字符串相加进行优化处理,如果整个表达式中所有参与运算的都是字符串直接量,Java会在编译时就把这个表达式的值计算出来,然后直接将结果赋值给字符串引用变量。因此上面题目中定义的String s = “a” + “b” + “c” +”d”;实际上相当于直接定义了”abcd”的字符串直接量,所以,上面的代码应该只创建了一个String对象。
而且这个字符串直接量会被放入字符串缓存池中。如下两行代码,
String s = “a” + “b” +”c” + “d”;
System.out.println(s == “abcd”);
由于s引用了字符串缓存池中的”abcd”字符串,因此上面输出结果应该为true。
54、Collection框架中实现比较要实现什么接口
Java集合框架中需要比较大小的集合包括TreeMap、TreeSet,其中TreeMap会根据key-value对中key的大小进行排序,而TreeSet则会对集合元素进行排序。
因此TreeMap的key、TreeSet的集合元素,都需要可以比较大小。集合框架中之比较大小的有两种方式:
A.自然排序:对于自然排序来说,要求TreeMap中的所有key都实现Comparable接口,实现该接口时需要实现一个int compareTo(T o)方法,用于判断当前对象与o对象之间的大小关系。如果该方法返回正整数,则说明当前对象大于o对象;如果该方法返回0,说明两个对象相等;如果该方法返回负整数,则说明当前对象小于o对象;JDK的很多类都已经实现了Comparable接口,例如String、Date、BigDecimal等。
B.定制排序:定制排序需要在创建TreeMap或TreeSet时传入一个Comparator对象,此时TreeMap或TreeSet不再要求key、集合元素本身是可比较大小的,而是由Comparator来负责比较集合元素的大小。Comparator本身只是一个接口,因此创建Comparator对象只能是创建它的实现类的对象,Comparator的实现类需要实现int compare(T o1, T o2)方法,该方法用于判断o1、o2两个对象的大小,如果该方法返回正整数,则说明o1大于o2、如果该方法返回负整数,则说明o1小于o2、如果返回0,则说明两个对象相等。
55、ArrayList和Vector的区别
这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的——这是由List集合规范制订的。
而且ArrayList与Vector底层都是基于数组的,因此它们的实现代码也大致相似。区别在于Vector是一个古老的集合,从JDK1.0开始就有了,因此它包含了大量方法名很长的方法,JDK 1.2开始引入集合框架,引入List接口,才让Vector实现了List接口,因此又增加了一些List接口中定义的方法。总体来说,ArrayList可以完全代替Vector,除了在一些很古老的API中强制要求使用Vector之外。
Vector还有一个特征:它是线程安全的,因此性能比较差。而ArrayList并不是线程安全的,因此性能较好。实际上即使我们要在多线程环境下使用List集合,也应该选择ArrayList,而不是Vector,因为Java还提供了一个Collections工具类,它可以把ArrayList包装成线程安全的集合类,例如如下代码:
List list = Collections.synchronizedList(newArrayList());
56、HashMap和Hashtable的区别
HashMap与Hashtable的区别类似于ArrayList与Vector的区别。
Hashtable与Vector都是JDK 1.0就有一个一个古老的集合,因此Hashtable是一个继承自Dictionary的古老集合。
从JDK 1.2引入集合框架的Map接口之后,Java让Hashtable也实现了Map接口,因此Hashtable也新增实现了一些Map接口中定义的方法。实际上Hashtable与HashMap底层的实现很相似,它们都是基于Hash表的实现。
HashMap与Hashtable的区别主要有如下两点:
A.HashMap允许使用null作为key或value,而Hashtable不允许。
B.HashMap是线程不安全的,因此性能较好;但Hashtable是线程安全的,因此性能较差。
实际上,实际在多线程环境下,Java提供了Collections工具类把HashMap包装成线程安全的类,因此依然应该使用HashMap,如下代码所示:
Map map = Collections. synchronizedMap(newHashMap());
简单的说,编程时应该尽量避免使用Hashtable,除非在一个古老的API中强制要求Hashtable。
57、List 和 Map 区别?
表面来看,List是一个只是存放单个元素的集合,List集合所包含的元素可以重复,元素按放入的先后顺序来存放,程序可以通过元素的索引来读取元素,因此List相当于一个动态数组;Map则是一个存放key-value对的集合,Map里存放的key-value对是无需的,Map包含的key是不允许重复的。程序可以key来取出该key对应的value。
深入阐述:如果换个角度来看,我们可以把List当成Map来看,List相当于一个key都是值的Map,程序通过元素的索引读取List集合的元素时,完全也可以当成Map根据key来读取value。从另一个角度来看,Map也可以当成元素索引可以是任意类型的List集合。
58、List, Set, Map是否继承自Collection接口?
List、Set是,Map不是。
59、List、Map、Set三个接口,存取元素时,各有什么特点?
Set集合是最接近Collection的集合,因此Set集合几乎没有在Collection增加什么方法。Set集合代表了集合元素无序、几何元素不允许重复的集合。
List集合则在Collection的基础上为元素增加了索引的特性,因此List集合代表了集合元素有序、集合元素可以重复的集合。
Map则代表了存放key-value对的集合,程序可以通过key来获取其中的value。
就Set集合来说,对于开发者而言,它的集合元素是无序的,似乎显得有些杂乱、无规律,但对计算机而言这不可能,因此计算机需要快速存、取Set集合中的元素。Set集合有两个实现类:HashSet与TreeSet,其中HashSet底层其实使用了一个数组来存放所有集合元素,然后通过Hash算法来决定每个集合元素在底层数组中存放位置,因此HashSet对集合元素的存、取就是Hash算法+数组存、取——也就是说只比数组存、取多了些Hash算法开销,因此性能非常快。TreeSet底层则完全是一个红黑树,因此红黑树是折衷平衡的排序二叉树,它底层没有数组开销,存、取元素时都是基于红黑树算法的,因此性能也不错。
对于List集合而言,主要有两个实现:ArrayList与LinkedList,其中ArrayList底层是基于数组的,而且ArrayList存、取元素本身就是通过元素索引来进行的,因此ArrayList对元素的存、取性能非常好,几乎等同于存、取数组元素。但则添加、删除元素时需要对数组元素进行“整体搬家”,因此添加、删除元素时性能较差。而LinkedList底层则是基于一个链表实现的,当从链表中存、取元素时,需要定位元素的位置,系统开销较大。但添加、删除元素时,只要修改元素的引用(相当于指针)即可,因此性能非常好。
对于Map集合而言,其底层存、取性能与Set集合完全一样。其实Set集合本身就是基于Map实现的——如果我们把Map集合的所有value都当成空对象处理、只考虑Map集合的key,Map集合就变成了Set集合。换个角度来看,如果我们向Set集合中添加的对象是key-value所组成的Entry对象,那么Set集合也就变成了Map集合。
60、说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用。
实际上Java提供了Collections工具类,它可以把ArrayList、LinkedList包装成线程安全的集合,因此实际编程中应该避免使用Vector。
61、去掉一个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);
}
还有一种简单的方式,HashSetset = new HashSet(vector);
62、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
说明:其实这个题目本身有问题!因为Set只是一个接口,它的不同实现类判断元素是否相等的标准是不同的。笼统地说,Set里的元素是不能重复的,判断元素重复使用equals()。而不是==。
对于HashSet而言,判断两个对象是否相等是通过equals()和hashCode()方法,只要两个对象通过 equals()比较返回false、或两个对象的hashCode()不相等,那么HashSet就会把它们当成不相同。
对于TreeSet而言,判断两个对象相等的唯一标准是:两个对象通过compareTo(Objectobj)比较是否返回0,与equals()方法无关。只要两个对象通过compareTo(Object obj)比较没有返回0,Java就会把它们当成两个对象处理——这一点是很多人容易误解的,不过我们可以通过《疯狂Java讲义》中的一个示例来说明:
class Z implements Comparable
{
intage;
publicZ(int age)
{
this.age= age;
}
//重写equals()方法,总是返回true
publicboolean equals(Object obj)
{
returntrue;
}
//重写了compareTo(Object obj)方法,总是返回正整数
publicint compareTo(Object obj)
{
return1;
}
}
public class TreeSetTest2
{
publicstatic void main(String[] args)
{
TreeSetset = new TreeSet();
Zz1 = new Z(6);
set.add(z1);
//输出true,表明添加成功
System.out.println(set.add(z1)); //①
//下面输出set集合,将看到有两个元素
System.out.println(set);
//修改set集合的第一个元素的age变量
((Z)(set.first())).age = 9;
//输出set集合的最后一个元素的age变量,将看到也变成了9
System.out.println(((Z)(set.last())).age);
}
}
上面程序中两个Z对象通过equals()比较总会返回true,但通过compareTo(Object obj)比较总是不会返回0,因此两次向TreeSet中添加同一个元素,TreeSet会把它们当成不同的对象进行处理,最后TreeSet集合中会显示有两个对象,但实际上是同一个对象。
63、你所知道的集合类都有哪些?主要方法?
最常用的集合接口是 Set、List、Queue,它们都是Collection的子接口,除此之外还有Map接口。
对于Set集合而言,它的常用实现类包括HashSet与TreeSet。HashSet还有一个子类:LinkedHashSet。
对于List集合而言,它的常用实现类包括ArrayList、Vector与LinkedList。
对于Queue集合而言,它有一个子接口Deque(代表双端队列),它的常用实现类包括ArrayDeque与LinkedList。
对于Map集合而言,它的常用实现类是HashMap与TreeMap。HashMap还有一个子类:LinkedHashMap。
至于这些集合的方法,由于集合类也就是所谓的“容器类”,因此它的方法无非就是向容器中添加、删除、取出、遍历元素的方法。
对于List集合而言,由于它的集合元素都有有序的、有索引的,因此它包括了大量根据索引来添加、删除、取出集合元素的方法。
对于Deque集合而言,由于它是双端队列,即可当成队列使用,也可当成栈使用,因此它增加栈、队列的方法,如offer、peek、push、pop等。
对Map而言,它所包含的无非就是根据key来添加、删除、取出value的方法。
64、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
对。
因为equals()方法可以用开发者重写,hashCode()方法也可以由开发者来重写,因此它们是否相等并没有必然的关系。
如果对象要保存在HashSet或HashMap中,它们的equals()相等,那么,它们的hashCode()返回值也应该相等。
65、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常!
根据TreeSet底层的实现:TreeSet底层的实现就是红黑树,因此当程序向TreeSet中添加集合元素时,程序会多次调用该对象的compareTo()方法与TreeSet中的集合元素进行比较,直到找到该元素在红黑树中应当所在节点位置。因此该问题的答案是:当前正在添加父类对象就多次调用父类对象的compareTo()方法;当前正在添加子类对象就多次调用子类对象的compareTo()方法。
至于程序是否抛出异常,则取决于compareTo()方法的实现,如果子类在实现compareTo()方法时,试图把被比较对象转换为子类对象之后再进行比较——如果TreeSet集合中已经包括了父类对象,这就会引起ClassCastException。
示例代码如下:
class A implements Comparable
{
intage;
publicA(int age)
{
this.age= age;
}
publicint compareTo(Object obj)
{
System.out.println(“AAAAAAAAAA”);
Atarget = (A)obj;
returnage > target.age ? 1 : age < target.age ? -1 : 0;
}
publicString toString()
{
returngetClass() + “,age:” + age;
}
}
class B extends A implements Comparable
{
publicB(int age)
{
super(age);
}
publicint compareTo(Object obj)
{
System.out.println(“BBBBBBBBB”);
Atarget = (A)obj;
returnage > target.age ? 1 : age < target.age ? -1 : 0;
}
}
public class TreeSetTest2
{
publicstatic void main(String[] args)
{
TreeSetset = new TreeSet();
set.add(newA(3));
set.add(newB(1));
set.add(newA(2));
for(Iteratorit = set.iterator(); it.hasNext() ;)
{
System.out.println(it.next());
}
}
}
上面程序可以看到,输出:
AAAAAAAAAA
BBBBBBBBB
AAAAAAAAAA
AAAAAAAAAA
第一次添加A对象,所以调用A对象的compareTo()方法;第二次添加B对象,所以程序调用了B对象的compareTo()方法;第三次再次添加A对象,由于集合中已经有两个对象,因此程序两次调用了A对象的compareTo()方法与集合中的元素进行比较。
66、说出一些常用的类,包,接口,请各举5个
常用的包有:
java.lang包下包括Math、System、StringBuilder、StringBuffer、Runtime、Thread、Runnable等。
java.util包下包括List、Set、Map,以及这些接口的常用实现类:ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等。
java.io包下包括InputStream、OutputStream、Reader、Writer、FileInputStream、FileOutputStream、FileReader、FileWriter、BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter等
java.sql包下包括Connection、Statement、PreparedStatement、ResultSet等。
java.NET包下包括Socket、ServerSocket、URL、URLConnection、DatagramPacket、DatagramSocket等。
如果为让别人感觉你对Android很熟悉,还应该增加一些Android常用的包、类,如:
android.app包下有:Activity、ListActivty、TabActivity、AlertDialog、AlertDialog.Builder、Notification、Service等。
android.content包下有:ContentProvider、ContentResolver、ContentUris、ContentValues、Context等
android.database包下有Cursor等
android.database.sqlite包下有:SQLiteDatabase、SQLiteOpenHelper等
android.graphics包下有Bitmap、BitmapFactory、Canvas、Color、Matrix、Paint、Path等。
android.widget包下有TextView、Button、CheckBox、RadioButton、ListView、GridView、Spinner、Gallery等。
67、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
字节流,字符流。字节流由InputStreamOutputStream派生出来,字符流由Reader、Writer派生出来。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。
68、字节流与字符流的区别
字节流和字符流区别非常简单,它们的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同:字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。
字节流直接是基于字节进行输入、输出的,因此它的适用性更广。字符流则在处理文本内容的的输入、输出时更加方便——不会出现读取半个字符的情形。
Java提供了将字节流转换为字符串的InputStreamReader和OutputStreamWriter,但没有提供将字符流转化为字节流的方法。因为:字节流比字符流的使用范围更广,但字符流比字节流操作方便。如果有一个流已经是字符流了,也就是说是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在有一个字节流,但我们知道这个字节流的内容都是文本内容,那么把它转换成字符流来处理就会更方便一些,所以Java只提供了将字节流转换字符流的转换流,没有提供将字符流转换成字节流的转换流。
69、什么是Java序列化,如何实现Java序列化?或者请解释Serializable接口的作用。
象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流(无论是从磁盘中获取,还是通过网络获取),都可以将这种二进制流恢复成原来的Java对象。
实现Java序列化有两种方式:
A.让Java类实现Serializable接口。
B.让Java类实现Externalizable接口,实现该接口时还必须实现readExternal、writeExternal O这两个方法。
一旦Java类实现了上面两种接口,接下来程序中就可通过ObjectInputStream、ObjectOutputStream来读取、保存Java对象。
Serializable接口只是一个标记接口,实现该接口无需实现任何方法,实现了该接口的类就是可序列化的类。
序列化对于Java开发非常重要,例如在web开发中,如果对象需要保存在了Session中,Tomcat在某些时候需要把Session中的对象序列化到硬盘,因此放入Session中的对象必须是可序列化的,要么实现Serializable接口,要么实现Externalizable接口。还有,如果一个对象要经过网络传输(比如RMI远程方法调用的形参或返回值),这个对象也应该是可序列化的。
70、描述一下JVM加载class文件的原理机制?
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过
加载
连接
初始化
三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是我们前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
A.从本地文件系统来加载class文件,这是前面绝大部分类加载方式。
B.从JAR包中加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就是放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
C.通过网络加载class文件。
D.把一个Java源文件动态编译、并执行加载。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:
1.Bootstrap ClassLoader:根类加载器。
2.Extension ClassLoader:扩展类加载器。
3.System ClassLoader:系统类加载器。
BootstrapClassLoader,被称为引导(也称为原始或根)类加载器。它负责加载Java的核心类。在Sun的JVM中,当执行java.exe的命令时使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。
ExtensionClassloader,被称为扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR包的类。
通过这种方式,我们就可以为Java扩展核心类以外的新功能,只要我们把自己开发的类打包成JAR文件,然后放入JAVA_HOME/jre/lib/ext路径即可(对于笔者的安装环境来说,扩展路径为:D:/Java/jdk1.7.0/jre/lib/ext)。
SystemClassloader,被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以类加载器作为父加载器。
71、heap和stack有什么区别。
stack内存指的是程序进入一个方法时,系统会专门为这个方法分配一块内存空间,这块内存空间也被称为该方法栈区,该方法的栈区专门用于存储该方法中定义的局部变量,包括基本类型的变量和引用变量。当这个方法结束时,该方法栈区将会自动被销毁,栈区中的所有局部变量都会随之销毁。
heap内存是Java虚拟机拥有的内存区,所有Java对象都将被放在heap内存内,位于heap内存中的Java对象由系统的垃圾回收器负责跟踪管理——也就是进行垃圾回收,当堆内存中的Java对象没有引用变量引用它时,这个Java对象就变成了垃圾,垃圾回收期就会在合适的时候回收它。
72、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?
肯定会执行。finally{}块的代码只有在try{}块中包含遇到System.exit(0);之类的导致Java虚拟机直接退出的语句才会不执行。
当程序执行try{}遇到return时,程序会先执行return语句,但并不会立即返回——也就是把return语句要做的一切事情都准备好,也就是在将要返回、但并未返回的时候,程序把执行流程转去执行finally块,当finally块执行完成后就直接返回刚才return语句已经准备好的结果。
例如我们有如下程序:
public class Test
{
publicstatic void main(String[] args)
{
System.out.println(newTest().test());;
}
staticint test()
{
intx = 1;
try
{
returnx;
}
finally
{
System.out.println(“finally块执行:” + ++x);
}
}
}
此时的输出结果为:
finally块执行:2
1
看到上面程序中finally块已经执行了,而且程序执行finally块时已经把x变量增加到2了。但test()方法返回的依然是1,这就是由return语句执行流程决定的,Java会把return语句先执行完、把所有需要处理的东西都先处理完成,需要返回的值也都准备好之后,但是还未返回之前,程序流程会转去执行finally块,但此时finally块中的对x变量的修改已经不会影响return要返回的值了。
但如果finally块里也包含return语句,那就另当别论了, 因为finally块里的return语句也会导致方法返回,例如把程序该为如下形式:
public class Test
{
publicstatic void main(String[] args)
{
System.out.println(newTest().test());;
}
staticint test()
{
intx = 1;
try
{
returnx++;
}
finally
{
System.out.println(“finally块执行:” + ++x);
returnx;
}
}
}
此时的输出结果为:
finally块执行:3
3
正如介绍的,程序在执行returnx++;时,程序会把return语句执行完成,只是等待返回,此时x的值已经是2了,但程序此处准备的返回值依然是1。接下来程序流程转去执行finally块,此时程序会再次对x自加,于是x变成了3,而且由于finally块中也有return x;语句,因此程序将会直接由这条语句返回了,因此上面test()方法将会返回3。
73、下面的程序代码输出的结果是多少?
public class smallT
{
publicstatic void main(String args[])
{
smallTt = new smallT();
intb = t.get();
System.out.println(b);
}
publicint get()
{
try
{
return1 ;
}
finally
{
return2 ;
}
}
}
输出结果是:2。
这个程序还是刚才介绍的return语句和finally块的顺序问题。
Java会把return语句先执行完、把所有需要处理的东西都先处理完成,需要返回的值也都准备好之后,但是还未返回之前,程序流程会转去执行finally块。但如果在执行finally块时遇到了return语句,程序将会直接使用finally块中的return语句来返回——因此上面程序将会输出2。
74、final, finally, finalize的区别。
final是一个修饰符,它可以修改类、方法、变量。
final修饰类时表明这个类不可以被继承。
final修饰方法时表明这个方法不可以被其子类重写。
final修饰变量时可分为局部变量、实例变量和静态变量,当final修饰局部变量时,该局部变量可以被一次赋值,以后该变量的值不能发生该变量;当final修饰实例变量时,实例变量必须由程序在构造器、初始化块、定义时这3个位置的其中之一指定初始值;当final修饰静态变量时,静态变量必须由程序在静态初始化块、定义时这2个位置的其中之一指定初始值。
finally是异常处理语句结构的一部分,表示总会执行的代码块。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收。但实际上重写该方法进行资源回收并不安全,因为JVM并不保证该方法总被调用。
75、运行时异常与一般异常有何异同?
Checked异常体现了Java的设计哲学:没有完善错误处理的代码根本就不会被执行!
对于Checked异常的处理方式有两种:
A.当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修补该异常。
B.当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
但Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try…catch块来捕捉Runtime异常。
76、error和exception有什么区别?
Error错误,一般是指虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。
Exception表示一种设计或实现问题。也就是说,程序员应该对这些情况进行考虑、并提供相应的处理。
77、Java中的异常处理机制的简单原理和应用。
程序运行过程中可能出现各种“非预期”情况,这些非预期情况可能导致程序非正常结束。
为了提高程序的健壮性,Java提供了异常处理机制:
try
{
s1…
s2…
s3…
}
catch(Exception ex)
{
//对异常情况的修复处理
}
对于上面处理流程,当程序执行try块里的s1、s2、s3遇到异常时,Java虚拟机将会把这个异常情况封装成异常对象,这个异常对象可以被后面对应的catch块捕捉到,这样保证这些异常会得到合适的处理。
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception,Error错误,一般是指虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。
Exception表示一种设计或实现问题。也就是说,程序员应该对这些情况进行考虑、并提供相应的处理。
异常有可分为Runtime异常和Checked异常,Checked异常体现了Java的设计哲学:没有完善错误处理的代码根本就不会被执行!对于Checked异常的处理方式有两种:
A.当前方法明确知道如何处理该异常,程序应该使用try…catch块来捕获该异常,然后在对应的catch块中修补该异常。
B.当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
实际上Java的Checked异常后来“争议不断”,因为Checked异常要求程序员要么显式声明抛出,要么进行捕捉,不能对Checked异常不闻不问,这样就给编程带来了一定的复杂度,比如spring、hibernate框架的一大特点就是把Checked异常包装成了Runtime异常。
Runtime异常则比较灵活,开发者既可以选择捕获Runtime异常,也可以不捕获。
78、请写出你最常见到的5个runtimeexception。
对于一个有1~2年左右编程经验的人来说,总会经常遇到一些常见的异常,其中有些就是Runtime Exception。比如:
NullPointerException - 当调用一个未初始化的引用变量(实际值为null)的实例Field、实例方法时都会引发该异常。
ArithmeticException - 算术异常。比如5/0将引发该异常。
ArrayIndexOutOfBoundsException:数组索引越界异常。
ClassCastException:类型转换异常。
IllegalArgumentException:参数非法的异常。
79、JAVA语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别代表什么意义?在try块中可以抛出异常吗?
try块表示程序正常的业务执行代码。如果程序在执行try块的代码时出现了“非预期”情况,JVM将会生成一个异常对象,这个异常对象将会被后面相应的catch块捕获。
catch块表示一个异常捕获块。当程序执行try块引发异常时,这个异常对象将会被后面相应的catch块捕获。
throw用于手动地抛出异常对象。throw后面需要一个异常对象。
throws用于在方法签名中声明抛出一个或多个异常类,throws关键字后可以紧跟一个或多个异常类。
finally块代表异常处理流程中总会执行的代码块。
对于一个完整的异常处理流程而言,try块是必须的,try块后可以紧跟一个或多个catch块,最后还可以带一个finally块。
try块中可以抛出异常。
80、Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?
在Java5以前,有如下两种:
第一种:继承Thread类,重写它的run()方法。
代码如下:
new Thread()
{
publicvoid run()
{
//线程执行体
}
}.start();
第二种:实现Runnable接口,并重写它的run()方法。
代码如下:
new Thread(new Runnable()
{
publicvoid run()
{
//线程执行体
}
}).start();
从上面代码不难看出,线程的执行体是一个run()方法,然后程序通过start()方法启动一条线程。
从Java 5开始,Java提供了第三种方式来创建多线程:实现Callable接口,并实现call()方法。Callable接口相当于Runnable接口的增强版,因为Callable接口中定义的call()方法既拥有返回值,也可以声明抛出异常。
代码如下:
new Thread(new FutureTask<Object >(newCallable<Object>()
{
publicObject call() throws Exception
{
//线程执行体
}
})).start();
不仅如此,Java 5还提供了线程支持,ExecutorService对象就代表了线程池,如果开发者利用ExecutorService来启动线程,ExecutorService底层会负责管理线程池。此时,开发者只要把Runnable对象传给ExecutorService即可。如下代码:
ExecutorService pool =Executors.newFixedThreadPool(3)
pool.execute(new Runnable()
{
publicvoid run()
{
//线程执行体
}
});
如果执行通过Callable方式实现的线程,则可按如下代码:
ExecutorService pool =Executors.newFixedThreadPool(3)
pool.execute(new FutureTask<Object>(new Callable<Object>()
{
publicObject call() throws Exception
{
//线程执行体
}
}));
用synchronized关键字修饰同步方法。需要指出的是,非静态的同步方法的同步监视器是this,也就是调用该方法对象,而静态的同步方法的同步监视器则是该类本身。因此使用synchronized修饰的静态方法、非静态方法的同步监视器并不相同,只有基于同一个同步监视器的同步方法、同步代码块才能实现同步。
反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
82、sleep() 和 wait() 有什么区别?
sleep()是Thread类的静态方法,它的作用是让当前线程从运行状态转入、阻塞状态,线程执行暂停下来,当一个线程通过sleep()方法暂停之后,该线程并不会释放它对同步监视器的加锁。
wait()是Object对象的方法,但实际上只有同步监视器才能调用该方法。当程序在同步代码块、或同步方法内通过同步监视器调用该方法时,将会导致当前线程释放对该同步监视器的加锁,而该线程则会进入该同步监视器的等待池中,直到该同步监视器调用notify()或notifyAll()来通知该线程。
83、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
如果有一个资源需要被一个或多个线程共享,这个资源就变成了“竞争”资源,此时多条线程必须按某种既定的规则、依次访问、修改这个“竞争”资源,当一条线程正在访问、修改该“竞争”资源时,其他线程不能同时修改这份“竞争”资源,这就是同步处理。
对于一个银行账户,如果有多个线程试图去访问这个账户时,如果不对多个线程进行同步控制,有可能账户余额只有1000块,但多个线程都试图取款800块时,这些线程同时判断余额之后,都会显示余额足够,从而导致每个线程都取款成功。这显然不是我们希望看到结果。
当程序试图执行一个耗时操作时,程序不希望阻塞当前执行流,因此程序也不应该试图立即获取该耗时操作返回的结果,此时就使用异步编程了,典型的应用场景就是Ajax。当浏览器通过JavaScript发出一个异步请求之后,JavaScript执行流并不会停下来,而是继续向下执行,这就是异步。程序会通过监听器来监听远程服务器响应的到来。
84、多线程有几种实现方法?同步有几种实现方法?
在Java5以前,有如下两种:
第一种:继承Thread类,重写它的run()方法。
代码如下:
new Thread()
{
publicvoid run()
{
//线程执行体
}
}.start();
第二种:实现Runnable接口,并重写它的run()方法。
代码如下:
new Thread(new Runnable()
{
publicvoid run()
{
//线程执行体
}
}).start();
从上面代码不难看出,线程的执行体是一个run()方法,然后程序通过start()方法启动一条线程。
从Java 5开始,Java提供了第三种方式来创建多线程:实现Callable接口,并实现call()方法。Callable接口相当于Runnable接口的增强版,因为Callable接口中定义的call()方法既拥有返回值,也可以声明抛出异常。
代码如下:
new Thread(new FutureTask<Object>(new Callable<Object>()
{
publicObject call() throws Exception
{
//线程执行体
}
})).start();
不仅如此,Java 5还提供了线程支持,ExecutorService对象就代表了线程池,如果开发者利用ExecutorService来启动线程,ExecutorService底层会负责管理线程池。此时,开发者只要把Runnable对象传给ExecutorService即可。如下代码:
ExecutorService pool =Executors.newFixedThreadPool(3)
pool.execute(new Runnable()
{
publicvoid run()
{
//线程执行体
}
});
如果执行通过Callable方式实现的线程,则可按如下代码:
ExecutorService pool =Executors.newFixedThreadPool(3)
pool.execute(new FutureTask<Object>(new Callable<Object>()
{
publicObject call() throws Exception
{
//线程执行体
}
}));
85、启动一个线程是用run()还是start()? .
启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态。run()方法是线程的线程执行体——也就是线程将要完成的事情。
86、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
当一个线程进行一个对象的synchronized方法之后,其他线程完全有可能再次进入该对象的其他方法。
不过要分几种情况来看:
1、如果其他方法没有使用synchronized关键字修饰,则可以进入。
2、如果当前线程进入的synchronized方法是static方法,其他线程可以进入其他synchronized修饰的非静态方法;如果当前线程进入的synchronized方法是非static方法,其他线程可以进入其他synchronized修饰的静态方法。
3、如果两个方法都是静态方法、或者都是非静态方法,并且都使用了synchronized修饰,但只要在该方法内部调用了同步监视器的wait(),则其他线程依然可以进入其他使用synchronized方法修饰的方法。
4、如果两个方法都是静态方法、或者都是非静态方法,并且都使用了synchronized修饰,而且没有在该方法内部调用了同步监视器的wait(),则其他线程不能进入其他使用synchronized方法修饰的方法。
87、线程的基本概念、线程的基本状态以及状态之间的关系
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程(Thread)也被称作轻量级进程(LightweightProcess),线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是互相独立的。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,我们必须确保线程不会妨碍同一进程里的其他线程。
线程的执行需要经过如下状态:
新建
就绪
运行
阻塞
死亡
各状态的转换关系如下图所示:
88、简述synchronized和java.util.concurrent.locks.Lock的异同?
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如,它的tryLock方法可以非阻塞方式去拿锁。
Java代码查错部分
1.
abstract class Name {
private String name;
public abstract boolean isStupidName(String name) {}
}
答案: 错。abstract method必须以分号结尾,且不带花括号。
2.
public class Something {
void doSomething () {
private String s = “”;
int l = s.length();
}
}
有错吗?
答案: 错。局部变量前不能放置任何访问修饰符 (private,public,和protected)。final可以用来修饰局部变量
(final如同abstract和strictfp,都是非访问修饰符,strictfp只能修饰class和method而非variable)。
3.
abstract class Something {
private abstract String doSomething ();
}
这好像没什么错吧?
答案: 错。abstract的methods不能以private修饰。abstract的methods就是让子类implement(实现)具体细节的,怎么可以用private把abstract method隐藏起来呢? (同理,abstractmethod前不能加final)。
4.
public class Something {
public int addOne(final int x) {
return ++x;
}
}
答案: 错。int x被修饰成final,意味着x不能在addOne method中被修改。
5.
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
o.i++;
}
}
class Other {
public int i;
}
和上面的很相似,都是关于final的问题,这有错吗?
答案: 正确。在addOne method中,参数o被修饰成final。如果在addOnemethod里我们修改了o的引用 (比如: o = new Other();),那么如同上例这题也是错的。但这里修改的是o的成员变量,(成员变量),而o的reference并没有改变,因此程序没有错误。
6.
class Something {
int i;
public void doSomething() {
System.out.println(“i = ” + i);
}
}
有什么错呢?
答案: 正确。输出的是”i = 0”。int i是定义实例变量。系统会对实例变量执行默认初始化,因此i的默认被赋值为0 。
7.
class Something {
final int i;
public void doSomething() {
System.out.println(“i = ” + i);
}
}
和上面一题只有一个地方不同,就是多了一个final。
答案: 错。使用final修饰的实例变量必须由程序员显式指定初始值,为final变量指定初始值有3个地方:
A.定义时指定初始值:
B.在初始化块中指定初始值。
C.在构造器中指定初始值。
8.
public class Something {
publicstatic void main(String[] args) {
Something s = new Something();
System.out.println(“s.doSomething() returns ” +doSomething());
}
public String doSomething() {
return “Do something …”;
}
}
答案: 错。静态成员不允许访问非静态成员。上面main方法是静态方法,而doSomething()是非静态方法,因此程序导致编译错误。
9.
此处,Something类的文件名叫OtherThing.java
class Something {
private static void main(String[] something_to_do) {
System.out.println(“Do something …”);
}
}
答案: 正确。由于Something类不是public类,因此Java源文件的主文件名可以是任意的。但public class的名字必须和文件名相同。
10.
interface A{
int x = 0;
}
class B{
int x =1;
}
class C extends B implements A {
public void printX(){
System.out.println(x);
}
public static void main(String[] args) {
new C().printX ();
}
}
答案:错误。在编译时会发生错误。因为C类既实现了A接口,也继承B,因此它将会从A接口、B类中分别继承到成员变量x,因此上面程序中System.out.println(x);代码所引用的x是不明确的。
对于父类的变量,可以用super.x来明确指定,而接口的属性默认隐含为 public staticfinal.所以可以通过A.x来明确指定。
11.
interface Playable {
voidplay();
}
interface Bounceable {
voidplay();
}
interface Rollable extends Playable,Bounceable {
Ballball = new Ball(“PingPang”);
}
class Ball implements Rollable {
privateString name;
publicString getName() {
returnname;
}
publicBall(String name) {
this.name= name;
}
publicvoid play() {
ball= new Ball(“Football”);
System.out.println(ball.getName());
}
}
答案: 错。”interface Rollable extendsPlayable, Bounceable”没有问题。interface可继承多个interfaces,所以这里没错。问题出在interface Rollable里的”Ball ball = new Ball(“PingPang”);”。在接口里声明的成员变量总是常量,也就是默认使用publicstatic final修饰。也就是说”Ballball = new Ball(“PingPang”);”实际上是”public static final Ball ball = newBall(“PingPang”);”。在Ball类的Play()方法中,”ball = newBall(“Football”);”尝试去改变了ball的引用,而这里的ball引用变量是在Rollable中定义的,因此它有final修饰,final修饰的引用变量是不能被重新赋值的,因此上面程序会导致编译错误。
Android技术部分
Android系统架构
Android 是由5部分组成,分别是:LinuxKernel、AndroidRuntime、Libraries、Application Framework、Applications。
1、Linux Kernel
Android基于Linux 2.6提供核心系统服务,例如:安全、内存管理、进程管理、网络堆栈、驱动模型。Linux Kernel也作为硬件和软件之间的抽象层,它隐藏具体硬件细节而为上层提供统一的服务。分层的好处就是使用下层提供的服务而为上层提供统一的服务,屏蔽本层及以下层的差异,当本层及以下层发生了变化不会影响到上层。也就是说各层各尽其职,各层提供固定的SAP(Service Access Point),专业点可以说是高内聚、低耦合。如果你只是做应用开发,就不需要深入了解Linux Kernel层。
2、Android Runtime
Android包含一个核心库的集合,提供大部分在Java编程语言核心类库中可用的功能。每一个Android应用程序都是Dalvik虚拟机中的实例,运行在他们自己的进程中。Dalvik虚拟机设计成可以在一个设备可以高效地运行多个虚拟机。Dalvik虚拟机可执行文件格式是.dex,dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。 大多数虚拟机包括JVM都是基于栈的,而Dalvik虚拟机则是基于寄存器的。两种架构各有优劣,一般来说,基于寄存器的虚拟机具有更好的性能表现,但在硬件通用性上略差。dx工具可以将* .class 转换成*.dex 格式。一个dex文件通常会有多个.class。由于dex有时必须进行最佳化,会使文件大小增加1-4倍,以.dex结尾。 Dalvik虚拟机依赖于Linux 内核提供基本功能,如线程和底层内存管理。
3、Libraries
Android包含一个C/C++库的集合,供Android系统的各个组件使用。这些功能通过Android的应用程序框架(Application Framework)暴露给开发者。下面列出一些核心库:
- 系统C库:由标准C系统库(libc)的BSD衍生,调整为基于嵌入式Linux设备。
- 媒体库:这些库支持播放和录制许多流行的音频和视频格式,以及静态图像文件,包括MPEG4、 H.264、 MP3、 AAC、 AMR、JPG、 PNG。
- 界面管理:管理访问显示子系统和无缝组合多个应用程序的二维和三维图形层
- SGL:基本的2D图形引擎
- OpenGL ES:一套开源的3D库。
- FreeType:位图和矢量字体渲染。
- SQLite:所有应用程序都可以使用的强大而轻量级的关系数据库引擎。
4、Application Framework
通过提供开放的开发平台,Android使开发者能够编制极其丰富和新颖的应用程序。开发者可以自由地利用设备硬件优势、访问位置信息、运行后台服务、设置闹钟、向状态栏添加通知等等,很多很多。开发者可以完全使用核心应用程序所使用的框架APIs。应用程序的体系结构旨在简化组件的重用,任何应用程序都能发布他的功能且任何其他应用程序可以使用这些功能(需要服从框架执行的安全限制)。这一机制允许用户替换组件。 所有的应用程序其实是一组服务和系统,包括:视图(View)–丰富的、可扩展的视图集合,可用于构建一个应用程序。包括包括列表、网格、文本框、按钮,甚至是内嵌的网页浏览器内容提供者(Content Providers)–使应用程序能访问其他应用程序(如通讯录)的数据,或共享自己的数据资源管理器(Resource Manager)–提供访问非代码资源,如本地化字符串、图形和布局文件 通知管理器(Notification Manager)–使所有的应用程序能够在状态栏显示自定义警告活动管理器(Activity Manager)–管理应用程序生命周期,提供通用的导航回退功能
5、Applications
Android装配一个核心应用程序集合,包括电子邮件客户端、SMS程序、日历、地图、浏览器、联系人和其他设置。所有应用程序都是用Java编程语言写的。从上面我们知道Android的架构是分层的,非常清晰,分工很明确。
1、 Android dvm的进程和Linux的进程, 应用程序的进程是否为同一个概念?
DVM指Dalvik的虚拟机。每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。而每一个DVM都是在Linux 中的一个进程,所以说可以认为是同一个概念。
2,sim卡的EF 文件有何作用
sim卡的文件系统有自己规范,主要是为了和手机通讯,sim本身可以有自己的操作系统,EF就是作存储并和手机通讯用的
3,嵌入式操作系统内存管理有哪几种?
页式,段式,段页,用到了MMU,虚拟空间等技术
4,什么是嵌入式实时操作系统, Android 操作系统属于实时操作系统吗?
嵌入式实时操作系统是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统作出快速响应,并控制所有实时任务协调一致运行的嵌入式操作系统。主要用于工业控制、军事设备、航空航天等领域对系统的响应时间有苛刻的要求,这就需要使用实时系统。又可分为软实时和硬实时两种,而Android是基于Linux内核的,因此属于软实时。
5、一条最长的短信息约占多少byte?
中文70(包括标点),英文160,160个字节。
6、 android中的动画有哪几类,它们的特点和区别是什么?
两种,一种是Tween动画、还有一种是Frame动画。
Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化。例如我们可以使用如下语法来定义一个Tween动画:
<?xml version=”1.0”encoding=”UTF-8”?>
<!– 指定动画匀速改变 –>
<set xmlns:android=”http://schemas.android.com/apk/res/android”
android:interpolator=”@android:anim/linear_interpolator”>
<!–定义缩放变换 –>
<scaleandroid:fromXScale=”1.0”
android:toXScale=”0.01”
android:fromYScale=”1.0”
android:toYScale=”0.01”
android:pivotX=”50%”
android:pivotY=”50%”
android:fillAfter=”true”
android:duration=”3000”/>
<!–定义透明度的变换–>
<alpha
android:fromAlpha=”1”
android:toAlpha=”0.05”
android:duration=”3000”/>
<!–定义旋转变换 –>
<rotate
android:fromDegrees=”0”
android:toDegrees=”1800”
android:pivotX=”50%”
android:pivotY=”50%”
android:duration=”3000”/>
</set>
定义Tween动画的XML文件应该放在/res/anim目录下。一旦定义了Tween动画之后,这个Tween动画即可作用于图片,也可以作用于普通View组件,只要调用View组件提供的如下方法来播放Tween动画即可:
startAnimation(Animation)
另一种Frame动画,Frame动画类似于电影。就是依次定义多张静态的图片、以及每张图片的显示事件,多张图片快速切换,利用人眼的视觉暂留形成动画。
定义Frame动画的语法格式如下:
<?xml version=”1.0”encoding=”utf-8”?>
<animation-listxmlns:android=”http://schemas.android.com/apk/res/android”
android:oneshot=[“true”| “false”] >
<itemandroid:drawable=”@[package:]drawable/drawable_resource_name”
android:duration=”integer”/>
</animation-list>
Frame动画本身还属于Drawable对象,是一个AnimationDrawable对象,因此这个XML资源文件应该方法在/res/drawable目录下,AnimationDrawable对象本身就可以播放,只要调用的它的start()方法开始播放即可。
7、Handler机制的原理?
为了理解Handler,先要介绍一下与Handler一起工作的几个组件:
Message:就是Handler接收和处理的消息。
Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到信息之后就把消息交给发送该消息的Handler进行处理。
MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时会在它的构造器中创建Looper对象。Looper提供的构造器代码如下:
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
从上面代码不难看出程序在初始化Looper时会创建一个与之关联的MessageQueue,这个MessageQueue就负责管理消息。
Handler:它的作用有两个:发送消息,程序使用Handler发送消息,被Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程中有一个MessageQueue,否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分两种情况处理:
- 程序UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息。
- 程序员自己启动的子线程,程序员必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可。prepare方法保证每个线程最多只有一个Looper对象,prepare方法的源代码如下:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only oneLooper may be created per thread”);
}
sThreadLocal.set(new Looper());
}
然后调用Looper的静态loop()方法来启动它,loop方法就是使用一个死循环不断都取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。
归纳起来:
Looper:每个线程只有一个,它负责管理MessageQueue,它会负责不断的从MessageQueue中取出消息,并将消息分给对应的Handler处理。
MessageQueue:由Looper负责管理。它采用先进先出的方式来所有Message。
Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。
8、说说MVC模式的原理,它在android中的运用?
Android的官方建议应用程序的开发采用MVC模式。
MVC是Model、View、Controller的缩写,MVC包含三个部分:
- 模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。
- 视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个MVC架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。
- 控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新Model对象状态的部分,控制器更重要的一种导航功能,向用户出发的相关事件,交给Model处理。
Android鼓励弱耦合和组件的重用,在android中MVC的具体体现如下:
- 视图层(View):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入,实际上我们也可以在Android中也可以使用JavaScript+HTML等的方式作为View层,当然这里需要进行Java和JavaScript之间的通信,Android提供了它们之间非常方便的通信实现。
- 控制层(Controller):Android的控制层通常是由Activity以及各种事件监听器来完成的,这就意味着不要把过多的代码写在Activity和事件处理方法中,而是要通过Activity交给Model层去处理。这样做的另外一个原因是Android中的Activity的响应时间是5s,如果耗时的操作放在这里,程序很容易引起ANR(程序没有响应)的异常。
- 模型层(Model):对数据库的操作、对网络等的操作以及各种具体的业务实现都应该在Model里面处理。
9、Activity的生命周期?
Activity的创建、生成、运行都是位于Android系统中的,从这个意义上来看,Android系统就相当于Activity的容器。换句话来说,当我们实现了一个Activity类、并配置它之后,这个Activity的方法都是由系统进行回调的。
Activity包含如下四个状态:
运行
暂停
停止
销毁
Activity的基本生命周期如下代码所示:
public classMyActivity extends Activity {
protected void onCreate(BundlesavedInstanceState);
protected void onStart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
}
你自己写的Activity会按需要重载这些方法,onCreate是免不了的,在一个Activity正常启动的过程中,他们被调用的顺序是 onCreate ->onStart -> onResume,Activity进入运行状态。当Activity被销毁时的顺序是onPause -> onStop -> onDestroy,此时Activity进行销毁状态。这样就是一个完整的生命周期。
如果该Activity运行过程中,有另一个Activity开启了,而且这个Activity是全屏的,那么会经过onPause->onStop,Activity将进入停止状态。当该Activity重新来到前台时,它会经过onRestart->onStart->onResume,它再次进行运行状态。如果新开启的Activity是一个Theme为Translucent 或Dialog 的Activity,该Activity将只是回调onPause,Activity进行暂停状态;恢复的时候回调onResume,Activity再次进行运行状态。
10、让Activity变成一个窗口:Activity属性设定
只需要设置一下Activity的主题就可以了,在AndroidManifest.xml 中定义 Activity时设置主题即可:
android:theme=”@android:style/Theme.Dialog”
或者也可以把Activity设置成半透明的对话框,设置如下主题即可:
android:theme=”@android:style/Theme.Translucent”
11、调用与被调用:为什么使用Intent?
Android使用Intent提供了一致的编程模型,使用Intent可以启动各种程序组件。
比如你要在你的应用程序中点击按钮,给某人打电话,很简单啊,看下代码先:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse(“tel:” + number));
startActivity(intent);
通过这个Intent就可以启动系统打电话的Activity。不仅如此,在我们系统,打电话、查看联系人、编辑联系人、上网啊等都可以通过Intent来启动相应的Activity。Intent提供了程序里各组件之间的解耦。
一般来说,当应用程序通过Intent表达了启动某个应用程序组件的意图之后,系统会根据配置程序组件时指定的IntentFilter进行匹配,只要配置应用程序组件时指定的IntentFilter能与启动程序的Intent匹配,那么该IntentFilter对应的组件就会被启动。
12、如何退出Activity?如何安全退出已调用多个Activity的Application?
对于单一Activity的应用来说,退出很简单,直接finish()即可。
当然,也可以用killProcess()和System.exit()这样的方法。但是,对于多Activity的应用来说,在打开多个Activity后,如果想在最后打开的Activity直接退出,上边的方法都是没有用的,因为上边的方法都是结束一个Activity而已。
在2.1之前,可以使用ActivityManager的restartPackage方法。
它可以直接结束整个应用。在使用时需要权限android.permission.RESTART_PACKAGES。
可是,在2.2,这个方法失效了。
在2.2添加了一个新的方法,killBackgroundProcesses(),需要权限 android.permission.KILL_BACKGROUND_PROCESSES。
可惜的是,它和2.2的restartPackage一样,根本起不到应有的效果。
另外还有一个方法,就是系统自带的应用程序管理里,强制结束程序的方法,forceStopPackage()。
它需要权限android.permission.FORCE_STOP_PACKAGES。
并且需要添加android:sharedUserId=”android.uid.system”属性
同样可惜的是,该方法是非公开的,他只能运行在系统进程,第三方程序无法调用。
因为需要在Android.mk中添加LOCAL_CERTIFICATE := platform。
而Android.mk是用于在Android源码下编译程序用的。
从以上可以看出,在2.2,没有办法直接结束一个应用,而只能用自己的办法间接办到。
现提供几个方法,供参考:
1、抛异常强制退出:
该方法通过抛异常,使程序ForceClose。
验证可以,但是,需要解决的问题是,如何使程序结束掉,而不弹出Force Close的窗口。
2、记录打开的Activity:
每打开一个Activity,就记录下来。在需要退出时,关闭每一个Activity即可。
3、发送特定广播:
在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。
4、递归退出
在打开新的Activity时使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭。
除了第一个,都是想办法把每一个Activity都结束掉,间接达到目的。
但是这样做同样不完美。
你会发现,如果自己的应用程序对每一个Activity都设置了nosensor,在两个Activity结束的间隙,sensor可能有效了。
但至少,我们的目的达到了,而且没有影响用户使用。
为了编程方便,最好定义一个Activity基类,处理这些共通问题。
13. 请介绍下Android中常用的五种布局?
1、 LinearLayout – 线性布局。它的常用属性:
orientation –容器内元素的排列方式。支持vertical 、horizontal 两个属性值。vertical: 子元素们垂直排列;horizontal: 子元素们水平排列。
gravity –内容的对齐形式。常用的有 top、bottom、left、right、center 等
2、 TableLayout – 表格式布局
表格布局主要以行、列的形式来管理子控件。每个TableRow代表一行,每向TableRow中添加一个子控件,就相当于添加了一个单元格。如果直接向TableLayout中添加子组件,这个子组件自己占用一行。
3、 RelativeLayout – 相对布局。它控制子组件的方式有两种:设置子组件位于RelativeLayout的顶端、底端、左边、右边、中间等。一旦某个子组件的位置确定下来,其他组件就能以该组件为基础,指定位于已有组件的上、下、左、右等位置了。
4、 FrameLayout – 帧布局。将 FrameLayout 内的元素一层覆盖一层地显示,在帧布局中,先添加的图片会被后添加的图片覆盖。
5、 AbsoluteLayout – 绝对布局。直接设置组件的大小、位置的布局方式,基本已经被淘汰了。它允许通过layout_x、layout_y来设置组件的位置。
14、Android数据存储方式有什么?
答:Android提供了5种方式存储数据:
(1)使用SharedPreferences存储数据;它是Android提供的用来存储一些简单配置信息的一种机制,采用了XML格式将数据存储到当前应用程序的数据文件夹内。
(2)文件存储数据;文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。
(3)SQLite数据库存储数据;SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库。
(4)使用ContentProvider存储数据;主要用于应用程序之间进行数据交换,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
(5)网络存储数据;通过网络上提供给我们的存储空间来上传(存储)和下载(获取)我们存储在网络空间中的数据信息。
15、请介绍下ContentProvider是如何实现数据共享的?
ContentProvider:Android应用程序能够将程序数据保存到文件、SQLite数据库中,甚至是任何有效的设备中。如果我们需要将这些程序数据与其他应用程序共享——也就是允许其他应用程序来读取、修改这些程序数据。此时就可以通过提供了一个ContentProvider来暴露操作接口,暴露ContentProvider时需要指定一个authority属性(由Uri的主机、Port部分组成)。
一旦某个应用程序提供了ContentProvider,其他程序就可通过ContentResolver来访问ContentProvider所暴露的数据。归纳起来说:ContentResolver负责向指定Uri执行C、R、U、D操作,而实际上这些操作将会由系统委托给该Uri对应的ContentProvider来去执行,ContentProvider执行完成后会把结果返回给ContentResolver。
16、如何启用Service,如何停用Service?
1.第一种是通过调用Context.startService()启动,调用Context.stopService()结束,startService()可以传递参数给Service
2.第二种方式是通过调用Context.bindService()启动、并绑定Service,调用Context.unbindservice()取消绑定,还可以通过ServiceConnection访问Service。
18. 注册广播有几种方式,这些方式有何优缺点?请谈谈Android引入广播机制的用意?
android中,不同进程之间传递信息要用到广播,可以有两种方式来实现。
第一种方式:在AndroidManifest.xml中注册广播,是一种比较推荐的方法,因为它不需要手动注销广播(如果广播未注销,程序退出时可能会出错)。
具体实现在AndroidManifest的application中添加:
上面两个android:name分别是广播名和广播的动作(这里的动作是表示系统启动完成),如果要自己发送一个广播,在代码中为:
Intent i = newIntent(“android.intent.action.BOOT_COMPLETED”);
sendBroadcast(i);
这样,广播就发出去了,然后是接收。
接收可以新建一个类,继承至BroadcastReceiver,也可以建一个BroadcastReceiver的实例,然后得写onReceive方法,实现如下:
protected BroadcastReceiver mEvtReceiver =new BroadcastReceiver() {
@Override
public void onReceive(Context context,Intent intent) {
String action = intent.getAction();
if (action.equals(“android.intent.action.BOOT_COMPLETED”)) {
//Do something
}
}};
第二种方式,直接在代码中实现,但需要手动注册注销,实现如下:
IntentFilter filter = new IntentFilter();
filter.addAction(“android.intent.action.BOOT_COMPLETED”);
registerReceiver(mEvtReceiver, filter); //这时注册了一个recevier ,名为mEvtReceiver,然后同样用上面的方法以重写onReceiver,
最后在程序的onDestroy中要注销广播,实现如下:
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mPlayerEvtReceiver);
}
Android系统中的广播是广泛用于应用程序之间通信的一种手段,它类似于事件处理机制,不同的地方就是广播的处理是系统级别的事件处理过程(一般事件处理是控件级别的)。在此过程中仍然是离不开Intent对象,理解广播事件的处理过程,灵活运用广播处理机制,在关键之处往往能实现特别的效果,
在Android 中如果要发送一个广播必须使用sendBroadCast 向系统发送对其感兴趣的广播接收器中。
使用广播必须要有一个intent对象必设置其action动作对象
使用广播必须在配置文件中显式的指明该广播对象
每次接收广播都会重新生成一个接收广播的对象
在BroadCast 中尽量不要处理太多逻辑问题,建议复杂的逻辑交给Activity 或者 Service 去处理.
17、请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。
Message:就是Handler接收和处理的消息。
Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到信息之后就把消息交给发送该消息的Handler进行处理。
MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时会在它的构造器中创建Looper对象。Looper提供的构造器代码如下:
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
从上面代码不难看出程序在初始化Looper时会创建一个与之关联的MessageQueue,这个MessageQueue就负责管理消息。
Handler:它的作用有两个:发送消息,程序使用Handler发送消息,被Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程中有一个MessageQueue,否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分两种情况处理:
- 程序UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息。
- 程序员自己启动的子线程,程序员必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可。prepare方法保证每个线程最多只有一个Looper对象,prepare方法的源代码如下:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only oneLooper may be created per thread”);
}
sThreadLocal.set(new Looper());
}
然后调用Looper的静态loop()方法来启动它,loop方法就是使用一个死循环不断都取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。
归纳起来:
Looper:每个线程只有一个,它负责管理MessageQueue,它会负责不断的从MessageQueue中取出消息,并将消息分给对应的Handler处理。
MessageQueue:由Looper负责管理。它采用先进先出的方式来所有Message。
Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。
18、AIDL的全称是什么?如何工作?能处理哪些类型的数据?
AIDL全称Android Interface Definition Language(AndRoid接口描述语言)是一种借口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界对象访问的目的.AIDL的IPC的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类.; 2. 调用aidl产生的class.理论上, 参数可以传递基本数据类型和String, 还有就是Bundle的派生类, 不过在Eclipse中,目前的ADT不支持Bundle做为参数,
具体实现步骤如下:
1、创建AIDL文件, 在这个文件里面定义接口, 该接口定义了可供客户端访问的方法和属性。
2、编译AIDL文件, 用Ant的话, 可能需要手动, 使用Eclipse plugin的话,可以根据adil文件自动生产java文件并编译, 不需要人为介入.
3、在Java文件中, 实现AIDL中定义的接口. 编译器会根据AIDL接口, 产生一个JAVA接口。这个接口有一个名为Stub的内部抽象类,它继承扩展了接口并实现了远程调用需要的几个方法。接下来就需要自己去实现自定义的几个接口了.
4、向客户端提供接口ITaskBinder, 如果写的是service,扩展该Service并重载onBind ()方法来返回一个实现上述接口的类的实例。
5、在服务器端回调客户端的函数. 前提是当客户端获取的IBinder接口的时候,要去注册回调函数, 只有这样, 服务器端才知道该调用那些函数
AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。由于远程调用的需要, 这些参数和返回值并不是任何类型.下面是些AIDL支持的数据类型:
1. 不需要import声明的简单Java编程语言类型(int,boolean等)
2. String, CharSequence不需要特殊声明
3. List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型.
实现接口时有几个原则:
- 抛出的异常不要返回给调用者. 跨进程抛异常处理是不可取的.
- IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你- 应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应. 这种情况应该考虑单起一个线程来处理.
- 不能在AIDL接口中声明静态属性。
IPC的调用步骤:
1. 声明一个接口类型的变量,该接口类型在.aidl文件中定义。
2. 实现ServiceConnection。
3. 调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递.
4. 在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的Service). 调用
YourInterfaceName.Stub.asInterface((IBinder)service)将参数转换为YourInterface类型。
5. 调用接口中定义的方法。你总要检测到DeadObjectException异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
6. 断开连接,调用接口实例中的ApplicationContext.unbindService()
19、请解释下Android程序运行时权限与文件系统权限的区别。
apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置。
android系统有的权限是基于签名的。
20、什么是ANR 如何避免它?
ANR:Application Not Responding,应用程序没有响应。当出现下列情况时,Android就会显示ANR对话框了:
- 对输入事件(如按键、触摸屏事件)的响应超过5秒
- 对于Broadcast,如果BroadcastReceiver超过10秒钟仍未执行完成。
Android应用程序完全运行在一个独立的线程中(例如main)。这就意味着,任何在主线程中运行的,需要消耗大量时间的操作都会引发ANR。因为此时,你的应用程序已经没有机会去响应输入事件和Broadcast。
因此,任何运行在主线程中的方法,都要尽可能的只做少量的工作。特别是活动生命周期中的重要方法如onCreate()和 onResume()等更应如此。潜在的比较耗时的操作,如访问网络和数据库;或者是开销很大的计算,比如改变位图的大小,需要在一个单独的子线程中完成 (或者是使用异步请求,如数据库操作)。但这并不意味着你的主线程需要进入阻塞状态已等待子线程结束 — 也不需要调用Therad.wait()或者Thread.sleep()方法。取而代之的是,主线程为子线程提供一个句柄(Handler),让子线程在即将结束的时候调用它。使用这种方法涉及你的应用程序,能够保证你的程序对输入保持良好的响应,从而避免因为输入事件超过5秒钟不被处理而产生的ANR。这种实践需要应用到所有显示用户界面的线程,因为他们都面临着同样的超时问题。
21、什么情况会导致Force Close ?如何避免?能否捕获导致其的异常?
答:一般像空指针啊,可以看起logcat,然后对应到程序中 来解决错误
22、简要解释一下activity、 intent 、intent filter、service、Broadcast、BroadcastReceiver
一个activity呈现了一个用户可以操作的可视化用户界面
一个service不包含可见的用户界面,它代表一个后台的长生命周期的对象。Activity可以连接到一个正在运行的服务中,连接后,可以通过服务中暴露出来的接口与其进行通信
一个BroadcastReceiver是一个接收广播消息并作出回应的组件,BroadcaseReceiver没有界面
intent:contentprovider在接收到ContentResolver的请求时被激活。
activity,service和broadcastreceiver是被称为intents的异步消息激活的。
一个intent是一个Intent对象,它保存了消息的内容。对于activity和service来说,它指定了请求的操作名称和待操作数据的URI
Intent对象可以显式的指定一个目标component。如果这样的话,android会找到这个component(基于 manifest文件中的声明)并激活它。但如果一个目标不是显式指定的,android必须找到响应intent的最佳component。
它是通过将Intent对象和目标的intent filter相比较来完成这一工作的。一个component的intent filter告诉android该component能处理的intent。intent filter也是在manifest文件中声明的。
23、IntentService有何优点?
答:IntentService的好处
* Acitivity的进程,当处理Intent的时候,会产生一个对应的Service
* Android的进程处理器现在会尽可能的不kill掉你
* 非常容易使用
24、横竖屏切换时候activity的生命周期?
1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
2、设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
3、设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
25. 如何将SQLite数据库(dictionary.db文件)与apk文件一起发布?
解答:可以将dictionary.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。可以将dictionary.db文件复制到res aw目录中
26. 如何将打开res aw目录中的数据库文件?
解答:在Android中不能直接打开res aw目录中的数据库文件,而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中,然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象,然后将该InputStream对象中的数据写入其他的目录中相应文件中。在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。
27. Android引入广播机制的用意?
答:a:从MVC的角度考虑(应用程序内)
其实回答这个问题的时候还可以这样问,android为什么要有那4大组件,现在的移动开发模型基本上也是照搬的web那一套MVC架构,只不过是改了点嫁妆而已。android的四大组件本质上就是为了实现移动或者说嵌入式设备上的MVC架构,它们之间有时候是一种相互依存的关系,有时候又是一种补充关系,引入广播机制可以方便几大组件的信息和数据交互。
b:程序间互通消息(例如在自己的应用程序内监听系统来电)
c:效率上(参考UDP的广播协议在局域网的方便性)
d:设计模式上(反转控制的一种应用,类似监听者模式)
28、Android的优势与不足
其实这个问题纯属考察你的知识面是否广,是否经常浏览一些相关文章。下面的回答也只是一些常见的解读。
Android平台手机 5大优势:
一、开放性
在优势方面,Android平台首先就是其开发性,开发的平台允许任何移动终端厂商加入到Android联盟中来。显著的开放性可以使其拥有更多的开发者,随着用户和应用的日益丰富,一个崭新的平台也将很快走向成熟
开发性对于Android的发展而言,有利于积累人气,这里的人气包括消费者和厂商,而对于消费者来讲,随大的受益正是丰富的软件资源。开放的平台也会带来更大竞争,如此一来,消费者将可以用更低的价位购得心仪的手机。
二、挣脱运营商的束缚
在过去很长的一段时间,特别是在欧美地区,手机应用往往受到运营商制约,使用什么功能接入什么网络,几乎都受到运营商的控制。从去年iPhone 上市,用户可以更加方便地连接网络,运营商的制约减少。随着EDGE、HSDPA这些2G至3G移动网络的逐步过渡和提升,手机随意接入网络已不是运营商口中的笑谈,当你可以通过手机IM软件方便地进行即时聊天时,再回想不久前天价的彩信和图铃下载业务,是不是像噩梦一样?
互联网巨头Google推动的Android终端天生就有网络特色,将让用户离互联网更近。
三、丰富的硬件选择
这一点还是与Android平台的开放性相关,由于Android的开放性,众多的厂商会推出千奇百怪,功能特色各具的多种产品。功能上的差异和特色,却不会影响到数据同步、甚至软件的兼容,好比你从诺基亚 Symbian风格手机 一下改用苹果 iPhone ,同时还可将Symbian中优秀的软件带到iPhone上使用、联系人等资料更是可以方便地转移,是不是非常方便呢?
四、不受任何限制的开发商
Android平台提供给第三方开发商一个十分宽泛、自由的环境,不会受到各种条条框框的阻扰,可想而知,会有多少新颖别致的软件会诞生。但也有其两面性,血腥、暴力、情色方面的程序和游戏如可控制正是留给Android难题之一。
五、无缝结合的Google应用
如今叱诧互联网的Google已经走过10年度历史,从搜索巨人到全面的互联网渗透,Google服务如地图、邮件、搜索等已经成为连接用户和互联网的重要纽带,而Android平台手机将无缝结合这些优秀的Google服务。
再说Android的5大不足:
一、安全和隐私
由于手机与互联网的紧密联系,个人隐私很难得到保守。除了上网过程中经意或不经意留下的个人足迹,Google这个巨人也时时站在你的身后,洞穿一切,因此,互联网的深入将会带来新一轮的隐私危机。
二、首先开卖Android手机的不是最大运营商
众所周知,T-Mobile在23日,于美国纽约发布了Android首款手机G1。但是在北美市场,最大的两家运营商乃AT&T和Verizon,而目前所知取得Android手机销售权的仅有 T-Mobile和Sprint,其中T-Mobile的3G网络相对于其他三家也要逊色不少,因此,用户可以买账购买G1,能否体验到最佳的3G网络服务则要另当别论了!
三、运营商仍然能够影响到Android手机
在国内市场,不少用户对购得移动定制机不满,感觉所购的手机被人涂画了广告一般。这样的情况在国外市场同样出现。Android手机的另一发售运营商Sprint就将在其机型中内置其手机商店程序。
四、同类机型用户减少
在不少手机论坛都会有针对某一型号的子论坛,对一款手机的使用心得交流,并分享软件资源。而对于Android平台手机,由于厂商丰富,产品类型多样,这样使用同一款机型的用户越来越少,缺少统一机型的程序强化。举个稍显不当的例子,现在山寨机泛滥,品种各异,就很少有专门针对某个型号山寨机的讨论和群组,除了哪些功能异常抢眼、颇受追捧的机型以外。
五、过分依赖开发商缺少标准配置
在 使用PC端的Windows Xp系统的时候,都会内置微软Windows Media Player这样一个浏览器程序,用户可以选择更多样的播放器,如Realplay或暴风影音等。但入手开始使用默认的程序同样可以应付多样的需要。在Android平台中,由于其开放性,软件更多依赖第三方厂商,比如Android系统的SDK中就没有内置音乐播放器,全部依赖第三方开发,缺少了产品的统一性。
29、Android 中有哪几种解析XML的类?官方推荐哪种?以及它们的原理和区别。
XML解析主要有三种方式,SAX、DOM、Pull。常规在PC上开发我们使用Dom相对轻松些,但一些性能敏感的数据库或手机上还是主要采用SAX方式。
SAX读取是单向的,优点:不占内存空间、解析属性方便,但缺点就是对于套嵌多个分支来说处理不是很方便。而DOM方式会把整个XML文件加载到内存中去,因此比较耗费内存空间。
官方推荐使用:Pull对于节点处理比较好,类似SAX方式,同样很节省内存.
30、DDMS和Trace View的区别?
DDMS是一个程序执行查看器,在里面可以看见线程和堆栈等信息,Trace View是程序性能分析器
31、Activity被回收了怎么办?
只有重新启动这个Activity了。
如果程序需要在Activity被回收之前能记录下该Activity运行时的状态信息,则可以从Activity的生命周期着手考虑:因为系统在内存紧张时只可能回收处于暂停、停止状态的Activity。当Activity进入暂停状态之前,一定要先回调onPause方法,进行停止状态之前,一定会先回调onPause、onStop方法,因此程序可以通过重写onPause方法,把Activity的运行状态记录到存储设备上,然后重写onResume()方法,并在该方法中判断之前是否存储过Activity的运行状态,如果有记录之前的运行状态,在onResume()方法中恢复该程序的运行状态即可。
32、java中如何引用本地语言
可以用JNI接口
33、谈谈Android的IPC机制
IPC是内部进程通信的简称,是共享”命名管道”的资源。Android中的IPC机制是为了让Activity和Service之间可以随时的进行交互,故在Android中该机制,只适用于Activity和Service之间的通信,类似于远程方法调用,类似于C/S模式的访问。通过定义AIDL接口文件来定义IPC接口。Servier端实现IPC接口,Client端调用IPC接口本地代理。
34、NDK是什么?
NDK是一些列工具的集合,NDK提供了一系列的工具,帮助开发者迅速的开发C/C++的动态库,并能自动将so和java 应用打成apk包。
NDK集成了交叉编译器,并提供了相应的mk文件和隔离CPU、平台等的差异,开发人员只需简单的修改mk文件就可以创建出so
35、ContentValues类是做什么的?
ContentValues和Map比较类似,也是负责存储一些名值对,但是他存储的名值对当中的名是String类型。
当程序通过SQLiteDatabase插入、修改数据时经常要用到ContentValues,此时ContentValues中的key代表要插入或更新的列名,而value则代表要插入或更新的列的值。
36、Android中Activity, Intent, Content Provider, Service各有什么区别?
答:Activity:是最基本的Android应用程序组件。一个Activity相当于一个空窗口,可以调用setContentView来设置该Activity所显示的界面。每个Activity都是一个独立的类,并且从Activity基类继承而来。
Activity的主要作用是向用户呈现界面,并负责与用户交互。
Intent:当程序需要启动某个其他程序组件(包括Activity、Service和ContentProvider),或者在多个组件时间进行数据交互时,程序可以通过Intent来表达这种启动“意图”。除此之外,Intent还可以携带组件交互时需要传递的数据。Intent包括Component、 Action、Category、Data、Type、Extra、Flag这些数据,其中Component、 Action、Category、Data、Type主要用于匹配该Intent想要启动的程序组件,Extra属性是一个Bundle对象,该对象的主要用于携带多个组件交互的数据。Flag则用于指定一些启动程序的“旗标”信息,比如启动Activity时可指定如下常用Flag
FLAG_ACTIVITY_NEW_TASK:使用一个新的Task来装载该Activity。
FLAG_ACTIVITY_NO_HISTORY:通过该Flag启动的Activity不会保存在历史Stack中。
FLAG_ACTIVITY_SINGLE_TOP:如果当前Task中已有该Activity,通过该Flag启动的
Activity将不会被启动(因为要保持只有一个实例)。
ContentProvider:Android应用程序能够将程序数据保存到文件、SQLite数据库中,甚至是任何有效的设备中。如果我们需要将这些程序数据与其他应用程序共享——也就是允许其他应用程序来读取、修改这些程序数据。此时就可以通过提供了一个ContentProvider来暴露操作接口,暴露ContentProvider时需要指定一个authority属性(由Uri的主机、Port部分组成)。一旦某个应用程序提供了ContentProvider,其他程序就可通过ContentResolver来访问ContentProvider所暴露的数据。归纳起来说:ContentResolver负责向指定Uri执行C、R、U、D操作,而实际上这些操作将会由系统委托给该Uri对应的ContentProvider来去执行,ContentProvider执行完成后会把结果返回给ContentResolver。
Service:Service与Activity很相似,它代表一个没有前台界面的后台服务,一般用于为应用程序提供后台的服务支持。Service运行时没有界面,因此不能与用户交互。
37、View, SurfaceView, GLSurfaceView有什么区别?
答:View是最基础的,必须在UI主线程内更新画面,速度较慢。
SurfaceView 是View的子类,使用了双缓机制,在新的线程中更新画面所以刷新界面速度比View快。
SurfaceView一般会与SurfaceHolder结合使用,调用SurfaceView的getHolder()方法即可获取SurfaceView关联的SurfaceHolder,SurfaceHolder用于向与之关联的SurfaceView上绘图,
SurfaceHolder提供了如下方法来获取Canvas对象:
- CanvaslockCanvas():锁定整个SurfaceView对象,获取该Surface上的Canvas。
- CanvaslockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas。
当对同一个SurfaceView调用上面两个方法时,两个方法所返回的是同一个Canvas对象。但当程序调用第二个方法获取指定区域的Canvas时,SurfaceView将只对Rect所“圈”出来的区域进行更新,通过这种方式可以提高画面的更新速度。
当通过lockCanvas()获取指定了SurfaceView上的Canvas之后,接下来程序就可调用Canvas进行绘图了,Canvas绘图完成后通过如下方法来释放绘图、提交所绘制的图形:
-unlockCanvasAndPost(canvas);
GLSurfaceView 是SurfaceView的子类,OpenGL专用的。
38、Adapter有什么作用?常见的Adapter有哪些?
答:Adapter是连接后端数据和前端显示的适配器接口。
常见的Adapter子接口包括ListAdapter和SpinnerAdapter。其中ListAdapter接口主要为ListView、GridView这种AdapterView提供数据和显示组件。而SpinnerAdapter主要为Spinner、Gallery这种AdapterView提供数据和显示组件。
ListAdapter、SpinnerAdapter它们有如下实现类:
ArrayAdapter:用法比较简单。功能也比较简单,只要传入数组或List集合作为列表组件的数据即可。
SimpleAdapter:用法略显复杂,但功能比较强大。程序需要传入一个List集合(List集合元素为Map)作为列表组件的数据。
SimpleCursorAdapter:功能和用法与SimpleAdapter十分相似。只是程序使用Cursor作为列表组件的数据。
BaseAdapter:这个Adapter主要供用户继承,然后需要重写它提供的getCount(、getItem()、getItemId、getView等方法。
39、Manifest.xml文件中主要包括哪些信息?
答:manifest:根节点,描述了package中所有的内容。
uses-permission:请求你的package正常运作所需赋予的安全许可。
permission:声明了安全许可来限制哪些程序能你package中的组件和功能。
instrumentation:声明了用来测试此package或其他package指令组件的代码。
application:包含package中application级别组件声明的根节点。
activity:用于定义Activity,Activity用于生成与用户交互的窗口界面。
receiver:IntentReceiver能使的application获得数据的改变或者发生的操作,即使它当前不在运行。
service:Service是能在后台运行任意时间的组件。
provider:ContentProvider是用来管理持久化数据并发布给其他应用程序使用的组件。