当前位置:   article > 正文

【Java基础知识总结 | 第四篇】Java基础重要知识(面试题)

【Java基础知识总结 | 第四篇】Java基础重要知识(面试题)

在这里插入图片描述

4.Java基础重要知识(面试题)

4.1基本数据类型

4.1.1 八种基本数据类型

Java 中有 8 种基本数据类型,分别为:

  • 6 种数字类型:
    • 4 种整数型:byteshortintlong
    • 2 种浮点型:floatdouble
  • 1 种字符类型:char
  • 1 种布尔型:boolean

这 8 种基本数据类型的默认值以及所占空间的大小如下:

基本类型位数字节默认值取值范围
byte810-128 ~ 127
short1620-32768(-2^15) ~ 32767(2^15 - 1)
int3240-2147483648 ~ 2147483647
long6480L-9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1)
float3240f1.4E-45 ~ 3.4028235E38
double6480d4.9E-324 ~ 1.7976931348623157E308
char162‘u0000’0 ~ 65535(2^16 - 1)
boolean1falsetrue、false

4.1.2 基本类型和包装类型的区别

基本类型 vs 包装类型

  1. 用途
    • 除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量
    • 包装类型可用于泛型,而基本类型不可以。
  2. 存储方式
    • 基本数据类型的 局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆
    • 包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
  3. 占用空间
    • 相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小
  4. 默认值
    • 成员变量包装类型不赋值就是 null
    • 基本类型有默认值且不是 null
  5. 比较方式
    • 对于基本数据类型来说,== 比较的是值
    • 对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法
  • 注意注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆中
public class Test {
    // 成员变量,存放在堆中
    int a = 10;
    // 被 static 修饰,也存放在堆中,但属于类,不属于对象
    // JDK1.7 静态变量从永久代移动了 Java 堆中
    static int b = 20;

    public void method() {
        // 局部变量,存放在栈中
        int c = 30;
        static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4.1.3包装类型的缓存机制

  1. Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据
  2. Character 创建了数值在 [0,127] 范围的缓存数据
  3. Boolean 直接返回 True or False
  4. 如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
  • 举例一:
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true

Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false

Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 举例二:注意新建包装类型对象
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);

//Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象
//结果为fasle
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4.1.4自动装箱和拆箱?原理?

(1)定义
  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;
(2)原理
  • 举例
Integer i = 10;  //装箱
int n = i;   //拆箱
  • 1
  • 2
  • 字节码文件:
   L1

    LINENUMBER 8 L1

    ALOAD 0

    BIPUSH 10

    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;

    PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;

   L2

    LINENUMBER 9 L2

    ALOAD 0

    ALOAD 0

    GETFIELD AutoBoxTest.i : Ljava/lang/Integer;

    INVOKEVIRTUAL java/lang/Integer.intValue ()I

    PUTFIELD AutoBoxTest.n : I

    RETURN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  1. 装箱其实就是调用了 ==包装类的valueOf()==方法
  2. 拆箱其实就是调用了 xxxValue()方法
  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

4.2变量

4.2.1成员变量与局部变量的区别

成员变量 vs 局部变量

  1. 语法形式

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

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

    • 成员变量是对象的一部分,它随着对象的创建而存在
    • 局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡
  4. 默认值:从变量是否有默认值来看

    • 成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:final 修饰的成员变量也必须显式地赋值
    • 而局部变量则不会自动赋值。

4.2.2静态变量的作用

  1. 静态变量也就是被 static 关键字修饰的变量。它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量
  2. 静态变量只会被分配一次内存,即使创建多个对象,这样可以节省内存。
  3. 静态变量是通过类名来访问的,例如StaticVariableExample.staticVar(如果被 private关键字修饰就无法这样访问了)。
public class StaticVariableExample {
    // 静态变量
    public static int staticVar = 0;
}
  • 1
  • 2
  • 3
  • 4

通常情况下,静态变量会被 final 关键字修饰成为常量。

public class ConstantVariableExample {
    // 常量
    public static final int constantVar = 0;
}
  • 1
  • 2
  • 3
  • 4

4.3面向对象基础

4.3.1面向对象三大特征

封装、继承、多态

(1)封装
  1. 封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性
  2. 举例:我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。
(2)继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能

核心点:

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

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

4.3.2辨别接口和抽象类的异同

(1)共同点
  1. 都不能被实例化。
  2. 都可以包含抽象方法。
  3. 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)
