Java基础知识面试题(面试题必备)

参考:感谢‍ThinkWon大神
原文链接:https://blog.csdn.net/ThinkWon/article/details/104390612
内容自己纯手打,对部分较冗长的答案做了提炼,加入了自己的见。

文章目录

1. Java概述

1.1 什么是编程

所谓编程就是人通过编写计算机可以认识的指令代码,计算机运行这些代码得到运行结果的过程就是编程。
为了使计算机能够理解人的意图,人类就必须将解决问题的思路,方法和手段通过计算机可以理解的形式告诉计算机(指令代码),计算机可以根据指令一步一步的去执行,完成某特殊的任务,这种人和计算机之间的交流就是编程。

1.2 谈谈你对Java的认识

Java是一门面向对象的编程语言,它不断吸收C++语言的各种优点,还摒弃了C++语言难以理解的多继承和指针等概念。Java语言具有<mark>功能强大和简单易用</mark>的两个特征,Java语言运行程序员以优雅的思维方式进行复杂的编程。

1.3 Java1.5之后的三大版本

  1. JavaSE:Java标准版本,允许开发和部署再桌面,服务器,嵌入式中使用Java程序
  2. JavaEE:Java企业版(J2EE的前身),帮助企业开发部署可移植性,安全性的服务端应用程序。
  3. JavaME:Java微型版(移动设备和嵌入式设备,如机顶盒,手机,打印机)

1.4 JVM、JRE和JDK的关系

  1. JVM(Java Virtaul Machine)指的就是Java虚拟机,Java程序需要运行再虚拟机上,不同的平台有不同的虚拟机,因此Java语言可以跨平台。
  2. JRE(Java Runtime Environment)指的是Java运行环境,包括Java虚拟机和核心类库,核心类库主要Java.lang,包含了Java程序必不可少的系统类,如基本数据类,字符串,线程,异常处理类等
  3. JDK(Java Development Kit) Java开发工具包,是提供给Java开发人员使用的,包含了jre环境和Java开发工具如java.exe,编译工具(javac.exe),打包工具(jar.exe)等

1.5 什么是跨平台性,原理是什么?

所谓Java的跨平台性指的是一次编译,可以在多个系统平台上运行。
实现原理:Java程序在虚拟机上运行,只要该系统可以安装相应的虚拟机,该系统就能运行Java程序。

1.6 Java语言的特点

  1. 简单易学,语法于C,C++很接近
  2. 面向对象(封装,继承,多态)
  3. 平台无关性(Java虚拟机实现)
  4. 健壮性:Java是强类型语言,支持异常处理,垃圾回收机制
  5. 支持多线程网络编程(为网络编程而诞生的)

1.7 什么是字节码,采用字节码的好处是什么?

字节码:Java源代码经过虚拟机编译后产生的(.class)文件,它不面向任何特定的处理器,只面向虚拟机。
采用字节码的好处:Java语言通过字节码的方式,解决了传统型解释型语言执行效率的问题,又保留了解释型语言可移植性好的特点,所以Java语言执行比较高效,而且字节码不针对指定机器,所以Java程序实现了一次编译,可以在多个计算机上运行。
Java程序运行经历的阶段

Java源代码--->编译器--->jvm可执行的Java字节码(即虚拟指令)--->jvm--->jvm解释器--->机器可执行的二进制机器码--->程序

1.8 Java的主类是什么?Java应用程序和Java小程序有什么区别?

主类:一个Java程序可以又有多个类,但是只能有一个主类,它指的是含有main()方法的类。
Java应用程序与Java小程序有什么区别
Java应用程序的主类不一定是public修饰,含有main()方法的类,是程序的入口。
Java小程序指的的主类是继承系统类JApplet和Applet的类,必须是public修饰,小程序没有main方法,主要是嵌入到浏览器的页面中,调用init(),或run()来启动,例如网页嵌入小游戏。

1.9 Java和C++有什么区别?

相同点

  1. 都是面向对象语言,都支持封装,继承,多态
  2. 都是强类型语言

不同点

  1. Java相比C++没有指针的概念,增加了GC避免了内存泄漏问题,更加安全。
  2. Java类只支持单继承,不支持多继承,而C++支持多继承,虽然Java类不支多继承,而接口支持多继承。
  3. Java有异常捕获机制用于捕获异常,增加了程序的容错能力,而C++没有。
  4. 内存分配:Java使用JVM对内存进行扫描,将长期未使用的空间进行回收再利用,使得资源得到充分利用,内存管理交给了JVM管理维护,避免了因内存泄漏等问题导致系统崩溃。C++使用new和delete来分配和释放内存,需要程序员自己来维护。

