Java面试题扫盲记录(持续更新中~)

1.JDK JRE JVM

1.1三者的基本概念可以概括如下:
  • JDK(Java Development Kit)是一个开发工具包,是Java开发环境的核心组件,并且提供编译、调试和运行一个Java程序所需要的所有工具,可执行文件和二进制文件,是一个平台特定的软件。
  • JRE(Java Runtime Environment)是指Java运行时环境,是JVM的实现,提供了运行Java程序的平台。JRE包含了JVM,但是不包含Java编译器/调试器之类的开发工具。
  • JJVM(Java Virtual Machine)是指Java虚拟机,当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。
1.2区别与联系:

 JDK是开发工具包,用来开发Java程序,而JRE是Java的运行时环境
 JDK和JRE中都包含了JVM
 JVM是Java编程的核心,独立于硬件和操作系统,具有平台无关性,而这也是Java程序可以一次编写,多处执行的原因。

2. 面向对象的几大特性? 多态怎么理解?

 面向对象是一种思想,可以将复杂问题简单化,让我们从执行者变为了指挥者。面向对象的三大特性为:封装,继承与多态。

  • 封装:将事物封装成一个类,减少耦合,隐藏细节。保留特定的接口与外界联系,当接口内部发生改变时,不会影响外部调用方。

  • 继承: 从一个已知的类中派生出一个新的类,新类可以拥有已知类的行为和属性,并且可以通过覆盖/重写来增强已知类的能力。

  • 多态: 多态的本质就是一个程序中存在多个同名的不同方法,主要通过三种方式来实现:
      通过子类对父类的覆盖来实现
      通过在一个类中对方法的重载来实现
      通过将子类对象作为父类对象使用来实现

2.1如何通过将子类对象作为父类对象使用来实现多态?

 把不同的子类对象都当作父类对象来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。这样操作之后,父类的对象就可以根据当前赋值给它的子类对象的特性以不同的方式运作。
 对象的引用型变量具有多态性,因为一个引用型变量可以指向不同形式的对象
向上转型:
 子类对象转为父类,父类可以是接口。
 公式:Father f = new Son(); Father是父类或接口,Son是子类。
向下转型:
 父类对象转为子类。公式:Son s = (Son) f;
【转型总结】
1.什么时候使用向上转型呢?
   提高程序的扩展性,不关注子类类型(子类类型被隐藏)
需要用子类的的特有方法吗?不需要,向上转型
2.什么时候使用向下转型呢?
    需要使用子类型的特有方法时
   但是一定要使用instenceof进行类型的判断。避免发生ClassCastException
【多态成员调用的特点】

  • 对于成员变量和静态函数,编译和运行都看左边

  • 对于成员变量和静态函数,编译和运行都看左边

  • 对于成员函数。编译看左边,运行看右边

3.修饰符:public;protect;default;private优先级

  • public:Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package)访问。
  • protect:介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
  • default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访问。
  • private:Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。

4. static方法是否能重写?

  •    在Java中,如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
  •    具体原因:在Java中我们静态方法的的选择是编译期就选择好的,是编译器自动根据声明类型帮我们选择的,它不依赖与任何对象。所以说从语义就可以看出static、final、private方法本身都是编译期绑定的(也叫前期绑定)这些方法不存在多态,他们是在还没有运行的时候,程序在编译器里面就知道该调用哪个类的哪个方法了,而其他可观察的普通方法的绑定是在运行的时候根据具体的对象决定的,因为从语义上看这些方法是可被继承的,有了多态而造成了不确定性。

5.Java语言的平台无关性是怎样实现的?

  1. JVM屏蔽了操作系统和底层硬件的差异
  2. Java面向JVM编程,先编译生成字节码文件,然后交给JVM解释成机器码执行
  3. Java的语言规范中规定了基本数据类型的取值范围和行为在各个平台上是保持一致的。

6.Java语言是编译型还是解释型语言?

   Java的执行经历了编译和解释的过程,是一种先编译,后解释执行的语言,不可以单纯归到编译性或者解释性语言的类别中。

7.接口和抽象类的区别。(重点)

抽象类和接口的主要区别可以总结如下。

  1.    抽象类中可以没有抽象方法,也可以抽象方法和非抽象方法共存 .
  2.    接口中的方法在JDK8之前只能是抽象的,JDK8版本开始提供了接口中方法的default实现
  3.     抽象类和类一样是单继承的;接口可以实现多个父接口 抽象类中可以存在普通的成员变量;接口中的变量必须是static final类型的,必须被初始化,接口中只有常量,没有变量。
7.1抽象类和接口应该如何选择?分别在什么情况下使用呢?

