2023年Java核心技术面试第八篇(篇篇万字精讲)

简介: 2023年Java核心技术面试第八篇(篇篇万字精讲)

十五 . 面向对象的基本要素:封装,继承,多态



15.1 封装:


封装:封装是将数据和功能包装在一个类中,通过对外提供公共接口来隐藏内部实现细节。这样可以保护数据免受外部直接访问和修改,只能通过类提供的方法进行操作,封装提供了数据的安全性和代码的可维护性。


15.1.1 例子:


我们可以创建一个名为"Person"的类来封装一个人的相关信息,如姓名、年龄和性别。通过定义公共方法如"setName"、"setAge"和"setGender"来设置这些属性值,而不直接暴露给外部代码。这样可以确保属性值的正确性和一致性。


public class Person {
    private String name;
    private int age;
    private String gender;
    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public static void main(String[] args) {
        Person person = new Person("John", 25, "Male");
        System.out.println("Person Name: " + person.getName());
        System.out.println("Person Age: " + person.getAge());
        System.out.println("Person Gender: " + person.getGender());
        // 修改属性值
        person.setName("Alice");
        person.setAge(30);
        person.setGender("Female");
        System.out.println("Updated Person Name: " + person.getName());
        System.out.println("Updated Person Age: " + person.getAge());
        System.out.println("Updated Person Gender: " + person.getGender());
    }
}


15.2 继承


继承:继承是指一个类(称为子类或派生类)可以继承另一个类(称为父类或基类)的属性和方法。

子类继承了父类的特性,并且可以在此基础上进行扩展或重写。继承实现了代码的重用和扩展性。


15.2.1 例子


我们可以定义一个"Animal"类作为父类,其中包含通用的属性和方法,如"eat"和"sleep"。然后可以创建子类如"Cat"和"Dog"来继承"Animal"类,并在子类中添加特定的属性和方法,如"meow"方法和"bark"方法。


// 定义父类 Animal
class Animal {
    // 父类的属性
    protected String name;
    // 父类的方法
    public Animal(String name) {
        this.name = name;
    }
    public void eat() {
        System.out.println(name + " is eating.");
    }
    public void sleep() {
        System.out.println(name + " is sleeping.");
    }
}
// 定义子类 Cat
class Cat extends Animal {
    // 子类的属性
    private String breed;
    // 子类的方法,并调用父类的构造方法
    public Cat(String name, String breed) {
        super(name); // 调用父类的构造方法
        this.breed = breed;
    }
    // 子类的自定义方法
    public void meow() {
        System.out.println(name + " is meowing.");
    }
}
// 定义子类 Dog
class Dog extends Animal {
    // 子类的属性
    private String breed;
    // 子类的方法,并调用父类的构造方法
    public Dog(String name, String breed) {
        super(name); // 调用父类的构造方法
        this.breed = breed;
    }
    // 子类的自定义方法
    public void bark() {
        System.out.println(name + " is barking.");
    }
}
// 测试代码
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat("Kitty", "Persian");
        cat.eat(); // 调用继承自父类的方法
        cat.sleep();
        cat.meow(); // 调用子类自定义的方法
        Dog dog = new Dog("Buddy", "Labrador");
        dog.eat(); // 调用继承自父类的方法
        dog.sleep();
        dog.bark(); // 调用子类自定义的方法
    }
}


输出:

Kitty is eating.
Kitty is sleeping.
Kitty is meowing.
Buddy is eating.
Buddy is sleeping.
Buddy is barking.


15.3 多态


多态:多态是指同一种操作可以作用于不同的对象,并根据对象的实际类型执行不同的行为。通过多态,可以提高代码的灵活性和可扩展性。


15.3.1 例子


定义了一个抽象类 Animal,并具有一个抽象方法 sound。然后,定义了两个子类 CatDog,它们分别继承自 Animal 并实现了 sound 方法。


在测试类中,创建了一个 Animal 类型的对象数组,并分别用 Cat 和 Dog 的实例来初始化数组的元素。然后通过循环遍历数组,并调用 sound 方法,可以看到根据对象的实际类型,程序会执行不同的行为。这就是多态的体现,相同的方法名 sound 可以作用于不同的对象,并根据对象的实际类型执行不同的行为。