1.10 Oracle JDK 和 OpenJDK 的对比

  1. 稳定性:Oracle JDK 的稳定性要优于 OpenJDK 。
  2. 更新周期:Oracle JDK 每三年发布一次,OpenJDK 没三个月发布一次。
  3. 开源:OpenJDK 完全开源,Oracle JDK并不是完全开源。

2. Java基础语法

2.1 Java数据类型

2.1.1 Java有哪些数据类型

  1. 数据类型:Java是强类型的语言,对每一种数据都定义了具体的数据类型,在内存中分配了不同大小的空间。
  2. Java有8大类基本数据类型
    数值:整数:int、byte、short、long ;小数:float、double、
    字符:char
    布尔:boolean

    注意:Java中boolea 所占的字节数并没有给出精确的定义,Java虚拟机给出的是4个字节,存储类型为int型,而boolea型数组存储的是每个1个字节。
  3. Java有三大引用数据类型
    (class)
    接口(interface)
    数组([])

2.1.2 switch(expr)语句,expr有那些类型

JDK5之前,expr只能是int,byte,short,char。
JDK5开始,expr可以支持枚举类型,enum
JDK7开始,expr可以支持String,Integer

2.1.3 2*8最有效率的方法

左移:2 <<< 3 相当于2*2^3
右移:2 >>> 3 相当于2/(2^3)

2.1.4 Math.round(expr)函数,Math.round(11.5)和Math.round(-11.5)分别等于多少

Math.round:表示四舍五入函数,它的原理时参数+0.5,再向下取整。

Math.round(11.5) ---> 11.5 + 0.5 = 12 -->向下取整 = 12
Math.round(-11.5) ---> -11.5 + 0.5 = -11 -->向下取整 = -11

补充两个函数:

Math.ceil():向上取整函数
Math.floor():向下取整函数

2.1.4 float i = 3.4;是否正确?


可见无法将双精度的3.4直接转成float,因为这可能会丢失精度,double(8个字节)—> float(4个字节)
解决方法

  float i = 3.4F;
  float a = (float)3.4;

2.1.5 short s1 = 1,s1 = s1+1与s1+=1,哪一个可以编译通过?


可见s1 = s1 +1;右边s1+1会自动转成高精度的int,此时显然不能转成低精度的short类型,故编译不能通过。
s1 += 1,会自动进行隐式的强制类型转换,s1 = (short)(s1+1);因此可以编译通过。

2.2 Java编码

Java语言采用的时Unicode编码标准,Unicode编码为每个字符定义了一个唯一的数值,因此在任何的平台上都可以放心使用。

2.3 Java注释

单行注释:
//
多行注释:
/**/
文档注释:
/**/
注意:多行注释和文档注释不能嵌套使用。

2.4 访问修饰符

protected、private、public、默认

2.4 运算符&和&&的区别

&运算符两种用法:按位与
按位与:有0的0,全1为1,例如:0&0 = 0,1&0 = 0,0&1=0,1&1=1
&&运算符:逻辑与或又称短路与运算,也是两边都为真(1),运算结果才为真(1),但是逻辑与&&会引起短路(左边表达式为false,停止判断右边的表达式这是和按位与两者最大的区别)

        int a = 1, b = 2,c = 3;
        if(a > 4 && (b = 3)>5){ 
            System.out.println("&&");
        }
        System.out.println(b); //2

        if(a > 4 & (b = 3)>5){ 
            System.out.println("&");
        }
        System.out.println(b);  //3

2.4 关键字

2.4.1 java中有没有goto

Java中有goto,但是被当作保留字了,所谓保留字指的是已经被Java定义下来的字了,不能被我们当作变量来使用。goto会降低程序的可读性,Java使用continue+break就可以做到。

2.4.2 final修饰类,方法,属性

final修饰类,这个类则不能被继承
final修饰方法,这个类则不能被重写(覆盖)

final修饰属性,这个属性时是不变的,也是不能改变的,变量常用大写字母表示。

2.4.3 final,finally,finalize()之间的区别

  1. final可以修饰类,方法,属性,修饰类时类不能被继承,修饰方法时方法不能被重写,修饰属性时,该属性不能被重新赋值。
  2. finally通常与trty-catch代码块配合使用,在处理异常时,通常将必须要执行的代码放到finally中,表示是否出现异常都必须执行该部分代码,常用是否资源。
  3. finalize():Object类的一个方法,Object类是所有类的父类,当我们使用System.gc()时,垃圾回收器就会去调用finalize()进行垃圾对象的回收。

2.4.4 this关键字

this关键字指的是当前对象的指针,或代表将来创建的对象,4个使用场景:

  1. 普通方法中使用,可以用来获取成员变量。
  2. 在普通方法中使用,可以用来调用其它方法,this.可以被省略。
  3. 可以在构造器使用this来区分局部变量和成员变量。
  4. 可以在构造器中调用其它构造器,但必须写在第一行。

