JavaSE面试问题总结

这篇博客汇总了JavaSE面试的常见问题,包括快速实现2的幂次、优化的查找算法、异常处理、数据类型转换、面向对象特性、字符串与字符串处理类、synchronized关键字、单例模式、final关键字、类加载过程、多态实现和初始化顺序等。通过实例代码和解析,深入探讨了这些面试中常考的知识点。

JavaSE面试问题总结

1.如何更快实现2的3次方

如何实现任意数的任意次幂

代码如下:

public static void main(String[] args) {
       Scanner sc=new Scanner(System.in);
        System.out.println("请输入基数num的值");
        int num=sc.nextInt();
        System.out.println("请输入幂power的值");
        int power=sc.nextInt();
        int result=1;
        for (int i=0;i<power;i++){
            result=result*num;
        }
        System.out.println(num+"的"+power+"次幂的值为:"+result);

若想要快速实现2的3次方 则只需将其进行移位处理

代码如下:

  int num2=1<<3;
        System.out.println(num2);

2.二分(折半)查找的优化问题

插值查找算法

可以发现二分查找每次都是选取中间的那个记录关键字作为划分依据的,那为什么不可以是其他位置的关键字呢?在有些情况下,使用二分查找算法并不是最合适的。举个例子:在1-1000中,一共有1000个关键字,如果要查找关键字10,按照二分查找算法,需要从500开始划分,这样的话效率就比较低了,所以有人提出了插值查找算法。说白了就是改变划分的比例,比如三分或者四分。

插值查找算法对二分查找算法的改进主要体现在mid的计算上,其计算公式如下:
在这里插入图片描述
而原来二分查找的公式是这样的:
在这里插入图片描述

代码实现:

 private static int binarySearch(int []arr,int i,int j,int data){
        if (i>j){
            return -1;
        }

        //若要优化二分查找 插值查找算法 则将mid赋值改为   int  mid=(j-i+1)/2+i;
        int mid=(j-i+1)/2+i;
        if (arr[mid]==data){//递归条件结束
            return mid;
        }
        if (arr[mid]>data){////i<->mid-1范围内中data
            return binarySearch(arr,i,mid-1,data);
        }
        else {//mid+1<->j范围内中data
            return binarySearch(arr,mid+1,j,data);
        }
    }

3.Java异常块中try块中return;语句和System.exit(0)语句之间是有区别的

return语句:用来从当前方法中退出,返回到调用该方法的语句处,并从紧跟该语句的下一条语句继续执行。
System.exit(0):从程序的任意地方直接一步到位的退出程序。

4.选择题:short a=0;a+=1;a=a+1;哪个会发生编译错误?

A.short a=10;a=a+1;//编译报错
由于a+1运算时会自动提升表达式的类型,为int类型, 结果赋值给short类型的a时,类型会不匹配,所以编译报错

B.short a=10;a+=1;//正常编译 因为java编译器会对+=做特殊处理,进行类型转换,因此可以正常编译。

5.整型数组,通过代码实现奇数在前,偶数在后的问题。

先计算出奇数的个数count,然后用双指针来遍历,一个从头遍历到count, 一个从数组尾部遍历到count。从前向后找到一个偶数的下标, 从后向前找到一个奇数的下标,然后交换对应的值。直到遍历完整个数组。
时间复杂度为O(n),空间复杂度为O(1)。


        int []arr={1,2,3,4,5,6,7,8,9};
        fun(arr);

        System.out.println(Arrays.toString(arr));


    }
    private static void fun(int[] arr){
        int front=0;//设置两个指针,一个指向头部,一个指向尾部
        int end=arr.length-1;
        if (arr.length==0){
            return;
        }
        while (front<end){
            while (front<arr.length&&arr[front]%2==1){
                front++;
            }
            while (end>=0&&arr[end]%2==0){
                end--;
            }
            if (front<end){//将前面的偶数与后面的奇数互换位置
               int temp= arr[front];
               arr[front]=arr[end];
               arr[end]=temp;
            }
        }
    }

6.二维数组中查找出最大元素,并打印出其下标

     int[][] c = new int[3][3];
        int a = 0, b = 0, d = 0;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                c[i][j] = (int) (Math.random() * 100);
            }


        }
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {

                System.out.print(c[i][j]);
                System.out.print(" ");
            }
            System.out.println(" ");
        }
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {

                if (c[i][j] > d) {
                    d = c[i][j];
                    a = i;
                    b = j;
                }
            }
        }
            System.out.println("最大值为:"+d);
            System.out.println("最大值坐标为:["+a+"]+["+b+"]");
        }

7.二维数组查找值得问题。注意:二维数组从上到下依次递增有序,从左到右依次递增有序

方法一:从上到下,从左至右依次遍历数组,找到target后返回true。

时间复杂度:O(mn)

