Java 知识总结 -- 基础2

Java 面试笔记总结 -- 基础2

以下内容总结于网络,仅供个人学习、复习使用,如有侵权,请告知删除🙏

1、面向对象和面向过程的区别?

​ 两者主要区别在于解决问题的方式不同。

​ 1、面向过程把解决问题的过程拆成一个一个的方法,通过一个个方法的执行解决问题

​ 2、面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。

面向对象开发的程序一般更易维护、易复用、易扩展。

2、成员变量与局部变量的区别?

​ 成员变量:定义在类中,方法之外。

​ 可以被public、private、static修饰。

​ 局部变量:定义在代码块或者方法之中。

​ 不可以被public、private、static修饰。

成员变量与局部变量区别
成员变量 局部变量
类中位置 类中,方法之外 方法之中
内存位置 堆内存 栈内存
生命周期 随着对象的创建而创建
随着对象的消失而消失
随着方法的调用而存在
随着方法的消失而消失
初始值 有默认初始值:
String是null,int是0
没有初始值,必须定义、
赋值之后才能使用

3、创建一个对象用什么运算符?对象实体和对象引用的区别

​ new运算符

​ new创建对象实例,对象实例在堆中,对象引用指向对象实体,对象引用在栈中

​ 一个对象引用可以指向0个或者一个对象。一个对象可以有 n 个引用指向它

4、对象相等和引用相等的区别

​ 对象相等比较的是对象的内容是否相等

​ 引用的相等 比较的是 他们指向的地址是否相等。(比较指向的是否是同一个对象)

5、类的构造方法的作用是什么?

​ 构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。

6、如果一个类没有声明构造方法,该程序能正确执行吗?

​ 如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。

7、构造方法有哪些特点?

  1. 名字与类名相同
  2. 没有返回值,但不能用void声明构造函数
  3. 生成的类的对象能自动执行,无需调用
  4. 构造方法不能被重写,但可以被重载。

8、面向对象的三大特征

​ 封装、继承、多态

​ 封装:把一个对象的属性隐藏在内部,不允许外部对象直接访问对象的内部信息。但是提供一些方法来操作属性。

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

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

​ 多态:一个对象具有的多种状态,不同的子类继承父类后 覆盖重写了 父类的同一个方法,产生了不同的行为。多态分为编译时多态、运行时多态。

​ 运行时多态有三个条件:

  1. 继承
  2. 重写
  3. 向上转型

​ 多态的特点:

  1. 对象类型和引用类型之间具有继承\实现关系
  2. 引用类型变量发出的方法调用的是哪个类中的方法,必须在运行期间才能确定

9、接口和抽象类

​ 共同点:

  1. 都不能被实例化
  2. 都可以包含抽象方法
  3. 都可以有默认的实现方法

​ 区别:

  1. 一个类只能及继承一个类,但是可以实现多个接口
  2. 接口用于对类的行为进行约束、抽象类用于代码复用
  3. 接口中的成员变量只能是public、static、final类型的,不能被修改且有初始值。抽象类的成员变量默认default,可以再子类中重新定义。

10、深拷贝和浅拷贝

​ 浅拷贝:浅拷贝会在堆上创建一个新的对象。如果原对象内部的属性是引用属性,浅拷贝会直接复制对象内部的引用地址。

​ 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

11、java常见类

Object:

​ Object是所有类的父类

/**
 * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
 */
public final native Class<?> getClass()
/**
 * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
 */
public native int hashCode()
/**
 * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
 */
public boolean equals(Object obj)
/**
 * naitive 方法,用于创建并返回当前对象的一份拷贝。
 */
protected native Object clone() throws CloneNotSupportedException
/**
 * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
 */
public String toString()
/**
 * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
 */
public final native void notify()
/**
 * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
 */
public final native void notifyAll()
/**
 * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
 */
public final native void wait(long timeout) throws InterruptedException
/**
 * 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
 */
public final void wait(long timeout, int nanos) throws InterruptedException
/**
 * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
 */
public final void wait() throws InterruptedException
/**
 * 实例被垃圾回收器回收的时候触发的操作
 */
protected void finalize() throws Throwable { }

12、== 和 equals()的区别

​ 对于基本数据类型:

  1. == 比较的是 值
  2. equals() 不能用于比较基本数据类型

​ 对于引用数据类型:

  1. == 比较的是对象的内存地址,是否引用同一个对象
  2. equals()判断两个对象是否相等

equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

//Object 类 equals() 方法:
public boolean equals(Object obj) {
     return (this == obj);
}

13、 hashCode() 有什么用?

​ 获取hash码(int 整数),这个哈希码的作用是确定该对象在哈希表中的索引位置。

​ hashcode相等,对象不一定相等。对象相等,hashcode一定相等。

