易错题
字符串
题一
public class Example {
String str = new String("good");
char[] ch = {'a','b','c'};
public static void main(String[] args) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str +"and");
System.out.print(ex.ch);
}
public void change(String str, char ch[]){
str= "test ok";
ch[0]= 'g';
}
}
A .test okandabc
B .test okandgbc
C .goodandabc
D .goodandgbc
解析:
链接:
final修饰的作用:
- final修饰基本数据类型,表示值不可变
- final修饰引用类型,表示引用的指向不可变
java 中String是final修饰的,是不可变,一旦初始化,其引用指向的内容是不可变的。
也就是说,String str = “aa”;str=“bb”;第二句不是改变“aa”所存储地址的内容,而是另外开辟了一个空间用来存储“bb”;同时由str指向
原来的“aa”,现在已经不可达,GC时会自动回收。
因此String作为参数传进来时候,str= “test ok”; 实际给副本引用str指向了新分配的地址,该地址存储“test ok”。
因此,原先的str仍然指向“good”
运行
public class Immutable {
String str = new String("good");
char[] ch = {'a','b','c'};
public static void main(String[] args) {
Immutable ex = new Immutable();
System.out.println("main str hashcode-:"+ex.str.hashCode());
ex.change(ex.str, ex.ch);
System.out.println("main str hashcode-:"+ex.str.hashCode());
System.out.print(ex.str +"and");
System.out.print(ex.ch);
}
public void change(String str, char ch[]){
System.out.println("func str hashcode-:"+str.hashCode());
str= "test ok";
System.out.println("func str hashcode-:"+str.hashCode());
ch[0]= 'g';
}
}
结果
main str hashcode-:3178685
func str hashcode-:3178685
func str hashcode-:-1422516182
main str hashcode-:3178685
goodandgbc
public static void main(String[] args) {
String a = "aaa";
System.out.println("str hashcode-:"+a.hashCode()+" str value:"+a);
a = "test";
System.out.println("str hashcode-:"+a.hashCode()+" str value:"+a);
}
至于这一段代码为什么变了
str hashcode-:96321 str value:aaa
str hashcode-:3556498 str value:test
其实没有变
这里 a 是一个引用,他指向了一个具体的 对象,当他执行完a = “test”时又创建了一个新的对象,引用a 重新指向了这个新的对象,原来的对象还在,并没有改变
引用的源文链接:
Java中的String为什么是不可变的
补充
数组有length属性,字符串只有length()方法
String a = "test";
a.length();
String[] s = {"a", "b"};
int length = s.length;
Integer
下面三种情况各输出什么结果
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.print(n1 == n2);
System.out.println("-------------------");
Integer n3 = Integer.valueOf(48);
Integer n4 = 48;
System.out.println(n3 == n4);
System.out.println("-------------------");
Integer n5 = Integer.valueOf(129);
Integer n6 = 129;
System.out.println(n5 == n6);
}
先直接说答案:ZmFsc2UsIGZhbHNlLCB0cnVl (base64解密后看结果可以用这个解密)
解析:
使用Integer a = 1;或Integer a = Integer.valueOf(1); 在值介于-128至127直接时,作为基本类型。
使用Integer a = new Integer(1); 时,无论值是多少,都作为对象。
查看jdk源码
在-128到127的数字被缓存在cache中
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer第二道
public class Tester{
public static void main(String[] args){
Integer var1=new Integer(1);
Integer var2=var1;
doSomething(var2);
System.out.print(var1.intValue());
System.out.print(var1==var2);
}
public static void doSomething(Integer integer){
integer=new Integer(2);
}
}
答案: MXRydWU= (base64解密后看结果可以用这个解密)
数组 易错
下面哪几个可以成功创建多维数组
A. float f[][] = new float[6][6];
B. float []f[] = new float[6][6];
C. float f[][] = new float[][6];
D. float [][]f = new float[6][6];
E. float [][]f = new float[6][];
答案:QUJERQ (base64解密后看结果可以用这个解密)
try catch块 执行结果是什么
public class Test{
public int add(int a,int b){
try {
return a+b;
}
catch (Exception e) {
System.out.println("catch语句块");
}
finally{
System.out.println("finally语句块");
}
return 0;
}
public static void main(String argv[]){
Test test =new Test();
System.out.println("和是:"+test.add(9, 34));
}
}
A: 和是:43 finally语句块
B: finally语句块 和是:43
答案: B
解析:
public abstract class Test {
public static void main(String[] args) {
System.out.println(beforeFinally());
}
public static int beforeFinally(){
int a = 0;
try{
a = 1;
return a;
}finally{
a = 2;
}
}
}
/**output:
1
*/
解答来源
从结果上看,貌似finally
里的语句是在return
之后执行的,其实不然,实际上finally
里的语句是在在return
之前执行的。那么问题来了,既然是在之前执行,那为什么a
的值没有被覆盖了?
实际过程是这样的:当程序执行到try{}语句中的return方法时,它会干这么一件事,将要返回的结果存储到一个临时栈中,然后程序不会立即返回,而是去执行finally{}中的程序, 在执行a = 2
时,程序仅仅是覆盖了a的值,但不会去更新临时栈中的那个要返回的值 。执行完之后,就会通知主程序“finally的程序执行完毕,可以请求返回了”,这时,就会将临时栈中的值取出来返回。这下应该清楚了,要返回的值是保存至临时栈中的。
把上面代码稍微改一下,如果finally中也有return
那么在执行这个return时,就会更新临时栈中的值。同样,在执行完finally之后,就会通知主程序请求返回了,即将临时栈中的值取出来返回。故返回值是2.
public abstract class Test {
public static void main(String[] args) {
System.out.println(beforeFinally());
}
public static int beforeFinally(){
int a = 0;
try{
a = 1;
return a;
}finally{
a = 2;
return a;
}
}
}
/**output:
2
*/
switch
jdk1.7之前byte,short ,int ,char
jdk1.7之后加入String
java8,switch支持10种类型
基本类型:byte char short int
包装类 :Byte,Short,Character,Integer String enum
实际只支持int类型 Java实际只能支持int类型的switch语句,那其他的类型时如何支持的
a、基本类型byte char short 原因:这些基本数字类型可自动向上转为int, 实际还是用的int。
b、基本类型包装类Byte,Short,Character,Integer 原因:java的自动拆箱机制 可看这些对象自动转为基本类型
c、String 类型 原因:实际switch比较的string.hashCode值,它是一个int类型
d、enum类型 原因 :实际比较的是enum的ordinal值(表示枚举值的顺序),它也是一个int类型 所以也可以说 switch语句只支持int类型
线程
通信
在Java中,常用的线程通信方式有两种,分别是利用Monitor实现线程通信、利用Condition实现线程通信。线程同步是线程通信的前提,所以究竟采用哪种方式实现通信,取决于线程同步的方式。
如果是采用synchronized关键字进行同步,则需要依赖Monitor(同步监视器)实现线程通信,Monitor就是锁对象。在synchronized同步模式下,锁对象可以是任意的类型,所以通信方法自然就被定义在Object类中了,这些方法包括:wait()、notify()、notifyAll()。一个线程通过Monitor调用wait()时,它就会释放锁并在此等待。当其他线程通过Monitor调用notify()时,则会唤醒在此等待的一个线程。当其他线程通过Monitor调用notifyAll()时,则会唤醒在此等待的所有线程。
JDK 1.5新增了Lock接口及其实现类,提供了更为灵活的同步方式。如果是采用Lock对象进行同步,则需要依赖Condition实现线程通信,33 Condition对象是由Lock对象创建出来的,它依赖于Lock对象。Condition对象中定义的通信方法,与Object类中的通信方法类似,它包括await()、signal()、signalAll()。通过名字就能看出它们的含义了,当通过Condition调用await()时当前线程释放锁并等待,当通过Condition调用signal()时唤醒一个等待的线程,当通过Condition调用signalAll()时则唤醒所有等待的线程。
同步
jvm中没有进程的概念 ,但是jvm中的线程映射为操作系统中的进程,对应关系为1:1。 在jvm中 是使用监视器锁来实现不同线程的异步执行, 在语法的表现就是synchronized 。
重写和重载
返回值不能作为重载的依据,有方法体的不能作为抽象函数
方法重写
参数列表, 返回类型 必须完全与被重写方法的相同;
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
父类的成员方法只能被它的子类重写。
声明为final的方法不能被重写。
声明为static的方法不能被重写,但是能够被再次声明。
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个方法,则不能重写这个方法。
总结为两同两小一大
两同:方法名和参数列表相同
两小:返回值或声明异常比父类小(或相同)
一大:访问修饰符比父类的大(或相同
方法重载
被重载的方法必须改变参数列表(参数个数或类型或顺序不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
servlet
生命周期
链接:https://www.nowcoder.com/questionTerminal/3dfd72f89070415185f09aebecd0e3f7
来源:牛客网
Servlet的生命周期一般可以用三个方法来表示:
init()
:仅执行一次,负责在装载Servlet时初始化Servlet对象
service()
:核心方法,一般HttpServlet中会有get,post两种处理方式。在调用doGet和doPost方法时会构造servletRequest和servletResponse
请求和响应对象作为参数。
destory()
:在停止并且卸载Servlet时执行,负责释放资源
初始化阶段:Servlet启动,会读取配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,将ServletConfig作为参数来调用init()方法。
doGet/doPost
doGet/doPost 则是在 javax.servlet.http.HttpServlet 中实现的
import java.util.*
能访问java/util目录下的所有类,不能访问java/util子目录下的所有类
java.util.*,只能读取其目录下的类,不能读取其子目录下的类。
因为其根目录和子目录下可能有同名类,若都能读取,则会混淆
异常
运行异常,可以通过java虚拟机来自行处理。非运行异常,我们应该捕获或者抛出,
非运行异常=检查异常 需要try catch捕获或者throws抛出
treeSet
TreeSet自然排序,LinkedHashSet按添加顺序排序
前台线程 && 后台线程
main()函数即主函数,是一个前台线程,前台进程是程序中必须执行完成的,而后台线程则是java中所有前台结束后结束,不管有没有完成,后台线程主要用与内存分配等方面。
后台线程也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。
前台线程和后台线程的区别和联系:
1、后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止。所有剩余的后台线程都会停止且不会完成。
2、可以在任何时候将前台线程修改为后台线程,方式是设置Thread.IsBackground 属性。
3、不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
4、托管线程池中的线程都是后台线程,使用new Thread方式创建的线程默认都是前台线程。
说明:
应用程序的主线程以及使用Thread构造的线程都默认为前台线程
使用Thread建立的线程默认情况下是前台线程,在进程中,只要有一个前台线程未退出,进程就不会终止。主线程就是一个前台线程。而后台线程不管线程是否结束,只要所有的前台线程都退出(包括正常退出和异常退出)后,进程就会自动终止。一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序
线程共享,线程私有
私有:java虚拟机栈,程序计数器,本地方法栈
共享:java堆,方法区
java创建对象的方式
Java有5种方式来创建对象:
1、使用 new 关键字(最常用): ObjectName obj = new ObjectName();
2、使用反射的Class
类的newInstance()
方法: ObjectName obj = ObjectName.class.newInstance();
3、使用反射的Constructor
类的newInstance()方法:ObjectName obj = ObjectName.class.getConstructor.newInstance();
4、使用对象克隆clone()方法: ObjectName obj = obj.clone();
5、使用反序列化(ObjectInputStream)
的readObject()
方法: try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) { ObjectName obj = ois.readObject(); }