.    根据抽象类和接口的不同之处,当我们仅仅需要定义一些抽象方法而不需要其余额外的具体方法或者变量的时候,我们可以使用接口。反之,则需要使用抽象类,因为抽象类中可以有非抽象方法和变量。

7.2关于接口的默认方法

   当一个类实现该接口时,可以继承到该接口中的默认方法。
   如果两个接口中存在同样的默认方法,实现类继承的是哪一个呢?这个时候,实现类那里会编译错误,大概意思就是说:有两个相同的方法,编译器不知道该如何选择了。

7.3如果实现的接口中有多个相同的默认方法该怎么办呢?

两种处理方式,如下所示:

  • 重写多个接口中的相同的默认方法
  • 在实现类中指定要使用哪个接口中的默认方法
7.4那么JDK8中为什么会出现默认方法呢?

   使用接口,使得我们可以面向抽象编程,但是其有一个缺点就是当接口中有改动的时候,需要修改所有的实现类。在JDK8中,为了给已经存在的接口增加新的方法并且不影响已有的实现,所以引入了接口中的默认方法实现。
   默认方法允许在不打破现有继承体系的基础上改进接口,解决了接口的修改与现有的实现不兼容的问题。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。在我们实际开发中,接口的默认方法应该谨慎使用,因为在复杂的继承体系中,默认方法可能引起歧义和编译错误。

8.什么是值传递和引用传递,区别及应用?

  • 值传递:意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。在方法调用中,实参会把它的值初始化一个临时的存储单元。因此形参与实参虽然有着相同的值,但是却有着不同的存储单元
  • 引用传递:意味着传递的并不是实际的对象,而是意味着传递了对象的一个对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。在方法调用中,传递的是对象(也可以看做是对象的地址),这时形参与实参的对象执行同一块存储单元,因此对形参的修改就会影响实参的值。
  • 注意:在Java语言中,原始数据类型在传递参数时都是按值传递的,而包装类型在传递参数时都是按引用传递的。

9.String类能否被继承?

   类被final关键字修饰,为不可变类,不能被继承。
    String类在JDK源码中的定义:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

10.为什么要用final来修饰String类?

  1. 为了实现字符串池
        字符串存储的位置和其他类型的常量不一样,字符串专门有个字符串池,也只有用final修饰才能实现字符串池。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  2. 为了线程安全
       因为字符串是不可变的,所以是线程安全的,同一个字符串实例可以被多个线程共享。
  3. 为了提高效率
       因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

11.讲讲final修饰符?

   final用于声明属性,方法和类。分别表示属性不可变,方法不可覆盖,类不可继承

  • . final属性:被final修饰的变量不可变。但是Java中的变量不可变有两种,一种是值不可变,另外一种是引用不可变。而final变量的不可变则是指的引用不可变,即它只能指向初始化时指向的那个对象,但是不关心对象内容是否变化。不过由于这个原因,final变量必须背初始化。
  • . final方法:当一个方法声明为final方法时,该方法不允许被任何子类进行重写。但是子类仍然可以使用这个方法。
  • . final参数:用来表示这个参数在函数内部不能被修改。
  • . final类:当一个类被声明为final时,此类不能被继承。其中的方法不能被重写。但是里面的成员变量可以被修改。如果不想其被修改,可以将其设置成final的。注意:一个类,不能既被修饰成abstract的,又被修饰成final的。

12.JDK中哪些类是不可被继承的?

  不能被继承的类是被final关键字修饰的类。一般比较基本的类型都是用final修饰的,这是为了防止扩展类无意间破坏原来方法的的实现。在JDK中,String,StringBuffer,StringBuilder等都是基本类型,都是不可被继承的。

13.说一下String,StringBuffer,StringBuilder的区别?

  1. 运行速度方面:StringBuilder>StringBuffer>String(String最慢原因:String为字符串常量,而另外两个均为字符串变量,即String对象一旦建立后对该对象是不可更改的。)
  2. 线程安全方面:StringBuffer是线程安全的,StringBuilder是线程不安全的。
  3. 使用情景:String适用于少量的字符串操作的情况下,StringBuilder适用于单线程下在字符缓冲区进行大量操作情况下,StringBuffer适用于多线程下在字符缓冲区进行大量操作情况下。
  4. String是不可变类,而StringBuffer和StringBuilder是可变类。String对象一旦创建,其值就不能被修改,也因为这个特性,其可以在共享场景中使用。StringBuffer和StringBuilder的对象被创建后,仍然可以被修改。
  5. 在实例化时,String可以通过构造函数进行实例化,也可以通过赋值方式进行初始化,而StringBuffer和StringBuilder只能通过构造函数进行初始化。

