java面试

本文深入讲解Java虚拟机、跨平台原理、JDK与JRE区别、static关键字含义、数据类型及自动拆装箱、方法覆盖与重载、构造方法、多继承、接口与抽象类区别、线程状态与同步机制、死锁预防、集合框架类等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

   Java是可以跨平台的编程语言,那我们首先得知道什么是平台,我们把CPU处理器与操作系统的整体叫平台。Java是解释执行的,编译为中间码的编译器与平台无关,编译生成的中间码也与平台无关(一次编译,到处运行),中间码再由解释器解释执行,解释器是与平台相关的,也就是不同的平台需要不同的解释器.
   java的跨平台不是java源程序的跨平台 ,如果是这样,那么所以语言都是跨平台的, java源程序先经过javac编译器编译成二进制的.class字节码文件(java的跨平台指的就是.class字节码文件的跨平台,.class字节码文件是与平台无关的),.class文件再运行在jvm上,java解释器(jvm的一部分)会将其解释成对应平台的机器码执行,所以java所谓的跨平台就是在不同平台上安装了不同的jvm,而在不同平台上生成的.class文件都是一样的,而.class文件再由对应平台的jvm解释成对应平台的机器码执行。 最后解释下机器码和字节码的区别: 一,机器码,完全依附硬件而存在~并且不同硬件由于内嵌指令集不同,即使相同的0 1代码 意思也可能是不同的~换句话说,根本不存在跨平台性~比如~不同型号的CPU,你给他个指令10001101,他们可能会解析为不同的结果~ 二,我们知道JAVA是跨平台的,为什么呢?因为他有一个jvm,不论哪种硬件,只要你装有jvm,那么他就认识这个JAVA字节码~~~~至于底层的机器码,咱不用管,有jvm搞定,他会把字节码再翻译成所在机器认识的机器码~~~

2.JDK和JRE的区别是什么?

   JRE:Java Runtime Environment

   JDK:Java Development Kit

   JRE顾名思义是java运行时环境,包含了java虚拟机,java基础类库,是使用java编写的程序运行所需要的软件环境,提供给想运行java程序的人使用的。

   JDK顾名思义是java开发工具包,是程序员使用java开发语言编写java程序所需要的开发工具包,是提供给程序员使用的,JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。

3.”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

   Static是静态的意思,可以用来修饰成员变量和成员函数,成员函数只能访问静态变量,不能访问非静态变量,静态变量是在类加载的时候绑定的,是静态绑定的,所以java中不可以覆盖static方法,类可以直接访问static方法,private修饰的方法只能在当前方法中使用,因此也不能被覆盖。

4.是否可以在static环境中访问非static变量?

   因为静态成员属于类,随着类的加载而加载到静态方法的内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态成员。

5.Java支持的数据类型有哪些?什么是自动拆装箱?

    java支持8中基本数据类型, byte short int long float double boolean char.自动拆箱就是把java的基本数据类型变成引用类型,这八种一次对应 Byte Short Integer Long Float Double Boolean Char,至于为什么要转换,这是因为引用类型可以new对象,可以调用java已经写好的方法。自动装箱就是把java的引用类型变成对应的基本数据类型。

6.Java中的方法覆盖(Overriding)和方法重载(Overload)是什么意思?

   方法重写的原则:

  • 重写方法的方法名称、参数列表必须与原方法的相同,返回类型可以相同也可以是原类型的子类型(从Java SE5开始支持)。
  • 重写方法不能比原方法访问性差(即访问权限不允许缩小)。
  • 重写方法不能比原方法抛出更多的异常。
  • 重写方法不能比原方法访问性差(即访问权限不允许缩小)。
  • 重写方法不能比原方法抛出更多的异常。
  • 被重写的方法不能是final类型,因为final修饰的方法是无法重写的。
  • 被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
  • 被重写的方法不能为static。如果父类中的方法为静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足重写条件,那么会发生编译错误;反之亦然。即使父类和子类中的方法都是静态的,并且满足重写条件,但是仍然不会发生重写,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
  • 重写是发生在运行时的,因为编译期编译器不知道并且没办法确定该去调用哪个方法,JVM会在代码运行的时候作出决定

    方法重载的原则:

  • 方法名称必须相同。
  • 参数列表必须不同(个数不同、或类型不同、参数类型排列顺序不同等)。
  • 方法的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为方法的重载。
  • 重载是发生在编译时的,因为编译器可以根据参数的类型来选择使用哪个方法。

   重载和重写的不同

   1.方法重写要求参数列表必须一致,而方法重载要求参数列表必须不一致

   2.方法重写要求返回的类型一致(或者子类型),而方法重载无要求

   3.方法重写用于子类继承父类的方法,而方法重载在同一类中的方法使用

   4.方法重写的对访问权限和抛出的异常有特殊的要求,而方法重载在这一方面没有任何的限制

   5.父类的一个方法只能被重写一次,而方法重载可以多次

   6.重写是运行时多态,重载是编译时多态

