Java基础知识(二)

一、面向对象三大特征

可以看一下这篇文章:

面向对象的三大特性_TQ@的博客-CSDN博客

二、接口和抽象类

1.接口

是一种定义了一组方法和常量的规范,用于描述一个对象应该具备的行为方式。通常用于定义一组相似功能的操作,而不关心具体实现细节。它只关注对象如何和外界进行交互,而不关心对象的内部状态和具体实现方式。

在Java中,接口使用interface关键字进行定义,并可以包含抽象方法、默认方法、静态方法和常量。实现类通过使用implements关键字来实现接口,并需要实现接口定义的所有方法。一个类可以实现多个接口,从而同时具备多个接口定义的行为方式。

2.抽象类

描述一种类的概念和行为方式,但是不能直接被实例化。抽象类可以包含构造方法、属性、具体方法、抽象方法等成员,可以被子类继承和实现,从而实现代码复用和约束子类的行为方式。

在Java中,抽象类使用abstract关键字进行定义,其中包含抽象方法和具体方法。抽象方法没有具体实现,只有方法签名,用于定义子类必须实现的方法;具体方法则有具体的实现,可以被继承或重写。子类通过使用extends关键字继承抽象类,实现其中未实现的抽象方法,从而实现具体功能

两者的共同点和区别:

共同点:

        ① 都是抽象的数据类型,无法被直接实例化。

        ② 都可以包含抽象方法,需要被子类或实现类重写实现具体的功能。

        ③ 都支持多态,可以通过接口或抽象类的引用访问其实现类或子类的对象,实现动态绑定。

        ④ 都可以用于实现代码复用和约束子类或实现类的行为方式。

区别:

        ① 接口只能包含抽象方法、常量、默认方法和静态方法,不允许定义属性和具体方法;抽象类可以包含抽象和具体的方法、属性和构造方法。

        ② 一个类只能继承一个抽象类,但是可以实现多个接口;因此抽象类更适合用于描述一种类的共性,接口更适合用于描述一种行为的规范。

        ③ 实现接口的类必须实现接口中声明的所有方法;而抽象类可以有部分方法已经实现,子类只需要实现未实现的抽象方法即可。

        ④ 接口可以在运行时动态地传递给方法或对象,具有更强的灵活性;而抽象类在编译时就确定了其子类的行为方式,不容易发生意外错误。

三、深拷贝和浅拷贝

1.深拷贝

是将对象及其所有相关的对象完全复制一份,包括对象中的所有属性和关联的对象。深拷贝创建了一个独立的副本,修改副本不会影响原始对象。(类似于我们平时的复制操作)

2.浅拷贝

是将对象被拷贝的引用复制给新的对象,新对象和原始对象共享同一份数据,只复制对象本身及其基本类型的属性,而不复制关联的对象。在浅拷贝中,如果修改了新对象或原始对象中的共享属性,另一个对象也会受到影响。(类似于我们平时创建桌面快捷键)

3.引用拷贝

是指将对象的引用(内存地址)复制给另一个变量,使两个变量引用同一个对象。当修改其中一个变量所引用的对象时,另一个变量也会反映出相同的修改。这是因为两个变量实际上指向的是同一个对象。

需要注意的是,深拷贝和浅拷贝的概念适用于对象的拷贝操作,而引用拷贝是指将引用(内存地址)赋值给其他变量。在Java中,如果要进行对象的深拷贝或浅拷贝,可以通过重写对象的 clone() 方法或使用序列化和反序列化来实现。引用拷贝则是直接将对象的引用赋值给其他变量。

四、Object 类

Object类是Java中所有类的根类。它定义了一些通用的方法,可以在所有对象上使用。

1.Object类的主要方法

equals(Object obj): 用于判断当前对象是否与给定对象相等。

hashCode(): 返回对象的哈希码值。

toString(): 返回对象的字符串表示。通常会被子类重写以提供更有意义的字符串描述。

getClass(): 返回对象的运行时类的Class对象。

