欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Java面试】——常见面试题汇总

程序员文章站 2024-03-23 13:46:46
...

JDK和JRE是什么?

JDK 是 Java 开发工具包,是 Java 开发环境的核心组件,并提供编译、调试和运行一个 Java 程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件。

JRE 是 Java 运行时环境,是 JVM 的实施实现,提供了运行 Java 程序的平台。JRE 包含了 JVM,但是不包含 Java 编译器 / 调试器之类的开发工具。

JVM 是 Java 虚拟机,当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理 / 垃圾回收和安全机制等。

这种独立于硬件和操作系统,正是 Java 程序可以一次编写多处执行的原因。

区别:

  1. JDK 用于开发,JRE 用于运行 Java 程序;
  2. JDK 和 JRE 中都包含 JVM;
  3. JVM 是 Java 编程语言的核心并且具有平*立性。

 

switch支持的类型

以java8为准,switch支持10种类型 

基本类型:byte, char, short ,int 

对于包装类 :Byte, Short, Character, Integer ,String, enum 

 

  1. 实际只支持int类型 Java实际只能支持int类型的switch语句,那其他的类型时如何支持的 a、基本类型byte char short 原因:这些基本数字类型可自动向上转为int, 实际还是用的int。 b、基本类型包装类Byte,Short,Character,Integer 原因:java的自动拆箱机制 可看这些对象自动转为基本类型 c、String 类型 原因:实际switch比较的string.hashCode值,它是一个int类型 如何实现的,网上例子很多。此处不表。 d、enum类型 原因 :实际比较的是enum的ordinal值(表示枚举值的顺序),它也是一个int类型 所以也可以说 switch语句只支持int类型

 

数组定义

一维数组:

    三种定义方法:

    1.String [] arr = new String[4];

    2.String [] arr = new String[]{"a","b","c","d"};

       注意这种写法->String [] arr = new String[4]{"a","b","c","d"}; 编译会不通过

    3.String [] arr = {"a","b","c","d"};

     ps:至于[]在变量名arr前还是后,这个写法没有固定的要求,但是最好按照书上的标准来。

二维数组:

    int [][] a = new int[3][4];             //3行4列

    int [][] a = new int[3][];               //定义时行数不可省略,列数可省略

   ps:至于[][]a这三个元素其实可以作全排列,不论怎么排都不影响最后的编译结果,写法没有固定的要求,但是最好按照书上的标准来。   

1. 定义一维数组时,必须显式指明数组的长度; 

2. 定义n维数组时,其一维数组的长度必须首先指明,其他维数组长度可以稍后指定; 

3. 采用给定值初始化数组时,不必指明长度; 

4. “[]” 是数组运算符的意思,在声明一个数组时,数组运算符可以放在数据类型与变量之间,也可以放在变量之后。

【Java面试】——常见面试题汇总

 

 

重载和重写

重写:

(1)发生在父类与子类之间

(2)方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同

(3)访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)

(4)重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

重载(Overload)

在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同

则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。

重载 总结:

(1)重载Overload是一个类中多态性的一种表现

(2)重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)

(3)重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准

 

双亲委派机制:

【Java面试】——常见面试题汇总

优势:

  1. 避免类的重复加载
  2. .保护程序安全,防止核心API被随意修改

启动类加载器可以抢在标准扩展类装载器之前去装载类,而标准扩展类装载器可以抢在类路径加载器之前去装载那个类,类路径装载 器又可以抢在自定义类加载器之前去加载它。所以Java虚拟机先从最可信的Java核心API查找类型,这是为了防止不可靠的类扮演被信任的类,试想一 下,网络上有个名叫java.lang.Integer的类,它是某个黑客为了想混进java.lang包所起的名字,实际上里面含有恶意代码,但是这种 伎俩在双亲模式加载体系结构下是行不通的,因为网络类加载器在加载它的时候,它首先调用双亲类加载器,这样一直向上委托,直到启动类加载器,而启动类加载 器在核心Java API里发现了这个名字的类,所以它就直接加载Java核心API的java.lang.Integer类,然后将这个类返回,所以自始自终网络上的 java.lang.Integer的类是不会被加载的。

   3.保证核心API包的访问权限

