java经典面试题集

  1. byte b = 3;
    b = 3 +7;

byte b = 3;
byte b1 = 3;
byte b2 = 7;
b = b1 + b2;

上面两种代码,为什么第一个编译不报错,第二个编译会报错?
解释:在java中整数默认是int型,byte b = 3;这个代码中实际上将3(默认是int型)进行了强制转换(由系统将常量进行了强制转换)同理第二行将10赋值给b也进行了强制转换,因此编译不会报错,在第二段代码中由于b1和b2是变量,系统无法将变量进行强制转换,无法确保变量的值在byte精度内,因此编译时会报错,如果是同样的类子将byte型换成int型,则两种代码编译都不会报错,原因是int型是默认的整型,即使变量的值大于int型范围的最大值,变量值是转换成负值,不会存在byte这种问题。

  1. System.out.println("5+5="+5+5);
    输出5+5=55,原因是任何类型和字符串相加,加号都是连接符。
    System.out.println("5+5="+(5+5));
    输出5+5=10
    Ps:java中字符用' '表示,字符串用" "表示。

  2. s+=4和s= s+4是否相同?
    short s=3;
    s+=4;

    short s=3;
    s=s+4;
    第一段代码编译通过,第二段代码编译不通过,原因在于s=s+4表达式的右边是变量,系统无法进行强制转换,而s+=4右边是常量,系统可以强制转换。s=s+4进行了两次运算,而s+=4只进行了一次运算,在本例中s+=4 实际上相当于s = (short)(s+4)。

  3. &&和&的区别:
    &:无论左边的运算结果是什么,右边都要参与运算
    &&:当左边为false时,右边不参与运算
    同理 |和||是一样的区别,区别是||:当左边为true时,右边不参与运算
    一个数异或同一个数两次,结果还是这个数
    左移几位相当于该数据乘以2的几次方,右移同理变成除以2的几次方
    对于高位出现的空位,原来高位是用什么就用什么补这个空位。
    (>>>无符号右移):数据进行右移时,高位出现的空位,无论原高位是什么,空位都用0补

  4. 简写格式什么时候用(三元运算符)
    当if else运算后,有一个具体的结果时,可以简写成三元运算符

6.if和switch的应用
if的应用场景:
1.对具体的值进行判断
2.对区间判断
3.对运算结果是boolean类型的表达式进行判断
switch的应用场景:
1.对具体的值进行判断,值得个数通常是固定的,对于个固定的值进行判断,建议使用switch语句,因为switch会将结果同时加入内存,运行效率相对高些。
for语句的规范:
for{a初始化表达式;b循环条件表达式;c循环后操作表达式}{
d执行语句:(循环体)
}
for语句的执行顺序,先执行a,在判断b是否为true,为真的话执行d,然后再执行c,执行完c后,再判断b是否为真,为真的话执行d,一直循环下去直到b为false,终止循环。
for和while的特点:
1.for和while可以进行互换
2.两者的格式有区别,如果需要通过变量来对循环进行控制,该变量只作为循环变量增量存在时,区别就出来了。区别在与for的循环变量作用域只在for循环中有效,而while中循环变量则是在循环外有效。
3.无限循环,while(true), for(;;)

7.给定一个有序的数组,如果给该数组中存储一个元素,并保持该数组还是有序的,请问如果获取元素存储的角标(位置)?
折半查找法,一般有序数组操作都是折半查找法;将返回值由-1改成min,就是返回该元素的角标位置。

public static int halfSearch(int[] arr ,int key) {
        int max,mid,min;
        max = arr.length -1;
        min = 0;
        
        while(min <= max) {
            mid = (max+min)>>1;  //位运算,相当于(max + min)/2
            if(key > arr[mid]) {
                min = mid + 1;
            }else if(key < arr[mid]){
                max = mid -1;
            }else {
                return mid;
            }   
        }
        //表示该数不存在
        return -1;
    }

  1. 二维数组在内存中的示例图:


    内存示意图.png

9.如何看待面向对象的思想?
1.面向对象的思想符合当前人们思考习惯(核心就是让合适的人做合适的事)
2.让复杂的事情变得简单化
3.相对于面向过程,让我们从程序的执行者变成程序的指挥者
4.其实,面试官你本身就在用面向对象的思想来思考问题,如果你接到软件开发的任务,你从需求分析、设计、开发、测试都能完成,但是这样特别耗时间,为了提高效率你就需要找到具有专业编程的对象来完成其中的部分功能,我就是这样的对象,你只需要指挥我完成指定的工作即可,我会给你一个非常满意的结果。所以面试官你就在用面向对象的思路来思考问题,以提高工作的效率。