// 定义父类 Animal
abstract class Animal {
    abstract void sound();
}
// 定义子类 Cat
class Cat extends Animal {
    void sound() {
        System.out.println("喵喵喵");
    }
}
// 定义子类 Dog
class Dog extends Animal {
    void sound() {
        System.out.println("汪汪汪");
    }
}
// 测试类
public class PolymorphismExample {
    public static void main(String[] args) {
        // 创建 Animal 对象数组
        Animal[] animals = new Animal[2];
        animals[0] = new Cat();
        animals[1] = new Dog();
        // 循环遍历数组并调用 sound 方法
        for (Animal animal : animals) {
            animal.sound();
        }
    }
}


15.3.2 小结:谈谈多态的继承的联系


继承是建立类之间的一种层次关系,子类可以继承父类的属性和方法,并且可以增加自己的特定实现;而多态是在继承关系中,通过父类的类型引用来指向子类的对象,并且根据对象的实际类型执行不同的行为。继承是一种静态的关系,而多态是一种动态的行为。继承和多态通常是一起使用,多态是继承的一种体现。


十六 . synchronized 和 ReentrantLock 的区别?



Java精心设计的高效并发机制,构建大规模应用的基础之一。


16.1 典型回答


synchronized 是Java内建的同步机制,也被人称作Intrinsic Locking,提供互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在哪里。


Java 5 以前,synchronized 是仅有的同步手段,在代码中,synchronized可以用来修饰方法,可以在特定的代码快上,本质上synchronized方法,等同于把方法全部语句用synchronized块包起来。


ReentrantLock ,通常称为再入锁,Java提供锁的实现,语义和synchronized差不多,再入锁可以直接通过代码,直接调用Lock()方法获取,代码书写更加灵活,ReentrantLock提供了很多实用的方法,实现了很多synchronized无法做到的细节控制,可以控制fairness,也就是公平性,或者利用定义条件等,明确调用unlock()方法释放,不然就会一直持有这个锁。


synchronized和ReentrantLock的性能比较,早期synchronized再很多场景下性能相差比较大,后续进行改进后,再低竞争的场景表现可能优于ReentrantLock。


16.2 深入理解底层锁的概念


16.2.1 synchronized


16.2.2 ReentrantLock


ReentrantLock是Java提供的可重入锁实现,它具有更细粒度的控制和更多的功能。相较于synchronizedReentrantLock提供了以下优势:


  1. 公平性(Fairness)控制:ReentrantLock可以通过构造方法的参数来指定是否按照线程请求锁的顺序获取锁(公平性),以避免某些线程长时间等待锁而产生饥饿现象。
  2. 可中断性(Interruptibility):ReentrantLock提供了可中断的获取锁的方法,即线程在等待锁的过程中可以被其他线程中断,并通过捕获InterruptedException来处理中断事件。
  3. 条件变量(Condition)支持:ReentrantLock内置了Condition接口,可以创建多个条件变量,使线程能够在特定条件满足时等待或被唤醒,从而实现更灵活的线程协作。
  4. 超时控制(Timeout):ReentrantLock提供了尝试获取锁的方法,可以指定一个超时时间,在超过指定时间后如果无法获得锁,则继续执行其他操作,避免线程长时间等待。


在高竞争的多线程场景下,ReentrantLock通常表现更好,因为它提供了对锁的更细粒度的控制,并且支持更多的高级功能。


16.2.2.1 例子:


16.2.2.1.1 公平性(Fairness)控制:


ReentrantLock提供了公平性控制的功能。通过在构造方法中指定fair参数为true,可以使得锁的获取按照线程请求的顺序进行,即先到先得的原则。这样可以避免某些线程一直获取不到锁而产生饥饿现象。


相比之下,synchronized关键字并没有提供公平性控制的选项,它的锁获取是非公平的。在多个线程同时竞争同一个锁时,synchronized无法保证等待时间最长的线程优先获得锁,可能会导致一些线程长时间等待锁而无法执行。


因此,如果对公平性有较高的要求,可以使用ReentrantLock来实现可重入锁,并通过设置fair参数为true来保证线程的公平竞争。


16.2.2.1.2 可中断性(Interruptibility):


假设有两个线程,线程A和线程B,它们竞争一个ReentrantLock锁。

import java.util.concurrent.locks.ReentrantLock;
public class InterruptExample {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                lock.lockInterruptibly(); // 可中断地获取锁
                System.out.println("线程A获得了锁");
                Thread.sleep(5000); // 模拟线程A执行一些操作
            } catch (InterruptedException e) {
                System.out.println("线程A被中断");
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                Thread.sleep(2000); // 等待2秒
                threadA.interrupt(); // 中断线程A
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        threadA.start();
        threadB.start();
    }
}