但是如果这个移动代码不是去试图替换一个被信任的类(就是前面说的那种情况),而是想在一个被信任的包中插入一个全新的类型,情况会怎样呢?比如一个名为 java.lang.Virus的类,经过双亲委托模式,最终类装载器试图从网络上下载这个类,因为网络类装载器的双亲们都没有这个类(当然没有了,因为 是病毒嘛)。假设成功下载了这个类,那你肯定会想,Virus和lang下的其他类痛在java.lang包下,暗示这个类是Java API的一部分,那么是不是也拥有修改Java.lang包中数据的权限呢?答案当然不是,因为要取得访问和修改java.lang包中的权 限,java.lang.Virus和java.lang下其他类必须是属于同一个运行时包的,什么是运行时包?运行时包是指由同一个类装载器装载的、属 于同一个包的、多个类型的集合。考虑一下,java.lang.Virus和java.lang其他类是同一个类装载器装载的吗?不是 的!java.lang.Virus是由网络类装载器装载的!

 

Java反射机制

反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射机制的优缺点:

优点:

1)能够运行时动态获取类的实例,提高灵活性;

2)与动态编译结合

缺点:

1)使用反射性能较低,需要解析字节码,将内存中的对象进行解析。

2)相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)

 

 

wait和sleep的区别

·  wait可以指定时间,也可以不指定;而sleep必须指定时间。

·  wait释放cpu资源,同时释放锁;sleep释放cpu资源,但是不释放锁,所以易死锁。

· wait必须放在同步块或同步方法中,而sleep可以再任意位置

 

sleep和yield的区别

【Java面试】——常见面试题汇总

【Java面试】——常见面试题汇总

 

下述两种方法分别创建了几个 Sring 对象?

// 第一种:直接赋一个字面量String str1 = "ABCD";

// 第二种:通过构造器创建String str2 = new String("ABCD");

解析:考察的是对 String 对象和 JVM 内存划分的知识。

答:String str1 = "ABCD";最多创建一个String对象,最少不创建String对象.如果常量池中,存在”ABCD”,那么str1直接引用,此时不创建String对象.否则,先在常量池先创建”ABCD”内存空间,再引用.

String str2 = new String("ABCD");最多创建两个String对象,至少创建一个String对象。new关键字绝对会在堆空间创建一块新的内存区域,所以至少创建一个String对象。

我们来看图理解一下:

【Java面试】——常见面试题汇总

  • 当执行第一句话的时候,会在常量池中添加一个新的ABCD字符,str1指向常量池的ABCD
  • 当执行第二句话的时候,因为有new操作符,所以会在堆空间新开辟一块空间用来存储新的String对象,因为此时常量池中已经有了ABCD字符,所以堆中的String对象指向常量池中的ABCD,而str2则指向堆空间中的String对象。

【Java面试】——常见面试题汇总

 【Java面试】——常见面试题汇总

 

String,Stringbuilder,Stringbuffer的区别

String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成新的String对象。

String: private final char value[];

每次+操作 : 隐式在堆上new了一个跟原字符串相同的StringBuilder对象,再调用append方法 拼接+后面的字符。

StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,从AbstractStringBuilder抽象类中我们可以看到

char[] value;

他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和StringBuilder来进行操作。 另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

 

 为什么要有包装类型?

术语:让基本类型也具有对象的特征

基本类型

包装器类型

boolean

Boolean

char

Character

int

Integer

byte

Byte

short

Short

long

Long

float

Float

double

Double

为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型)因为容器都是装object的,这是就需要这些基本类型的包装器类了。

自动装箱:new Integer(6);,底层调用:Integer.valueOf(6)

自动拆箱: int i = new Integer(6);,底层调用i.intValue();方法实现。

二者的区别:

声明方式不同:基本类型不使用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间

存储方式及位置不同:基本类型是直接将变量值存储在栈中,而包装类型是将对象放在堆中,然后通过引用来使用;

初始值不同:基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null;

使用方式不同:基本类型直接赋值直接使用就好,而包装类型在集合如Collection、Map时会使用到。

 