面向对象的三个特征:封装、继承、多态

9.成员变量和局部变量的区别
1:成员变量定义在类中,整个类都可以访问
局部变量定义在函数,语句,局部代码块中,只有所属区域有效

  1. 成员变量存在于堆内存的对象中
    局部变量存在于栈内存的方法中

3.成员变量随着对象的创建而创建,对象的消失而消失
局部变量是随所在区域的执行而存在,随所在区域的结束而消失

4.成员变量有默认初始值(int型为0,String型为null)
局部变量没有默认初始值

10.基本数据类型和引用数据类型的参数传递

基本数据类型和引用数据类型参数传递区别.png

11.static(静态变量)的特点:
1.static是一个修饰符,用于修饰成员
2.static修饰的成员被所有的对象共享
3.static优先于对象存在,因为static成员随着类的加载就已经存在
4.static修饰的成员多了一种调用方式,可以直接被类名调用。类名.静态成员
5.static修饰的数据是共享数据,对象中存储的是特有的数据

成员变量和静态变量的区别:
1.两个变量的生命周期不同
成员变量随对象的创建而存在,随对象的回收而消失
静态变量随类的创建而存在,随类的消失而消失

2.调用方法不同
成员变量只能被对象调用
静态变量可以被对象和类调用

3.别名不同
成员变量又名实例变量
静态变量又名类变量

4.数据存储位置不同
成员变量数据存储在堆内存的对象中,所有也叫对象的特有数据
静态变量数据存储在方法区(数据共享区)的静态区,也叫数据共享区

静态使用的使用注意事项:
1.静态方法只能访问静态成员(非静态方法可以访问静态和非静态)
2.静态方法中不能使用this和super关键字
3.主函数是静态的。
主函数main解析:
public:因为权限必须最大。
static:不需要对象,直接用主函数所属类名调用即可
void:主函数没有具体返回值
main:函数名,不是关键字,只是一个jvm识别的固定的名字
String[] args:这是主函数的参数列表,是一个数组类型参数,元素都是字符串类型

静态什么时候使用?
1.静态变量
1.当分析对象中所具有的成员变量的值都是相同的,这个变量就可以被静态修饰
2.只要数据在对象中是不同的,就是对象的特有特质,必须定义在对象中,就定义非静态,如果是相同的数据,对象不需要做修改,只需要使用即可,就可以存在静态变量中。

2.静态方法
参考该函数的功能是否有访问到对象中的特有的数据,换种说法,就是该函数是否需要访问对象中特有的数据,如果需要就定义非静态,不需要就定义成静态,如果没有访问特有数据的就定义为静态的。

静态代码块 构造代码块 局部代码块
1.static { 静态代码块 } 类一加载就执行
2.在类中{ 构造代码块 } 对象调用构造函数就执行
3.在函数中{ 局部代码块 } 在函数中顺序执行

12.一个对象实例化的过程
Person p = new Person();
1.JVM会读取指定路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有父类的话)
2.在堆内存中开辟空间,分配地址
3.并在对象空间中,对对象的属性进行默认初始化
4.调用对应的构造函数进行初始化
5.在构造函数中,第一行会调用父类的构造函数进行初始化
6.父类的初始化完毕后,对子类的属性进行显示初始化
7.再进行子类构造函数的特定初始化
8.初始化完成后,将地址值赋值给引用变量

13.多态中转型的特点:
1.Animal c = new Cat(); //自动类型提升,猫提升为动物类型,称为向上转型
向上转型的作用是限制对特有功能的访问。
2.Cat c1 = (Cat)c; //向下转型,目的是为了使用子类中特有的功能。
3.注意:转型自始至终都是子类对象在做着类型的转换
4.instanceof用于判断对象的具体类型,(比如:a instanceof Cat),通常用于在向下转型前用于健壮性的判断(比如:if(a instanceof Cat){ Cat c = (Cat)a;})

多态中成员特点:
1.成员变量(int x=3),编译和运行都看等号的左边
2.成员函数(非静态)(void show(){....}),编译看左边,运行看右边
3.静态函数(static void show(){.....}),编译和运行都看等号的左边,其实对于静态方法,是不需要对象的,直接用类调用