public static boolean search(int target, int[][] array) {

        int m = array.length;
        if (m == 0) {
            return false;
        }
        int n = array[0].length;
        for (int i = 0; i < m - 1; i++) {
            for (int j = 0; j < n - 1; j++) {
                if (target == array[i][j]) {
                    System.out.println(i + " " + j + " ");
                    return true;
                }
            }
        }
        return false;
        }

方法二:循环遍历数组的每一行,把每一行看作一个依次递增的数组,使用优化二分查找的方法,和目标值terget比较。

1.若当前值比目标值target大,则end=mid-1;
2.若当前值比目标值target小,则end=mid+1;
3.若当前值比目标值target相等,则返回true;

时间复杂度:O(log n)

public static boolean search(int target, int[][] array) {
    int m = array.length;
        if (m == 0) {
            return false;
        }
        for (int i = 0; i < m; i++) {
            int begin = 0;
            int end = m - 1;
            while (begin <= end) {
                int mid = (end - begin +1) / 2+begin;
                if (target > array[i][mid]) {
                    begin = mid + 1;

                } else if (target < array[i][mid]) {
                    end = mid - 1;
                } else {
                    System.out.println(i + " " + mid + " ");
                    return true;
                }
            }

        }
        return false;
    }

8.谈谈你对面向对象的理解

首先,什么是对象?Java中有一种思想叫做“万物皆对象”,对象就是具有某些特殊属性(成员变量)和行为方式(方法)的实体。现实生活中的任何事物都可以看作是对象,无论是具体或抽象的事物,比如:一个动物或者是城市的变化。具有两个特征:属性和行为。
面向对象:人们为了解比1+1=2更复杂的问题,将现实的事物抽象出来,把现实生活的事物以及关系,抽象成类,通过继承,实现,组合的方式把万事万物都给容纳。
eg:有一天你想吃鱼香肉丝了,怎么办呢?你有两个选择
1、自己买材料,肉,鱼香肉丝调料,蒜苔,胡萝卜等等然后切菜切肉,开炒,盛到盘子里。
2、打开外卖APP,点一份鱼香肉丝。
看出来区别了吗?1是面向过程,2是面向对象。

在面向对象时,首先你不需要知道鱼香肉丝怎么做,降低了问题的耦合性;其次,当你想改变订单时,你可以重新下单,提高了可维护性。

9.Java面向对象的特征有哪些?

1、封装

隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。

2、继承

提高代码复用性;继承是多态的前提。

3、多态

父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。

10.谈谈你对String,StringBuffer,StringBuilder的理解

1.String是一个不可变类,StringBuilder是一个可变类,
String对象的相加底层都是通过StringBuilder的append方法进行字符串连接的。
2.在循环中,进行String字符串相加,其效率很低,会生成很多的String对象,此时
应该直接使用StringBuilder进行字符串连接,所有字符串连接完成后,可以调用
toString方法转成String类型的字符串对象
3.StringBuffer相当于是线程安全的StringBuilder,专门用于多线程环境中

eg:

public static void main(String[] args) {
        String str="hello";
        str=str+"wor";
        str=str+"ld";
        System.out.println(str);


        StringBuilder sb=new StringBuilder("hello");
        sb.append("wor");
        sb.append("ld");
        System.out.println(sb.toString());

    }

代码输出:
hello world
hello world

11.谈谈你对synchronized关键字的理解

线程安全的解决方法还有synchronized,提供了线程同步的方式

synchronized的使用方法

synchronized关键字可以 修饰方法或代码块,确保多个线程同一时刻,只能有一个线程处理方法或者是同步块,确保线程对访问变量的可见性,有序性,原子性。

1.修饰普通方法

 //修饰普通方法
    public synchronized void add() {
        //do something  
    }

synchronized加在普通方法上,锁住的是当前的对象实例

2.修饰静态方法

 //修饰静态方法
    public static synchronized void update() {
        //do something
    }

synchronized加在静态方法上锁住的是当前的class实例,class数据存储在方法区中,锁的静态方法相当于是全局锁。

3.修饰代码块

//修饰代码块
    public void del(Object obj) {
        synchronized (obj) {
            //do something
        }
    }

synchronized加在obj 实例上,锁的是当前的obj代码块

synchronized的特点

synchronized修饰的方法或者代码块相当于并发中的临界区,在同一时刻JVM只允许一个线程进入执行。synchronized通过锁机制达到同一时刻只允许一个线程进入执行的效果,实现线程的并发原子性,有序性,可见性。

12.谈谈你对单例模式的理解

单例模式:

因为进程的需要,有时候我们只需要某个类同时保留一个对象,不希望有多个对象存在是,我们就要考虑使用单例模式。

特点

1.单例模式只能有一个实例
2.单例模式必须创建自己的唯一实例
3.单例模式必须向其他对象提供这一实例

