Java常见易错问题记录

1.for循环的流程问题:

例题:

package demo;

public class testFor {
    static boolean foo(char c) {
        System.out.print(c);
        return true;
    }

    public static void main(String[] args) {
        int i = 0;
        for (foo('A'); foo('B') && (i < 2); foo('C')) {
            i++;
            foo('D');
        }
    }
}

问:输出结果是多少?
for(初始化条件;判断条件;每次循环完毕执行)
第一次循环:初始化条件foo(‘A’); 输出A
判断条件foo(‘B’) && (i < 2),满足判断条件:输出B
进入循环体:输出D
这次循环结束:输出C
所以第一次循环输出了:ABDC 此时i=1

第二次循环:
不用执行初始化条件,直接到判断条件,满足判断条件,输出B,
进入循环体:输出D
这次循环结束:输出C
第二次循环结束,输出了BDC
此时i=2

这时不满足foo(‘B’) && (i < 2)判断条件,输出B
循环结束,不进入循环体,不执行 foo(‘C’)
综合起来就输出了ABDCBDCB

2.移位运算

移位运算提高了运算效率
左移:<<
右移:>>
无符号右移:>>>
常有用左移代替乘法运算提高效率的问题。

3.String StringBuilder StringBuffer

1、String代表不可变字符序列。每次对String类型进行变更,都会生成一个新的对象,然后将指针指向新的 String 对象。这样会影响性能,效率低下。
2、StringBuilder代表可变的字符序列。线程不安全,效率高。
3、StringBuffer代表可变字符序列,线程安全,效率比StringBuilder低。

4.短路运算(&& ||)

&&和&
&&和&都可以表示逻辑与,两边都为true ,运算结果为true
&&只要是第一个条件不成立为false,就不会再去判断第二个条件,最终结果直接为false,而&会运行第二个条件

||和|表示逻辑或。
两个判断条件其中有一个成立最终的结果就是true,区别和逻辑与类似,|会执行两边的条件。

5.基本数据类型的长度

1byte (字节)= 8bit(位)
基本数据类型的字节数:
byte:1字节
short:2字节
int:4字节
long:8字节
float:4字节
double:8字节
char:2字节
boolean:1/8字节

范围:
int short byte long 都为整数类型,范围是 - 2^(n-1) 至2^(n-1)-1 (n为位数)
float和double为浮点数类型,因为计算机只认识0和1,要表示小数的话,需要有某种规范,
例如:
float占32bit 其中1bit表示符号,8bit表示指数,23bit表示尾数
double占64bit 其中1bit表示符号,11bit表示指数,52bit表示尾数

6.hashcode和equals方法

equal()相等的两个对象他们的hashCode()肯定相等,用equal()是绝对可靠的。
hashCode()相等的两个对象他们的equal()不一定相等,hashCode()不是绝对可靠的。

7.深拷贝和浅拷贝

浅拷贝:
(1) 对于基本数据类型的成员变量,直接将属性值赋值给新的成员变量。对于基础类型,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
深拷贝:
(1) 对于基本数据类型的成员变量,和浅拷贝一样,直接将属性值赋值给新的成员变量,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 深拷贝消耗资源比浅拷贝多

浅拷贝不同于直接进行引用传递,浅拷贝后生成了新的对象,而不是把原对象的地址直接给新对象。

8.Java异常机制

首先,Throwable是所有异常的父类,Error和Exception都实现了Throwable,其中Exception又分为RuntimeException和其他Exception。
RuntimeException可以不编写异常处理的程序代码,依然可以编译成功,它是在程序运行时才有可能发生的,而其它Exception一定要编写异常处理的程序代码才能使程序通过编译。
Java默认的异常处理方式:
1、抛出异常
2、终止程序
如果加上捕获的代码,可以针对不同的情况做处理,也就是try - catch - finally块。
其中try是必须的,至少要有一个 catch 或者 finally 块。

