面试整理笔记

文章介绍了Java中的多态性,包括静态多态(方法重载)和动态多态(方法重写)。接着讨论了对象拷贝的深拷贝与浅拷贝概念,解释了它们在内存管理中的差异。接着提到了Java中的四种引用类型:强引用、软引用、弱引用和虚引用,以及它们与垃圾回收的关系。最后,概述了HashMap的数据结构和工作原理,包括哈希表、扩容策略和冲突解决机制。
摘要由CSDN通过智能技术生成

面向对象三大特性:封装、继承、多态

重点说下多态:多态

多态分为静态多态和动态多态。静态多态(也称为方法重载)是指在编译时就已经根据方法的参数类型、个数和顺序等确定了具体调用哪个方法。动态多态(也称为方法重写)是指在程序运行时,根据对象的实际类型来确定调用哪个方法。

深拷贝、浅拷贝

在Java中,对象的拷贝方式可以分为深拷贝和浅拷贝两种方法:

1. 浅拷贝(Shallow Copy):浅拷贝是指拷贝一个对象时,仅仅拷贝它的引用而不是它的实际内容。这意味着,当源对象或目标对象中的任何一个对象发生变化时,另一个对象也会随着变化。Java中的默认拷贝方式就是浅拷贝。

2. 深拷贝(Deep Copy):深拷贝是指拷贝一个对象时,不仅要拷贝它的引用,还要拷贝它的实际内容。这意味着,即使源对象或目标对象中的任何一个对象发生变化,另一个对象也不会随着变化。

总结来说,浅拷贝只拷贝引用, 目标对象或目标对象中的任何一个对象发生变化时,拷贝对象跟随变化,深拷贝拷贝内容和引用,目标对象或目标对选哪个中的对象发生变化时,深拷贝内容不会变化。

在Java中,实现深拷贝的方式有很多种,例如:

1. 实现Cloneable接口并重写clone()方法:在这种方式下,我们需要对每一个引用类型的成员变量都进行clone()操作,从而完成深拷贝。

2. 序列化与反序列化:Java中提供了一种对象序列化的机制,我们可以将一个对象序列化为二进制流,然后再反序列化为一个新的对象。这样就可以实现深拷贝。

总的来说,深拷贝和浅拷贝的区别在于是否拷贝对象的成员变量内容。如果需要拷贝对象的成员变量内容,就需要使用深拷贝。否则使用浅拷贝即可。

强、软、弱、虚四种引用

在Java中,引用可以分为4类:强引用、软引用、弱引用和虚引用。它们的强度依次逐渐减弱,即强引用 > 软引用 > 弱引用 > 虚引用。

1. 强引用(Strong Reference):又称为强类型引用,是指在代码中普遍存在的引用,它会使得被引用的对象始终处于存活状态。如果一个对象具有强引用,那么它就不能被垃圾回收。

2. 软引用(Soft Reference):是一种相对强度较弱的引用,它可以在系统内存不足时被回收,这样能够避免内存溢出的情况发生。软引用通常用在对内存敏感的应用程序中,比如缓存功能,当内存不足时可以优先回收那些缓存对象。

3. 弱引用(Weak Reference):是指被弱引用关联的对象只能生存到下一次垃圾回收之前。在Java中,一旦一个对象只被弱引用关联时,这个对象就可以被回收。弱引用通常用于保存那些非必须对象的引用,当系统内存不足时,这些非必须对象会被回收。

4. 虚引用(Phantom Reference):是一种类似于没有引用的引用类型,即不能通过虚引用访问对象本身,也不能通过虚引用获得对象的引用。当对象被垃圾回收器回收时,虚引用会被放入一个引用队列中,通过这个引用队列可以跟踪对象被回收的状态,以便在对象被回收后执行一些必要的清理操作。

虚引用是最弱的一种引用类型,其主要作用是跟踪对象的回收和某些对象生命周期的管理。

他们与GC的关系:

  1. 强引用:强引用指向的对象在没有被任何其他对象引用时,也不会被系统回收。只有当该对象不再被任何强引用所引用时(即不可达),才会被系统回收。

  2. 软引用:软引用指向的对象可能被系统回收,但只有在内存不足时才会回收。当系统内存不足时,Java虚拟机会根据一定的算法回收一些软引用指向的对象,以释放内存。

  3. 弱引用:弱引用指向的对象可能被系统回收,只要该对象不再被任何强引用所引用,就会被垃圾回收器回收。与软引用不同的是,弱引用总是被垃圾回收器优先考虑回收,因此更容易被回收。

  4. 虚引用:虚引用指向的对象可以被垃圾回收器随时回收,但在回收之前会被放入一个引用队列中,以便在对象被回收时得到通知。虚引用主要用于对象的回收跟踪和管理,对于对象的生命周期没有任何影响。

总的来说,不同的引用类型对应不同的垃圾回收方式,它们的主要作用是帮助程序员控制对象的生命周期和有效地使用内存空间,从而提高应用程序的性能和稳定性。

HashMap 算法

基于哈希表实现,在jdk1.7时使用数组加链表实现,在jdk 1.8时使用使用数组+链表(或红黑树)实现。在添加元素时,元素的hashCode 与哈希表的容量16相& 得到一个16 范围内的数,然后将这个元素放在数组对应的位置,如果这个位置已经有了其他元素,那么就发生了哈希碰撞,使用链表继续往后面添加元素,当插入元素的数量大于12个(容量乘加载因子0.75)时,HashMap扩容为原来的一倍,在jdk 1.8以后,数组里链表长度大于8时会使用红黑树。