注意:<mark>this关键字不可以出现了static修饰的方法中</mark>,因为static修饰的变量和方法开始都和类在堆的方法区中被初始化,而这个区域中而对象还没被创建,当然对象也不会被创建这在这个地方。

2.4.5 super关键字

super关键字指向自己父对象的一个指针,这个父对象是离自己最近的一个父对象,2个使用场景

  1. 普通方法中,super可以调用父类的成员变量和方法,用于与子类区分
  2. 在子类构造器中可以使用super调用父类构造器,而且也必须写在第一行。

注意:子类的构造器必须调用父类的构造器,没有父类的构造器就会报错。另外,在构造器中,this和super不能同时出现在第一行,换句话说,只能使用其一。
至于为什么super和this只能放构造器的第一行,主要将对象间的属性关系削弱,简单的理解这么规定就是Java官方提醒我们,这样写容易造成可读性变差,维护起来比较难。(了解)

2.4.6 super和this的区别

  1. super指向父类的引用,可以调用父类的构造器,方法和变量
  2. this指向当前对象的引用,可以调用当前对象的构造器,方法和变量
  3. 在构造器中,this和super只能出现其一,并且都只能出现在第一行。
  4. this在本类的构造器中调用本类的其它构造器,而super在子类中调用父类的构造器
  5. this和super都指向对象,因此都不能出现在静态区中,包括static修饰的方法,变量,static块中。
  6. 从本质出发,this是一个指向当前对象的引用,有当前对象的地址值,可以直接输出this,而super只是一个关键字,它只可以调用父类的方法和变量,并不能输出super关键字。

2.4.7 static关键字

static修饰的方法或变量,可以不用创建对象,使用类直接调用,这样就可以做到不依赖于具体的对象实现某一功能。
static关键字还有一个比较关键性的作用:用静态代码快来优化程序性能。static代码块可以放在类的任何位置中,一个类中也可以有多个静态代码块,在类被初次加载后,会按照静态代码快的顺序来依次指向下来,并且每个静态代码块只会被执行一次,常用来初始化一些操作。

2.4.8 static关键字的独特之处

  1. 被static修饰的变量和方法是独立于该类的任何对象的,也就是说,不属于任何一个具体实例对象,而是被类的实例所共享的,换句话说,类的任何对象都可以调用它,并改变它。
  2. 类第一次被加载后,静态属性/方法/代码块就被加载到了方法区(存储静态代码和类信息),可以先初始化,还可以后面对其赋值。
  3. 静态变量在类加载的时候分配空间,以后创建类不会再分配了,但可以在(方法区)任意赋值。
  4. 静态只能访问静态,非静态可以访问非静态,也可以访问静态,用于方法和属性之间共享数据。

2.4.9 static应用场景

因为static是被类的实例对象所共享,所以<mark>如果某个成员变量是被所有对象所共享,那么它就应该设置为静态变量</mark>
比较常见的应用场景:

  1. 修饰成员变量
  2. 修饰成员方法
  3. 静态代码块
  4. 静态内部类
  5. 静态导包

2.5 流程控制语句

2.5.1 continue,break,return的区别

continue:遇到continue跳出当前循环,继续下一次循环。
break:遇到break跳出当前循环,不继续下一次循环。
return:用于方法的终止,结束当前方法,不会往下执行。

2.5 Java如何跳出多层循环

跳出多层循环可以在循环的外部定义一个标记,当循环执行到要跳出的条件时,使用break语句指定跳出的标记

    public static void main(String[] args) { 
        ok:
        for (int i = 0; i < 100; i++) { 
            for (int j = 0; j < 100; j++) { 
                i += j;
                if(j == 5){ 
                    break ok;
                }
                System.out.println(i);

            }
        }
        System.out.println("over----------");
    }

需要注意的时,标记ok:必须定义在循环语句的上方

3. 面向对象

3.1 什么是面向对象和面向过程有什么区别

3.1.1 面向过程

面向过程时具体化的,流程化的,解决问题你要一步一步的去分析,然后一步一步的实现。
优点:性能要由于面向对象,因为面向对象的类的调用之间需要实例化,开销很大。面向过程的应用场景有单片机,嵌入式开发,性能是最重要的因素。
缺点:不易维护,不易扩展。

3.1.2 面向对象

面向对象是模型化的,你只需要抽取出一个类,这个类可能是官方为我们实现好的,里面有你需要用的方法和解决问题的手段,不需要我们一步一步去实现,你只需要会用就可以了。
优点:易维护,易扩展。由于面向对象有封装,继承,多态的特性,可以设计出低耦合的系统,使得程序更加灵活,易于维护。
缺点:性能会比面向过程低。

3.1.3 区别