try{
可能出现异常的代码放在try代码块中。如果这段代码发生异常,程序中断,并且抛出异常类所产生的对象,然后由catch来接管
}
catch(异常类  对象名){
抛出的对象如果属于catch()括号内欲捕获的异常类,则catch会捕获此异常,然后执行catch块的语句
}
finally{
不管前面的代码是否执行,最后一定会运行finally块里的程序代码。finally通常用作资源的释放工作。
}

finally的程序代码块运行结束后,程序再回到try-catch-finally块之后继续执行。
当代码中出现return时,一定是finally语句块执行完成后才会去执行相应的return代码,无论return语句在什么位置。

throws 和 throw
throws语句用在方法定义时声明该方法要抛出的异常类型
如果一个方法可能会出现异常,但不想或者没有能力处理这种异常,可以在方法声明处用。
throw抛出的只能够是可抛出类Throwable或者其子类的实例对象。

9.String s = "xxx"和String s = new String(“xxx”)的区别

JDK1.8
1、String s = “xxx”。JVM首先会去字符串常量池中查找是否存在"xxx"这个对象,如果不存在,则在字符串常量池中创建"xxx"这个对象,然后将池中"xxx"这个对象的地址赋给"xxx"对象的引用s;如果存在,直接将池中"xxx"这个对象的地址赋给引用s。
2、String s = new String(“xxx”) 首先会去字符串常量池中查找是否存在"xxx"这个对象,如果不存在,则在字符串常量池中创建"xxx"这个对象,并且再到堆中创建"xxx"这个对象,将堆中对象的地址赋给"xxx"对象的引用s,如果存在,也会在堆中创建对象,赋给引用变量s的依然是堆中的地址。

关于字符串常量池中存放的是对象还是对象的引用,在jdk1.8中,存放的是对象,证据数String类里intern()方法的注释。

关于数组初始化时使用new或者不用new,和字符串是不同的,创建对象时,数组内容都是放在堆中,然后把地址赋给引用变量。

10.s5 == s2,s1==s5,返回值分别是什么?

String s1=“ab”;
String s2=“a”+“b”;
String s3=“a”;
String s4=“b”;
String s5=s3+s4;

返回值分别是false,false。

在执行String s = xxx的时候,JVM首先会去字符串常量池中寻找这个字符串对象,如果存在,将地址赋给引用s,如果不存在,就在字符串常量池中创建字符串对象,将地址赋给引用s。

而"a"+“b” 由于编译器的优化(通过StringBuilder完成),该语句在class文件中就相当于String s = “ab”,(s1 == s2 为true)。为什么s5 == s2为false呢,因为s5=s3+s4,是两个字符串变量相加。这两个变量不是final类型的,无法在编译期确认,然后两个字符串相加是通过StringBuilder实现的,新建了一个对象。因此s5==s2为false。

11. ==和equals()的区别

== : 对于基本类型来说 == 比较值是否相等,对于引用类型来说,==比较地址是否相等

equals:Object类中的equals()方法是直接用==来比较的,因为所有的类都继承了Object类,equals方法是可以被重写的,所以默认情况下比较的是地址值,根据重写,可以比较内容。例如String类中的equals()方法,先比较地址值,如果相同直接返回true,再比较内容,如果内容相同返回true,不同则返回false。

12. 静态变量和实例变量的区别

静态变量属于类,实例变量属于对象。类加载之后,就可以使用静态变量,无论创建多少个对象,静态变量在内存中仅有一份。实例变量必须创建对象后,才会被分配空间,才可以使用。

13. 父类的静态方法能否被子类重写

不可以,但是可以继承,静态方法属于类。子类如果定义相同名称的静态方法,并没有实现重写。子类对象赋给父类的引用,如果子类父类有同名static方法,然后通过父类的引用调用方法的话,调用的是父类的静态方法。

14. final,finalize()和finally{}的不同之处

fianl用来修饰类,方法,变量。被修饰的类不能被继承,方法不能被重写,变量不能被改变。

finalize()是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件。

finally{}用于异常处理try-catch块中,表示一定会执行的代码,常用于关闭某些资源。