final有哪些用法?

(1)被final修饰的类不可以被继承

(2)被final修饰的方法不可以被重写

(3)被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.

(4)被final修饰的方法,JVM会尝试将其内联,以提高运行效率

(5)被final修饰的常量,在编译阶段会存入常量池中.

【Java面试】——常见面试题汇总

 

Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。

 

Java中IO流

Java 中 IO 流分为几种?

(1)按照流的流向分,可以分为输入流和输出流

(2)按照操作单元划分,可以划分为字节流和字符流

(3)按照流的角色划分为节点流和处理流。

InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

【Java面试】——常见面试题汇总

【Java面试】——常见面试题汇总 

 

==和equals区别

==较的是两个引用在内存中指向的是不是同一对象(即同一内存空间),也就是说在内存空间中的存储位置是否一致。如果两个对象的引用相同时(指向同一对象时),“==”操作符返回true,否则返回flase。

equals用来比较某些特征是否一样。我们平时用的String类等的equals方法都是重写后的,实现比较两个对象的内容是否相等

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。

我们来看看String重写的equals方法:

它不止判断了内存地址,还增加了字符串是否相同的比较。

public 

public boolean equals(Object anObject) {
    //判断内存地址是否相同
    if (this == anObject) {
        return true;
    }
    // 判断参数类型是否是String类型
    if (anObject instanceof String) {
        // 强转
        String anotherString = (String)anObject;
        int n = value.length;
        // 判断两个字符串长度是否相等
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 一一比较 字符是否相同
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

 

 

 

GET和POST的区别

【Java面试】——常见面试题汇总

  • GET在浏览器回退时是无害的,而POST会再次提交请求。
  • GET产生的URL地址可以被Bookmark,而POST不可以。
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。
  • GET请求只能进行url编码,而POST支持多种编码方式。
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
  • GET请求在URL中传送的参数是有长度限制的,而POST么有。
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  • GET参数通过URL传递,POST放在Request body中。

参考文章:get和post其实是没有区别的

 

事务的四大特性(ACID)

 

原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;

一致性(Consistency): 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

 原子性是指事务的原子性操作,对数据的修改要么全部执行成功,要么全部失败,实现事务的原子性,是基于日志的Redo/Undo机制。

 一致性是指执行事务前后的状态要一致,可以理解为数据一致性。隔离性侧重指事务之间相互隔离,不受影响,这个与事务设置的隔离级别有密切的关系。

 持久性则是指在一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、更新将会持久化到书库中。

原子性、隔离性、持久性都是为了保障一致性而存在的,一致性也是最终的目的。
 

【Java面试】——常见面试题汇总

什么是 ORM 框架?

对象-关系映射(Object-Relational Mapping,简称ORM),面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。

 

说说 sleep() 方法和 wait() 方法区别和共同点?

两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 。

两者都可以暂停线程的执行。

Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。

wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。

 

并行和并发有什么区别?

并行是指两个或者多个事件在同一时刻发生;

并发是指两个或多个事件在同一时间间隔发生。

并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。

在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。

所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

并发:线程轮流使用cpu

 

threadlocal

线程本地存储区(Thread Local Storage 简称为TLS)每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

 

使用线程池的好处:

 

1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

 

String, StringBuffer,StringBuilder的区别

java中String、StringBuffer、StringBuilder是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题。现在总结一下,看看他们的不同与相同。

1.可变与不可变

String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。

private final char value[];

      String 为不可变对象,一旦被创建,就不能修改它的值. . 对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。

char[] value;

     StringBuffer:是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象 , 它只能通过构造函数来建立,  如: StringBuffer sb = new StringBuffer();

不能通过赋值符号对他进行付值. , 如 sb = "welcome to here!";//error

对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer中赋值的时候可以通过它的append方法.      sb.append("hello");

2.是否多线程安全

String中的对象是不可变的,也就可以理解为常量, 显然线程安全 。

AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。

StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是 线程安全的 。

StringBuilder并没有对方法进行加同步锁,所以是 非线程安全的 。

 3.StringBuilder与StringBuffer共同点

StringBuilder与StringBuffer有公共父类AbstractStringBuilder( 抽象类 )。

StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。

最后,如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。

效率比较String < StringBuffer < StringBuilder,但是在String S1 =“This is only a”+“simple”+“test”时,String效率最高。

 

被final修饰的变量必须要被初始化

那初始化的方式有:

1、定义时初始化;

2、final 成员变量可以在初始化块中初始化,但是不可在静态初始化块中初始化,只能是静态的final成员变量才能在静态初始化块中初始化

3、在类的构造器中初始化,但是静态final成员变量是不能在构造函数是初始化的。

 

Java程序初始化顺序:

 

 

1)初始化父类静态变量、静态代码块 


2)初始化子类静态变量、静态代码块 