14.之前说StringBuffer是基本类型,不能被继承,为什么又说它是不可变类?

  因为StringBuffer的append方法的底层实现就是创建一个新的目标对象,然后将各个字符引用串接起来,这就是一个新的对象了,本质上就是栈多了一个变量,而堆上面并没有变化(为什么是在栈上呢?因为触发到的arraycopy方法是native的,也就是其生成的变量会放到本地方法栈上面去)。

  那为什么我们没有把StringBuffer划分为不可变类呢?它明明在栈中创建了一个新的对象。因为一般的对象和数组都会在堆中进行存储,除非是发生了栈上分配现象。我们相比较String对象的存储,就可以知道,StringBuffer对象在此处并不符合栈上分配的条件( 将线程私有的对象打散分配在栈上,可以在函数调用结束后自行销毁对象,不需要垃圾回收器的介入,有效避免垃圾回收带来的负面影响,栈上分配速度快,提高系统性能),所以我们可以知道,StringBuffer的append方法并不会在堆上创建新的StringBuffer对象且内容是结果字符串,而是在arraycopy方法的帮助下,将各个字符引用连接起来。因此StringBuffer是一个可变类。
  详细讲解:https://www.cnblogs.com/NYfor2018/p/10353257.html

15.Java中泛型的理解。

  1. 本质:自jdk1.5后,java通过泛型解决了容器类型安全这一问题。泛型的本质是参数化类型,就是将数据类型作为参数的一种语法。参数类型可以用在类、接口和方法的创建中,分别成为泛型类、泛型接口、泛型方法。
  2. 作用:使用泛型能写出更加灵活通用的代码;将代码安全性检查提前到编译期;能够省去强制类型转换。

16.谈谈Java序列化与反序列化的过程

  • 定义:java序列化是将java对象转换为字节序列的过程,java反序列化是指把字节序列恢复成java对象的过程。
  • 作用:作用:实现数据的持久化;可以实现远程通信;在进程间传递对象。
  • JDK类库中序列化的步骤:
      (1)创建一个对象输出流,它可以包装一个奇特类型的目标输出流,如文件输出流:
objectOutputStream oos=new objectOutputStream(new FileOutStream(c:\\object.out));

    (2)通过对象输出流writeObject()方法写对象:

((oos.writeObject(new a("xiaoxiao","145263","female"))

注意:所有要实现序列化的类都要继承Serializable接口。

  • JDK类库中反序列化的步骤
      (1)创建一个对象输入流,它可以包装一个其他类型输入流,如文件输入流:
objectInputStream ois=new ObjectInputStream(new FileInputStream("object.out"));

    (2)通过对象输出流的readObject()方法读取对象:

a aa=(a)ois.readObject();

    (3)为了正确读数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。

17.说一下覆盖和重载的区别

  1. 覆盖是子类和父类之间的关系,是垂直关系,而重载是一个类中各个方法之间的关系,是水平关系。
  2. 覆盖只能由一个方法或者一对方法产生关系;重载是多个方法之间的关系。
  3. 覆盖要求参数列表相同;重载要求参数列表不同。
  4. 覆盖关系中,调用方法体是根据对象的类型(对象对应存储空间的类型)来决定,而重载是根据调用时的实参列表与形参表来选择方法体的。

18.“==”、equals()和hashcode()有什么区别?

  • “==”用来比较两个变量的值是否相等。也就是说该运算符用来比较变量对应的内存中所存储的数值是否相等。要比较两个基本数据类型的数据或者两个引用变量是否相等只能用这个运算符。具体来说,如果比较的是两个引用变量,实际上比较的是两个对象是否指向同一块内存。而如果要比较两个引用对象中的具体内容是否相等则是不能的。
  • equals()是Object类中的一个方法,每一个对象都具有这个方法。Object类中的这个方法是直接用 “==”方法对两个变量进行比较的。所以在没有覆盖此方法的时候,equals()比较的是两个对象的引用。它特殊的地方在于它可以被覆盖,以此来达成不同的比较效果。
  • hashcode()方法时从Object类中继承过来的,也用来鉴定两个对象是否相等。Object类中的hashcode()方法返回对象在内存中地址转换的一个int值,如果没有覆盖hashcode()方法,任何对象的hashcode()方法都是不相等的。
  • equals()方法与hashcode()方法的关系如下:两个对象的
    equals()方法返回值我true,则这两个对象的hashcode()方法的返回值也相等。而hashcode()方法的返回值不相等,则 equals()方法的返回结果一定为true。但是当hashcode()方法的返回值相等,quals()方法的返回结果不一定为true。

19.i++与++i的区别是什么?

  这是两种自增方式。它们的不同点在于:i++是在程序执行后进行自增,++i是在程序执行前进行自增。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值