java基础面试题----持续补充

主文章(我总结的面试题的索引目录—进不去就说明我还没写完)
https://blog.csdn.net/grd_java/article/details/122357831
字节码指令可以到这篇文章查:https://www.cnblogs.com/longjee/p/8675771.html

文章目录

1. 语法细节

1. switch是否能作用在byte上,long呢,String呢?

  1. Java 5之前,switch(expr),expr只能是byte、char、int、short
  2. Java 5之后,java引入enum枚举类型,expr可以支持enum类型
  3. Java 7开始,expr可以作用于字符串String类型,long类型在目前所有版本中都不支持

2. java注释有几种,注释有什么用

  1. java注释有3种,//单行注释,/* 多行注释 * /,/**文档注释 */,其中多行和文档注释不能嵌套
  2. 注释作用:复杂程序中,适当加入注释,可以增加程序可读性,利用程序修改、调试和交流,注释内容在程序编译时会被忽略,不会对程序执行结果产生任何影响

3. & 和 && 的区别,| 和 || 的区别

  1. &运算符有两种用法:按位与,逻辑与
  2. &&运算符是短路与运算,逻辑与和短路与,差别巨大。二者都要求运算符左右两端布尔值都是true,运算结果才为true。
  3. &&称为短路与的原因是,如果左边表达式为false,右边表达式直接短路,不进行运算
  4. 而&无论前面已经运算的是不是false,都会和后面的进行运算,不会短路
  5. 逻辑或运算符(|)和短路或运算符(||)的差别也是如此

4. final 的作用

  1. 修饰符,可以修饰类、属性、方法,表示最终的,被修饰的对象不可改变
  2. 被final修饰的类不可被继承
  3. 被final修饰的方法不可以被重写
  4. 被final修饰的变量不可以被改变,针对的是变量的引用,引用不可变,引用指向的内容可变

4. final int a = 1; a = 10;正确吗?final Test test = new Test(“zhangsan”);test.name = “lisi”;正确吗?

  1. final int a = 1; a = 10;不正确,因为1是一个常量,在堆栈中存着,a变量指向了1.此时执行a=10;会将a的引用指向10,而final修饰的变量不可以变换引用,所以不正确
  2. final Test test = new Test(“zhangsan”);test.name = “lisi”;是正确的,test的引用始终没变过,final修饰的变量,只有引用不可变内容可变test.name = "lisi";改的是内容

5. break,continue,return的区别和作用

  1. break 跳出当前层循环,不在执行当前循环体(不影响外层循环),switch中直接跳出switch
  2. continue 跳出本次循环,继续执行下次循环(结束本次循环,直接判断下一个循环条件)
  3. return 程序返回,不在执行下面代码(结束当前方法,直接返回)

6. java多重嵌套循环中,如何跳出指定层循环?

  1. java提供循环标签,break和continue可以指定跳出相应标签的循环
    java基础面试题----持续补充

7. == 和 equals 区别是什么

  1. ==:运算对象是基本数据类型时,比较的是值(两个值相不相同)。运算的是引用类型时,比较两个对象内存地址是否相同(看两个对象是不是同一个对象)
  2. equals():判断两个对象是否相等
  1. 类没有覆盖equals()方法,则比较两个对象内存地址,和 == 一样
  2. 类覆盖了equals()方法,看具体覆盖的实现,比如我们判断两个对象内容是否相等,相等返回true