(2)不同点
  1. 单继承、多实现

  2. 成员变量修饰符不同、赋值不同:

    • 接口中的成员变量只能是 public static final 类型 的,不能被修改且必须有初始值
    • 抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值
  3. 用途不同:

    • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。
    • 抽象类主要用于代码复用,强调的是所属关系。

4.3.3辨别深拷贝、浅拷贝、引用拷贝

  • 浅拷贝
    • 浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点)
    • 如果原对象内部的属性是 引用类型 的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
(1)浅拷贝

举例说明浅拷贝,实现Cloneable接口,并重写clone()方法,直接调用父类Obejct的clone()方法

public class Address implements Cloneable{
    private String name;
    // 省略构造函数、Getter&Setter方法
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

public class Person implements Cloneable {
    private Address address;
    // 省略构造函数、Getter&Setter方法
    @Override
    public Person clone() {
        try {
            Person person = (Person) super.clone();
            return person;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 测试:
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// true
System.out.println(person1.getAddress() == person1Copy.getAddress());
  • 1
  • 2
  • 3
  • 4

从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。

(2)深拷贝

这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

@Override
public Person clone() {
    try {
        Person person = (Person) super.clone();
        person.setAddress(person.getAddress().clone());
        return person;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 测试:
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// false
System.out.println(person1.getAddress() == person1Copy.getAddress());
  • 1
  • 2
  • 3
  • 4

从输出结构就可以看出,显然 person1的克隆对象和 person1 包含的 Address 对象已经是不同的了

(3)引用拷贝

简单来说,引用拷贝就是两个不同的引用指向同一个对象。

以下图来描述浅拷贝、深拷贝、引用拷贝:

浅拷贝、深拷贝、引用拷贝示意图

4.4 Object类

4.4.1==和equals的区别

==是运算符,equals是方法

(1)==
  • 若比较的对象是基本数据类型,则比较数值是否相等

  • 若比较的对象是引用数据类型,则比较的是对象的内存地址是否相等

  • 不管是比较基本数据类型,还是引用数据类型的变量,其比较的都是值,只是引用类型变量存的值是对象的地址。引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址。

(2)equals

equals方法比较的是对象的内容是否相同

equals()方法存在于Object类中,而Object类是所有类的父类。在Object类中定义了equals方法:

public boolean equals(Object obj) {
    return (this == obj);
}
  • 1
  • 2
  • 3
  • 如果类未重写equals方法

    调用equals时,会调用Object中的equals方法(实际使用的也是 == 操作符)

  • 如果类重写了equals方法

    调用equals时,会调用该类自己的equals方法(一般是比较对象的内容是否相同)。比如:

    • String:比较字符串内容是否相同
    • Integer:比较对应的基本数据类型int的值是否相同

4.5 String

4.5.1 String、StringBuffer、StringBuilder的区别

三者的区别可以从可变性、线程安全性、性能三个角度去分析:

  1. 可变性

    • String 是不可变的

    • StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用 finalprivate 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法

      abstract class AbstractStringBuilder implements Appendable, CharSequence {
          char[] value;
          public AbstractStringBuilder append(String str) {
              if (str == null)
                  return appendNull();
              int len = str.length();
              ensureCapacityInternal(count + len);
              str.getChars(0, len, value, count);
              count += len;
              return this;
          }
          //...
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
  2. 线程安全性

    • String 中的对象是不可变的,也就可以理解为常量,线程安全。
    • AbstractStringBuilderStringBuilderStringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacityappend、、insertindexOf 等公共方法。
      • StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
      • StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
  3. 性能

    • 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String
    • StringBuffer 每次都会StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
    • 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
  • 总结:
    • 操作少量的数据: 适用 String
    • 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
    • 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

4.5.2 String为什么是不可变的?

  1. String 类中使用 private 和 final 两个关键字修饰字符数组(作用:保存字符串)
  2. final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象
  3. 但是当数组存放的对象是引用类型时,这个数组保存的字符串的内容是可变的(虽然不能再指向其他对象但是可以修改里面对象的数据
  4. 真正不可变的原因:
    • 保存字符串的数组被 final 修饰且为私有的,并且String没有提供/暴露修改这个字符串的方法
    • String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
  //...
}
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/273831
推荐阅读
相关标签
  

闽ICP备14008679号