7.Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?

   构造方法是在类创建的时候被调用,每一个类都有构造方法,在程序员没有给类提供构造方法的前提下,java编译器会为这个类创建一个默认的构造方法。

   java中的构造方法重载是指一个类可以创建多个构造方法。每一个构造方法必须有他唯一的参数列表。

   java没有复制构造方法。

8.Java支持多继承么?

   Java中类不支持多继承,但是java中的接口支持多继承,即一个接口可以有多个父接口

9.接口和抽象类的区别是什么

   1.语法层面的区别

       1)抽象类可以提供成员方法的实现细节,而接口只能存在public static 方法

       2)抽象类的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的

       3)抽象类中可以有静态代码块和静态方法,而接口中不允许

       4)一个类只能继承一个抽象类,而一个类可以有多个接口

   2.设计层面上的区别

       1)抽象类是对事物的抽象,接口是对行为的抽象。抽象类是对整个类进行抽象,包括属性、行为,但是接口是对类局部进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

       2)设计层面不同,抽象类作为很多子类的父类,他是一种模板式设计。而接口是一种行为规范,是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

下面看一个网上流传最广泛的例子:门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:

1

2

3

4

abstract class Door {

    public abstract void open();

    public abstract void close();

}

  或者:

1

2

3

4

interface Door {

    public abstract void open();

    public abstract void close();

}

  但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:

  1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;

  2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。

  从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

interface Alram {

    void alarm();

}

 

abstract class Door {

    void open();

    void close();

}

 

class AlarmDoor extends Door implements Alarm {

    void oepn() {

      //....

    }

    void close() {

      //....

    }

    void alarm() {

      //....

    }

}

10.什么是值传递和引用传递

    一:搞清楚基本类型和引用类型的不同之处

    int num = 10;

    String str = "hello";

num 是基本类型,值就直接保存在变量中,而str是引用类型,保存的只是实际对象的地址。一般称这种对象为引用。

   二:搞清楚赋值运算符的作用

  num = 20;

  str = "java";

  对于基本类型num,赋值运算符会直接改变变量的值,原来的值被覆盖掉。

  对于引用类型str,赋值运算会改变引用中所保存到地址,原来的地址被覆盖掉。但是原来的对象不会变

   三:调用方法时发生了什么?参数传递基本上就是赋值操作

   第一个例子:基本类型(值没有改变,传值操作)

第二个例子:没有提供改变自身方法 的引用类型(值没有改变)

第三个例子:提供了改变自身方法的引用类型

图解

第四个例子:提供了改变自身方法 的引用类型,但是不使用,而是使用赋值运算符

图解:

11.进程和线程的区别是什么?

a.地址空间和其他资源:进程间相互独立,同一进程的各线程共享。某进程内的线程在其他进程不可见

b.通信:进程间的通信IPC,线程间可以直接读写进程数据段来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

c.调度和切换:线程上下文的切换比进程上下文的切换要快得多

d.在多线程os中,进程不是一个可执行的实体

12.创建线程有几种不同的方式?你喜欢哪一种?为什么?

    1.继承Thread类(真正意义上的线程类),是Runnable接口的实现。

    2.实现Runnable方法,并重写里面的run方法。

    3.使用Executor框架创建线程池。

    一般情况下,常见的是第二种。

    Runnable接口有以下好处:

    1.避免继承的局限,一个类可以继承多个接口。

    2.适合资源的共享

13.概括的解释下线程的几种可用状态

1. 新建( new ):新创建了一个线程对象。

2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。

3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。

4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:

(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。

(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。

(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。            当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。

5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

14.同步方法和同步代码块的区别是什么?

    争用条件

   为什么要使用同步,由上面的方法可以看出,当多个线程操作一个共享的资源变量时,将会导致数据不准确,相互产生冲突。

    1.同步方法

    即有synchronized关键字修饰的方法。 

    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 

    内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    2.同步代码块

   即有synchronized关键字修饰的语句块。 

    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    代码如: 

    synchronized(object){ 

    }

15.在监视器内部,是如何做到线程同步的?程序应该做到哪种级别的同步?

    在java虚拟机中,每个对象通过逻辑关联监视器,每个监视器和一个对象引用相关联,为了实现监视器的互斥功能,每个对象都关联这一把锁。

    一旦方法被synchronized修饰,那么这个部分就放入到了监视器的监视部分,确保一次只有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码。

   另外java还提供了显示监视器和隐式监视器两种方案。

16.什么是死锁

所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。死锁产生的4个必要条件:

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求

17.如何确保N个线程可以访问N个资源同时又不导致死锁?

死锁,基本就是资源不够,互相需要对方资源却不肯放弃自身资源。N线程访问N资源,为了避免死锁,可以为其加锁并指定获取锁的顺序,这样线程按照顺序加锁访问资源,依次使用依次释放,可以避免死锁。

18.Java集合框架类的基本接口有哪些?

总共有两大接口:Collection和Map,一个是元素集合,一个是键值对集合;其中List和Set接口继承了Collection接口,一个是有序元素的集合,一个是无序元素的集合;而ArrayList和linkedlist实现了List接口,Hashset实现了set接口;HashMap和HashTap实现了Map接口,并且HashTap是线程安全的,但是HashMap性能更好

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值