8. String str = "i"与String str=new String(“i”)一样吗?

  1. 不一样,因为内存分配方式不一样
  2. String str = "i"的方式,java虚拟机会将其分配到常量池中(常量池:存储常量)
  3. String str=new String(“i”)的方式,会将其分配到堆内存中(堆:存储new出来的),然后堆指向常量池,并不是直接指向常量池的"i"
  4. 还不理解,看下面代码
    public static void main(String[] args) {
        String a = "abc";//直接将"abc"放在常量池,然后栈中a直接指向常量池"abc"
        String b = "a"+"bc";//直接将结果算出来"abc",然后栈中b直接指向常量池"abc"
        String c = new String("abc");//new String是多余的,先在堆中开辟内存,c指向堆,堆指向常量池"abc"
        System.out.println(a.equals(b)+"     "+c.equals(a));//true true,表示a、b、c的值相同
        System.out.println(a.hashCode()+"    "+ b.hashCode()+"     "+c.hashCode());//3个hashCode值都相同,表示他们最终指向的都是同一个"abc"
        System.out.println(
                System.identityHashCode(a)+"    "+
                System.identityHashCode(b)+"    "+
                System.identityHashCode(c)
        );//356573597    356573597    1735600054,可见a和b的内存地址相同,c的内存地址不同,因为a和b直接指向常量池,而c指向堆

    }
  1. 拓展(下面是代码的字节码文件,也就是运行时,执行的汇编指令,先看代码再看字节码
    java基础面试题----持续补充
    public static void main(String[] args) {
        String a = "abc";//直接将"abc"放在常量池,然后栈中a直接指向常量池"abc"
        String b = "a"+"bc";//直接将结果算出来"abc",然后栈中b直接指向常量池"abc"
        String d = "abc"+"";//直接将结果算出来"abc",然后栈中d直接指向常量池"abc"
        /**
         * 变量参与运算,
         * 根据a拿到"abc",
         * 然后StringBuilder sb = new StringBuilder();
         * 然后sb.append("abc");然后sb.addpen("")
         * sb.toString(),将StringBuilder搞成String,源码是return new String(value, 0, count);
         * 既然new了,那么现在堆中开辟空间
         * call指向堆,然后堆指向"abc"
         */
        String call = a + "";
        System.out.println(a.hashCode()+"   "+call.hashCode());//96354   96354,说明call和a的值都是常量池的"abc"
        System.out.println(
                System.identityHashCode(a)+"    "+
                System.identityHashCode(b)+"    "+
                System.identityHashCode(d)+"    "+
                System.identityHashCode(call)
        );//356573597    356573597    356573597    1735600054,a、b、d 都直接指向常量池"abc" . call指向堆,然后堆指向"abc"

    }

9. String、StringBuffer、StringBuilder的区别于联系

  1. String类是不可变类,一个String对象被创建后,字符序列不可变(字符串是常量),直至对象被销毁(当我们变量换引用后,原来的字符串常量,没有引用指向它,就会被当做垃圾,被JVM底层垃圾回收算法回收)
  2. StringBuffer类代表一个字符序列可变的字符串,通过append、insert、reverse、setCharAt、setLength等方法操作其内容。当需要生成最终字符串时,通过toString方法将其转变为String(并不是StringBuffer变成String,而是根据值,返回一个String对象)
  3. JDK1.5新增了一个StringBuilder类,与StringBuffer类似,构造方法和方法基本相同。但是StringBuffer是线程安全的,StringBuilder是线程不安全的,性能略高。通常,创建一个可变字符串,优先StringBuilder
  4. StringBuffer:JDK1.0就有,效率相对较低,线程安全
  5. StringBuilder:JDK1.5加入,效率高,线程不安全
  6. 三个类,底层维护的都是char[],但是String的char[] 是使用final修饰的,所以String类和它的char[]的引用不可改变,但是char[]中数组值可变,但是因为String类的char[]是private我们没法改,所以我们常说String是常量,不可变,想要变,只能new一个新的String或者指向其它字符串常量
  7. StringBuffer的数组,有初始长度为16,当满了以后,是可以扩容的(建立一个容量更大的新数组,然后将满的数组值拷贝),所以叫可变长字符串

10. int 和 Integer 的区别

  1. java很纯洁,但为了编程方便,依然引入了基本数据类型int,char等等,为了将这些基本数据类型当成对象操作,java为每种基本数据类型都引入了对应包装类(wrapper class),int包装类为Integer,Java 5开始引入了自动装箱/拆箱机制,二者可以相互转换。
  2. Java为每个原始类型都提供了包装类型
    boolean—>Boolean、char—>Character、byte—>Byte、short—>Short、Integer—>Integer、long—>Long、float—>Float、double—>Double

11. Java异常关键字

  1. try - 用于监听。可能抛出异常的代码放在try语句块中,当他们发生异常时,异常就被抛出
  2. catch - 用于捕获异常。catch用来捕获try语句块中发生的异常。
  3. finally - finally语句块总是会被执行。主要用于回收try块里打开的资源(数据库连接,网络连接,IO连接等)。finally执行完成后,才会回来执行try或者catch块中的return或throw语句,如果finally中使用return或throw等终止方法的语句,则不会跳回执行,直接停止
  4. throw - 用于抛出异常
  5. throws - 用在方法签名上,声明该方法可能抛出异常。

12. Java为什么只能单继承,但可以多实现接口

  1. 多实现方法,不会冲突,因为接口中没有方法体,所以实现都是再实现类中编写,,比如实现的两个接口都有a方法,因为两个接口都没方法体,具体实现再实现类中,不会冲突
  2. 多继承方法内容,容易冲突,继承的两个类都有a方法,两个父类各有各的实现,就会冲突

13. int value = 0;if(value||(value==0)) return true;会返回true吗?

  1. 不能,&&、||、!这些符号,都只能作用于boolean值,value是int类型数据,无法运算,编译错误

14. 已知String继承Object类,下面两个泛型集合,可以自动向上转型么?

List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
list1 = list2;
  1. 不能,会编译错误;A继承B类,但是C< A >和C< B >不存在继承关系,是并列关系;

15. 已知String继承Object类,下面两个泛型集合,可以自动向上转型么?

List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<? extends Object> list3 = null;
list3 = list1;
list3 = list2;
  1. 完全可以,<? extends Object>是泛型受限(规定上限),只要是Object或继承于Object都可以受限转型

16. 已知A继承B类,下面代码可以正常运行吗?

List<Object> o = new ArrayList<>();
List<A> a = new ArrayList<>();
List<B> b = new ArrayList<>();
List<? extends B> list3 = null;
list3 = a;
list3 = b;
list3 = o;
  1. list3 = o;会编译错误,因为Object不是B类的子类

17. 已知A继承B类,下面代码可以正常运行吗?

List<Object> o = new ArrayList<>();
List<A> a = new ArrayList<>();
List<B> b = new ArrayList<>();
List<? super B> list3 = null;
list3 = a;
list3 = b;
list3 = o;
  1. list3 = a;会编译错误,<? super B> 是泛型受限(规定下限),只要是B类或B类的父类都可以受限转型,但是A类是B的子类,所以不可以受限转型,编译错误。

18. 下面代码中,对与方法参数List<?> list的操作,最终输出结果是什么?

public class A{
	public void a(List<?> list){
		for(Object o:list){
			System.out.println(o)
		}
		list.add("abc");
		list.add(null);
		Object s = list.get(0);
		System.out.println(s)
	}
}
class B{
	public static void main(String[] args){
		A a = new A();
		a.a(new ArrayList<String>());
		a.a(new ArrayList<Integer>());
	}
}
  1. list.add(“abc”);这行代码会编译错误,其它代码没问题,因为使用泛型通配符<?>,无法确定list中规定什么数据类型,所以无法进行具体类型的赋值操作,list.add(null);是没有问题的

2. 类型转换题

1. short s1=1;s1 = s1+1;有错吗?short s1=1;s1+=1;有错吗?

  1. 1int类型,s1short类型,s1+1自动向上转型,变成int型结果,需要强转(short)(s1+1)才可以赋值给s1
  2. short s1=1;s1+=1;可以正常编译,s1+=1;相当于s1=(short(s1+1));其中有隐含的强制类型转换

3. 概念题

1. java 面向对象的特征主要有几个方面?

  1. 封装:把对象属性私有化,有选择的提供可以被外界访问的属性和方法,如果外界没有任何访问某个类的方法,那么这个类没有意义
  2. 继承:继承父类的功能,以父类为基础,子类可以增加新的功能,也可以使用父类的功能,方便代码复用,是多态的前提
  1. 子类拥有父类非private的属性和方法
  2. 子类可以拥有自己的属性和方法,子类就是对父类进行扩展
  3. 子类可以自己实现父类的方法(重写)
  1. 多态:一个功能的多种访问(实现)形式,例如我们一个接口,可以有多个实现类,例如List接口,我们可以用ArrayList或LinkedList的实现。虽然调的都是List的方法,但是实现不同。同时也有方法重载,同名方法,根据参数列表不同,可以有不同的实现
  1. 重载:同名方法a,根据参数列表不同,调用不同的a方法
  2. 重写:将父类a方法推翻,自己定义新的实现
  3. 多态虽然提高扩展性,但是提高的不彻底,真正提高还得是反射,传个字符串可以反射任意类

2. 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

  1. 两个都是实现多态的方式,区别在于前者实现的是编译时的多态性,后者实现的是运行时的多态性
  2. 重载:发生在同一个类中方法名相同参数列表不同参数类型不同,个数不同,顺序不同),与方法返回值和访问修饰符无关,重载方法不能根据返回类型进行区分
  3. 重写:发生在父子类中方法名、参数列表必须相同,返回值小于等于父类(自动向上转型),抛出的异常小于等于父类,访问修饰符大于等于父类(里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象);
  4. 如果父类方法访问修饰符为private则子类不能重写,子类中就算有同名方法,也不是重写