线程A通过调用lock.lockInterruptibly()方法来可中断地获取锁。而线程B在等待2秒后,调用threadA.interrupt()方法来中断线程A。


如果线程A在等待锁的过程中被中断,会触发InterruptedException异常,然后线程A可以根据自己的需求进行相应的处理。在上述代码中,线程A会打印"线程A被中断"。


这个例子展示了ReentrantLock的可中断性,通过中断一个等待锁的线程,可以让它在等待过程中响应中断并进行相应的处理。而synchronized关键字并没有提供直接的中断支持,无法中断正在等待锁的线程。


16.2.2.1.3 条件变量(Condition)支持:


ReentrantLock内置了Condition接口,通过它可以创建多个条件变量,实现更灵活的线程协作。

Condition接口提供了以下几个方法:


  • await():使当前线程等待,并释放锁,直到被其他线程显式地唤醒或被中断。
  • awaitUninterruptibly():与await()类似,但不响应中断。
  • signal():唤醒一个等待该条件的线程。
  • signalAll():唤醒所有等待该条件的线程。


例子:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                lock.lock();
                System.out.println("线程A开始等待");
                condition.await(); // 等待条件满足
                System.out.println("线程A被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                Thread.sleep(2000); // 等待2秒
                lock.lock();
                System.out.println("线程B发出唤醒信号");
                condition.signal(); // 发出唤醒信号
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });
        threadA.start();
        threadB.start();
    }
}


线程A通过调用condition.await()方法使自己等待条件满足,然后线程B在等待2秒后,通过调用condition.signal()方法唤醒线程A。


使用条件变量,我们可以实现更加灵活的线程协作,线程可以根据特定的条件进行等待或唤醒。这在一些生产者消费者模型、线程池等场景中非常有用。而synchronized关键字并没有直接提供这种条件变量的支持。


16.2.2.1.4 超时控制(Timeout):


,ReentrantLock提供了尝试获取锁的方法,并且可以指定一个超时时间。如果在指定时间内无法获取到锁,则可以继续执行其他操作,避免线程长时间等待。

ReentrantLock提供了以下几个尝试获取锁的方法:


  • tryLock():尝试获取锁,如果成功获取到锁则返回true,否则立即返回false
  • tryLock(long timeout, TimeUnit unit):尝试在指定的超时时间内获取锁,如果在指定时间内成功获取到锁则返回true,否则在超时后返回false


例子:

线程A调用lock.tryLock(3, TimeUnit.SECONDS)方法,在3秒内尝试获取锁。如果在3秒内成功获取到锁,则执行相应的操作,否则在超时后输出"线程A尝试获取锁超时"。


通过使用tryLock()方法并指定超时时间,我们可以避免线程长时间等待,并在超时后执行其他操作。这在一些需要控制等待时间的场景中非常有用。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutExample {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            try {
                if (lock.tryLock(3, TimeUnit.SECONDS)) { // 在3秒内尝试获取锁
                    try {
                        System.out.println("线程A获得了锁");
                        Thread.sleep(5000); // 模拟线程A执行一些操作
                    } finally {
                        lock.unlock();
                        System.out.println("线程A释放了锁");
                    }
                } else {
                    System.out.println("线程A尝试获取锁超时");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread threadB = new Thread(() -> {
            try {
                Thread.sleep(2000); // 等待2秒
                lock.lock(); // 线程B获得锁
                System.out.println("线程B获得了锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("线程B释放了锁");
            }
        });
        threadA.start();
        threadB.start();
    }
}


tips:


TimeUnit.SECONDS 是 Java 中的一个枚举常量,它表示时间单位为秒。


lock.tryLock(3, TimeUnit.SECONDS) 表示在 3 秒内尝试获取锁。这里的 3 就是指定的时间,而 TimeUnit.SECONDS 则表示时间单位为秒。


  • TimeUnit.NANOSECONDS:纳秒
  • TimeUnit.MICROSECONDS:微秒
  • TimeUnit.MILLISECONDS:毫秒
  • TimeUnit.MINUTES:分钟
  • TimeUnit.HOURS:小时
  • TimeUnit.DAYS:天


相关文章
|
11天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
45 7
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
67 2
|
2天前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
27 11
|
18天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
45 14
|
28天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
11天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
22天前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
27 6
|
28天前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。
|
1月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
81 2