Java SE 的一些问题笔记

Java SE 的细节问题

什么是面向对象?谈谈你对面向对象的理解

面向对象思想:

将事物抽象成一个个类,可以帮助我们更加高效的去解决问题,代码将可重用,好维护,扩展性强。

面向对象的三大特征:

  • 封装
    类中不想向外部直接暴露的属性将其使用 private 修饰,如果需要操作被 private 修饰的属性,可对外提供对应的 setter/getter 方法即可。隐藏了对象的属性和实现细节,仅仅对外提供接口。

  • 继承
    当子类继承父类时,就可以拥有父类的属性和方法,无需重新编写父类的属性和方法直接拥有,并且在其基础上进行功能扩展以及有自己的属性和方法。

    需要注意的是父类被 private 修饰的属性和方法是不会被子类继承的。

  • 多态
    同一个行为有不同的表现方式。
    多态的特点:继承,重写,父类引用指向子类对象。

面向对象编程思想中,最重要的就是继承和多态这两个概念,继承是面向对象的一个基石,提高了代码复用性,多态的前提是类之间必须要有继承,多态提高了代码的可扩展性。

Java 泛型了解么?什么是类型擦除?泛型的三种使用方式?

Java 泛型是 JDK5 中应用的一个新特性,它的作用编译时提供了类型安全检查,这个机制让程序员在编译时就检查到非法的类型。泛型的本质是参数化类型,也就是说将所操作的数据类型指定为参数

Java 的泛型是伪泛型,因为 Java 在运行期间,所有的泛型信息都会被擦除,也就是通常说的类型擦除

参数化类型:也就是说所操作的数据类型被指定为一个参数。

泛型的使用方式:

  1. 泛型类

    public class Context<T>{
        private T key;
        
        public void Context(T key){
            this.key = key;
        }
        
        public T getKey(){
            return key;
        }
    }
    

    ​ 实例化类:

    Context<String> context = new Context<String>("Tom");
    

    一般作用是在于容器类上。

  2. 泛型接口

  3. 泛型方法

泛型的作用

  1. 类型安全
  2. 消除强制类型转换
  3. 更好的代码复用性

==与 equals 的区别

==作用于基本数据类型比较的的是值,引用数据类型比较的是对象的内存地址。

equals() 不能用于基本数据类型值(并不包括基本数据类型的包装类)的变量,只能比较两个对象是否相等,equals() 方法存在于Object 类中:

public boolean equals(Object obj) {
        return (this == obj);
}

注意:一个类中如果没有重写 equals() 方法,默认使用的是Object类中的 equals() 的方法,这个时候比较的就是对象的内存地址了。

String 和 new String() 的区别

String

String str = "abc";这一代码会在字符串常量池中查找“ abc ”,如果没有则新建一个字符串对象,然后将 str 指向“ abc ”,String str2 = "abc";此时 srt2 会直接在字符串常量池中找到“abc”对象并引用

new String()

String str3 = new String("abc") 很明显就是在堆区创建对象并分配内存,虽然其中的String 实例的 value 指向的是常量池中的“abc”,但是最终还是堆区对象,如果 str3 == str2 返回的是false,因为 str2 引用的是常量池中的“abc”,而 str3 引用的是堆区新建的 String 实例,所以比较的地址肯定是不相等的。那为什么两者的 value 地址相等?注意:== 真正比较的是两者的内存地址,而不是 value,value 只是 String 中的属性,str3 引用的 String 实例中的 value 属性就是运用了常量池中的 “abc” 的 value。

hashCode()与equals()

final 关键字的作用

final 关键字作用在不同的地方效果不同:

  • 修饰类,则该类不能被继承,如果需要可以手动设置成员变量为 final,但是该类下的所有成员方法都会被隐式的指定为 final 方法。
  • 修饰方法,被修饰的方法不会被覆盖(重写),另外类中所有被 private 修饰的都会隐式添加 final,因为 private 修饰的方法外部不可见,无法被重写,所以会隐式添加 final
  • 修饰方法形参,如果是基本数据类型,则表示不能修改值,如果是引用数据类型则可以修改其对象的值,实际上就算不加 final,形参的修改也无法影响实参。
  • 修饰变量,就是声明常量。

为什么 Java 只有值传递。

首先我们要搞清楚程序语言中,传递过程中的传递分为两种情况:即值传递和引用传递。

值传递:在方法调用的时候,传递的参数是实际参数的副本。这样在函数中如果对参数进行修改,实际参数是不受影响的。

引用传递:在方法调用的时候,传递的参数是实际参数的内存地址,这样在函数中对参数进行修改,将会影响到实际参数。

我们的重点应该是关注传递的参数是否是实际参数的副本。

当实际参数是对象时,容易混淆两者的关系。当我们实参是对象时,其实传递的是实参地址的副本

根据引用传递的说法,我们传递的确实是实参的内存地址,但是我们需要注意的一点是,我们在函数中对参数进行修改,是否正真的影响了实际参数。在判断实参是否受影响的时候,我们应该注意实参传入的是什么?如果实参传入的是地址,则需要关注地址是否发生了影响,而不是地址指向的对象的变化。

这样一来,Java 中是值传递了,因为 Java 在基本数据类型中传递的是值的副本,在引用数据类型中传递的是地址的副本。在函数中对形参进行操作,如果操作的是地址指向对象的内容,那肯定是不影响地址本身,而是对象内容,如果操作的是将形参地址指向一个新的地址,此时实参的地址也不会发生变化。这就是为什么 Java 只有值传递的原因。

StringBuffer & StringBuilder 有什么区别?