上面的优缺点就是他们主要区别,另外面向对象的底层实现还是面向过程,只不过将面向过程封装起来,便以我们使用的面向对象了。

3.2 面向对象的三大特性

3.2.1 封装

封装就是把一个对象的属性私有化,然后提供一些外部可以访问到属性的方法,如果不想外部访问,就可以不提供,但是如果一个类都没有外部可访问的方法和属性,那也就没什么意义了。

3.2.2 继承

Java只支持单继承,继承是指在已存在基础类上建立新类的技术,这个新类可以定义新的方法和属性,可以重写父类的方法,也可以使用父类继承下来非private的方法和属性,通过继承我们可以更方面的复用以前的代码。

3.3.3 多态

多态是指同一种行为,具有不同的表现方式,换句话说,多态就是一个接口,使用不同的实例可以执行不同的操作。
Java多态其实就是父类只能使用子类的非静态成员方法,而如果想用子类所有的属性,就强转成子类就好了。
多态存在的三个前提:

1. 要有继承关系
2. 子类要重写父类的方法
3. 父类引用指向子类对象

3.3 类与接口

3.3.1 抽象类和接口

抽象:所谓抽象是对类对象的共同特征抽取出来构造类的过程,包括数据抽象和行为抽象,抽象只关注对象有哪些行为和属性,并不关注这些行为的细节。
抽象类和接口对比:抽象类是用来捕捉子类的通用特性,接口时抽象方法的集合,接口的方法都是抽象方法。从设计层面上看,抽象类是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为规范。
抽象类和接口异同点
<mark>相同点:</mark>

  • 接口和抽象类都不能被实例化
  • 接口和抽象类都位于继承的顶端,用于被其它类实现或继承
  • 都包含抽象方法,子类必须实现它们

<mark>不同点:</mark>

参数 抽象类 接口
声明 abstract interface
类中方法 可以是抽象方法可以不是,如果是抽象方法子类必须实现 都是抽象方法
类的变量 abstract类中可以不用初始化 interface必须初始化
实现 extends 如果子类不是抽象类的话,需要提供父类所有抽象方法的实现 implements 需要提供接口类所有抽象方法的实现
构造器 可以包含构造器 不可以包含构造器
访问修饰符 可以任意 接口默认是public,而且不能定义为private或protected
多继承 不支持多继承,只能继承一个抽象类 接口可以多实现

背: Java8接口提供了默认方法和静态方法,以此来减少抽象类和接口之间的差异性,现在我们已经可以为接口提供默认的实现方法,并且不再强制子类来实现它。
接口和抽象类的选取上遵循的原则:

  • 代码复用性高,又要为子类提供通用功能的,优先使用抽象类
  • 如果需要用到多继承考虑使用接口,功能也更强大。
  • 制定一套行为规范,子类必须去实现它,则考虑使用接口。

3.3.2 普通类和抽象类有什么区别

  • 普通类不能包含抽象方法,抽象类可以包含抽象方法,而且子类继承它就必须实现它的抽象方法
  • 抽象类不能被实例化,普通类可以

3.3.3 抽象类能不能用final修饰

不能,因为final修饰的类是不能被继承的,而抽象类的设计就是为了能被继承的。

3.3.4 创建对象使用什么关键字?对象实例和对象引用有什么区别?

创建对象使用new,将创建好的实例对象放在堆内存中,而对象的引用(地址)则放到栈内存中。可以有多个引用指向同一个实例对象,一个引用只能指向一个或0个实例对象。

3.3.5 Java创建对象有几种方式?

四种。分别是

1. new 关键字,最常用
2. 反射,调用java.lang.Class或者java.lang.reflect.Constructor的newInstance()方法
3. 调用对象的clone()方法
4. 反序列化,调用java.io.ObjectInputStream的readObject()方法创建对象

3.4 变量与方法

3.4.1 成员变量和局部变量有什么区别?

变量:在程序执行过程中,某个范围内它的值可以发生改变的量,本质上讲,变量其实是内存中的一块小区域。
成员变量:方法外部,类内部定义的变量
局部变量:方法内部

<mark>成员变量和局部变量区别</mark>

参数 成员变量 局部变量
作用域 整个类中 方法或语句块里{}
存储位置 随对象的存在而存在,放到堆内存中 存储在堆内存中,随方法的调用或语句块的执行存在,执行结束局部变量内存也就是自动释放
生命周期 随对象的存在而存在 方法的调用或语句块的执行存在,执行结束局部变量内存也就是自动释放
初始值 有初始值 没有初始值

变量使用原则:遵循就近原则,先寻找局部比较近的变量,没有再往远找。

3.4.2 Java中定义一个没有使用的无参构造的作用

作用体现在我们如果用到了继承,子类的构造函数会默认先调用父类的构造函数执行,使用语句super(),当然父类中如果只定义了有参构造,而子类也没有去调用它,则直接编译不通过。