单例模式的实现(懒汉式、饿汉式)

饿汉式

饿汉式单例模式都会浪费内存空间

public class HungryMan {
    private HungryMan(){

    }
    private final static HungryMan HUNGRY_MAN=new HungryMan();
    private static HungryMan getInstance(){
        return HUNGRY_MAN;
    }
}

DCL懒汉式

public class LazyMan {
    private LazyMan(){

    }
    //加上volatile关键字防止指令重排
    private volatile static LazyMan lazyMan;
//双重检测锁的懒汉式单例  简称DCL懒汉式
    public static LazyMan getInstance(){
        //加锁
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 有可能发生指令重排现象
                     */
                }
            }
        }

        return lazyMan;
    }
}

13.谈谈你对final关键字的理解

1.修饰类,类就不能被继承

2.修饰成员方法,方法不能被重写

3.修饰成员变量时,变量的值不能被修改

14.谈谈类的加载过程

类加载的过程主要分为三部分:

  • 加载
  • 链接
  • 初始化

而链接又可以细分为三个小部分

  • 验证
  • 准备
  • 解析

在这里插入图片描述

1.JVM先从磁盘上寻找Student.class字节码文件(加载)

jvm从系统环境变量得CLASSPATH里面找字节码文件的搜索路径 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar(. 代表当前工程目录下)通过类加载器从Stdent.class字节码文件中,相当于把这个类的户口信息先加载到JVM中,这个户口信息称Class对象

2.验证类的Class对象的合法性,开辟内存空间(链接)

准备、验证的过程,验证当前类能够在JVM上执行,给类的static静态成员开辟内存空间,如果当前类还有基类,继续递归加载其基类

3.初始化(初始化)

给static静态成员初始化,调用类的static静态初始化块

JVM默认提供的类加载器

  • BootstrapClassLoader(原生类加载器)C:\ProgramFiles\Java\jdk1.8.0_192\jre\lib
    使用原生代码编写(c&c++),负责加载JVM运行依赖的jar包
  • ExtClassLoader(扩展类加载器)C:\ProgramFiles\Java\jdk1.8.0_192\jre\lib\ext
  • AppClassLoader(应用类加载器) CLASSPATH路径下去加载相应的字节码文件( SystemClassLoader)

类加载器都是通过“双亲委托模型”加载

双亲委托模型:遇见一个需要加载的类,Student类理应由AppClassLoader来加载,实际上AppClassLoader先请求它的父类ExtClassLoader看能否加载Student类,ExtClassLoader在往上请求BootstrapClassLoader能否加载Student类,实际上BootstrapClassLoader无法加载Student类,ExtClassLoader也无法加载 Student类,最后只能由AppClassLoader自己加载Student类,Student类就是这样加载起来的,把这种加载方式称为“双亲委托模型”

好处:防止类被重复的加载

15.多态的实现原理

多态就是动态绑定的过程,是指在执行期间而不是编译期间判断引用对象的实际类型调用相关方法。多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同的操作。

多态的优点:

(1)可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。

(2)可扩充性(extensibility。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。

(3)接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。

(4)灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。

(5)简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

16.子类的初始化顺序过程

  1. 静态成员变量首先初始化(注意,Static可以看做一个静态成员,其执行顺序和其在类中申明的顺序有关)
  2. 普通成员初始化
  3. 执行构造函数。
class A{
    public int i = method();
    public static int j = method2();
    public int k = 0;
    public A(){
        System.out.println(1);
    }
    public int method(){
        System.out.println(2);
        return 2;
    }
    public static int method2(){
        System.out.println(3);
        return 3;
    }
}
public class B extends A{
    public int m = method3();
    public static int n = method4();
    public int t = 0;
    public B(){
        System.out.println(4);
    }
    public int method3(){
        System.out.println(5);
        return 5;
    }
    public static int method4(){
        System.out.println(6);
        return 6;
    }
    public static void main(String[] args){
        System.out.println(7);
        A a = new B();
    }
}

运行结果为:
3,6,7,2,1,5,4

16.谈谈你对重写和重载的认识

override(重写)

1、方法名、参数、返回值相同。

2、子类方法不能缩小父类方法的访问权限。

3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。

4、存在于父类和子类之间。

5、方法被定义为final不能被重写。

overload(重载)

1、参数类型、个数、顺序至少有一个不相同。

2、不能重载只有返回值不同的方法名。

3、存在于父类和子类、同类中。

17.抽象类和接口的区别

抽象类:在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:

abstract void fun();

在《JAVA编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。

2)抽象类不能用来创建对象;

3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。

接口:接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象。在Java中,定一个接口的形式如下:

public] interface InterfaceName {

}

接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法

在这里插入图片描述

什么时候使用接口,什么时候使用抽象类

  • 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
  • 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
  • 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值