3. java内存分配

  1. 常量池:存放常量,例如"abc".处于方法区中,方法区的一部分
  2. 堆:存放用new产生的数据
  3. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象)
  4. 寄存器:我们在程序中无法控制

4. 根据代码,判断结果

1. 下面代码中,两个数是否交换成功?

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println("输出交换前的两个数:"+a+"---"+b);
        changeNum(a,b);
        System.out.println("输出交换后两个数"+a+"---"+b);
    }
    public static void changeNum(int num1,int num2){
        int t;
        t = num1;
        num1 = num2;
        num2 = t;
    }
}
  1. a和b的值交换没有成功,num1和num2的倒是换成功了
  2. 我们调用方法changeNum(a,b),此时传递的是值,将10和20传了过去给num1和num2
  3. changeNum()方法里面做的任何操作,和a、b已经没有任何关系了
  4. 最终输出的还是10和20
    java基础面试题----持续补充

5. 集合

1. Comparable 和 Comparator

  1. Comparabel接口(内部比较器),java.lang包下,有个一个compareTo(Obj)方法来排序,比较Obj和this(内部的自己)谁大
  2. Comparator接口(外部比较器),java.util包下,有一个compare(Obj1,Obj2)方法来排序,比较Obj1和Obj2谁大
  3. 一般对集合自定义排序,需要重写compareTo或compare方法,当我们需要对集合实现两种排序方式,比如一个song对象中歌名和歌手名分别采用一种排序方式的话,我们可以重写compareTo方法和使用自制的Comparator方法,或者两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort()
  4. 假设num1 = 10;num2=20;如何比较他俩谁大呢?num1-num2>0,表示num1>num2。如果=0,表示他俩一样大,如果<0表示num2比num1大
  5. 那么上面两个数比较当然好比较,但是换成对象呢?该如何比较两个对象谁大谁小呢?这就需要你用这两个接口为对象定制比较规则(同样,返回值>0,表示num1>num2…)
  1. 下面分别介绍Comparable和Comparator的用法,以及如何对集合正确排序
  1. Comparable需要让对象实现Comparable接口,集合排序时Collections.sort(list);会自动调用对象重写的compareTo()
  2. Comparator直接用匿名内部类实现即可,将两个需要排序的对象给它,集合排序,Collections.sort(list,new Comparator())即可
  1. Comparabel接口(内部比较器),实现更简单, Comparator接口(外部比较器)更灵活