clone(): 创建并返回对象的浅拷贝(Shallow Copy)。

finalize(): 当垃圾回收器确定没有对该对象的引用时,会调用该方法进行资源释放等清理操作。

notify()notifyAll(): 用于线程间的通信,唤醒等待该对象监视器的线程。

wait(): 使当前线程暂停执行,直到其他线程调用该对象的notify()notifyAll()方法进行唤醒。

 2.其中一些类需要注意的点

(1)"==" 和" equal "的区别

在Java中,"==" 和 "equals()" 是用于比较对象的两个不同的方法,区别有:

① "==" 操作符比较的是对象的引用:

       A. 当使用 "==" 操作符比较基本类型时,比较的是它们的值是否相等

       B. 当使用 "==" 操作符比较对象引用时,比较的是对象的内存地址是否相等

②" equals()" 方法比较的是对象的内容:

        A.equals()方法是Object类中定义的方法,默认情况下使用的是==操作符进行比较。因此,如果没有在具体的类中重写equals()方法,则默认比较的是对象的引用,效果与==操作符相同。

        B.通过在类中重写equals()方法,可以根据对象的特定属性来比较两个对象的内容是否相等。一般而言,重写equals()方法时通常也需要重写hashCode()方法,以保持一致性。

(2)为什么重写 equals() 时必须重写 hashCode() 方法

这是为了保证一致性,在 Java 中,equals() 方法用于比较对象的内容是否相等,而 hashCode() 方法用于获取对象的哈希码,这在集合类(如 HashMap、HashSet 等)中经常用到。为了保持一致性,当重写 equals() 方法时,通常也需要重写 hashCode() 方法。

① 保证一致性: 哈希表(如 HashMap、HashSet 等)使用哈希码来分配对象的存储位置。当我们在集合中存储对象时,哈希表会根据 hashCode() 方法返回的哈希码来决定存储的位置。如果重写了 equals() 方法,但没有重写 hashCode() 方法,那么当对象内容相等但哈希码不同的情况下,会导致两者的不一致性,因为它们会被错误地存储到不同的位置,无法正确查找或者删除。

② 维护集合的性能: 当在哈希集合中存储大量对象时,哈希码的分布质量直接影响了集合的性能。如果在重写了 equals() 方法的同时不重写 hashCode() 方法,可能会导致哈希码分布不均匀,从而使得集合的性能下降,甚至出现退化情况。

因此,为了保持对象在集合中的正确存储和检索行为,以及维护哈希集合的性能,当我们重写了 equals() 方法时,最好也同时重写 hashCode() 方法,以确保两个方法的行为是一致的。

五、String类

1.String、StringBuffer和StringBuilder的区别

三者在Java中用于处理字符串的类,它们之间有以下区别:

可变性:String类是不可变的,意味着一旦创建了String对象,其值就不能被修改。对String对象进行拼接、替换等操作会创建新的String对象,原始的String对象不变。StringBuffer和StringBuilder类是可变的,可以动态修改字符串的内容。它们提供了一系列方法用于修改字符串,如追加(append)、插入(insert)、删除(delete)等操作。

线程安全性:String类是线程安全的,因为它的不可变性保证了多线程环境下的安全性。StringBuffer类是线程安全的,它的方法使用了synchronized修饰符来保证多线程环境下的同步访问。StringBuilder类是非线程安全的,它的方法没有使用synchronized修饰符,不保证多线程环境下的同步访问。

性能:String类由于不可变性的特性,在进行频繁修改操作时会产生大量的临时对象,对内存和性能有一定的开销。StringBuffer类适用于多线程环境下的字符串拼接和修改,虽然具有线程安全性,但在性能上相对较低。StringBuilder类适用于单线程环境下的字符串拼接和修改,它没有线程安全的开销,相对而言性能较高。

2.为什么String是不可变的

① 保存字符串的数组被 final 和private修饰,并且String 类没有提供修改这个字符串的方法。

② String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