14、为什么要有 hashcode?

		`hashCode()` 和 `equals()`都是用于比较两个对象是否相等。

15、为什么重写 equals() 时必须重写 hashCode() 方法?

​ 因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

​ 将对象放入到HashMapHashSet中时,有两个方法需要特别关心: hashCode()equals()hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到HashMapHashSet中,需要重写 hashCode()equals()方法。

String

16、String、StringBuffer、Stringbuilder

​ String是不可变得,StringBuffer、StringBuilder是可变的

String、StringBuffer 线程安全。StringBuilder线程不安全。

使用:

  1. 操作少量数据:String
  2. 单线程操作大量数据:StringBuilder
  3. 多线程操作大量数据:StringBuffer

17、String 为什么是不可变的?

  1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
  2. String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

18、Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?

​ 新版的String支持两种编码:Latin-1和UTF-16。如果字符串中的汉字没有超过Latin-1可表示的范围,那就默认使用Latin-1编码方案。Latin-1编码中 byte占1字节,char占用2字节。byte更省空间。如果字符串中的汉字超过了Latin-1可表示的范围,byte和char所占空间是一样的。(绝大部分字符串对象不超过Latin-1容纳范围)

19、高级for循环(在集合中的简写形式)

​ 格式:for(数据类型 变量名:被遍历的集合或者数组){}

​ 例:

String[] arr = {"he", "llo", "world"};
StringBuilder s = new StringBuilder();
for (String value : arr) {
    s.append(value);
}

=======
 for(int i=0;i<arr2.length;i++){
  s2.append(arr2[i]);
}   

20、字符串拼接用“+” 还是 StringBuilder?

  1. ​ 字符串通过 + 拼接时,实际上是通过 StringBuilder 调用 append() 方法实现,拼接完成之后调用 toString()方法得到一个 String 对象。
  2. ​ 再循环体中使用 + 拼接字符串也能成功,但 会在循环体中 循环 创建多个StringBuilder 对象。浪费资源。如果直接使用 StringBuilder 对象进行字符串拼接,就不会存在这个问题。

21、String中equals() 和 Object中equals() 有何区别?

​ String中的equals()是被重写过得,是为了比较两个字符串的值是否相等,而Object中的equals方法比较的是两个对象的内存地址。

22、字符串常量池

​ 字符串常量池是 JVM 为了提升性能和减少内存消耗 对 String类开辟的一块区域,主要目的是为了避免字符串的重复创建

23、String s1 = new String("abc");这句话创建了几个字符串对象?

​ 会创建 1 或 2 个字符串对象。

​ 如果字符串常量池中不存在字符串对象“abc”的引用,那么会在堆中创建 2 个字符串对象“abc”。

String s1 = new String("abc");

​ 如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。

// 字符串常量池中已存在字符串对象“abc”的引用
String s1 = "abc";
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc");

24、inner()方法

​ 作用:将指定的字符串对象的引用保存在字符串常量池中,其分为两种情况:

  1. 如果字符串常量池中保存了对应的字符串对应的引用,直接返回该引用。

  2. 如果字符串常量池中没有保存该字符串对应的引用,那就在常量池中创建一个指向这个字符串对象的引用并返回。

    // 在堆中创建字符串对象”Java“
    // 将字符串对象”Java“的引用保存在字符串常量池中
    String s1 = "Java";
    // 直接返回字符串常量池中字符串对象”Java“对应的引用
    String s2 = s1.intern();
    // 会在堆中在单独创建一个字符串对象
    String s3 = new String("Java");
    // 直接返回字符串常量池中字符串对象”Java“对应的引用
    String s4 = s3.intern();
    // s1 和 s2 指向的是堆中的同一个对象
    System.out.println(s1 == s2); // true
    // s3 和 s4 指向的是堆中不同的对象
    System.out.println(s3 == s4); // false
    // s1 和 s4 指向的是堆中的同一个对象
    System.out.println(s1 == s4); //true
    

25、String 类型的变量和常量做“+”运算时发生了什么?

​ 不加 final 关键字拼接

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。

在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 常量折叠(Constant Folding) 的代码优化。

对于 String str3 = "str" + "ing"; 编译器会给你优化成 String str3 = "string";

并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:

  • 基本数据类型( bytebooleanshortcharintfloatlongdouble)以及字符串常量。
  • final 修饰的基本数据类型和字符串变量
  • 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )

引用的值在程序编译期是无法确定的,编译器无法对其进行优化。

对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。

字符串使用 final 关键字声明之后,可以让编译器当做常量来处理。

final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true

final 关键字修改之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。

如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。

final String str1 = "str";
final String str2 = getStr();
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 在堆上创建的新的对象
System.out.println(c == d);// false
public static String getStr() {
      return "ing";
}
posted @ 2022-05-27 19:17  大星星不见了  阅读(22)  评论(0编辑  收藏  举报