1、Java语言特点/优势
1.1、什么是面向对象编程
2、Java的八种基本数据类型
2.1、为什么要有基本数据类型的封装类
2.2、Java自动装箱和拆箱
笔试题-1
笔试题-2
2.3、为什么浮点数运行时,会有丢失精度的风险?
2.4、补充知识:
3、重载和重写的区别
重载
重写
4、equals与==的区别
==
equals
5、hashCode的作用
5.1、如果hashCode相同,equals方法一定相同吗?
5.2、如果equals相同,hashCode方法一定相同吗?
6、String、StringBuffer、StringBuilder 的区别?
7、Java中接口和抽象类区别
8、Object类中有哪些方法?
8.1、说说深拷贝和浅拷贝?
9、Excption与Error包结构
运行时异常
被检查异常
错误
1、Java语言特点/优势
-
简单易学,有丰富的类库
-
面向对象(Java中最重要的特性,让程序耦合更低,内聚更高)
-
平台无关性(JVM是Java跨平台性的使用根本)
-
支持多线程
1.1、什么是面向对象编程
一般个人感觉可以结合面向对象和面向过程的区别来回答?
-
面向过程:是一种分析和解决问题的步骤,采用函数,一步一步的实现,性能较高。
-
强调的是步骤与操作
-
-
面向对象:把一个问题分解为多个对象,建立对象目的不是为了解决某一个步骤(不类似面向过程编程中的函数),而是为了描述一个事物在整个问题过程中产生的行为。然后最后通过调控多个对象,完成某一个问题。
-
强调的是对象和数据抽象,以及通过这些抽象来表达和控制对象的行为和相互作用。
-
面向对象有封装、继承、多态的特 性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要 低。
-
继承:继承是从已有类得到继承信息创建新类的过程
-
封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
-
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应
-
-
2、Java的八种基本数据类型
一般来说就是说出8种基本的数据类型出来就行。
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | - | false | Boolean |
char | 2 | \u0000(null) | Character |
2.1、为什么要有基本数据类型的封装类
-
空值处理:基本数据类型必须有一个具体的值,不能表示“空”或“未定义”的状态。而封装类可以使用
null
值 -
方法丰富性:封装类提供了丰富的静态方法和实例方法,如解析字符串为数值、格式化输出、比较等,增加了编程的便利性。
-
自动装箱与拆箱:Java支持基本类型与其封装类之间的自动转换,称为装箱(boxing)和拆箱(unboxing)。这使得在使用封装类时仍能保持一定的便捷性,同时享受封装类带来的好处。
2.2、Java自动装箱和拆箱
-
装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的 valueOf(int) 方法
-
拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的 intValue方法
笔试题-1
给出下面程序的结果:
public class Demo1 {public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;Integer i3 = 200;Integer i4 = 200;
System.out.println(i1==i2);System.out.println(i3==i4);
}
}
运行结果:
true
false
分析:
直接说重点了,这个要看Integer类中的valueOf方法
@IntrinsicCandidatepublic static Integer valueOf(int i) {//在IntegerCache设置的上限和下限之间的话们直接返回这个值if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
再看IntegerCache类,这是一个私有内部类:
private static class IntegerCache {// 定义缓存范围的下限,默认为-128static final int low = -128;// 定义缓存范围的上限,可以通过系统属性"java.lang.Integer.IntegerCache.high"配置static final int high;// 存储整数对象的静态缓存数组static final Integer[] cache;// 档案缓存,用于从归档中加载的缓存static Integer[] archivedCache;
// 静态初始化块,用于初始化IntegerCache的配置static {// 初始化high值,默认为127int h = 127;// 尝试从系统属性获取用户设置的缓存上限String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {// 更新high值,确保它在合理范围内h = Math.max(parseInt(integerCacheHighPropValue), 127);// 确保数组大小不超过Integer.MAX_VALUEh = Math.min(h, Integer.MAX_VALUE - (-low) - 1);} catch(NumberFormatException nfe) {// 如果属性值不能转换为整数,则忽略}}high = h;
// 尝试从归档中加载已存在的缓存CDS.initializeFromArchive(IntegerCache.class);// 计算缓存大小int size = (high - low) + 1;
// 如果归档缓存存在且足够大,则使用归档缓存if (archivedCache == null || size > archivedCache.length) {// 创建新的整数对象数组Integer[] c = new Integer[size];// 初始化数组元素int j = low;for(int i = 0; i < c.length; i++) {c[i] = new Integer(j++);}// 更新归档缓存archivedCache = c;}// 将归档缓存指派给实际使用的缓存cache = archivedCache;// 确保[-128, 127]范围内的整数已被缓存(遵循JLS7规范)assert IntegerCache.high >= 127;}
// 私有构造器,防止外部实例化private IntegerCache() {}
}
总的来说:
-
就是Integer内部采用了享元设计模式(类似常量池),在【-128,127】范围内,如果对象已经被创建过,直接返回。
-
Integer
类通过一个静态内部类IntegerCache
来实现这一特性,它在类加载时就初始化了一个包含指定范围(默认是-128到127)的Integer
对象数组。
笔试题-2
public class Demo1 {public static void main(String[] args) {Double i1 = 100.0;Double i2 = 100.0;Double i3 = 200.0;Double i4 = 200.0;
System.out.println(i1==i2);System.out.println(i3==i4);}
}
结果:
false
false
分析:
-
如
Integer
、Long
、Short
、Byte
等类确实利用了这种设计模式来优化性能。 -
Double
和Float
这类浮点数封装类由于其表示的数值范围更广,精度要求更高,且在许多情况下不太适合简单的值共享,所以通常没有实现类似的缓存机制。
2.3、为什么浮点数运行时,会有丢失精度的风险?
这个跟计算机使用二进制存储数据有关,那些不能被二进制精确表示的分数(如,0.1,转换为二进制是无限循环小数),在存储时必须采用近似处理,导致精度损失。
2.4、补充知识:
-
.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间, 必须通过实例化开辟数据空间之后才可以赋值。(可以去了解JVM对象创建时候,对栈与堆内存分配)
public class OverloadExample {
void print(int a) {System.out.println("Printing an integer: " + a);}
void print(String s) {System.out.println("Printing a string: " + s);}
}
3、重载和重写的区别
重载
先给出示例:
public class OverloadExample {void print(int a) {System.out.println("Printing an integer: " + a);}void print(String s) {System.out.println("Printing a string: " + s);}
}
发现有两个相同名称的方法,但是形参不同。这就是重载。
重载:指的是在同一个类中可以有多个同名方法,只要它们的参数列表(参数类型、参数个数或参数顺序)不同
重写
class Parent {void display() {System.out.println("Parent's display method");}
}
class Child extends Parent {// 重写父类的display方法@Overridevoid display() {System.out.println("Child's display method");}
}
上述,Child类继承Parent父类,对父类的display方法进行方法名、形参不动、方法体内部的修改,这就是重写。
重写发生在子类继承父类的情况下,子类可以提供一个与父类方法签名完全相同的方法实现,即方法名、返回类型、参数列表必须完全一致,但访问权限不能比父类更严格(可以相同或更宽)
对于这个区别,可以根据字面意思来直接的区分。
4、equals与==的区别
==
-
== 对比的是变量内存中存放的对象的内存地址(堆)
-
作用:用来判断两个对象地址是否相同。比较的是真正意义上的指针操作
equals
-
比较的是两个对象内存是否相等
-
由于所有类都是继承java.lang.Object类,所有使用所有对象,如果没堆该方法进行覆盖的话,还是调用Object中的方法,而Object类中的equals方法返回的是==的判断
5、hashCode的作用
-
该方法作用可以理解为:返回的就是根据对象的内存地址换算出的一个值。
5.1、如果hashCode相同,equals方法一定相同吗?
这个问题问的是对hash算法的理解。
其实hash算法,有时候会发生hash冲突的情况,导致两个对象的hash码是相同的。
所以说:当两个对象的hashCode值相同,并不代表这两个对象是相同的。
5.2、如果equals相同,hashCode方法一定相同吗?
如果equals()
相同,代表两个对象的hash码是相同的,则代表hashCode()
应该相同。
这也同时提出了,如果你重写了equals()
方法,通常也应该重写hashCode()
方法,以保持两者的一致性。
6、String、StringBuffer、StringBuilder 的区别?
这3个类都是用于处理字符串的类,但是它们之间存在一些区别:
-
不可变性与可变性:
-
String
:字符串是不可变的。一旦创建,其内容就不能更改。对字符串的任何修改操作实际上都会创建一个新的字符串对象。 -
StringBuffer
:是可变的字符串对象,意味着可以在原对象上修改字符串内容,不会创建新的对象。线程安全,适合在多线程环境中使用。 -
StringBuilder
:同样可变,可以在原对象上修改字符串内容,但它是非线程安全的,因此在单线程环境下性能较StringBuffer
更好。
-
-
线程安全:
-
String
:因为是不可变的,所以在多线程环境下的读取是安全的,但不涉及修改操作。 -
StringBuffer
:为保证线程安全,其方法大多通过synchronized
关键字修饰,这使得在多线程访问时能够同步,但降低了单线程情况下的性能。 -
StringBuilder
:未进行线程同步,因此在单线程中使用效率更高,但在多线程环境下不安全。
-
-
性能:
-
String
:频繁的修改会导致大量的临时字符串对象创建,消耗更多内存和CPU时间。 -
StringBuffer
:由于同步机制,相比StringBuilder
在执行速度上较慢。 -
StringBuilder
:由于没有同步开销,是三者中在单线程环境下的性能最优选择,特别是在做字符串拼接等操作时。
-
-
使用场景:
-
String
:适用于字符串常量或不经常改变的字符串操作。 -
StringBuffer
:适用于多线程环境下,需要对字符串进行修改的情况。 -
StringBuilder
:适用于单线程环境下,大量字符串操作,如拼接、替换等。
-
7、Java中接口和抽象类区别
Java中的接口(Interface)和抽象类(Abstract Class)都是用于实现抽象化的概念,帮助设计更为灵活和可扩展的代码结构。
区别如下:(个人觉得最后一点是它们最重要的区别)
-
成员方法实现:
-
接口:在Java 8之前,接口中的所有方法默认都是抽象的,没有方法体。但从Java 8开始,接口可以包含默认方法(带有默认实现)和静态方法,但仍主要是为了定义行为契约。
-
抽象类:可以包含抽象方法(没有实现)以及具体实现的方法。
-
-
多重继承:
-
接口:支持多重继承,即一个类可以实现多个接口。
-
抽象类:不支持多重继承,一个类只能继承一个抽象类。
-
-
成员变量:
-
接口:只能有静态常量(public, static, final),从Java 9开始还可以有私有方法和私有静态方法,主要用于辅助默认方法的实现。
-
抽象类:可以有各种类型的成员变量(实例变量、静态变量等),并且这些变量可以有不同的访问修饰符。
-
-
访问修饰符:
-
接口:接口中的方法默认为public(即使不写也是public),且从Java 9开始,接口可以有private方法。
-
抽象类:方法可以是private、protected、public或包访问权限,提供了更灵活的可见性控制。
-
-
构造方法:
-
接口:没有构造方法。
-
抽象类:可以有构造方法,通常用于被子类调用以初始化抽象类中的成员变量。
-
-
设计意图:
-
接口:强调“能做什么”(What to do),定义了一组行为规范,是一种契约,关注的是行为的规范。
-
抽象类:不仅定义了“能做什么”,还可能包含部分“怎么做”的实现,关注的是对象的共有特性,是一种模板设计模式。
-
8、Object类中有哪些方法?
对象复制与构造
-
protected Object clone()
创建并返回此对象的一个浅拷贝。子类可以覆盖此方法以实现深拷贝逻辑。 -
这个clone方法可以配合Cloneable接口重写clone方法实现深拷贝
等价性判断
-
boolean equals(Object obj)
检查某个其他对象是否与当前对象逻辑上“相等”。通常需要根据对象的内容而非引用进行比较,需重写以实现自定义逻辑。
生命周期管理
-
protected void finalize()
垃圾回收器在准备释放对象占用的内存之前调用的方法,可用来执行清理操作。现代Java实践中不鼓励使用此方法。
类信息获取
-
Class<? extends Object> getClass()
返回对象的实际运行时类,用于反射操作。
哈希值计算
-
int hashCode()
返回对象的哈希码值,用于哈希表(如HashMap
)中快速查找。如果两个对象通过equals()
判断相等,它们的哈希码也必须相等。
线程通信
-
void notify()
唤醒在此对象监视器上等待的一个线程,常用于同步控制。 -
void notifyAll()
唤醒在此对象监视器上等待的所有线程。 -
void wait()
使当前线程等待,直到其他线程通过调用notify()
或notifyAll()
唤醒它。 -
void wait(long timeout)
使当前线程等待,直到被唤醒或超时时间到达。 -
void wait(long timeout, int nanos)
更精确的等待控制,允许设置纳秒级别的超时,但实际精度受系统限制。
文本表示
-
String toString()
返回对象的字符串表示形式,便于打印或显示。通常应覆盖此方法以提供有意义的信息。
8.1、说说深拷贝和浅拷贝?
浅拷贝和深拷贝的主要区别在于处理对象内部引用类型成员的方式。
-
浅拷贝:拷贝对象的基本数据类型成员变量值,但对于引用类型成员变量(如对象、数组等),它仅拷贝引用而不是实际的对象内容,导致原对象和拷贝对象共享同一块内存地址的引用类型数据。因此,如果修改了拷贝对象或原对象中的引用类型成员,另一方也会受到影响
-
浅拷贝更适合那些只包含基本数据类型或者不需要完全独立拷贝的场景
-
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
-
-
深拷贝:对一个对象进行完全独立的拷贝,包括其所有的基本数据类型成员和引用类型成员。对于引用类型,它会递归地创建这些引用对象的新实例,确保原对象和拷贝对象之间完全独立,互不影响。即使修改了拷贝对象中的引用类型成员,也不会影响到原对象的对应数据。
-
深拷贝则用于需要完全独立复制对象状态,特别是包含复杂对象结构时。
-
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新 的内存,
-
总的来说:浅拷贝与深拷贝的关键区别在于处理对象内部的引用类型成员时,前者共享引用,后者创建全新的实例。
9、Excption与Error包结构
Java可抛出(Throwable)的结构分为三种类型:
-
被检查的异常(CheckedException)
-
运行时异常 (RuntimeException)
-
错误(Error)。
运行时异常
-
定义:RuntimeException及其子类都被称为运行时异常
-
特点:Java编译器在编译时候不会检查它
-
举例:编译器不会强调它要被throws声明抛出;或也不会需要使用”try catch“进行捕获
-
-
常见的五种运行时异常:
-
ClassCastException(类转换异常)
-
IndexOutOfBoundsException(数组越界)
-
NullPointerException(空指针异常)
-
ArrayStoreException(数据存储异常,操作数组是类型不一致)
-
BufferOverflowException
-
被检查异常
-
定义:Exception类以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异 常。
-
特点:编译器会对此进行检查。
-
举例:要么通过throws声明抛出;要么通过”try catch“将其抛出
-
-
被检查的异常适用于那些不是因程序引起的错误情况
错误
-
定义 : Error类及其子类。
-
特点:和运行时异常一样,编译器不会对其进行错误检查
-
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修 复这些错误的。例如,VirtualMachineError就属于错误。出现这种错误会导致程序终止运行。
10、说说你理解的Java反射
1、首先我们要知道什么是反射(定义):
反射就是在程序运行时候,获取任意一个类的所有属性与方法,并可以调用它的任意一个方法。
Java中,只要给定类的名字,都可通过反射机制来获得类的所有信息。
总的来说:Java的反射就是在运行时动态获取和操作类信息,使得程序能更加灵活的处理对象。
2、反射的使用场景:在一些框架中,大量的使用了反射机制。比如说jdbc中:
Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类
3、反射的实现方式(获取Class对象的4种方式):
-
Class.forName(“类的路径”);
-
类名.class
-
对象 名.getClass()
-
基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
4、实现Java反射的类:
-
Class:表示正在运行的Java应用程序中的类和接口 注意: 所有获取对象的信息都需要Class类 来实现。
-
Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
-
Constructor: 提供关于类的单个构造方法的信息以及它的访问权限
-
Method:提供类或接口中某个方法的信息
5、反射的优缺点:
-
优点:
-
运行时动态获取类信息,提供灵活性
-
-
缺点:
-
使用反射,性能较低:需要解析字节码,将内存中的对象进行解析
-
相对不安全,破坏了类的封装性(获取了类的私有方法和属性
-