3.4.3 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?

这是继承的一种规则,目的是为了先初始化的父类,以便子类可以使用父类的一些方法和属性进而来帮助初始化子类。

3.4.4 一个类的构造方法作用是什么?如果没有程序还能正常运行吗?

主要完成类对对象的初始化工作,如果没有的话可以运行,会默认加上一个无参构造。

3.4.5 构造方法有那些特性

  1. 没有返回值
  2. 方法名必须和类名一致
  3. 创建对象时自动调用,无需手动调用

3.4.6 静态变量和实例(普通)变量有什么区别?

静态变量:不属于任何一个实例对象,但共享于所有实例对象,属于类,在内存中只会有一份,类加载后JVM会为静态变量分配一块内存区域。
实例变量:属于实例对象,每次创建对象,都会为每个对象分配变量内存空间,实例变量属于实例对象,在内存中,有多少个对象就有多少个实例变量。

3.4.7 静态方法和实例(普通)方法的区别

外部方法调用上:可以以类名.静态方法名调用静态方法,也可以使用对象.静态方法名调用静态方法,而普通方法只能以对象.普通方法,所以调用静态方法可以不用创建对象。
内部方法调用上:在类中,静态方法内部不能调用非静态的方法/变量,普通方法可以调用静态/非静态 的方法/变量。

3.4.8 为什么静态方法调用非静态成员是非法的?

这还要从代码被加载进内存说起,静态方法和类信息随着类加载器的加载首先被加载到了堆内存的方法区中,注意这个时候还没有对象的存在。而非静态成员是属于对象的,如果没有对象是不能被调用的,因此不能再静态方法中使用非静态成员,必须使用对象调用才可以使用。

3.4.9 什么是返回值,返回值的作用是什么?

返回值是指我们获取某个方法体中代码执行后产生的结果,前提是该方法可以产生结果。作用是接收结果,用于其它的操作。

3.5 内部类

3.5.1 什么是内部类

在Java中,内部类就是定义在一个类中,和定义变量的方式一样,内部类本身就是一个类的属性。

3.5.2 内部类的分类

内部类可以分为:
成员内部类
局部内部类
匿名内部类
静态内部类

3.5.3 成员内部类

成员位置上的非静态内部类,就是成员内部类

public class TestInnerClass { 

    public static int radius = 1;
    public  String name  = "circle";

    public static void main(String[] args) { 
        TestInnerClass tc = new TestInnerClass();  //先创建外部类
        TestInnerClass.Inner inner =  tc.new Inner(); //再创建内部类
        inner.visit();
    }

     class Inner{ 
        public void visit(){ 
            System.out.println("visit outer static variable :" + radius);
            System.out.println("visit outer variable :" + name);
        }
    }
}

成员内部类内部可以访问外部类的静态和非静态变量/方法,需要主要的是成员内部类的对象创建方法,需要先创建外部对象再创建内部类对象。

3.5.4 局部内部类

定义在方法内部的类

public class Outer { 

    private  int out_a = 1;
    private static int STATIC_b = 2;

