Java面试八股文(day01)

Java面试八股文(day01)

每日一更新,选自各大网站真实面经,欢迎补充!


文章目录

  • Java面试八股文(day01)
  • 一、Java面向对象有哪些特征?
    • 1. 封装
    • 2. 继承
    • 3. 多态
  • 二、equals 与 == 的区别
  • 三、HashCode的作用
  • 四、String、StringBuffer和StringBuilder的区别
    • 1.String
    • 2.StringBuffer和StringBuilder
  • 五、ArrayList和LinkedList的区别
  • 六、HashMap和HashTable的区别
  • 七、Java的四种引用类型
    • 强引用Strong Reference
    • 软引用Soft Reference
    • 弱引用weak Reference
    • 虚引用PhantomReference


一、Java面向对象有哪些特征?

封装、继承、多态

1. 封装

编写一个类就是对数据和数据的操作封装,封装即时隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
例如:

  1. 使用private 关键字修饰成员变量。
  2. 对需要访问的成员变量,提供对应的getXxx 和 setXxx 方法

2. 继承

子类继承父类的特征和行为。提供继承信息的类被称为父类(基类),得到继承信息的类被称为子类(派生类)。
注意

  1. 一个类只能继承一个父类,构造方法不能被继承。
  2. 支持多层继承
  3. 父类private 的属性和方法不能继承

格式例如
class 父类 { }
class 子类 extends 父类 { }
super 和 this :
super.成员变量—父类 super.成员方法 —父类
this.成员变量—本类 this.成员方法 —本类

3. 多态

多态定义:同一个行为具有多个不同表现形式或形态能力。
多态实现方式
方式一:重载与重写

重载:在一个类中,同一个方法名称,不同的参数类型及个数,可以完成不同功能。

重写:同一个方法名称,子类继承父类的方法,根据操作的子类不同,所完成的功能也不同。
方式二:抽象类和抽象方法
抽象方法:修饰符 abstract 返回值类型 方法名(参数列表){}
抽象类:abstract class 类名{}
特点:
1、抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
2、抽象类不可以是实例化
3、抽象类的子类,可以是抽象类,也可以是具体类。如果子类为具体类,需要重写抽象类中所有抽象方法
方式三:接口
定义:是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。
接口无法被实例化,但是可以被实现. 一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

二、equals 与 == 的区别

1.==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所 指向的内存空间的值是不是相同
2.==是指对内存地址进行比较 , equals()是对字符串的内容进行比较
3.==指引用是否相同, equals()指的是值是否相同

代码如下(示例):

        //创建一个引用
        String A = new String("abcd");
        //创建另一个引用,A和B内容一致
        String B = new String("abcd");
        //将abcd放入常量池
        String C = "abcd";
        //常量池中存在"abcd",所以不会再创建
        String D = "abcd";
        //true
        System.out.println(A.equals(B));
        //false
        System.out.println(A == B);
        //true
        System.out.println(C.equals(D));
        //true
        System.out.println(C == D);

三、HashCode的作用

首先我们需要知道Java中的集合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合时,就要调用1000次equals方法。这显然会大大降低效率。 于是,Java采用了哈希表的原理。

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。

(1)如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;

(2)如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;