3.字符串常量池的作用

字符串常量池(String Pool)是Java中的一种特殊的存储区域,用于存放字符串常量

字符串常量池的作用主要有以下几个方面:

① 节省内存空间:由于String类的不可变性质,相同的字符串常量只需在内存中存储一份即可。使用字符串常量池可以避免创建大量相同内容的字符串对象,从而节省内存空间。

② 提升性能:字符串常量池具有字符串缓存的效果。当创建一个String对象时,JVM首先检查字符串常量池中是否存在该字符串,如果存在,则直接返回常量池中的引用;如果不存在,则将该字符串添加到常量池中,并返回引用。这样可以减少对象的创建和垃圾回收的压力,提高程序的性能。

③ 字符串比较优化:由于常量池中的字符串是唯一的,因此可以使用"=="运算符进行字符串的比较,而无需通过equals()方法逐字符比较。这样可以提高字符串比较的效率。

需要注意的是,字符串常量池是在堆内存(Heap)中的,并且每个类加载器都维护着一个独立的字符串常量池。通过字面量方式创建的字符串常量会被自动放入常量池,而通过new关键字创建的字符串对象则不在常量池中。

String s1 = "Hello"; // 字符串常量池中创建一个"Hello"对象
String s2 = "Hello"; // 直接从常量池中获取"Hello"对象的引用,无需再次创建
String s3 = new String("Hello"); // 在堆内存中创建一个新的String对象,不在常量池中

System.out.println(s1 == s2); // 输出true,s1和s2引用同一个对象
System.out.println(s1 == s3); // 输出false,s1和s3引用不同的对象

六、异常

是指程序执行过程中遇到的错误或意外情况。当程序出现异常时,会打断正常的程序流程,并通过抛出异常(Throw Exception)的方式来通知调用者或上层代码。异常提供了一种机制,让我们能够优雅地处理错误情况,保证程序的稳定性和可靠性。

1.检查异常(Checked Exception)

这些异常是在编译阶段就可以被检测到的异常,需要在代码中显式处理或声明。如果没有处理或声明,编译器会报错。常见的编译时异常包括IOException、SQLException等。处理编译时异常的方式通常是使用try-catch语句块来捕获异常并进行处理。

try {
    // 可能会抛出编译时异常的代码
} catch (异常类型1 e1) {
    // 异常处理逻辑
} catch (异常类型2 e2) {
    // 异常处理逻辑
} finally {
    // 可选的finally块,无论是否发生异常,都会执行其中的代码
}

2.非检查异常(Unchecked Exception)

这些异常在编译阶段不会被检测到,只有在程序运行过程中才会抛出。常见的运行时异常包括NullPointerException、ArrayIndexOutOfBoundsException等。运行时异常可以不进行捕获或声明,但如果不处理,则会导致程序异常终止。

除了使用try-catch语句块来捕获和处理异常,还可以在方法声明中使用throws关键字将异常抛给上层调用者,让上层代码处理异常。这样的方法可以称为带有throws声明的方法。

public void someMethod() throws ExceptionType {
    // 可能会抛出异常的代码
}

无论是捕获异常还是抛出异常,都可以在异常发生时执行特定的逻辑,如记录日志、回滚事务等。

 3.常见的运行时异常

① NullPointerException(空指针异常):当代码尝试访问一个空对象的成员变量或调用空对象的方法时,会抛出NullPointerException异常。

② ArrayIndexOutOfBoundsException(数组下标越界异常):当代码中使用了非法的数组下标值进行访问数组元素时,会抛出ArrayIndexOutOfBoundsException异常。

③ ClassCastException(类转换异常):当尝试将一个对象转换为不兼容的类型时,会抛出ClassCastException异常。例如,将一个不是子类的对象强制转换为某个子类类型。

④ IllegalArgumentException(非法参数异常):当方法接收到一个不合法的参数时,会抛出IllegalArgumentException异常。通常在方法内部使用条件判断来验证参数的合法性。