14.内部类访问特点:
1.内部类可以直接访问外部类中的成员
2.外部类要访问内部类,必须建立内部类的对象
3.如果内部类中定义了静态成员,那个该内部类也必须是静态的
class Outer {
class Inner{
void show(){
System.out.println("Inner runing");
}
}
public void method(){
Inner in = new Inner();
new.show();
}
}

class Demo{
public static void main(String[] args){
//直接访问外部类中内部类的成员,格式如下
Outer.Inner in = new Outer().new Inner(); //需要创建外部类对象
in.show();

      //如果内部类是静态的(外部类一加载,内部类就存在),格式如下:
      Outer.Inner in = new Outer.Inner();                       //不需要创建外部类对象
      in.show();

      //如果内部类是静态的,内部类中方法也是静态的
      Outer.Inner.show();                                             //不需要创建对象,直接类调用
  }

}

为什么内部类能访问外部类中的成员呢?
那是因为内部类持有外部类的引用。 外部类名.this

匿名内部类前提内部类必须继承或者实现一个外部类或者接口。
匿名内部类:其实就是一个匿名子类对象
格式:new 父类or接口(){子类内容}

15.Object类的方法
1.Object.equals(Object b) 判断对象是否相等(对象的地址是否相同),一般在子类中覆盖
2.Object.hashCode(Object b ) 返回对象的哈希值,一般和equals在子类中一起重写
3.Object.getClass 返回当前运行时的类
4.Object.toString() 实际相当于Object.getClass().getName() + "@" + Integer.toHex(Object.hashCode()); 一般需在子类中重写

16.异常:是在运行时期发生的不正常情况
异常分类:
1.编译时检测异常,只要是Exception和其子类都是,除了特殊子类RuntimeException体系
2.编译时不检测异常(运行时异常),就是Exception中RuntimeException和其子类

自定义异常时,要么继承Exception,要么继承RuntimeException
throws和throw的区别:
1.throws使用在函数上,throw使用在函数内
2.throws抛出的是异常类,可以抛出多个,用逗号隔开
throw抛出的是异常对象

异常处理的捕捉形式:
try
{ //需要被检测异常的代码 }
catch(异常类 变量) //该变量用于接收发生异常的对象
{ //处理异常的代码 }
finally //一般用于资源的释放
{ //一定会执行的代码 }

异常处理原则:
1.函数内容如果抛出需要检测的异常,那么函数上必须声明,否则在函数内用trycatch捕捉,不然函数编译失败
2.如果调用了声明异常的函数,要么trycathch要么throws,不然编译失败
3.什么时候catch,什么时候throws
功能内容可以解决,用catch
解决不了,用throws告诉调用者,由调用者解决
4.一个功能抛出多个异常,调用时必须对应多个catch针对处理
抛出几个异常,就catch几个

16.多线程图解

多线程图解.png

创建线程的第一个方式:继承Thread类
第二个方式:实现Runnable接口,1.定义类实现Runnable接口 2.覆盖接口中的run方法,将线程的任务方法封装到run方法中 3.通过Thread类创建线程对象,并将Runnable接口子类对象作为Thread类的构造函数的参数传递 4.调用线程的start()方法开启线程
实现Runnable方法的好处:
1.将线程中的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象
2.避免了java单继承的局限性

线程安全问题的产生的条件
1,多个线程在操作共享数据
2,操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码中,其他线程参与了运算,就会产生线程安全问题。
解决思路:
就是将执行操作共享数据的多条代码封装起来,当有线程执行这些代码时,其他线程不可以参与运算。必须等当前线程把这些代码执行完毕后,其他线程才可以参与运算。
在java中,同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){ //对象一般用obj对象
需要被同步的代码;
}
同步的好处:
解决了线程安全问题
同步的弊端:
相对而言,降低了效率,因为同步外的线程会判断同步锁

同步的前提:必须有多个线程并使用同一个锁

同步函数使用的锁是this,
静态同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可用当前 类名.class表示
同步代码块和同步函数的区别:
同步函数的锁是固定的this,同步代码块的锁是任意的对象

建议使用同步代码块

单例模式中的懒汉式中多线程的安全问题解决方法,(饿汉式中多线程不会出现安全问题)
class Single
{
private static Single s = null;
private single(){}
public static Single getInstance()
{
if(s == null){
s = new Single();
}
return s;
}
}
其中if(s == null){s=new Single();}这步会产生线程安全问题,最好的解决办法:
if(s == null){
synchronized(Single.class){
if(s == null){
s = new Single();
}
}
}
return s;

你可能感兴趣的:(java经典面试题集)