(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起。这样一来实际调用equals方法的次数就大大降低了。

所以hashCode在上面扮演的角色为寻域(寻找某个对象在集合中区域位置)。hashCode可以将集合分成若干个区域,每个对象都可以计算出他们的hash码,可以将hash码分组,每个分组对应着某个存储区域,根据一个对象的hash码就可以确定该对象所存储区域,这样就大大减少查询匹配元素的数量,提高了查询效率。

废话少说,我用代码来演示一下:

public class HashTest {  
    private int i;  
  
    public int getI() {  
        return i;  
    }  
  
    public void setI(int i) {  
        this.i = i;  
    }  
  
    public int hashCode() {  
        return i % 10;  
    }  
  
    public final static void main(String[] args) {  
        HashTest a = new HashTest();  
        HashTest b = new HashTest();  
        a.setI(1);  
        b.setI(1);  
        Set<HashTest> set = new HashSet<HashTest>();  
        set.add(a);  
        set.add(b);  
        System.out.println(a.hashCode() == b.hashCode());  
        System.out.println(a.equals(b));  
        System.out.println(set);  
    }  
} 

输出结果为:

true
false
[HashTest@1, HashTest@1]

这时问题出现了,因为我们只是重写了HashCode方法,从上面的结果可以看出,虽然两个对象的HashCode相等,但是实际上两个对象并不是相等,因为我们没有重写equals方法,那么就会调用Object默认的equals方法,Object的equals方法调用的是 == 进行比较两个对象,显示这是两个不同的对象。

这里我们将生成的对象放到了HashSet中,而HashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象ab都被放到了HashSet中,这样HashSet就失去了他本身的意义了。

下面我们继续重写equals方法:

public class HashTest {  
    private int i;  
  
    public int getI() {  
        return i;  
    }  
  
    public void setI(int i) {  
        this.i = i;  
    }  
  
    public boolean equals(Object object) {  
        if (object == null) {  
            return false;  
        }  
        if (object == this) {  
            return true;  
        }  
        if (!(object instanceof HashTest)) {  
            return false;  
        }  
        HashTest other = (HashTest) object;  
        if (other.getI() == this.getI()) {  
            return true;  
        }  
        return false;  
    }
  
    public int hashCode() {  
        return i % 10;  
    }  
  
    public final static void main(String[] args) {  
        HashTest a = new HashTest();  
        HashTest b = new HashTest();  
        a.setI(1);  
        b.setI(1);  
        Set<HashTest> set = new HashSet<HashTest>();  
        set.add(a);  
        set.add(b);  
        System.out.println(a.hashCode() == b.hashCode());  
        System.out.println(a.equals(b));  
        System.out.println(set);  
    }  
}  

输出结果:

true
true
[HashTest@1]

从结果我们可以看出,现在两个对象就完全相等了,HashSet中也只存放了一份对象。

四、String、StringBuffer和StringBuilder的区别

1.String

String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。
例如: String str = “a”; str = str + “b”;
这一步在内存看来并不是将原有的“a”变为“ab”,而是开辟一块新的内存并赋值“ab”。

2.StringBuffer和StringBuilder

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。因为对这2个类进行修改都是在本身存在的那个对象上进行的。

StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

五、ArrayList和LinkedList的区别

底层数据结构不同,
ArrayList底层是基于数组实现的,更适合进行随机查找
LinkedList底层是基于链表实现的,更适合删除和添加
ArrayList和LinkedList都实现了List接口,但是LinkedList额外实现了Deque
因此LinkedList还可以当作队列来进行使用。
注:
如果不熟悉数组和链表,建议查看一下相关博文。
简单来说就是数组因为是在内存中开辟了一段连续的内存空间,使得在查找时可以根据动态下标访问方便快捷,但是插入和删除的效率很低,无法进行动态扩展。
但是链表不是连续内存,而是通过指针指向将元素进行关联,所以插入和删除效率高,非连续内存,无固定大小,可以随时扩展,无法随机访问,查找效率低。

六、HashMap和HashTable的区别

1.Hashtable是线程安全,HashMap是非线程安全。HashMap的性能会高于Hashtable,我们平时使用时若无特殊需求建议使用HashMap,在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步。

2.HashMap可以使用null作为key,不过建议还是尽量避免这样使用。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。

3.HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。

4.HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。HashMap扩容时是当前容量翻倍即:capacity 2,Hashtable扩容时是容量翻倍+1即:capacity (2+1)。

5.HashMap和Hashtable的底层实现都是数组+链表结构实现

6.Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模。

当然在真实的开发情况下:如果你不需要线程安全,那么使用HashMap,如果需要线程安全,那么使用ConcurrentHashMap。HashTable已经被淘汰了,不要在新的代码中再使用它。(HashTable官方注释原话)

七、Java的四种引用类型

首先,引用类型一般是针对于java中对象来说的,Java为引用类型专门定义了一个类叫做Reference。Reference是跟Java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解Java的垃圾回收是怎么工作的。
Java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。

强引用Strong Reference

Object obj = new Object();
我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。
强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。

软引用Soft Reference

软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。

先看下SoftReference的定义:

public class SoftReference<T> extends Reference<T>

SoftReference继承自Reference。它有两种构造函数:

 public SoftReference(T referent) 
 public SoftReference(T referent, ReferenceQueue<? super T> q) 

第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

我们举个SoftReference的例子:

public void softReference(){
    Object obj = new Object();
    SoftReference<Object> soft = new SoftReference<>(obj);
    obj = null;
    log.info("{}",soft.get());
    System.gc();
    log.info("{}",soft.get());
}

输出结果:

22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4

可以看到在内存充足的情况下,SoftReference引用的对象是不会被回收的。

弱引用weak Reference

weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

同样的WeakReference也有两个构造函数:

public WeakReference(T referent)public WeakReference(T referent, ReferenceQueue<? super T> q)

含义和SoftReference一致,这里就不再重复表述了。

我们看下弱引用的例子:

public void weakReference() throws InterruptedException {
    Object obj = new Object();
    WeakReference<Object> weak = new WeakReference<>(obj);
    obj = null;
    log.info("{}",weak.get());
    System.gc();
    log.info("{}",weak.get());
}

输出结果:

22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null

我们看到gc过后,弱引用的对象被回收掉了。

虚引用PhantomReference

PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

和SoftReference和WeakReference不同的是,PhantomReference只有一个构造函数,必须传ReferenceQueue:

public PhantomReference(T referent, ReferenceQueue<? super T> q)

看一个PhantomReference的例子:

public void usePhantomReference(){
        ReferenceQueue<Object> rq = new ReferenceQueue<>();
        Object obj = new Object();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
        obj = null;
        log.info("{}",phantomReference.get());
        System.gc();
        Reference<Object> r = (Reference<Object>)rq.poll();
        log.info("{}",r);
    }

运行结果:

07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db

我们看到get的值是null,而GC过后,poll是有值的。

因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null。

你可能感兴趣的:(java,面试)