3)初始化父类普通成员变量、代码块、执行父类构造方法 


4)初始化子类成员变量、代码块、子类构造方法

——————————————————————————————————————————————

父类静态成员变量 父类静态代码块

子类静态成员变量 子类静态代码块

父类非静态成员变量,父类非静态代码块,

父类构造函数

子类非静态成员变量,子类非静态代码块,

子类构造函数

 

Object 类中方法及说明如下:

registerNatives()   //私有方法

getClass()    //返回此 Object 的运行类。
hashCode()    //用于获取对象的哈希值。
equals(Object obj)     //用于确认两个对象是否“相同”。
clone()    //创建并返回此对象的一个副本。
toString()   //返回该对象的字符串表示。   
notify()    //唤醒在此对象监视器上等待的单个线程。   
notifyAll()     //唤醒在此对象监视器上等待的所有线程。   
wait(long timeout)    //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或        者超过指定的时间量前,导致当前线程等待。   
wait(long timeout, int nanos)    //在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
wait()    //用于让当前线程失去操作权限,当前线程进入等待序列
finalize()    //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

Java IO

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。

  • 节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader.
  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

JAVA常用的节点流:

  • 文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流。
  • 字符串 StringReader StringWriter 对字符串进行处理的节点流。
  • 数 组 ByteArrayInputStream ByteArrayOutputStreamCharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。
  • 管 道 PipedInputStream PipedOutputStream PipedReaderPipedWriter对管道进行处理的节点流。

常用处理流(关闭处理流使用关闭里面的节点流)

  • 缓冲流:BufferedInputStrean BufferedOutputStream BufferedReader BufferedWriter  增加缓冲功能,避免频繁读写硬盘。
  • 转换流:InputStreamReader OutputStreamReader 实现字节流和字符流之间的转换。
  • 数据流 DataInputStream DataOutputStream  等-提供将基础数据类型写入到文件中,或者读取出来.

流的关闭顺序

  1. 一般情况下是:先打开的后关闭,后打开的先关闭
  2. 另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
  3. 可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。

 

 

数组转换为List集合:Arrays.asList()

String[] myArray = { "Apple", "Banana", "Orange" }; 
List<String> myList = Arrays.asList(myArray); 

//上面两个语句等价于下面一条语句 

List<String> myList = Arrays.asList("Apple","Banana", "Orange");

 

Arrays.asList()将数组转换为集合后,底层其实还是数组, 
【Java面试】——常见面试题汇总

 

传递的数组必须是对象数组,而不是基本类型。

Arrays.asList()是泛型方法,传入的对象必须是对象数组。

当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身!

我们使用包装类型数组就可以解决这个问题。

Integer[] myArray = { 1, 2, 3 };Copy to clipboardErrorCopied

使用集合的修改方法:add()remove()clear()会抛出异常。

List myList = Arrays.asList(1, 2, 3);

myList.add(4);//运行时报错:UnsupportedOperationException

myList.remove(1);//运行时报错:UnsupportedOperationException

myList.clear();//运行时报错:UnsupportedOperationExceptionCopy to clipboardErrorCopied

Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。

如何正确的把数组转换成ArrayList?

  1. 1.List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    
    
    2.
    
    //JDK1.5+
      static <T> List<T> arrayToList(final T[] array) {
    
      final List<T> l = new ArrayList<T>(array.length);
    
      for (final T s : array) {
    
        l.add(s);
    
      }
    
      return (l);
    }