可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致。
2、说说&和&&的区别
&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则只要有一方为false,则结果为false。
&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如对于if(str !=null&& !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。
&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如0x31 &0x0f的结果为0x01。
3、switch语句能否作用在byte上,能否作用在long上,能否作用在String上
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量,整数表达式可以是int基本类型或Integer包装类型,由于byte,short,char都可以隐含转换为int,所以这些类型以及这些类型的包装类型也是可以的。显然long不可以,1.7开始String可以。
4、Object有哪些公用方法
equals(),hashcode(),clone(),getClass(),notify(),notifiall(),wait(),toString()
5、float f=3.4;是否正确
不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F
6、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语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
7、char型变量中能不能存贮一个中文汉字?为什么
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以char型变量中当然可以存储汉字。如果某个特殊的汉字没有被包含在unicode编码字符集中,那么这个char型变量中就不能存储这个特殊汉字。
补充说明:unicode编码占用两个字节,所以char类型的变量也是占用两个字节。
8、用最有效率的方法算出2乘以8等於几
2 << 3,因为将一个数左移n位,就相当于乘以了2的n次方,那么一个数乘以8只要将其左移3位即可,而位运算cpu直接支持的,效率最高,所以2乘以8等於几的最效率的方法是2<< 3。
9、使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的。
例如对于如下语句:
final StringBuffer a=new StringBuffer("immutable");
a=new StringBuffer("");//执行如下语句将报告编译期错误:
a.append(" broken!");//但是执行如下语句则可以通过编译:
10、"=="和equals方法究竟有什么区别
==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。
equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如对于下面的代码:
String a=new String("foo");
String b=new String("foo");
两条new语句创建了两个对象,然后用a、b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以表达式a==b将返回false,而这两个对象中的内容是相同的,所以表达式a.equals(b)将返回true。
如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
boolean equals(Object o){
return this==o;
}
这说明如果一个类没有自己定义equals方法,它默认的equals方法(从Object类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果。
11、静态变量和实例变量的区别
在语法定义上的区别:
静态变量前要加static关键字,而实例变量前则不加
在程序运行时的区别:
实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
例如,对于下面的程序,无论创建多少个实例对象,永远都只分配了一个staticVar变量,并且每创建一个实例对象,这个staticVar就会加1;但是每创建一个实例对象,就会分配一个instanceVar,即可能分配多个instanceVar,并且每个instanceVar的值都只自加了1次。
public class VariantTest {
public static int staticVar = 0;
public int instanceVar = 0;
public VariantTest(){
staticVar++;
instanceVar++;
System.out.println("staticVar="+ staticVar + "instanceVar="+ instanceVar);
}
}
12、是否可以从一个static方法内部发出对非static方法的调用
不可以。因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时不需要创建对象可以直接调用。也就是说当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立,所以一个static方法内部发出对非static方法的调用。
13、Integer与int的区别
int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。
int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。
Integer提供了多个与整数相关的操作方法,例如将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。
public class test11 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 =150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
如果不明就里很容易认为两个输出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四个变量都是Integer对象引用,所以下面的==运算比较的不是值而是引用。如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1==f2的结果是true,而f3==f4的结果是false。
14、Math.round(11.5)等於多少? Math.round(-11.5)等於多少?(ceil向上取整,floor向下取整,round四舍五入)
Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。
15、下面的代码有什么不妥之处?
if(username.equals(“zxx”){
}
//username可能为NULL,会报空指针错误;改为"zxx".equals(username)
int x= 1;
return x==1?true:false;
//这个改成return x==1;就可以!
16、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型
重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)
至于Overload的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个Overloaded的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。
在使用重载要注意以下的几点:
1.在使用重载时只能通过不同的参数样式。例如不同的参数类型,不同的参数个数,不同的参数顺序
2.不能通过访问权限、返回类型、抛出的异常进行重载
3.方法的异常类型和数目不会对重载造成影响;
重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。
在覆盖要注意以下的几点:
1.覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果
2.覆盖的方法的返回值必须和被覆盖的方法的返回一致
3.覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类
4.被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
17、构造器Constructor是否可被override
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
18、接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?抽象类中是否可以有静态的main方法
接口可以继承接口。抽象类可以实现(implements)接口,抽象类可以继承具体类。抽象类中可以有静态的main方法。
19、java中实现多态的机制是什么
靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
20、abstract class和interface有什么区别
含有abstract修饰符的class即为抽象类,abstract类不能创建实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
下面比较一下两者的语法区别:
-
抽象类可以有构造方法,接口中不能有构造方法
-
抽象类中可以有普通成员变量,接口中没有普通成员变量
-
抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法
-
抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型
-
抽象类中可以包含静态方法,接口中不能包含静态方法
-
抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
-
一个类可以实现多个接口,但只能继承一个抽象类
-
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用。
21、String s = "Hello";s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s原先指向一个String对象内容是"Hello",然后我们对s进行了+操作,那么s所指向的那个对象是没有发生改变的。这时s不指向原来那个对象了,而指向了另一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为 String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时应该考虑使用StringBuffer类,它允许修改而不是每个不同的字符串都要生成一个新的对象。并且这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Test {
private String s;
public Test() {
s = "Initial Value";
// 而非 s= new String("Initial Value");
}
}
后者每次都会调用构造器,生成新对象,性能低下且内存开销大并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象无论内容是否相同。
22、String 是最基本的数据类型吗?
不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型,剩下的都是引用类型。
23、是否可以继承String类
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类
24、String s = new String("xyz");创建了几个StringObject?二者之间有什么区别
两个或一个,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。如果以前就用过’xyz’,这句代表就不会创建”xyz”自己了,直接从缓冲区拿。
25、String和StringBuffer的区别
它们可以储存和操作字符串,即包含多个字符的字符数据。String类提供了数值不可改变的字符串,而StringBuffer类提供的字符串值可以修改。当你知道字符数据要改变的时候你就可以使用StringBuffer,可以使用StringBuffers来动态构造字符数据。
26、String和StringBuilder、StringBuffer的区别
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有则直接用常量池中的),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。
new String("acb").equals(new String("abc")//String实现了equals方法,结果为true,
StringBuffer("a").equals(new StringBuffer("a")//而StringBuffer没有实现equals方法,结果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;
}
27、请说出下面程序的输出
public static void main(String[] args) {
String s1 = "Programming";
String s2 = new String("Programming");
String s3 = "Program";
String s4 = "ming";
String s5 = "Program" +"ming";
String s6 = s3 + s4;
System.out.println(s1 == s2);
System.out.println(s1 == s5);
System.out.println(s1 == s6);
System.out.println(s1 == s6.intern());
System.out.println(s2 == s2.intern());
String s7 = new StringBuilder("go").append("od").toString();
String s8 = new StringBuilder("ja").append("va").toString();
System.out.println(s7.intern() == s7);//true
System.out.println(s8.intern() == s8);//false
}
//false
//true
//false
//true
//false
28、数组有没有length()这个方法? String有没有length()这个方法
数组没有length()这个方法,有length的属性。String有有length()这个方法。
29、下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d";
String s1 = "a";
String s2 = s1 + "b";
String s3 = "a" + "b";
System.out.println(s2== "ab");//false
System.out.println(s3== "ab");//true
第一条语句打印的结果为false,第二条语句打印的结果为true,这说明javac编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象。所以String s ="a" + "b" + "c" + "d"只创建了一个对象。
30、try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后
从下面例子的运行结果中可以发现,try中的return语句调用的函数先于finally中调用的函数执行,也就是说return语句先执行,finally语句后执行。所以返回的结果是2。return并不是让函数马上返回,而是return语句执行后,将把返回结果放置进函数栈中,此时函数并不是马上返回,它要执行finally语句后才真正开始返回。
public class TestC {
public static void main(String[] args) {
System.out.println("结果: " + new TestC().test());
}
static int test(){
int i = 1;
try {
System.out.println("try里面的i : " + i);
return i;
}finally{
System.out.println("进入finally...");
++i;
System.out.println("fianlly里面的i : " + i);
}
}
}
try里面的i : 1
进入finally...
fianlly里面的i : 2
结果: 1
31、解释内存中的栈(stack)、堆(heap)和方法区(methodarea)的用法
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间
而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为FromSurvivor和To Survivor)、Tenured
方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分
栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。
String str = new String("hello");
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。
32、hashcode()和equals()
hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所有对于需要大量并且快速的对比的话如果都用equal()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性(https://www.cnblogs.com/keyi/p/7119825.html)
33、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对
不对,如果两个对象x和y满足x.equals(y) ==true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;如果两个对象的hashCode相同,它们并不一定相同。
34、final, finally, finalize的区别
final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。内部类要访问局部变量,局部变量必须定义成final类型
finally是异常处理语句结构的一部分,表示总是执行
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用,只调用一次。
35、error和exception有什么区别?运行时异常与一般异常有何异同?
Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。比如说内存溢出。
Exception:表示可恢复的异常,这是可捕捉到的。
Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。如果不想终止,则必须扑捉所有的运行时异常
36、Java中的异常处理机制的简单原理和应用
异常是指java程序运行时(非编译)所发生的非正常情况或错误,与现实生活中的事件很相似,现实生活中的事件可以包含事件发生的时间、地点、人物、情节等信息,可以用一个对象来表示,Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception
Error表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如说内存溢出和线程死锁等系统问题。
Exception表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。
提示答题者:就按照三个级别去思考:虚拟机必须宕机的错误,程序可以死掉也可以不死掉的错误,程序不应该死掉的错误;
37、请写出你最常见到的5个runtime exception。
比较有印象的系统异常有:NullPointerException、ArrayIndexOutOf、BoundsException、ClassCastException、FileNotfindException
38、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?
throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。throw是具体向外抛异常的动作,所以它是抛出一个异常实例。真实的抛出一个异
throws语句用在方法声明后面,表示抛出异常,由该方法的调用者来处理。throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。说明你有那个可能,倾向
try是将会发生异常的语句括起来,从而进行异常的处理
catch是如果有异常就会执行他里面的语句
finally不论是否有异常都会进行执行的语句
39、说出一些常用的类,包,接口
常用的类: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
常用的接口:RemoteList、Map 、Document、Session(Hibernate),HttpSession、NodeList,Servlet,HttpServletRequest,HttpServletResponse,Transaction(Hibernate)
40、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法?
有两种实现方法,分别是继承Thread类与实现Runnable接口
public class Test extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
new Test().start();
System.out.println(Thread.currentThread().getName());
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
//线程池的方式
ExecutorService pool =Executors.newFixedThreadPool(3);
for(int i=0;i<10;i++){
pool.execute( new Runnable() {
public void run() { System.out.println("ExecutorService"+Thread.currentThread().getName());
}
});
}
Executors.newCachedThreadPool().execute(new Runnable() {
public void run() { System.out.println("newCachedThreadPool"+Thread.currentThread().getName());
}
});
Executors.newSingleThreadExecutor().execute(new Runnable() {
public void run() { System.out.println("newSingleThreadExecutor"+Thread.currentThread().getName());
}
});
用synchronized关键字修饰同步方法
41、同步有几种实现方法
同步的实现方法有两种,分别是synchronized,wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争
42、sleep()和 wait()有什么区别
sleep是线程类Thread的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep方法并不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法或notifyAll后本线程才进入对象锁定池准备获得对象锁进入运行状态。wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法,调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。
public class Test { public static void main(String[] args) { new Thread(new Thread1()).start(); new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable { public void run() { // 由于这里的Thread1和下面的Thread2内部run方法要用同一对象作为监视器,我们这里不能用this,因为在Thread2里面的this和这个Thread1的this不是同一个对象。 //我们用MultiThread.class这个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对象。 synchronized (Test.class) { System.out.println("1"); try { // 释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开了synchronized关键字管辖的代码范围, //另一种方式就是在synchronized关键字管辖的代码内部调用监视器对象的wait方法。这里,使用wait方法释放锁。 Test.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("2"); } } } private static class Thread2 implements Runnable { public void run() { synchronized (Test.class) { System.out.println("3"); // 由于notify方法并不释放锁,即使thread2调用下面的sleep方法休息了100毫秒,但thread1仍然不会执行,因为thread2没有释放锁,所以Thread1无法得不到锁。 Test.class.notify(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("4"); } } } } //运行结果:1 3 4 2
43、同步和异步有何异同,在什么情况下分别使用他们
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
44、下面两个方法同步吗
class Test{ //它用的同步锁是当前类的字节码,与非静态的方法不能同步 synchronized static void sayHello3(){ } //非静态的方法用的是this synchronized void getX(){} } }
45、启动一个线程是用run()还是start()
启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。
46、Thread.start()与Thread.run()有什么区别?
Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run()方法。
47、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法
其他方法前是否加了synchronized关键字,如果没加,则能。
如果这个方法内部调用了wait,则可以进入其他synchronized方法。
如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
48、什么是线程
一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的栈。线程在切换时负荷小,因此线程也被称为轻负荷进程。一个进程中可以包含多个线程。
49、进程与线程的区别
一个进程至少有一个线程,线程必须依赖进程才能运行。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
50、线程池的作用
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
1.降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。
//常用线程池:ExecutorService 是主要的实现类,其中常用的有
Executors
.newSingleThreadPool()
.newFixedThreadPool()
.newcachedTheadPool()
.newScheduledThreadPool()
51、线程的基本状态以及状态之间的关系
新建:用new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。
等待:当线程在new之后,并且在调用start方法前,线程处于等待状态。
就绪:当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。
运行状态:处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。只有处于就绪状态的线程才有机会转到运行状态。
阻塞状态:阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。阻塞状态分为三种:
-
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
-
同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。
-
其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态:当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。
wait必须在synchronized内部调用。调用线程的start方法后线程进入就绪状态,线程调度系统将就绪状态的线程转为运行状态,遇到synchronized语句时,由运行状态转为阻塞,当synchronized获得锁后,由阻塞转为运行,在这种情况可以调用wait方法转为挂起状态,当线程关联的代码执行完后,线程变为结束状态。
52、简述synchronized和java.util.concurrent.locks.Lock的异同
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。Lock还有更强大的功能,例如它的tryLock方法可以非阻塞方式去拿锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** 两个线程加两个线程减 */ public class ThreadTest3 { private static int j; private static final Lock lock = new ReentrantLock(); public static void main(String[] args) { for (int i = 0; i < 2; i++){ new Thread(new Runnable() { public void run() { while (true) { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"j--=" + j--); } finally { lock.unlock(); } } } }).start(); new Thread(new Runnable() { public void run() { while (true) { lock.lock(); try { System.out.println(Thread.currentThread().getName()+"j++=" + j++); } finally { lock.unlock(); } } } }).start(); } } }
53、Volatile和synchronized的区别
粒度不同,前者针对变量 ,后者锁对象和类
syn阻塞,volatile线程不阻塞
syn保证三大特性,volatile不保证原子性
syn编译器优化,volatile不优化
Volatile具备两种特性:
保证此变量对所有线程的可见性,指一条线程修改了这个变量的值,新值对于其他线程来说是可见的,但并不是多线程安全的。
禁止指令重排序优化。
Volatile如何保证内存可见性:
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
54、同步异步、阻塞非阻塞
同步:就是一个任务的完成需要依赖另外一个任务,只有等待被依赖的任务完成后,依赖任务才能完成。
阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作。
非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作,等这个慢的完成后,CPU才会接着完成后续的操作。非阻塞会造成线程切换增加,增加CPU的使用时间能不能补偿系统的切换成本需要考虑。
55、synchronized关键字与Lock锁机制的区别问题
同步代码块其实自身是具有自动上锁、自动解锁功能的。Lock锁机制则是手动解锁,手动上锁的
用synchronized修饰的同步代码块还有同步方法是有同步锁对象的。Lock锁机制是没有同步锁对象的
因为synchronized修饰的同步代码块还有同步方法是具有锁对象的,因此,可以调用notify()、wait()、notifyAll()的方法。但是因为Lock锁机制是不具有锁对象的,因此是不可以去调用notify()、wait()、notifyAll()方法的,否则会发生报错。
Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
在同步代码块中,同步锁就是这个代码块所属的那个对象在同步方法中(非静态),同步锁就是调用这个方法的那个对象 在同步方法中(静态),同步锁就是这个方法所属的这个类的字节码文件,即类名.class
56、什么是死锁
死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就JavaAPI而言,线程死锁可能发生在一下情况。
1.当两个线程相互调用Thread.join()
2.当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。
57、ACID
1.原子性(Atomicity):事务要么成功(可见),要么失败(不可见)。不存在事务部分成功的情况。
2.一致性(Consistency):数据库在事务开始前和结束后都应该是一致的。
3.隔离性(Isolation):一个事务不会看到另外一个还未完成的事务产生的结果。每个事务就像在单独、隔离的环境下运行一样。
4.持久性(Durability):成功提交的事务,数据是持久保留,不会因为系统失败而丢失。
字节流,字符流。字节流继承于InputStream OutputStream,字符流继承于InputStreamReader OutputStreamWriter。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。
59、字节流与字符流的区别
字符流是字节流的包装,字符流则是直接接受字符串,字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码的字节形式,读取也是反之的道理。
60、什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。
将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,序列化是将内存中的对象序列化成二进制字节文件。例如要将java对象存储到硬盘或者传送给网络上的其他计算机就需要序列化,因为硬盘存储和网络传输都是通过二进制字节来传输的。我们可以调用OutputStream的writeObject方法来实现序列化,如果要让java帮我们做,被传输的对象必须实现serializable接口,这样javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。需要被序列化的类必须实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。如果对象要经过分布式系统进行网络传输远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。
61、适配器模式
将一个接口适配到另一个接口,InputStreamReader将Reader类适配到InputStream,从而实现了字节流到字符流的转换。
62、