你是如何理解OOP面向对象的?
面向对象编程(OOP)是一种编程范式,它将程序设计构建在对象的概念上,对象是数据的封装,以及操作这些数据的方法。
在Java中,OOP的核心概念包括封装、继承、多态和抽象。
在面试中,当被问及面向对象编程(OOP)的理解时,我通常会从以下几个方面进行解释:
封装:封装是OOP的基本原则之一,它指的是将数据(属性)和行为(方法)封装在一个单元中,并对外部隐藏对象的内部实现细节。通过封装,可以实现数据的安全性和保护,同时提供统一的接口供外部访问。
继承:继承允许一个类(子类)继承另一个类(父类)的属性和方法,从而可以重用已有类的代码并扩展其功能。继承可以建立类之间的层次关系,提高代码的可重用性和扩展性。
多态:多态是指同一个方法调用在不同对象上有不同的行为。通过多态,可以实现方法的重载和方法的重写,提高代码的灵活性和可扩展性。
抽象:抽象是将对象的共同特征提取出来形成类的过程,通过抽象可以隐藏对象的复杂性,只展示对象的关键特征。抽象类和接口是实现抽象的方式,可以定义规范和约束,促使代码的规范化和标准化。
你是怎么理解多态?
多态是面向对象编程的重要概念,它指的是同一操作作用于不同的对象上时,可以产生不同的行为。在Java中,多态可以通过方法重写(Override)和方法重载(Overload)来实现。方法重写指的是子类可以重写父类的方法,以改变其行为,而方法重载指的是在同一个类中可以定义多个同名方法,但参数列表不同。多态提高了代码的灵活性和可维护性,使得代码更容易扩展和重用。
一句话( 同一个行为具有多个不同表现形式或形态的能力。父类引用指向子类对象,例如 List<String> list = new ArrayList<String>();就是典型的一种多态的体现形式。)
重载与重写有什么区别?
方法重载(Overload)是指在同一个类中定义多个方法,它们具有相同的名称但参数列表不同。重载的方法可以有不同的返回类型,但是方法的参数类型、个数或顺序必须不同。编译器根据调用时传入的参数类型和数量来确定调用哪个重载方法。
方法重写(Override)是指子类重新定义(覆盖)父类的方法,使得子类中的方法行为与父类中的方法不同。重写的方法具有相同的方法签名,包括方法名、参数列表和返回类型。重写的方法在运行时由对象的实际类型决定,而不是由引用变量的类型决定。
接口与抽象类的区别?
方法实现:
抽象类可以包含具体方法的实现,而接口中的方法都是抽象的,不包含实际的实现代码。
抽象类可以有构造方法,而接口不能有构造方法。
多继承:
一个类只能继承一个抽象类,但可以实现多个接口。
这意味着,如果一个类需要继承其他类的实现,那么它只能继承一个抽象类,但可以实现多个接口。
成员变量:
抽象类可以包含成员变量,而接口中的成员变量默认都是
public static final
类型的常量,不能被修改。设计目的:
抽象类用于描述一种“is-a”的关系,即子类是父类的一种特殊类型。
接口用于描述一种“has-a”的关系,即类具有某种特定的能力或行为。
修饰符:
抽象方法可以有public、protected和default这些修饰符、接口:只能是public
sleep和wait在线程里有什么区别?
调用方式:
sleep() 是Thread类的静态方法,可以直接通过 Thread.sleep() 调用。
wait() 是Object类的实例方法,需要在同步代码块或同步方法中通过对象的 monitor 调用,如 synchronized(obj) { obj.wait(); }。
使用位置:
sleep() 可以在任何地方使用,不需要在同步代码块中。
wait() 必须在同步代码块中使用,因为调用 wait() 方法会释放对象的锁。
释放锁:
sleep() 方法不会释放锁,线程在调用 sleep() 后仍然持有锁。
wait() 方法会释放对象的锁,使得其他线程可以进入同步代码块或同步方法。
唤醒方式:
sleep() 方法会在指定的时间后自动唤醒,或者可以被其他线程中断。
wait() 方法需要通过 notify() 或 notifyAll() 方法来唤醒,即等待其他线程通知唤醒。
异常处理:
sleep() 方法在线程睡眠过程中被中断会抛出 InterruptedException 异常。
wait() 方法在等待过程中被中断会抛出 InterruptedException 异常,并且需要在 catch 块中重新设置中断状态。
整理一下:
sleep方法:
属于Thread类中的方法;会导致程序暂停执行指定的时间,让出cpu执行权给其他线程,但是他的监控状态依然保持着,当指定时间到了之后,又会自动恢复运行状态;在调用sleep方法的过程中,线程不会释放对象锁。(只会让出CPU,不会导致锁行为的改变)
wait方法:
属于Object类中的方法;在调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify方法后本线程才进入对象锁定池准备。获取对象锁进入运行状态。(不仅让出CPU,还释放已经占有的同步资源锁)
== 和 equals的区别?
==:
基本数据类型,比较值;引用数据类型比较地址值(两个对象是否指向同一内存地址)。
equals:
Object类中的equals方法默认行为与"=="相同,比较的是对象的引用是否相等。
重写的equals用于比较两个对象的内容是否相等,即判断两个对象的值是否相等。
String能被继承吗?为什么用final修饰?
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
String 类是最常用的类之一,为了效率,禁止被继承和重写。
为了安全。String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。
String buffer和String builder区别
线程安全性:
StringBuffer中的方法大都采用了 synchronized 关键字进行修饰是线程安全的,所有的方法都经过同步处理,可以在多线程环境下安全使用。
StringBuilder是非线程安全的,不进行同步处理,不能保证在多线程环境下的安全性。
性能:
由于StringBuffer的所有方法都经过同步处理,因此在单线程环境下的性能通常比StringBuilder略差。
StringBuilder不进行同步处理,因此在单线程环境下的性能通常比StringBuffer好。
final、finally、finalize
说实话他们没有任何的关联性、可比性
final:用来修饰类、方法、变量。修饰类时,不能被继承。修饰方法时,不能被重写。修饰变量时,使用该变量时不能被改变
finally:通常情况下放在try....catch的后面构造最终执行代码块。
finalize:是Object类中的一个方法,用于在垃圾回收器回收对象之前执行一些清理操作。
在Java中,finalize方法可以被子类重写,用于在对象被销毁前执行一些清理操作,如关闭文件、释放资源等。
Object中有哪些方法
equals(Object obj):用于比较两个对象是否相等。
hashCode():返回对象的哈希码值。
toString():返回对象的字符串表示。
getClass():返回对象的运行时类。
clone():创建并返回对象的副本。
finalize():在对象被垃圾回收器回收之前调用。
notify():唤醒在该对象上等待的单个线程。
notifyAll():唤醒在该对象上等待的所有线程。
wait():导致当前线程等待,直到其他线程调用notify()或notifyAll()方法唤醒它。
wait(long timeout):导致当前线程等待指定的毫秒数。
wait(long timeout, int nanos):导致当前线程等待指定的毫秒数加上额外的纳秒数。
finalize():在对象被垃圾回收器回收之前调用
集合
Collection集合常用方法:
add() 添加元素、remove() 移除元素、removely() 根据条件移除元素、clear() 清空集合元素
contains() 判断集合中是否包含指定元素、isEmpty() 判断集合是否为空
Collection迭代器的使用:
通过集合对象获取迭代器,hasNext判断是否还有元素、next获取元素
HashMap底层源码,数据结构
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //判断当前桶是否为空,如果为空进行初始化 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //如果当前的hash槽为空,直接插入 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //如果不为空(hash冲突) else { Node<K,V> e; K k; //判断两个元素是否相等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //判断当前是否为红黑树 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //遍历链表,判断是否有相同元素 for (int binCount = 0; ; ++binCount) { //没有相同的直接插入到链表最后 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //判断个数,是否需要转换为红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //判断key是否相同,相同新值替换旧值 if (e != null) { // existing key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; return oldValue; } } return null; }
数据结构:数组+链表+红黑树(jdk1.8)
数组是key value的形式 根据对象的 key 的hash 值确定在数组那个节点。
链表作用解决hash冲突
红黑树提升查询性能,JDK 8引入了红黑树来优化HashMap,在链表长度超过一定阈值(默认为8)时,将链表转换为红黑树。红黑树是一种平衡二叉查找树,可以提高查找、插入、删除等操作的效率,时间复杂度为O(log n)。当链表长度较长时,转换为红黑树可以提高HashMap的性能。
HashMap和HashTable区别
同步性:
HashTable是线程安全的,所有的方法都是同步的,即在多线程环境下可以直接使用而不需要额外的同步措施。
HashMap是非线程安全的,不支持同步,如果需要在多线程环境下使用,可以通过Collections工具类的synchronizedMap方法来实现同步。
空键值:
HashTable不允许键或值为null,如果键或值为null,会抛出NullPointerException。
HashMap允许键和值为null,即可以存储键或值为null的条目,null可以作为键,这样的键只有一个。
继承关系:
HashTable是Hashtable类的子类,而Hashtable是Dictionary类的子类,属于JDK早期的类。
HashMap是Map接口的实现类,属于JDK 1.2引入的集合框架。
性能:
HashMap相对于HashTable来说,性能更好,因为HashTable的所有方法都是同步的,而HashMap的非同步性能更高。
在单线程环境下,HashMap的性能通常优于HashTable。
是否提供contains方法:
HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。
线程的创建方式
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程
使用线程池创建线程
线程的状态转换?
新建状态:线程对象被创建时,它处于新建状态,此时还没有调用start()方法启动线程。
就绪状态:当调用start()方法后,线程进入就绪状态,处于就绪状态的线程已经具备运行的条件,等待获取CPU的执行权。
运行状态:当线程获取CPU执行权时,进入运行状态,线程正在执行run()方法中的代码。
阻塞状态:线程在某些情况下会进入阻塞状态,如等待I/O、获取锁、调用sleep()方法等。处于阻塞状态的线程暂时停止执行,直到条件满足后重新进入就绪状态。
等待状态:线程调用Object.wait()、Thread.join()、LockSupport.park()等方法会进入等待状态。处于等待状态的线程需要其他线程的唤醒或者特定条件的满足才能重新进入就绪状态。
超时等待状态:线程调用Thread.sleep()、Object.wait(timeout)、Thread.join(timeout)等方法会进入超时等待状态。处于超时等待状态的线程会在一定时间后自动重新进入就绪状态。
终止状态:线程执行完run()方法中的代码或者调用了stop()方法后,进入终止状态。处于终止状态的线程不会再转换到其他状态。
java中有几种流
反射
反射是指程序在运行时可以访问、检测和修改它本身状态或行为的能力。在Java中,反射机制允许程序在运行时检查类、接口、字段和方法,以及在运行时实例化对象、调用方法、获取和设置字段的值等。反射提供了一种动态操作类和对象的方式,使得程序具有更大的灵活性和扩展性。
以下是反射的一些主要应用和特点:
动态加载类:
反射允许程序在运行时动态加载和实例化类,而不需要在编译时确定类的类型。
获取类信息:
反射可以获取类的构造函数、字段、方法等信息,包括访问修饰符、参数类型、返回类型。
调用方法:
反射可以在运行时动态调用类的方法,包括公有方法、私有方法、静态方法等。
访问和修改字段:
反射可以获取和设置类的字段的值,包括公有字段、私有字段等。
实现通用框架:
反射可以用于实现通用的框架和工具,如序列化、对象映射、依赖注入等。
反射是一种强大的机制,但也需要谨慎使用,因为它会降低程序的性能和安全性。在实际开发中,反射通常用于框架、库和工具的设计和实现,以及一些特定的动态操作需求。
什么是 java 序列化,如何实现 java 序列化?
Java序列化是指将Java对象转换为字节流的过程,以便可以将其存储到文件、数据库中,或者通过网络进行传输。序列化后的字节流包含了对象的数据和类型信息,可以在需要时进行反序列化,将字节流转换回原始的Java对象。
序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口
JAVA(基础篇)面试题整理
于 2024-05-29 14:55:46 首次发布