HashMap是Java中非常常用的一种数据结构,它基于哈希表实现,可以用来存储键值对。HashMap内部由一个数组和一个链表(或红黑树)组成,其中数组元素称为桶(bucket),每个桶可以存储一个链表(或红黑树)。

当插入一个元素时,HashMap会先计算该元素的哈希值(调用hashCode()方法),然后根据哈希值将元素插入到相应的桶中。如果发现多个元素的哈希值相同,那么它们将会以链表(或红黑树)的形式存储在同一个桶中,这就是冲突解决的方式。

在HashMap中,桶的数量默认为16,如果需要插入的元素数量超过了桶的数量,那么将会触发扩容操作(例如元素数量到达了0.75倍的桶数量),此时HashMap会自动将桶的数量扩大一倍,并将所有元素重新散列到新的桶中。这个过程需要遍历原有的所有元素,对它们重新计算哈希值,并将它们插入到新的桶中,这一过程的时间复杂度为O(n)。

在Java8中,当一个桶中存储的元素数量超过了8个时,HashMap会将这个桶转换为红黑树,以提高查找效率,这个过程被称为“树化”。树化之后,查找一个元素的平均时间复杂度将从O(n)变为O(logn)。此外,HashMap还会定期执行“树退化”操作,即将桶中存储的所有元素转换回链表形式,以避免树结构对内存的占用过大。

总的来说,HashMap采用哈希表实现,可以提供快速的查找和插入,并可以自动调整桶的数量大小,以保持存储空间的最优化。同时,它还支持链表(或红黑树)在桶中的冲突解决方式,能够满足大部分的数据存储需求。

Handler 相关

下面是Handler的消息发送过程:

1. 创建Handler对象

创建Handler对象时,会自动关联当前线程的消息队列(MessageQueue)和消息循环器(Looper)。

```java

Handler handler = new Handler();


```

2. 发送消息

调用 sendXXX() 方法发送消息,其中 XXX 可以是 Message、Runnable 等。

```java

handler.sendMessage(message);
handler.post(runnable);


```

3. 创建消息对象

如果是发送 Message 类型的消息,则需要创建 Message 对象并设置消息内容。通过调用 Handler 的 obtainMessage() 方法可以获取一个可重复使用的消息对象,避免了频繁创建对象的开销,并可以提高性能。


Message message = handler.obtainMessage();
message.what = MSG_UPDATE;
message.obj = data;
handler.sendMessage(message);


 

4. 消息入队

将消息对象添加到消息队列中,等待消息循环器转发。```java

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return enqueueMessage(msg, SystemClock.uptimeMillis() + delayMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.markInUse();
    msg.when = uptimeMillis;
    Message p = mMessages;
    boolean needWake;
    if (p == null || uptimeMillis < p.when) {
        // New head, wake up the event queue if blocked.
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
    } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // and the message is the earliest asynchronous message in the queue.
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || uptimeMillis < p.when) {
                break;
            }
            if (needWake && p.isAsynchronous()) {
                needWake = false;
            }
        }
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
    }
    if (needWake) {
        mQueueAwake = true;
        mLock.notifyAll();
    }
    return true;
}


```

5. 转发消息

消息循环器不停地从消息队列中取出消息,并将其转发给对应的 Handler 去处理。```java

private static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}


```

6. 消息处理

Handler 接收到消息后,会调用自己的 handleMessage() 方法去处理消息内容。

```java

public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_UPDATE:
            updateUI(msg.obj);
            break;
        ...
        default:
            super.handleMessage(msg);
            break;
    }
}


```

7. 清除消息

如果消息的延时时间过长或者不再需要被处理的消息,可以通过 removeCallbacks() 方法或者 removeMessages() 方法手动清除。

```java

handler.removeCallbacks(runnable);
handler.removeMessages(MSG_UPDATE);


```

这就是 Handler 的消息发送过程,通过消息队列和消息循环器的配合,我们可以实现线程间的通信以及异步任务的处理。

View事件分发

  1. public boolean dispatchTouchEvent(MotionEvent event)

dispatchTouchEvent() 是 View 的事件分发方法。在分发阶段,它由父 View 调用,用于将事件传递给当前 View 进行处理。

  1. public boolean onInterceptTouchEvent(MotionEvent event)

onInterceptTouchEvent() 是 View 的拦截事件方法,可以用于判断是否要拦截父 View 传递过来的事件。

  1. public boolean onTouchEvent(MotionEvent event)

onTouchEvent() 是 View 的事件处理方法,用于响应各种类型的事件。在响应事件时,一般需要调用 MotionEvent 的 getX()、getY() 等方法来获取事件的具体坐标位置等信息。

事件分发处理流程:

dispatchTouchEvent 方法 :接收到触摸事件后从根布局的 dispatchTouchEvent 方法开始 向内层所有子View 或 ViewGroup 的 dispatchTouchEvent 做递归遍历,最后一层View 或ViewGroup 的dispatchTouchEvent 方法不被调用,同时这个方法负责调起onInterceptTouchEvent 方法和onTouchEvent方法。

onInterceptTouchEvent方法: 返回true 拦截当前事件交由 onTouchEvnet 处理 ,不在向内层view 传递,内层dispatchTouchEvent  不在调用。onTouchEvent 返回 true 消费事件,返回false 事件继续向上层转发,返回 false ,继续递归调用子Viewe 的 dispatchTouchEvent 方法,事件继续向内转发。

onTouchEvent :消费 点击事件,返回true 不再向父View 的 onTouchEvent 转发事件,返回false 继续转发。

如果都不拦截不消费

最后交由Activity 的 onTouchEvent 处理事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值