• Java基础


    哔哩哔哩 Java 开发工程师面试该如()何准备? - 知乎

    一. JavaOOP面试题

    (1)什么是B/S架构? 什么是C/S架构 Java 都有那些开发平台?

    B/S(Browser/Server),浏览器/服务器程序

    C/S(Client/Server),客户端/服务端,桌面应用程序

    1、 C/S和B/S各有优势,C/S在图形的表现能力上以及运行的速度上肯定是强于B/S模式的,不过缺点就是他需要运行专门的客户端,而且更重要的是它不能跨平台,用c++在windows下写的程序肯定是不能在linux下跑的。如qq

    2、B/S模式,它不需要专门的客户端,只要浏览器,而浏览器是随操作系统就有的,方便就是他的优势了。

    而且,B/S是基于网页语言的、与操作系统无关,所以跨平台也是它的优势,而且以后随着网页语言以及浏览器的进步,

    B/S在表现能力上的处理以及运行的速度上会越来越快,它的缺点将会越来越少。尤其是HTML5的普及,在图形的渲染方面以及音频、文件的处理上已经非常强大了。

    不过,C/S架构也有着不可替代的作用。

    JAVA SE:主要用在客户端开发

    JAVA EE:主要用在web应用程序开发

    JAVA ME:主要用在嵌入式应用程序开发

    Java SE 全称(Java Platform,Standard Edition)以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序,包含了支持 Java Web 服务开发的类,为(Java EE)提供基础。

    Java EE 全称(Java Platform,Enterprise Edition)这个版本以前称为 J2EE。企业版帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web 2.0 应用程序。

    Java ME 全称(Java Platform,Micro Edition)这个版本以前称为 J2ME。为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
     

    (2)什么是JDK?什么是JRE?Java语言有哪些特点

    jdk和jre的区别为:JRE是java运行时环境而JDK是java开发工具包,JDK包含JRE,但是JRE可以独立安装。

    JDK:java development kit (java开发工具),JDK 是用于开发 Java 程序的最小环境。

    JRE:java runtime environment (java运行时环境),是提供给 Java 程序运行的最小环境。

    JRE包含了java虚拟机、java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。JDK是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。运行java程序只需安装JRE。如果需要编写java程序,需要安装JDK。 

    1. 简单易学、有丰富的类库
    2. 面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)
    3. 与平台无关性(JVM是Java跨平台使用的根本)
    4. 可靠安全
    5. 支持多线程

    (3)面向对象和面向过程的区别 什么是数据结构?

    面向过程:
    一种较早的编程思想,顾名思义就是该思想是站在过程的角度思考问题,强调的就是功能行为,功能的执行过程,即先后顺序,而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现。使用的时候依次调用函数就可以了。

    面向对象:
    一种基于面向过程的新编程思想,顾名思义就是该思想是站在对象的角度思考问题,我们把多个功能合理放到不同对象里,强调的是具备某些功能的对象。

    具备某种功能的实体,称为对象。面向对象最小的程序单元是:类。面向对象更加符合常规的思维方式,稳定性好,可重用性强,易于开发大型软件产品,有良好的可维护性。

    在软件工程上,面向对象可以使工程更加模块化,实现更低的耦合和更高的内聚。

    数据结构:计算机保存,组织数据的方式

    (4)Java的数据结构有那些? 什么是OOP?

    Java的数据结构:

    1. 线性表(ArrayList)
    2. 链表(LinkedList)
    3. 栈(Stack)
    4. 队列(Queue)
    5. 图(Map)
    6. 树(Tree)

    OOP: 面向对象编程

    (5)类与对象的关系?

    类是对象的抽象,对象是类的具体,类是对象的模板,对象是类的实例。

    (6)Java中有几种数据类型标识符命名规则。instanceof关键字的作用

    整形:byte,short,int,long
    浮点型:float,double
    字符型:char
    布尔型:boolean

    instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

    boolean result = obj instanceof Class

    (7)什么是隐式转换,什么是显式转换

    显示转换就是类型强转,把一个大类型的数据强制赋值给小类型的数据。

    隐式转换就是大范围的变量能够接受小范围的数据;隐式转换和显式转换其实就是自动类型转换和强制类型转换。

    (8)Char类型能不能转成int类型?能不能转化成string类型,能不能转成double类型什么是拆装箱?

    Char在java中也是比较特殊的类型,它的int值从1开始,一共有2的16次方个数据;

    Char < int < long < float < double;Char类型可以隐式转成int,double类型,但是不能隐式转换成string;如果char类型转成byte,short类型的时候,需要强转。
     

    1. 装箱就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的valueOf(int) 方法
    2. 拆箱就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的intValue方 法

    (9)Java中的包装类都是那些?一个java类中包含那些内容?

    byte:Byte,
    short:Short,
    int:Integer,
    long:Long,
    float:Float,
    double:Double,
    char:Character ,
    boolean:Boolean

    基本数据类型的定义都是直接在栈中,如果是包装类型来创建对象,就和普通对象一样。

    基本数据类型都在stack中,而引用类型,变量是放在stack中,真正有内容的东西放在heap中,也就是当new了一个新的引用类型,他就会放在堆中,同时栈中的引用类型变量会指向堆中你new出来的东西!

    (10)那针对浮点型数据运算出现的误差的问题,你怎么解决?

    为什么浮点数运算有精度丢失风险

    • 原因:计算机在存储浮点数时,指数和尾数能占用的bit位是固定的,十进制小数在转二进制小数时乘2取整,直到不存在小数为止,如果在运算时超过尾数限制的bit位长度,就会被截断,所以就导致小数精度发生损失
    • 解决方法:定义一个误差范围;或使用bigdecimal;使用近似值

    BigDecimal解决精度问题的底层原理:
    十进制整数在转化为二进制数时不会有精度问题,所以将十进制小数扩大N被让它在整数维度上进行计算(BigInteger类型),并记录小数点位置即可;
    BigDecimal进行运算时分解为两部分,BigInteger间的计算,以及小数点位置scale的更新;例如加法运算时,先按整数计算,然后更新小数点的位置为两者小数位小的那个值

    BigDecimal使用时避雷点
    不要使用BigDecimal(double),存在精度损失风险,因为实际存储的值是double数值的二进制表示,可能会有精度损失
    使用除法注意事项:使用roundingMode指明结果的保留规则,否则遇到无限循环小数时,会报错
    BigDecimal等值比较时,使用CompareTo(),而不是equals(),因为equals会比较精度,而compareTo会忽略精度,那些无实际意义的0会自动忽略

    务必使用BigDecimal.valueOf(1.01),或者使用 new BigDecimal("1.01") ———— 而不要使用new BigDecimal(1.01)

    查看源码可以知道,BigDecimal.valueOf(double val) 的底层是 return new BigDecimal(Double.toString(val));

    如下图,源码中 “是把double先转换成字符串,再转BigDecimal” 。源码文档中也说明了参数直接为double的精度问题。
    第二句中说了建议使用new BigDecimal(String val)的方式,而不要使用new BigDecimal(Double val);
    而且最后一句也说了建议使用BigDecimal.valueOf(double)方式;

    二、java集合和泛型面试题

    (1)ArrayList和linkedList的区别

    ArrayList: 基于动态数组实现

    LinkedList:基于双向链表实现

    如果新建数据没有给定初始值,ArrayList是每次在进行add操作的时候去进行扩容的,扩容则会涉及到Arrays.copyOf深拷贝,会造成内存和性能的消耗,所以在生产项目中,推荐大家尽量在创建ArrayList对象的时候就指定其容量。

    (2)HashMap和HashTable的区别

    HashMap和Hashtable都是Java常见的基于哈希表实现的Map接口的实现类,它们都用于存储键值对映射关系。下面是它们的区别

    1. 数据结构
    HashMap和Hashtable都是基于哈希表实现的Map接口的实现类,但是它们采用的哈希算法和数据结构有所不同。

    HashMap
    HashMap底层采用数组+链表/红黑树的数据结构实现,当哈希冲突发生时,会使用链表或者红黑树来解决冲突。HashMap中有一个负载因子(load factor)的概念,默认情况下负载因子为0.75,如果容量和负载因子的乘积大于元素个数时,就需要进行扩容操作。扩容一般是将原来的HashMap数组翻倍,再重新计算哈希码,将元素插入到新的数组中。

    Hashtable
    Hashtable底层也采用数组+链表的数据结构进行实现,当哈希冲突发生时,使用链表来解决冲突。与HashMap不同的是,Hashtable在JDK 8及以前没有使用红黑树解决哈希冲突,这导致了其效率相对较低。初始容量为11,负载因子为0.75,每次扩容时容量翻倍再加1。HashTable容量可以为任意整数,最小为1。

    2. 线程安全性
    线程安全性指在多线程环境下,数据的并发访问是否会产生问题。HashMap和Hashtable在线程安全性上有所不同。

    HashMap
    HashMap不是线程安全的类,即多个线程同时操作HashMap可能导致出现错误的结果或者抛出ConcurrentModificationException异常。但是,可以通过Collections的synchronizedMap方法来使HashMap变成线程安全的类。下面是一个使用synchronizedMap方法实现的线程安全的HashMap示例代码:

    Map map = new HashMap<>();
    Map syncMap = Collections.synchronizedMap(map);
    Hashtable
    Hashtable是线程安全的类,即多个线程同时操作Hashtable中的元素也不会产生错误的结果或者抛出ConcurrentModificationException异常。

    3. null值和null键
    null值和null键是Java中非常常见的情况,HashMap和Hashtable在处理null值和null键上也有所不同。

    HashMap
    HashMap中可以存储null值和null键,但是要注意,当使用null作为键时,由于无法调用null的hashCode()方法,因此只能将其放在哈希表的第一个位置,它们是无序的。对于null值,因为可以使用null调用equals()方法,所以可以用作值。

    Hashtable
    Hashtable不允许存储null值和null键,否则将会抛出NullPointerException异常。

    4. 性能比较
    HashMap和Hashtable在性能上也有所不同,下面我们来具体分析一下。

    HashMap
    由于HashMap采用链表和红黑树的数据结构,可以更好地处理哈希冲突,因此HashMap的查找、插入和删除操作都是常数时间O(1),它的性能相对于Hashtable更高

    Hashtable
    Hashtable没有使用红黑树解决哈希冲突,而且所有方法都加了同步锁,相对于HashMap而言,Hashtable的效率比较低。另外,由于Hashtable不支持null键和null值,因此对其进行操作时要额外小心。Hashtable的查找、插入和删除操作平均时间复杂度为O(1),但是在极端情况下,因为哈希冲突的原因,可能会退化到O(n)。

    5. 应用场景
    根据上述的区别和特点,我们可以得出以下建议:

    如果线程安全的Map集合,并且不需要存储null键或null值,可以选择Hashtable;
    如果需要高效、非线程安全的Map集合,并且需要存储null键或null值,可以选择HashMap;
    如果需要高效、线程安全的Map集合,可以选择使用ConcurrentHashMap。
     

    concurrentHashMp与hashtable的区别:

    ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
    底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现,JDK1.8采⽤的数据结构跟 HashMap1.8 的结构⼀样,数组+链表/红⿊⼆叉树。 Hashtable 和JDK1.8 之前的 HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的;

    实现线程安全的⽅式(重要): ① 在 JDK1.7 的时候, ConcurrentHashMap (分段锁)对整个桶数组进⾏了分割分段( Segment ),每⼀把锁只锁容器其中⼀部分数据,多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,并发控制使⽤ synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap ,虽然在 JDK1.8 中还能看到Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable (同⼀把锁) :使⽤ synchronized 来保证线程安全,效率⾮常低下。当⼀个线程访问同步⽅法时,其他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤ put 添加元素,另⼀个线程不能使⽤ put 添加元素,也不能使⽤ get,竞争会越来越激烈效率越低。
     

    【精选】面试题:ConcurrentHashMap 和 Hashtable 的区别_穿城大饼的博客-CSDN博客

    (3)Collection包结构,与Collections的区别泛型常用特点(待补充)

    Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set;Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

    “泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如

    List iniData = new ArrayList<>()

    使用泛型的好处?

    以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。

    (4)说说List,Set,Map三者的区别 Array与ArrayList有什么不一样? Map有什么特点

    (5)集合类存放于Java.util包中,主要有几种接口什么是list接口

    (6)说说ArrayList(数组) Vector(数组实现、线程同步)说说LinkList(链表)

    Vector跟ArrayList其实一样一样的。包括扩容机制。只不过操作Vector的方法都是被synchronized修饰了的同步方法,不管是 add get remove 还是其他,所以,它的速度肯定会慢的,现在也慢慢不建议用了。

    (7)什么Set集合 HashSet ( Hash表)

    HashSet集合概述和特点【应用】
    • 底层数据结构是哈希表

    • 存取无序

    • 不可以存储重复元素

    • 没有索引,不能使用普通for循环遍历

    Set集合实现去重的原理是

    先看元素的HashCode()值也就是哈希值是否相同,再看equals方法返回的结果

    如果哈希值不同,则说明元素不相同,将元素添加到集合中

    如果哈希值相同,则继续判断equals方法, 返回true则存入, false则丢弃

    TreeSet

    • 有序(底层在添加元素的时候会自动进行排序)
    • 可以对元素进行排序(自然排序(默认)、定制排序)

    底层是红黑树

    红黑树【理解】
    红黑树的特点

    平衡二叉B树

    每一个节点可以是红或者黑

    红黑树不是高度平衡的,它的平衡是通过"自己的红黑规则"进行实现的

    红黑树的红黑规则有哪些

    • 每一个节点或是红色的,或者是黑色的
    • 根节点必须是黑色
    • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
    • 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)
    • 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

    三、java多线程面试题

    (1)JAVA阻塞队列原理

    Lock 和 Condition 类

    Lock 和 Condition 类也是Java并发编程提供的新特性。Condition类提供了与wait()、notify()、notifyAll()类似的机制,并且更加灵活和可控。Condition类提供了两个主要的方法:await() 和 signal() 方法。

    await() 方法会释放所持的锁并将当前线程挂起,使它进入等待队列中,直到被signal()或signalAll() 唤醒。signal() 方法会唤醒正处于等待队列中的一个线程,如果有多个线程等待,则由系统自行决定唤醒哪个线程。

    在BlockingQueue中,ReentrantLock 和 Condition 类共同实现了 await() 和 signal() 方法。put() 方法获取锁后,如果队列已满,则调用notFull.await() 将该线程挂起;take() 方法则获取锁,如果队列为空,则调用 notEmpty.await() 挂起线程。当有线程往队列中放数据或者从队列中取数据时,都会调用 notFull.signal() 或 notEmpty.signal() 操作唤醒阻塞线程。

    (2)CyclicBarrier、CountDownLatch、Semaphore的用法

    三者的区别

    CountDownLatch可以理解为是同步计数器,作用是允许一个或多个线程等待其他线程执行完成之后才继续执行,比如打dota、LoL或者王者荣耀时,创建了一个五人房,只有当五个玩家都准备了之后,游戏才能正式开始,否则游戏主线程会一直等待着直到玩家全部准备。在玩家没准备之前,游戏主线程会一直处于等待状态。如果把CountDownLatch比做此场景都话,相当于开始定义了匹配游戏需要5个线程,只有当5个线程都准备完成了之后,主线程才会开始进行匹配操作。

    CountDownLatch实际完全依靠AQS的共享式获取和释放同步状态来实现,初始化时定义AQS的state值,每调用countDown实际就是释放一次AQS的共享式同步状态,await方法实际就是尝试获取AQS的同步状态,只有当同步状态值为0时才能获取成功。

    CyclicBarrier可以理解为一个循环同步屏障,定义一个同步屏障之后,当一组线程都全部达到同步屏障之前都会被阻塞,直到最后一个线程达到了同步屏障之后才会被打开,其他线程才可继续执行。

    CyclicBarrier是循环同步屏障,同步屏障打开之后立马会继续计数,等待下一组线程达到同步屏障。而CountDownLatch仅单次有效。

    从源码可以看出CyclicBarrier的实现原理主要是通过ReentrantLock和Condition来实现的,主要实现流程如下:

    1、创建CyclicBarrier时定义了CyclicBarrier对象需要达到的线程数count

    2、每当一个线程执行了await方法时,需要先通过ReentrantLock进行加锁操作,然后对count进行自减操作,操作成功则判断当前count是否为0;

    3、如果当前count不为0则调用Condition的await方法使当前线程进入等待状态;

    4、如果当前count为0则表示同步屏障已经完全,此时调用Condition的signalAll方法唤醒之前所有等待的线程,并开启循环的下一次同步屏障功能;

    5、唤醒其他线程之后,其他线程继续执行剩余的逻辑。

    Semaphore字面意思是信号量,实际可以看作是一个限流器,初始化Semaphore时就定义好了最大通行证数量,每次调用时调用方法来消耗,业务执行完毕则释放通行证,如果通行证消耗完,再获取通行证时就需要阻塞线程直到有通行证可以获取。

    Semaphore的释放许可证实际就是调用AQS的共享式释放同步状态的方法,然后调用内部类Sync重写的AQS的tryReleaseShared方法,实现逻辑就是不停CAS设置state的值加上需要释放的数量,直到CAS成功。这里少了AQS的逻辑解析,有兴趣可自行回顾AQS的共享式释放同步状态的实现原理。

    (4)如何在两个线程之间共享数据

    在两个线程间共享数据,可以使用以下方法:

    1. 全局变量:将需要共享的数据定义为全局变量,两个线程都可以访问和修改该变量。
    2. 传递参数:将需要共享的数据作为参数传递给两个线程中的一个或者多个函数。
    3. 锁定机制:使用线程锁来保证同时只有一个线程访问共享数据。Python 中的 threading 模块提供了锁机制,比如使用 Lock、RLock、Semaphore 等。
    4. 使用队列:创建一个队列,两个线程中的一个生产数据并将数据放入队列中,另一个线程则消耗这些数据。
    5. 使用共享内存:共享内存是多个进程或线程间共享数据的一种方式。Python 提供了 multiprocessing 模块中的 Value 和 Array 类来实现共享内存。

    (5)Java中用到的线程调度进程调度算法

    (6)死锁

    产生死锁的原因:

    1. 竞争资源

      产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)

      产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

    2. 进程间推进顺序非法

      若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

    产生死锁的必要条件:

      互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。

      请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。

      不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。

      环路等待条件:在发生死锁时,必然存在一个进程——资源的环形链。

    预防死锁的方法:

      资源一次性分配:一次性分配所有资源,这样就不会再有请求了(破坏请求条件)

      只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏请保持条件)

      可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)

      资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

    (7) wait/notify 与 await与signal 的区别

    6.condition条件变量提高线程通信效率_然而,然而的博客-CSDN博客

    Condition
    Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。

    Condition类能实现synchronized和wait、notify搭配的功能,另外比后者更灵活,Condition可以实现多路通知功能,也就是在一个Lock对象里可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度线程上更加灵活。

    而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在这个对象上。线程开始notifyAll时,需要通知所有的WAITING线程,没有选择权,会有相当大的效率问题。

    一种比较流行的说法就是,除非深思熟虑,否则尽量使用 notifyAll() 。
     

    举个例子,在多生产者消费者模式下,待处理的数据队列只有一条数据了,理想场景下,消费者在处理掉一条数据后,理论上应该唤醒生产者再生产一条新的待消费数据。可是notify()是随机唤醒,也就是它可能会唤醒一个消费者线程,这个消费者线程,发现没有待处理的数据,此时条件不满足,又主动进入等待队列。

    这就是多个Condition的强大之处。

    假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。

    那么假设只有一个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间!Synchronized的notify也会存在类似的问题,不能精准唤醒。

    四、java反射面试题

    (1)除了使用new创建对象之外,还可以用什么方法创建对象?

    java创建对象的方式

    1. new关键字
    2. Class.newInstance
    3. Constructor.newInstance
    4. Clone方法
    5. 反序列化

    (2)Java反射创建对象效率高还是通过new创建对象的效率高?

    Java反射和使用new关键字创建对象的效率是有差距的。使用new关键字创建对象是直接调用构造函数进行实例化,效率较高。而Java反射是在运行时动态地加载类、调用方法或获取字段的机制,它需要进行额外的类加载、方法解析和访问权限检查等操作,因此比直接使用new关键字创建对象更加耗时。

    (3)java反射的作用

    Java中的反射是指在运行时动态地获取类的信息以及操作类的成员变量、方法和构造函数的能力。Java反射机制允许程序在运行时获取类的内部信息,包括类的名称、父类、实现的接口、类的字段和方法等,并且可以通过反射机制调用类中的方法和构造函数,访问或修改类中的成员变量。通过反射,可以在运行时动态地创建对象、调用方法、修改变量值等,这样就可以实现一些动态性很强的功能,例如在运行时动态地加载类、动态地调用方法等。反射机制在一些框架和工具库中得到广泛应用,如Spring框架、Hibernate ORM框架等。

    (4)哪里会用到反射机制?

    框架与插件机制:反射可以使框架和插件机制更加灵活,它可以让程序在运行时动态地加载和调用类,这样就可以实现动态扩展和更新功能。例如,常见的Java框架,如Spring、Hibernate和JUnit等,都使用了反射机制来实现依赖注入、ORM映射和单元测试等功能。

    序列化和反序列化:反射可以在对象序列化和反序列化中使用,序列化是将对象转换为字节流的过程,而反序列化是将字节流转换为对象的过程。通过反射,可以在运行时获取对象的类型信息,并且在序列化和反序列化过程中使用这些信息来生成字节码,从而实现对象的序列化和反序列化。

    动态代理:反射可以实现动态代理,动态代理是一种设计模式,它允许程序在运行时动态地创建一个代理对象,代理对象可以在不改变原始对象的情况下为其提供额外的功能。通过反射,可以动态地创建代理对象,并在代理对象上调用方法,从而实现动态代理的功能。

    单元测试:反射可以在单元测试中使用,它可以让测试代码动态地调用被测试代码中的私有方法和属性,从而实现对代码的全面测试。在JUnit等单元测试框架中,就有很多使用反射机制的测试工具,如ReflectionTestUtils和Whitebox等。

    (5)反射的实现方式:

    任何一个类都是Class的实例对象,这个实例对象有三种表示方式:

    任何一个类都有一个隐含的静态成员变量class

    第一种表示方式: 

    Class c1=Foo.class;//注意Class要大写C
    

     第二种表达方式:已经知道该类的对象通过getClass方法

    Class c2 = foo1.getClass(); //c1 ,c2 表示了Foo类的类类型(class type)

    第三中表达方式:
    ​​​​​​​

    1. Class c3=null;
    2. try
    3. {
    4. c3=Class.forName("Foo的相对路径");
    5. }
    6. catch (Exception e)
    7. {
    8. e.printStackTrace();
    9. }
    10. //可以使用newInstance方法创建Foo的实例对象
    11. try
    12. {
    13. Foo foo=(Foo)c3.newInstance();
    14. //(Foo)Foo是Foo的接口
    15. foo.print();
    16. }catch (Exception e){e.printStackTrace();
    17. }


    ​​​​​ 通过Foo的接口来找到Foo的类类型 然后通过newInstance()方法来初始化一个类 生成一个实例对象。 使用newInstance方法时必须保证: 这个类已经加载 这个类已经连接了。 完成这俩步的正是class的静态方法forName()方法 这个方法调用了启动类加载器(java api的加载器) 动态加载类 try{ Class c=class.forName(args[0]); 类名 表示名 =(接口)c.newInstance(); 表示名.方法(); }catch(Exception e){e.printStrckTrace}

    最常用的是第一种方式.

    但如果只获得一个字符串,例如获得String类对应的Class对象,则不能使用String.class方式,而是使用Class.forName("java.lang.String")。注意:如果要想获得基本数据类型的Class对象,可以使用对应的打包类加上.TYPE,例如,Integer.TYPE可获得int的Class对象,但要获得Integer.class的Class对象,则必须使用Integer.class。在获得Class对象后,就可以使用表13.4中的方法来取得Class对象的基本信息

    (6)实现Java反射的类

    (7)反射机制的优缺点:

    优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

    缺点:1. 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;2. 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

    (8)Java反射API

    1. 访问Class对应的类包含的构造方法

    方法    说明
    Constructor getConstructor(Class[] params)    返回此Class对象所包含的指定的public构造方法,params参数是按声明顺序指定该构造方法参数类型的Class对象的一个数组。构造方法的参数类型与params所指定的参数类型匹配。如:Constructor co = s.getConstructor(String.class,Integer.class); //s是某个Class对象


    Constructor[] getConstructors(Class[] params)    返回此Class对象所包含的所有public构造方法


    Constructor getDeclaredConstructor(Class[] params )    返回此Class对象所包含的指定的构造方法,与访问级别无关(可访问paivate构造方法)


    Constructor[] getDeclaredConstructors( )    返回此Class对象所包含的所有构造方法,与访问级别无关(可访问paivate构造方法)


    2. 访问Class对应的类包含的方法

    方法    说明
    Method getMethod(String name, Class[] params)    返回此Class对象所包含的指定的public方法,name参数用于指定方法名称,params参数是按声明顺序指定该方法参数类型的Class对象的一个数组。构造方法的参数类型与params所指定的参数类型匹配。如:Method m = s.getMethod(“info”,String.class,Integer.class); //s是某个Class对象


    Method[] getMethods()    返回此Class对象所包含的所有public方法


    Method getDeclaredMethod(String name, Class[] params)    返回此Class对象所包含的指定的方法,与访问级别无关(可访问paivate方法)


    Method[] getDeclaredMethods( )    返回此Class对象所包含的所有方法,与访问级别无关(可访问paivate方法)


    3. 访问Class对应的类包含的属性

    方法    说明
    Field getField(String name)    返回此Class对象所包含的指定的public属性,name参数用于指定属性名称,如:Field age1 = s.getField(“age”); //s是某个Class对象,age为属性名


    Field[] getFields()    返回此Class对象所包含的所有public属性


    Field getDeclaredField(String name)    返回此Class对象所包含的指定的public属性,与访问级别无关(可访问paivate属性)


    Field[] getDeclaredFields( )    返回此Class对象所包含的所有属性,与访问级别无关(可访问paivate属性)


    4. 访问Class对应的类包含的注解

    方法    说明
    < A extends Annotation > A getAnnotation(Class annotationClass)    试图获取该Class对象所表示类上的所有注解,如果该类型的注解不存在则返回null。annotationClass参数对应于注解类型的Class对象
    Annotation[] getAnnotations()    返回此类上的所有注解
    Annotation[] getDeclaredAnnotations()    返回直接存在于此类上的所有注解

    5. 访问Class对应的类包含的其他信息

    方法    说明
    Class[] getDeclaredClasses()    返回Class对应的类所在的外部类
    Class[] getDeclaringClasses()    返回Class对应的类所在的内部类
    Class[] getInterfaces()    返回Class对应的类所实现的全部接口
    Class[] getPackage()    返回Class对应的类所在的包
    Class[] getName()    返回Class对应的类的名称
    Class[] getSimpleName()    返回Class对应的类的简称

     

  • 相关阅读:
    关于js_表单事件的介绍与使用
    个人博客项目 - 测试报告
    c语言用json解析库(jansson)检测字符串是否是json格式的数据
    如何优雅的备份MySQL数据?看这篇文章就够了
    JMETER与它的组件们
    centos7下部署oracle 11g
    基于STM32+腾讯云IO+微信小程序设计的混凝土智能养护系统
    java毕业设计钢材销售平台登录mybatis+源码+调试部署+系统+数据库+lw
    next-key lock案例
    新时代博物馆运营的智能管理与控制
  • 原文地址:https://blog.csdn.net/xyr05288/article/details/133925972