我们通过查看源码发现两者都继承了 AbstractStringBuilder 这个类。
可以看到有一个 char[] 属性,并没有像 String 一样是被 final 修饰,所以这就决定了两者是可变的(两者构造方法都是调用了父类的构造方法),而 String 是不可变的。
线程安全
String 中的对象是不可变的,可以理解为常量,所以是线程安全。StringBuffer 方法加了同步锁或对调用的方法加了同步锁,所以线程是安全的。StringBuilder 方法没有加同步锁,所以线程不安全。
性能
String 类型进行改变时,都会新建一个 String 对象,然后指向新的 String 对象。StringBuffer 是对本身进行操作,并不会新建对象。由于 StringBuilder 不是线程安全的所以通常情况下效率是要高于 StringBuffer 的。
使用
少量字符串操作直接使用 String
单线程操作字符串缓冲区下操作大量数据: StringBuilder
多线程操作字符串缓冲区下操作大量数据:StringBuffer

为什么说String不可变?

通过看 String 的源码得知,实际上 String 的字符串内容时一个字符数组 : private final char value[]; 并且这个字符数组被 final 修饰了,这就代表了 String 是不可变的

我们来看看什么是不可变对象:
(1)类内部所有的字段都是final修饰的。
(2)类内部所有的字段都是私有的,也就是被private修饰。
(3)类不能够被集成和拓展。、
(4)类不能够对外提供哪些能够修改内部状态的方法,setter方法也不行。
(5)类内部的字段如果是引用,也就是说可以指向可变对象,那我们程序员不能获取这个引用。

而我们的 String 类型正好满足。

为什么要设计 String 不可变?

java变量自增问题(i++和++i的区别)

实际上 i++;++i; 的执行过程和结果是一样的,主要是区分这两个的赋值过程。

code:

@Test
    public void demo_01(){
        int i = 0;
        int a = i++; // 0
        System.out.println(a);
        i = 0;
        int b = ++i; // 1
        System.out.println(b);
    }

看到 a = i++; 这行代码,结果是 i=0,因为 i++ 是先赋值再计算。

看到 b = ++i; 这行代码,结果是 i=1,因为 ++1 是先计算再赋值。

因为在两者赋值的时候运算和引用的顺序不一样(字节码文件详细),所以只要记住口诀就可以了。还有一点就是 ++i 可以做左边值,而 i++ 只能做右边值。

ArrayList 和 LinkedList 有什么区别?

考点:主要考察面试者对两者的数据结构的理解。

两者都是线性结构,主要的区别是结构不同。

ArrayList

  • 用的是桶位数组为数据存储核心。
  • 由于有索引,查询非常快。
  • 增删改稍慢。

LinkedList

  • 用的是对象的引用为核心。
  • 对比ArrayList占用内存,一个节点包含了前后两个节点的引用。
  • 查询慢,增删改快。

Java 中接口和抽象类的区别

相同点:

  1. 都不能被实例化
  2. 都能被作为引用类型
  3. 一个类继承抽象类或实现接口,必须实现抽象方法和接口定义的方法,否则这个类必须是抽象类或接口。

不同点:

接口:

  1. 只能有方法,成员变量实际上是常量,因为默认public static final修饰的静态常量。
  2. 不能有静态方法
  3. 成员只能是public
  4. 接口可以多实现

抽象类:

  1. 可以有自己的构造方法
  2. 成员可以是private、默认、protected、public
  3. 有抽象方法的一定是抽象类,但是抽象类不一定有抽象方法。
  4. 可以包含静态方法。
  5. 只能单继承

什么时候使用接口?什么时候使用抽象类?

抽象类:

将一个概念将其抽象,对类抽象,例如:动物,植物,食物。。。我们需要去描述一类事物的基本共同属性时,使用抽象类将其描述。

接口:

用于规范事物的特征或行为(方法),局部抽象,例如:飞翔,潜水。。。只要有这些行为的类都能实现对应接口,实现具体方法。

抽象类是对类抽象而接口则是对类的行为的约束和统一规范。

重载和重写的区别?

重载发送在同一个类中,方法名必须相同,参数个数,排列,以及类型不同,来区分重载的方法,访问修饰符和返回值可以不同,发送在编译期间。

重写发生在子类与父类之间,方法名和参数列表相同,返回值和抛出异常范围小于父类的范围,访问修饰符范围必须大于或等于父类的范围。

注意重载方法之间的特点是:方法名相同,参数列表不同,返回值不能作为用来区分重载方法。

Java中的四个引用

JDK 1.2之前,对象的引用只分为两种 “已被引用”和“未被引用”。无法描述特殊情况下的对象,比如,内存紧张时,应该抛弃一些对象来释放内存。

所以在 JDK 1.2之后,对其对象的状态进行了扩展,分为“强引用”,“软引用”,“弱引用”,“虚引用”

强引用:
A a = new Object(); 只要 a 还指向 Object 对象,则Object 对象不会被垃圾回收,除非 A a = null; 手动置为 null。

只要强引用存在,对象就永远不会被垃圾回收机制回收,就算内存不足,JVM也只会抛出内存溢出异常。手动置为 null,垃圾回收器会在合适的时候回收对象。

软引用:
软引用用来描述一些非必须但仍有用的对象,内存足够则不会被回收,当内存不足时,垃圾回收器会回收这些对象,如果回收之后,还是不足,则会提示内存溢出异常。常常用于缓存技术,网页缓存,图片缓存等。。。

弱引用:
比软引用弱一点,无论内存是否足够,只要 JVM 垃圾回收机制启动,则会回收弱引用相关联的对象。

虚引用:
持有虚引用的对象,任何时候都有可能被回收,因为就和没有一样。

posted @ 2022-01-05 10:07  CN_DADA  阅读(46)  评论(0编辑  收藏  举报