import java.util.*;

public class Test implements Comparable<Test>{
    private Integer age;

    public Test(Integer age) {
        this.age = age;
    }

    /**
     * 返回一个int值,正数表示自己(this)比o大,0表示this=o2,负数表示this小于o2
     */
    @Override
    public int compareTo(Test o) {
        //根据年龄决定谁大
        return this.age-o.age;
    }

    @Override
    public String toString() {
        return "Test{" +
                "age=" + age +
                '}';
    }

    public static void main(String[] args) {
        Test test = new Test(1);
        Test test1 = new Test(2);
        ArrayList<Test> tests = new ArrayList<>();
        tests.add(test1);
        tests.add(test);
        System.out.println("list集合排序前"+Arrays.toString(tests.toArray())+"\n\n");
        System.out.println("==========Comparable排序===========");

        int i = test.compareTo(test1);
        System.out.println("Comparable:test和test1谁大(正数表示test大,0表示相等,负数表示test1大)"+i);
        Collections.sort(tests);
        System.out.println("list集合排序后"+Arrays.toString(tests.toArray())+"\n\n");


        System.out.println("==========Comparator排序===========");
        Comparator<Test> comparator = new Comparator<Test>() {

            /**
             * 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
             */
            @Override
            public int compare(Test o1, Test o2) {
                //根据年龄决定谁大
                return o2.age-o1.age;
            }
        };
        int compare = comparator.compare(test, test1);
        System.out.println("Comparator:test和test1谁大(正数表示test1大,0表示相等,负数表示test大)"+compare);
        Collections.sort(tests, new Comparator<Test>() {
            /**
             * 返回一个int值,正数表示o2>o1,0表示o1=o2,负数表示o2小于o1
             */
            @Override
            public int compare(Test o1, Test o2) {
                return o2.age-o1.age;
            }
        });
        System.out.println("list集合排序后"+Arrays.toString(tests.toArray()));
    }
}

1. ArrayList和LinkedList 的区别是什么?

  1. 数据结构不同:ArrayList是动态数组的数据结构,LinkedList是双向链表
  2. 随机访问效率不同:ArrayList随机访问效率更高,拿到下标直接取。LinkedList是线性数据存储方式,需要移动指针依次从前往后查找
  3. 增加和删除效率不同:非首尾增加和删除,LinkedList要比ArrayList效率高,ArrayList增删操作要影响数组内其它数据下标。往中间插入,需要其它数据向后移动。LinkedList直接改前驱和后缀
  4. 内存空间占用:LinkedList相较更费内存,除了存储数据,还要存储前驱和后缀的引用
  5. 线程安全:哥俩都不同步,都线程不安全
  6. 频繁读取数据,用ArrayList,插入,删除多,用LinkedList
上一篇:异常处理


下一篇:写一个计算器,要求实现加减乘除功能,并且能够循环接受新的数据,通过用户交互实现