    public void testFunctionClass(){ 
        int inner_c =3;
        class Inner { 
            private void fun(){ 
                System.out.println(out_a);
                System.out.println(STATIC_b);
                System.out.println(inner_c);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
    public static void testStaticFunctionClass(){ 
        int d =3;
        class Inner { 
            private void fun(){ 
                // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
                System.out.println(STATIC_b);
                System.out.println(d);
            }
        }
        Inner  inner = new Inner();
        inner.fun();
    }
}

局部内部类在方法内部,只能访问外部类的静态变量和方法,创建方式在方法内部new 类名

3.5.5 匿名内部类

匿名内部类就是没有名字的内部类,在实际开发中用的比较多。

public class TestAnonymous { 

    public void test(){ 
        new Service() { 
            @Override
            public void method() { 
                System.out.println("匿名内部类");
            }
        }.method();
    }
}
//匿名内部类必须继承或实现某一接口
interface Service{ 
    void method();
}

匿名内部类有一下特点:

  1. 匿名内部类必须继承一个抽象或实现某一接口
  2. 匿名内部类中不能定义任何静态方法/成员
  3. 当所在的方法形参被匿名内部类使用时,必须声明为final
  4. 匿名内部类不能是抽象的,它必须实现继承的类或接口的所有抽象方法。

创建方式

new 抽象类/接口{
 匿名内部类实现
}

3.5.6 静态内部类

public class TestStaticInnerClass { 

    public static int radius = 1;
	public  String name  = "circle";
	
    public static void main(String[] args) { 
        TestStaticInnerClass.StaicInner inner = new StaicInner();
        TestStaticInnerClass.StaicInner inner2 = new TestStaticInnerClass.StaicInner();

        inner.visit();
    }

    public static class StaicInner{ 
        public void visit(){ 
      	  //System.out.println(name); 报错
            System.out.println("visit outer variable :" + radius);
        }
    }
}

可见静态内部类,只能访问外部类的静态成员/方法,创建静态内部类的方法
外部类.静态内部类 类名 = new 外部类.静态内部类()
外部类.静态内部类 类名 = new 静态内部类()

3.5.7 内部类的优点

  1. 内部类对象可以访问到外部类对象内容,包括私有数据
  2. 内部类不为同一包中的其它类所见,起到了很好的封装性
  3. 匿名内部类可以很方面的定义回调

3.5.8 内部类使用场景

  1. 当某个类除了它的外部类,不被其它类所使用时,可以考虑内部类
  2. 适当使用内部类,可以增加程序的灵活性和可扩展性
  3. 匿名内部类安卓中比较常用,如事件***

3.5.9 局部内部类和匿名内部类访问局部变量时,为什么要将局部变量定义为final

看例子:

我们如果没有对a进行修改,是可以正常输出a的,但是对a进行修改后,就出现了报错。

报错原因其实是生命周期不一致造成的,outMethod方法内的局部变量,a存储在栈中(如果是引用类型则对象存储在堆中),当程序执行到 inner.innerMethod();后a被销毁,但是内部类对它的引用还在,因此引用一个不存在的变量就会报错,而如果使用final修饰了,局部变量就会被加载到方法区的常量池中,不会被销毁。

3.5.10 内部类运行结果

public class Outer { 
    private int age = 12;

    class Inner { 
        private int age = 13;
        public void print() { 
            int age = 14;
            System.out.println("局部变量:" + age);  //14
            System.out.println("内部类变量:" + this.age);  //13
            System.out.println("外部类变量:" + Outer.this.age);  //12
         }
    }

    public static void main(String[] args) { 
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }

}

3.7 重写与重载

3.7.1构造器能否内重写

构造器是不能被继承的,因此,更不能被重写。

3.7.2 重写与重载的区别,重载的方法能否根据返回值进行区分(重要)

重载:使用在类中,方法与方法之间的重载指的是方法名相同,参数不同(参数类型,个数,顺序),与返回值无关,所以不能依据返回值来判断方法是否重载。
重写:出现在父子类之间,方法名,参数列表必须相同,子类的返回值必须<=父类的返回值类型(继承树),子类的访问修饰符必须>=父类,(private default protected public),如果父类的方法访问修饰符是private,则子类就不是重写了。

3.8 对象相等判断

3.8.1 ==与equals的区别是什么?

  1. == :作用是判断两个对象的地址值是否相等,即判断两个对象是否是同一个对象(基本数据类型是比较值,引用数据类型是比较地址)
  2. equals:作用是比较两个对象是否相等,有两种情况:
    • 情况一:如果此比较类型重写了Object的equals方法,则比较两个对象的内容是否相同。
    • 情况二:如果比较类型没有重写Object的equals方法,则仍是按照 == 进行比较
String a = new String("ab");
        String b = new String("ab");

        String c = "ab";
        String d = "ab";

        if(a == b){    //false
            System.out.println("a == b");
        }
        if(c == d){    //true
            System.out.println("c == d");
        }
        if(a.equals(b)){  //true
            System.out.println("a EQ b");
        }
        if(42 == 42.0){  //true
            System.out.println("42 == 42.0");
        }

<mark>说明:</mark>

  • String的equals方法是被重写过的,Object类的equals也是比较 == ,经过String的重写后只比较对象的内容是否相等,相等返回true,否则返回false.
  • 当创建String对象时,虚拟机会在常量池中寻找已经存在的值和要创建对象的值是否相同,如果能找到,就把引用赋值它,如果没有就重新创建一个对象放到常量池中。

关于String创建对象的独特之处参考:https://www.cnblogs.com/Young111/p/11273851.html

3.8.2 hashCode与equals(重要)?

<mark>注意此类问题容易扯到HashMap的底层原理</mark>
equals:方法是Object的方法,作用是比较两个对象是否是同一个对象
hashCode:方法也是Object的方法,返回值是返回一个哈希码,这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode与equals之间的关系
两个对象,A,B 则A.equals(B) 返回true,现在看主要看它是否重写equals,如果没有重写,此时它们的hashCode一定相等,否则可能不等(可以通过重写hashcode可以让其相等)。
两个对象的hashCode相等,两个对象不一定相等。
两个对象的hashCode不相等,equals一定不等。

<mark>面试官可能又会深挖你一个问题?为什么重写equals的方法时必须也要重写hashCode方法呢?</mark>

  1. 可以提前校验两个对象是否相等,避免每个都调用equals方法,影响效率,好处再很多地方都有体现,如hashmap。
  2. Java api规范要求重写hashcode,重写之后可以保证两个对象的hashcode的值相同。

3.8.3 对象相等与指向它们的引用相等有和区别?

对象相等指的是内存中的内容是否相等,即对比对象是不是同一个。而引用相等指的是比较它们内存中的地址地址是否相等。

3.7 函数参数传值

3.7.1 为什么Java只有值传递?

值传递的重要特征是在内存中拷贝了一份副本内容进行传递,因此对于Java,基本数据类型传值传的是拷贝完副本给形参,由于传递的是副本,所以不会引起原数据的改变。而引用类型传的是在内存中拷贝的一个内存地址过去,此时实际参数指向的地址和原对象的引用指向了同一个对象,它们的改变是会互相影响的。

3.7.2 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递

仍然是值传递,上面已经说的很清楚了,在Java中,判断是不是值传递只要看传递过去的值是否在内存中拷贝了一份副本内容进行传递,如果是就是值传递,显然,对象的引用传递也在内存中copy了一个副本地址进行传递,副本改变属性值,是会影响到原对象的属性值。

3.7.3 引用传值和值传递有什么区别

值传递:值传递的重要特征是在内存中拷贝了一份副本内容进行传递,因此对于Java,基本数据类型传值传的是拷贝完副本给形参,由于传递的是副本,所以不会引起原数据的改变。
引用传递:引用类型传的是在内存中拷贝的一个内存地址过去,此时实际参数指向的地址和原对象的引用指向了同一个对象,它们的改变是会互相影响的。

3.8 Java包

3.8.1 JDK中常用的包有哪些

  • java.lang:系统基础类
  • java.io:与输入输出有关的包,常用于文件操作
  • java.net:网络编程相关
  • java.util:系统辅助类,特别是集合类
  • java.sql:操作数据库库相关的

3.8.2 import java和javax有什么区别

刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。

3.9 Java IO流

3.9.1 Java IO有哪几种

  • 按照流的流向:输入流/输出流
  • 按照操作单元:字节流/字符流
  • 按照流的角色:节点流/处理流

3.9.2 BIO,NIO,AIO有什么区别?

  • BIO:Block IO 同步阻塞IO,就是我们平时使用的传统IO,特点是简单使用方面,并发处理能力低。
  • NIO:Non IO 同步非阻塞IO,客户端与服务的通过(channal)通道通讯,实现多路复用。
  • AIO:Asynchronous IO ,是NIO的升级版本,实现了异步的非堵塞IO,,异步IO的操作基于事件和回调机制。

3.9.3 File有哪些常用的方法

  • exists:判断文件路径是否存在
  • createFile:创建文件
  • createDirtory:创建文件夹
  • delete():删除一个文件或目录
  • copy():复制文件
  • size():查看文件个数
  • read():读取文件
  • write():写入文件

3.10 反射

3.10.1 什么是Java的反射

反射就是在程序的运行中,将类的各个组成部分封装成对象,对于任意的对象,我们可以调用的它的方法和属性,这种获取动态信息以及动态调用对象的功能称之为反射。

补充两个小知识点:
静态编译:编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象,反射用到。

3.10.2 Java的反射的优缺点

优点:可以在运行过程中操作这些对象,可以解耦代码,提高代码灵活度和可扩展性。
缺点:性能会降低,反射相当于一系列解释的操作,通知jvm要做的是,性能比之间的Java代码要慢

3.10.3 获取Class的三种方式

1. Class.forName(“类的权限路径”)
2. 通过对象.getClass
3. 通过类名.class

4. 常用的API

4.1 String相关

4.1.1 字符型常量和字符串常量的区别,什么是字符串常量池

形式上:字符常量定义用的是单引号,字符串常量用的是双引号
含义上:字符常量相当于一个整形的ascii值,可以参与表达式运算,字符串常量代表一个内存地址
占内存大小:字符常量只占一个字节,而字符串常量占若干个字节,至少有一个结束的字符 \n

字符串常量池
字符串常量池位于堆内存中,专门用于存储字符串常量,可以提高内存使用效率,避免多块内存空间存储相同的字符串,在创建字符串时,jvm会先在常量池中寻找有没有存在的字符串常量,如果找到,就将引用赋值过去,如果没找到则实例化一个字符串放进常量池,并返回引用。

4.1.2 String有哪些常用的方法

  • indexOf:返回字符串中字符的索引位置
  • equals:比较两个字符串是否内容相等
  • trim:删除字符串两端的空白
  • replace:字符串的替换,替换后返回,原来值没有变化
  • split:以某一条件分割字符串,返回字符串数组
  • length():返回字符串长度
  • charAt:返回指定索引的字符
  • toLowerCase/toUpperCase:将字符串转成小写/大写字符
  • substring:截取字符串

4.1.3 String是基本数据类型吗

不是基本数据类型,基本数据类型有8类,分别是int、short、long、byte、double、float、char、booean;剩下的都是引用类型,jdk 1.5引入的枚举也是引用类型。

4.1.4 String为什么是不可变的?真的是不可变吗

不可变!
String内部维护这一个final修饰的字符数组

/** The value is used for character storage. */
private final char value[];

肯定不可变!此时可能面试官会甩出一道题

  String str = "Hello";
  System.out.println(str);  //Word
  //System.out.println(str.hashCode()); 69609650
  str = str + " Word" ;
  System.out.println(str);   //Hello Word
  //System.out.println(str.hashCode()); 387817944

然后说这不是变了吗?
其实这并不是String变了,而是str指向的引用变了,开始时在在常量池中创建了”Hello”对象并将引用返回给str,str = str + ” Word” ;后str的引用值被str + ” Word”所覆盖,换句话说,只是引用地址发生改变了。开始时str指向的”Hello”并没有发生变化,所以说String还是不变的!

4.1.5 是否可以继承String

String 类被final修饰,不可以被继承

4.1.6 String str = “i” 与String str = new String(“i”);

这两种创建对象并不一样,String str = “i”对象只会被创建一次,而String str = new String(“i”);对象创建了两次。内存的分配位置也不同,String str = “i”创建的对象会被JVM放到常量池中,而String str = new String(“i”)常量池和堆内存中各有一个

4.1.7 String str = new String(“i”);创建了几次对象

两次对象,一个是常量池中的i,一个是堆内存中的i

4.1.8 String有没有length(),数组有没有

数组中有length属性,而String没有,但是String有length()方法,注意JavaScript中获取字符串长度有length

4.1.9 在是用HashMap时,用Strig做key有什么好处

HashMap内部的实现时通过key来确定value的值,当我们的key为String时,因为String是不变的,所以它的hashcode只需要算一次就被缓存下来了,这相比其它对象要快。

4.1.10 String的StringBuff和StringBuilder有什么区别

可变性:String内部由一个private final char value[]维护着,所以它是不可变的。StringBuff和StringBuilder都继承了AbstractStringBuilder类,AbstractStringBuilder类也是用字符数组保存字符串的,但是它是 char[] value,即字符串是可变的。
线程安全性:String对象是不可变的,也可以理解为常量,因此是线程安全的。AbstractStringBuilder是StringBuilder和StringBuff的公共父类,定义了一些公共的方法,如insert,append,indexOf,expandCapacity等方法,但是StringBuffer对其加了同步锁,所以是线程安全的,而StringBuilder并没有对方法加同步锁,所以是非线程安全的。
性能:每一次对String操作时,都会使它指向一块新的内存空间,生成一个新的String对象,StringBuffer每一次对StringBuffer对象操作,不会生成新的对象并改变引用。相同情况下,使用StringBuilder会比StringBuffer提高性能10%-15%,但是要冒着线程不安全的风险

4.1.11 实现字符串反转

方法有很多,介绍3个

  1. 逆序遍历
String str = "liuzeyu";
for (int i = str.length()-1; i >= 0; i--) { 
        System.out.println(str.charAt(i));
}
  1. StringBuider或StringBuff的reverse方法
    使用StringBuider和StringBuff的reverse方法即可
  2. 借助栈先进后出的思想
String str = "liuzeyu";
            Stack<Character> stack = new Stack();
            for (int i = 0; i < str.length(); i++) { 
                    stack.push(str.charAt(i));
            }
            while (stack.size() != 0){ 
                  System.out.println(stack.pop());
            }

4.2 包装类相关

4.2.1 自动拆箱和自动装箱

装箱:将基本类型封装成引用类型
拆箱:将引用类型(包装类型)转换成基本类型

4.2.2 int与integer有什么区别

int是基本类型,属于Java 中8大基本类型的一种。
integer属于引用类型的包装类,jdk 5之后引入了自动拆箱和自动装箱
int的包装类是integer
常见的基本类型对应的包装类

基本类型 int char boolean byte double long short float
包装类 Ingeter Character Boolean Byte Double Long Short Float

4.2.3 integer a = 127与integer b = 127相等吗

会相等,因此当整形的值位于 -128~127之间,Integer可以自动拆箱和装箱成功,原理时直接引用常量池中的Integer对象,不会去重写new,但是如果不在这个返回内,两对象就是不同对象。

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1);  //false

    Integer c1 = 127;
    Integer d1 = 127;
    System.out.println(c1 == d1);  //true

Intger的创建对象原理和String的类似:

   Integer a = new Integer(3);
   Integer b = 3;
   int c = 3;
   System.out.println( a == b); //false
   System.out.println( c == b); //true
   System.out.println( c == a); //true