java工程师面试题之javaSE
1.switch支持的参数类型?
java5之前支持char、short、int、byte,java5又支持了enum类型,java7及以后再支持String类型,long类型都不支持
|
int |
short |
byte |
char |
enum |
String |
Long |
Java5以前 |
√ |
√ |
√ |
√ |
|
|
|
Java5-java7 |
√ |
√ |
√ |
√ |
√ |
|
|
Java7及以后 |
√ |
√ |
√ |
√ |
√ |
√ |
|
2. 八种基本数据及封装类
基本数据类型 |
int |
short |
byte |
char |
boolean |
float |
long |
double |
封装类 |
Integer |
Short |
Byte |
Character |
Boolean |
Float |
Long |
Double |
区别:
-
包装类型可以为 null,而基本类型不可以(数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出
NullPointerException
的异常。)
2. 包装类型可用于泛型,而基本类型不可以(因为泛型在编译时只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型不是Object及子类,因此会报错。)
3. 基本类型比包装类型更高效(基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用,需占较多的内存,而且对于多次用到的数值,使用包装类型就显得麻烦)
4. 两个包装类型的值相同时,却并不相等(两个包装类型指向了不同的地址)
补充:自动拆箱与包装
把基本类型转换成包装类型的过程叫做装箱(boxing)。反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing)
//输出的是true int a = 100; Integer b = 100; System.out.println(a == b);*/ //输出的true Integer c = 100; Integer d = 100; System.out.println(c == d); //输出的是false c = 200; d = 200; System.out.println(c == d);
第一题直接装箱,肯定是相等;
第二题和第三题的结果可能有点疑问,这里做个解释:java将int转为Integer是用的Integer.valueOf(),而根据源码可知,它对于-128到127的整数转成包装类型时不是去新建一个Integer对象,而是直接去IntegerCache中取出一个Integer对象,这样两个对象相等,所以返回是true,而对于超过这个范围的整数,就是常见的新建Integer对象了,由于对象地址不同,所以返回是true
3.equals与==的区别
- ==常用于判断基本数据类型是否相等,而equals常用于判断对象相等
- 当==与equals都用来判断对象时,如果对象引用地址相同,==返回true,否则会返回false,因此==是来判断对象的内存地址是否相同。而equals返回true或者false主要取决于重写实现,如果没有重写,则单单判断内存地址是否相同,与==一样,当判断String对象,Integer对象时,默认会进行重写,这时只要值相同就会返回true.
补充:
String a="abc"; String b="abc"; System.out.println(a==b);//返回true System.out.println(a.equals(b));//返回true String str2 = new String("abc"); System.out.println(a == str2);//返回false System.out.println(a.equals(str2));//返回true
Java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串常量池。当使用
String a = "abc"
这样的语句进行定义一个引用的时候,首先会在字符串常量池中查找是否已经相同的对象,如果存在,那么就直接将这个对象的引用返回给a,如果不存在,则需要新建一个值为"abc"的对象,再将新的引用返回a。String a = new String("abc");
这样的语句明确告诉JVM想要产生一个新的String对象,并且值为"abc",于是就在堆内存中的某一个小角落开辟了一个新的String对象。
4. Object有哪些公用方法?
- clone方法:实现对象的浅复制,只有当对象实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里把参数改变,这就需要在类中复写clone方法,如下:
public class Hello implements Cloneable{ int x =15; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public static void main(String []args) throws CloneNotSupportedException { Hello hello = new Hello(); Hello hello2 = (Hello)hello.clone(); hello2.x = 20; System.out.println(hello.x);//返回15 System.out.println(hello2.x);//返回20 } }
- getClass方法:final修饰,不能被重写,获得运行时类型。
package com.example.demo; public class Hello{ public static void main(String []args) throws CloneNotSupportedException { Hello hello = new Hello(); System.out.println(hello.getClass());//返回com.example.demo.Hello } }
- toString方法:该方法用得比较多,一般子类要进行重写,否则返回“类型@HashCode“,如aaa@qq.com
- finalize方法:该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。
- equals方法:常用于对象相等比较,子类一般都要重写这个方法,否则和==一样.
- hashCode方法:该方法常用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。一般必须满足obj1.equals(obj2)==true,可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相同不一定就满足equals(有可能equals被重写了)。不过为了提高效率,应该尽量使上面两个条件接近等价。如果不重写hashCode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。
- wait方法:不可被重写,就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生:
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
8.notify方法:不可被重写,该方法唤醒在该对象上等待的某个线程。
9.notifyAll方法该方法唤醒在该对象上等待的所有线程。
补充:
作者: MoonGeek
出处:https://www.cnblogs.com/moongeek/p/7631447.html
java多线程学习之wait、notify/notifyAll 详解
1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁
4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
6、notify 和 notifyAll的区别
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
7、在多线程中要测试某个条件的变化,使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。如下代码:
public class K { //状态锁 private Object lock; //条件变量 private int now,need; public void produce(int num){ //同步 synchronized (lock){ //当前有的不满足需要,进行等待,直到满足条件 while(now < need){ try { //等待阻塞 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我被唤醒了!"); } // 做其他的事情 } } }
显然,只有当前值满足需要值的时候,线程才可以往下执行,所以,必须使用while 循环阻塞。注意,wait() 当被唤醒时候,只是让while循环继续往下走.如果此处用if的话,意味着if继续往下走,会跳出if语句块。
8、实现生产者和消费者问题
如上图,假设有一个公共的容量有限的池子,有两种人,一种是生产者,另一种是消费者。需要满足如下条件:
1、生产者产生资源往池子里添加,前提是池子没有满,如果池子满了,则生产者暂停生产,直到自己的生成能放下池子。
2、消费者消耗池子里的资源,前提是池子的资源不为空,否则消费者暂停消耗,进入等待直到池子里有资源数满足自己的需求。
实现:
1.先定义一个仓库接口,包含消费和生产方法
1 public interface AbstractStorage { 2 void consume(int num); 3 void produce(int num); 4 }
2. 仓库接口实现类
1 import java.util.LinkedList; 2 3 /** 4 * 生产者和消费者的问题 5 * wait、notify/notifyAll() 实现 6 */ 7 public class Storage1 implements AbstractStorage { 8 //仓库最大容量 9 private final int MAX_SIZE = 100; 10 //仓库存储的载体 11 private LinkedList list = new LinkedList(); 12 13 //生产产品 14 public void produce(int num){ 15 //同步 16 synchronized (list){ 17 //仓库剩余的容量不足以存放即将要生产的数量,暂停生产 18 while(list.size()+num > MAX_SIZE){ 19 System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:" 20 + list.size() + "\t暂时不能执行生产任务!"); 21 22 try { 23 //条件不满足,生产阻塞 24 list.wait(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 30 for(int i=0;i<num;i++){ 31 list.add(new Object()); 32 } 33 34 System.out.println("【已经生产产品数】:" + num + "\t【现仓储量为】:" + list.size()); 35 36 list.notifyAll(); 37 } 38 } 39 40 //消费产品 41 public void consume(int num){ 42 synchronized (list){ 43 44 //不满足消费条件 45 while(num > list.size()){ 46 System.out.println("【要消费的产品数量】:" + num + "\t【库存量】:" 47 + list.size() + "\t暂时不能执行生产任务!"); 48 49 try { 50 list.wait(); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 56 //消费条件满足,开始消费 57 for(int i=0;i<num;i++){ 58 list.remove(); 59 } 60 61 System.out.println("【已经消费产品数】:" + num + "\t【现仓储量为】:" + list.size()); 62 63 list.notifyAll(); 64 } 65 } 66 }
3.实现生产者类
1 public class Producer extends Thread{ 2 //每次生产的数量 3 private int num ; 4 5 //所属的仓库 6 public AbstractStorage abstractStorage; 7 8 public Producer(AbstractStorage abstractStorage){ 9 this.abstractStorage = abstractStorage; 10 } 11 12 public void setNum(int num){ 13 this.num = num; 14 } 15 16 // 线程run函数 17 @Override 18 public void run() 19 { 20 produce(num); 21 } 22 23 // 调用仓库Storage的生产函数 24 public void produce(int num) 25 { 26 abstractStorage.produce(num); 27 } 28 }
4.实现消费者类
1 public class Consumer extends Thread{ 2 // 每次消费的产品数量 3 private int num; 4 5 // 所在放置的仓库 6 private AbstractStorage abstractStorage1; 7 8 // 构造函数,设置仓库 9 public Consumer(AbstractStorage abstractStorage1) 10 { 11 this.abstractStorage1 = abstractStorage1; 12 } 13 14 // 线程run函数 15 public void run() 16 { 17 consume(num); 18 } 19 20 // 调用仓库Storage的生产函数 21 public void consume(int num) 22 { 23 abstractStorage1.consume(num); 24 } 25 26 public void setNum(int num){ 27 this.num = num; 28 } 29 }
测试
1 public class Test{ 2 public static void main(String[] args) { 3 // 仓库对象 4 AbstractStorage abstractStorage = new Storage1(); 5 6 // 生产者对象 7 Producer p1 = new Producer(abstractStorage); 8 Producer p2 = new Producer(abstractStorage); 9 Producer p3 = new Producer(abstractStorage); 10 Producer p4 = new Producer(abstractStorage); 11 Producer p5 = new Producer(abstractStorage); 12 Producer p6 = new Producer(abstractStorage); 13 Producer p7 = new Producer(abstractStorage); 14 15 // 消费者对象 16 Consumer c1 = new Consumer(abstractStorage); 17 Consumer c2 = new Consumer(abstractStorage); 18 Consumer c3 = new Consumer(abstractStorage); 19 20 // 设置生产者产品生产数量 21 p1.setNum(10); 22 p2.setNum(10); 23 p3.setNum(10); 24 p4.setNum(10); 25 p5.setNum(10); 26 p6.setNum(10); 27 p7.setNum(80); 28 29 // 设置消费者产品消费数量 30 c1.setNum(50); 31 c2.setNum(20); 32 c3.setNum(30); 33 34 // 线程开始执行 35 c1.start(); 36 c2.start(); 37 c3.start(); 38 39 p1.start(); 40 p2.start(); 41 p3.start(); 42 p4.start(); 43 p5.start(); 44 p6.start(); 45 p7.start(); 46 } 47 }
输出
【要消费的产品数量】:50 【库存量】:0 暂时不能执行生产任务! 【要消费的产品数量】:20 【库存量】:0 暂时不能执行生产任务! 【要消费的产品数量】:30 【库存量】:0 暂时不能执行生产任务! 【已经生产产品数】:10 【现仓储量为】:10 【要消费的产品数量】:30 【库存量】:10 暂时不能执行生产任务! 【要消费的产品数量】:20 【库存量】:10 暂时不能执行生产任务! 【要消费的产品数量】:50 【库存量】:10 暂时不能执行生产任务! 【已经生产产品数】:10 【现仓储量为】:20 【已经生产产品数】:10 【现仓储量为】:30 【要消费的产品数量】:50 【库存量】:30 暂时不能执行生产任务! 【已经消费产品数】:20 【现仓储量为】:10 【要消费的产品数量】:30 【库存量】:10 暂时不能执行生产任务! 【已经生产产品数】:10 【现仓储量为】:20 【要消费的产品数量】:50 【库存量】:20 暂时不能执行生产任务! 【要消费的产品数量】:30 【库存量】:20 暂时不能执行生产任务! 【已经生产产品数】:10 【现仓储量为】:30 【已经消费产品数】:30 【现仓储量为】:0 【要消费的产品数量】:50 【库存量】:0 暂时不能执行生产任务! 【已经生产产品数】:10 【现仓储量为】:10 【要消费的产品数量】:50 【库存量】:10 暂时不能执行生产任务! 【已经生产产品数】:80 【现仓储量为】:90 【已经消费产品数】:50 【现仓储量为】:40