⑤ ArithmeticException(算术异常):当出现除零操作或其他算术错误时,会抛出ArithmeticException异常。

⑥ UnsupportedOperationException(不支持的操作异常):当调用对象的某个方法,而该方法不支持当前操作时,会抛出UnsupportedOperationException异常。

⑦ ConcurrentModificationException(并发修改异常):当在迭代集合(如List、Set)的过程中,通过除迭代器本身的方式修改了集合的内容,会抛出ConcurrentModificationException异常。

⑧ RuntimeException(运行时异常的基类):RuntimeException是其他运行时异常的父类,它包含了常见的运行时异常,如IllegalStateException、NumberFormatException等。这些异常通常是由代码逻辑错误引起的。

3.Exception 和 Error 有什么区别

他们都是Throwable的子类,用来表示程序出现了不正常的情况

Exception:是程序正常运行过程中可能出现的异常情况,需要进行捕获或声明处理。

Error:是系统级别的错误或异常情况,无法通过代码进行恢复和处理。如内存溢出(OutOfMemoryError)、栈溢出(StackOverflowError)

4.Throwable 类常用方法有哪些

Throwable是Java中所有错误或异常的超类,定义了异常和错误的基本属性和行为。其中,常用的方法包括:

① getMessage():获取异常或错误的详细描述信息,通常是由构造方法传入的字符串。

② toString():获取异常或错误的简短描述信息,通常包括异常或错误的类型和详细描述信息。

③ printStackTrace():控制台打印异常或错误的堆栈跟踪信息,用于定位错误的位置和原因。

④ getCause():获取导致异常或错误的原因,通常返回一个Throwable对象

⑤ getStackTrace():获取异常或错误的堆栈跟踪信息,返回一个数组,每个元素表示一条堆栈跟踪信息。

⑥ fillInStackTrace():重新生成堆栈跟踪信息,可以用于覆盖原有的堆栈跟踪信息,使其更准确或清晰。

这些方法都是从Throwable类继承而来,可以在子类中直接使用。在捕获和处理异常时,getMessage()和printStackTrace()方法可用于输出错误信息以及堆栈跟踪信息,方便开发人员进行问题定位和修复;getCause()方法则可用于查找并处理异常的原因,避免出现像“链式异常”的情况

5.try-catch-finally 

try {
    // 可能会抛出异常的代码块
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
} finally {
    // 无论是否捕获到异常,都会执行的代码块(除了程序强制退出)
}

需要注意的是:不可以在 finally 语句块中使用 return

在finally代码块中使用return语句会导致异常被屏蔽,并且返回的结果会覆盖在try或catch代码块中的返回结果。这是由于finally代码块中的return语句会直接结束方法的执行,不再执行try或catch代码块中的return语句。

public static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

public static void main(String[] args) {
    System.out.println(test());  // 输出2,而不是1
}

6.异常处理需要注意的点

① 异常的捕获:在使用异常时,一定要为可能发生异常的代码加上try-catch语句或者throws声明。否则,如果程序出现异常,可能会导致程序崩溃或者无法正常运行。

② 异常的细化:在处理异常时,尽量使用细化的异常处理方式。即通过多个catch块来分别处理不同类型的异常,而不是只用一个catch块处理所有异常。这样可以使得代码更加清晰,并且更容易定位和解决问题。

③ 不要忽略异常:对于捕获到的异常,一定要进行适当的处理,不要简单地将其抛弃。对于不能立即处理的异常,可以通过日志记录等方式来进行记录,方便后续的排查和解决问题。

④ 异常捕获的粒度:在编写代码时,需要考虑异常捕获的粒度。过于宽泛的异常捕获会导致程序在出现异常时无法及时定位问题,过于细化的异常捕获可能会导致代码重复和冗余。需要根据实际情况来选择合适的异常捕获粒度。

⑤ 异常的抛出:在方法抛出异常时,应该提供有意义的异常信息,便于程序员识别和解决问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tina@Qian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值