Java基础

1. Java面向对象编程三大特性:封装 集成 多态

1.1 封装

隐藏对象的属性和实现细节,仅对外公开访问方法,控制程序中属性的读和写的访问级别。

1.2 继承

在一个现有类的基础之上,增加新的方法或重写已有方法,从而产生一个新类。

关于继承如下3点:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类的私有属性和方法子类是无法访问的,只是拥有
  2. 子类可以拥有自己属性和方法,既子类可以对父类进行扩展
  3. 子类可以用自己的方式实现父类的方法(重写)

1.3 多态

对象在不同时刻表现出来的不同状态。在编译时并不能确定,只有在运行期间才能决定

所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,既一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定

1.3.1 Java 中实现多态的方式

  1. 继承:多个子类对同一方法的重写
  2. 接口:实现接口并覆盖接口的统一方法

2. 构造器 Constructor 是否被override

再讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以Constructor 也就不能被 override(重写),但是可以overload(重载),所以你可以看到一个类中有多个构造函数情况

3. 重载和重写的区别

  • 重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
  • 重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类方法访问修饰符为private则子类就不能重写该方法

4. String、 StringBuffer 和 StringBuilder 的区别是什么?String为什么是不可变的?

  1. 可变性

    • String 对象是不可变

      String类中使用final 关键字修饰字符数组来保存字符串private final char value[]所以String 对象是不可变的

    • StringBuilder 与 StringBuffer 是可变的

      StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,而AbstractStringBuilder 中也是使用字符数组保存字符串char[] value但是没有用final关键字修饰,所以这两种对象都是可变的

      AbstractStringBuilder.java 的源码

      abstract class AbstractStringBuilder implements Appendable, CharSequence {
          char[] value;
          int count;
          AbstractStringBuilder() {
          }
          AbstractStringBuilder(int capacity) {
              value = new char[capacity];
          }
      
  2. 线程安全性

    • String 线程安全

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

    • AbstractStringBuilder的子类

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

      • StringBuffer 线程安全

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

      • StringBuilder 非线程安全

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

  3. 性能

    1. String(少修改,性能好)

      每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象

    2. StringBuffer(线程安全,性能比StringBuilder差一些)

      StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用

    3. StringBuilder (非线程安全,比StringBuffer性能好一些)

      相同情况下使用StringBuild相比StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险

5. 自动装箱与拆箱

  • 装箱:将基本类型用他们对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本数据类型

6. 在一个静态方法内调用一个非静态成员为什么是非法的?

由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量的成员

7. 在java中定义一个不做事且没有参数的构造方法的作用?

主要是用来解决,父类只定义了有参构造函数,子类又没有用super来调用父类特定的构造方法导致的异常

java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误。因为java 程序在父类中找不到没有参数的构造方法可供执行,解决办法是在父类里加上一个不做事且没有参数的构造方法

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

  1. 在方法上
    1. 接口的方法默认是public,所有方法在接口中不能有实现(java8开始接口可以有默认实现)
    2. 而抽象类可以有非抽象的方法
  2. 变量
    • 接口中除了static、final变量,不能有其他变量
    • 而抽象类则不一定
  3. 访问修饰符上
    • 接口方法默认修饰符是public
    • 抽象类可有其他修饰符
  4. 从设计层面
    • 接口是对行为的抽象,是一种行为规范
    • 抽象是对类的抽象,是一种模板设计

9. 成员变量与局部变量的区别有哪里?

  1. 从语法形式上看
    • 成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数
    • 成员变量可以被public、privite、static 等等修饰符修饰,而局部变量不能被访问修饰符以及static所修饰
    • 成员变量和局部变量都能被final所修饰
  2. 从变量在内存中存储方式来看

    • 如果成员变量使用static修饰的,那么这个成员变量是属于类的,如果没有使用static 修饰,这个成员变量是属于实例的。而对象存在与堆内存(也就是成员变量存在堆),局部变量存在与栈内存
  3. 从变量在内存中生存时间上看

    • 成员变量是对象的一部分,他随着对象创建而存在
    • 而局部变量随着方法的调用而自动消失
  4. 成员变量如果没有被赋初始值:

    • 这会自动以类型的默认值而复制(一种情况例外,被final修饰的成员变量也必须显式赋值)

    • 而局部变量不会自动赋值

10. 一个类的构造方法的作用是什么,若一个类没有声明构造方法,该程序能正常执行吗?为什么?

主要作用是完成对类对象的初始化工作。可以执行,因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法

11. 构造方法有哪些特点

  1. 名字与类名相同
  2. 没有返回值
  3. 生成类的对象时自动执行,无需调用

12. 静态方法和实例方法有何不同

  1. 在外部调用静态方法时,可以使用“类名.方法名”的方式,也可以使用“对象名.方法名”的形式,而实例方法只有后面的这种方式,也就是说,调用静态方法可以无需创建对象
  2. 静态方法在访问本来的成员时,只允许访问静态成员(既静态成员变量和静态方法),而不允许访问实例成员变量和实例方法,实例方法则无此限制

13. 对象的相等与指向他们的引用相等,两者有什么不同

  • 对象相等:比的是内存中存放的内容是否相等
  • 引用相等,比较的是他们指向的内存地址是否相等

14. == 与 equals(常问)

  • == :

    他的作用是判断两个对象的地址是不是相等。既,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)

  • equals():

    他的作用也是判断两个对象是否相等。但他一般有两种使用情况

    • 情况1:类没有覆盖equals()方法

      则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象

    • 情况2:类覆盖了equals()方法

      一般如果我们覆盖equals()方法来比较两个对象的内容是否相等。若他们的内容相等,则返回true

15 hashCode 与 equals(重要)

面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”

15.1 hashCode()介绍

hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整型。这个哈希码的作用是确定该对象在哈希表中的索引位置

hashCode()定义在JDK的Object.java 中,这就意味着java中的任何类都包含有hashCode()函数

散列表存储的键值对(key-value),他的特点是:能根据“键”快速的检索出对应的“值”,这其中就利用到了散列码(可以快速找到需要的对象)

15.2 为什么要有hashCode

以hashMap,HashSet为代表的hash是怎么检查重复的呢?

  1. 当你把对象加入 HashSet 时,hashSet 会先计算对象的hashCode来判断对象加入的位置
  2. 同时会与其他已经加入的对象的hashCode值做比较
    1. 如果没有相符的hashCode,就假设对象没有重复出现
    2. 如果发现相同的hashCode 值的对象,就调用equals()方法来检查是否真的相同。
    3. 如果相同就不会让他插入成功,如果不同的话就会让他重新散列到其他位置

结论

hashCode() 的作用就是获取哈希码,也称散列码,它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode 在散列表中才有用,在其他情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置

15. hashCode()与equals()的相关规定

  1. 如果两个对象相等,则hashCode一定也是相同的
  2. 两个对象相等,对两个对象分别调用equals 方法都返回true
  3. 两个对象有相同的hashCode值,他们也不一定是相等的
  4. 因此,equals方法被覆盖过,则hashCode 方法也必须被覆盖
  5. hashCode() 的默认行为是对堆上的对象产生独特值,如果没有重写hashCode()则该class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

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

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

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

results matching ""

    No results matching ""