15. switch中能否使用string做参数

在JDK7之前,不可以,无法通过编译。在JDK7后可以,查看源码可以知道,对字符串内容做了hashCode()。

16. int和Integer的区别

int和Integer的区别

1、Integer是int的包装类,int则是java的一种基本数据类型

2、Integer变量必须实例化后才能使用,而int变量不需要

3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。

4、Integer的默认值是null,int的默认值是0

17. 线程、进程、协程的区别

1、进程是程序执行的时候资源分配的基本单元,线程是CPU调度的基本单元。
2、一个进程至少包含一个线程,进程间的资源是独立的,同一进程的线程间的资源是共享的。
3、协程是一个特殊的函数,一个线程可以有多个协程。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。

18. 如何控制多线程执行顺序

方法一:join()方法
join()作用:让主线程等待子线程运行结束后才能继续运行。
例如:new三个线程
thread1.start();
thread1.join();

thread2.start();
thread2.join();

thread3.start();

运行顺序:
thread1
thread2
thread3
方法二:ExecutorService
利用并发包里的Excutors的newSingleThreadExecutor产生一个单线程的线程池,而这个线程池的底层原理就是一个先进先出(FIFO)的队列。用submit依次添加了1线程,按照FIFO的特性,执行顺序也就是添加的顺序。
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(thread1);

19. 线程的生命周期和切换

线程的生命周期:
1、新建
当我们创建一个Thread对象的时候,线程就处于新建状态。
2、就绪
执行start(),线程就变成就绪状态,等待CPU调度、分配资源。
3、运行
当就绪状态的线程获得了执行权,就会执行run()里的内容,运行状态下的线程,通过yield()方法可以切换到就绪状态,
4、阻塞
当线程试图获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程就会进入阻塞状态,如果想从阻塞状态进入就绪状态必须获取到其他线程所持有的锁。
运行中的线程通过wait()、sleep()、join()使线程进入阻塞状态,阻塞状态的线程不能直接转为运行状态,必须先变为就绪状态,等待CPU调度
5、死亡
run()执行完毕或者抛出了异常,死亡状态的线程不能切换到其他状态

wait()会释放对象锁资源而sleep()不会释放对象锁资源。但是 wait 和sleep 都会释放cpu资源
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

20. 死锁的产生条件

1、互斥条件:一个资源每次只能被一个进程所使用
2、不剥夺条件:一个进程未使用完一个资源不会释放,只能由使用这个资源的进程主动释放
3、请求与保持:一个进程请求另一个资源,并没有释放自己正在使用的资源
4、循环等待条件:几个进程之间形成一种头尾相接的循环等待资源关系,每一个进程已获得的资源同时被下一个进程所请求
四个条件全部满足,形成死锁。

21. 同步方法和同步代码块

每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁。synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。

synchronized(obj){
...
}
  • 任何线程进入同步代码块、同步方法之前,必须获得锁(java每个对象都有一个内置锁),被synchronized修饰的方法,获取的锁是当前对象的锁,被synchronized修饰的代码块可以指定获取哪个对象的锁(当前对象就是this)。
  • synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

22. HashMap

概述

HashMap实现了Map接口,存储k-v型数据,在JDK1.8之前,HashMap由数组+链表实现。自JDK1.8之后,改为由数组+链表+红黑树实现。

当我们要向HashMap里存储值的时候,会计算key的hash值,根据key的hash值来找到要存储的位置,如果这个位置有元素存在了,即发生了hash冲突,就转为链表形式存储,默认情况下,当链表长度大于8,且数组长度大于64的时候,HashMap就把链表转为红黑树存储。使用数组+链表的时候,时间复杂度取决于链表的长度,为O(n),转为红黑树之后,时间复杂度为O(logn)效率更高。

HashMap的key和value都可以为null,且key不可重复,value可重复。HashMap是线程不安全的,当需要线程安全的时候可以采用ConcurrentHashMap或者使用Collections类的synchronizedMap方法来满足。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值