Java基础复习01

介绍一下 Object 常见方法?

  1. toString( ) 返回的是对象的字符串表示,默认为 「class 名 + @ + hashCode 的十六进制表示」,我们一般会在子类将它重写为打印各个字段的值,在调试和打日志中用的多。

  2. equals( ) 和 hashCode( ) 通常用于对象之间的比较。其中 equals( ) 用于判断两个对象是否相等,默认使用 “= =” 判断,hashCode( ) 用于获取对象的哈希码,默认以对象的内存地址为参考。在实践中,为了保证元素在 HashMap 和 HashSet 等集合中的正确存储,通常需要将它俩一起重写。

equals() 方法
用途:判断两个对象是否“相等”。
默认行为:在 Object 类中,默认使用 == 运算符比较对象的内存地址。
重写原则:当你希望两个对象在某些属性相同时被视为相等时,需要重写 equals()。比如,两个 Person 对象如果拥有相同的 ID,则应该被视为相等。
hashCode() 方法
用途:返回对象的哈希码,用于确定对象在哈希表中的位置。
默认行为:在 Object 类中,默认基于对象的内存地址生成哈希码。
重写原则:当 equals() 被重写时,hashCode() 也应该被重写。确保相等的对象必须具有相同的哈希码。

在 HashMap 和 HashSet 等集合中,对象首先通过 hashCode() 方法确定位置,然后通过 equals() 方法判断是否相等。如果两个对象是“相等的”,它们的哈希码也必须相同。如果不这样做,可能会导致数据结构行为异常,如无法正确检索元素。

equals() 方法检查两个 Person 对象的 id 是否相同。
hashCode() 方法仅使用 id 字段计算哈希码,因为 equals() 也是基于 id 的比较。

  1. wait( ),notify( ) 以及 notifyAll( ) 通常用于线程间的协作和同步。其中 wait( ) 使当前线程释放锁并进入等待状态,直至被其它线程的 notify 或 notifyAll 唤醒。

notify( ) 会在对该对象调用了 wait( ) 的线程中,随机挑选一个唤醒,解除其阻塞状态。而 notifyAll( ) 会唤醒所有在该对象上等待的线程。wait( ) 搭配 notify( ) 可以实现一个简单的“生产-消费模型”:生产者线程产生消息后,调用 notify( ) 唤醒消费者。消费者被唤醒后消费消息,消费完成后调用 wait( ) 继续等待。

Java为什么被称为平台无关性语言?

Java 语言具有平台无关性的关键在于 JVM。虽然不同的操作系统使用不同的机器指令集来执行任务,同一份代码在不同的操作系统上可能无法直接执行,但是 Java 源文件经过 javac 编译器编译后形成的二进制字节码,可以被各个操作系统的 JVM 翻译成该操作系统所需的指令集,进而执行。这可以提高 Java 程序的可移植性,因为只需针对不同操作系统提供对应的 JVM 即可,无需修改源代码。

= =和equals有什么区别?

首先 = = 是一个操作符 ,equals 是超类 Object 中的方法,默认是用 = = 来比较的。也就是说,对于没有重写 equals 方法的子类,equals 和 = = 是一样的。

而 = = 在比较时,根据所比较的类的类型不同,功能也有所不同:对于基础数据类型,如 int 类型等,比较的是具体的值;而对于引用数据类型,比较的是引用的地址是否相同。

对于重写的 equals 方法,比的内容取决于这个方法的实现。

讲一下equals()与hashcode(),什么时候重写,为什么重写,怎么重写?

首先 equals( ) 是 Object 中的方法,默认是用 = = 来比较的。hashCode( ) 也是 Object 类的方法,根据一定的规则将与对象相关的信息,比如对象的内存地址,映射成一个数值,这个数值称作为哈希值。

有时候我们想要自定义类的比较规则时,需要重写 equals( ),但是为了保证类在 HashSet 和 HashMap 等集合中的正确存储,也要同时重写 hashCode( ) 。

以 HashMap 为例, HashMap底层在添加相同的元素时,会先调用两个对象的 hashCode( ) 是否相同,如果相同还会再用 equals( ) 比较两个对象是否相同。

假设有一个 Person 类,有 name 和 age 两个字段,我们现在重写 equals( ) 规定只有两个 Person 的 name 和 age 都相同时,才认为两个 Person 相等。现在 new 出两个 name 和 age 都相同的 Person,分别添加到 HashMap 中。

我们期望最后 HashMap 中只有一个 Person,但其实是有两个。原因在于添加第二个 Person 时,先比较的是两个 Person 的 hashCode( ),注意此时我们没有重写 hashCode( ) ,那么分别 new 出来的 Person 的哈希值肯定是不同的,到这里 HashMap 就会将两个 Person 认定为不同的元素添加进去。

解决的办法就是重写 hashCode( ),最简单的返回 name 和 age 的哈希值的乘积即可。

讲一下重载和重写的区别?

重载是指在同一个类中定义多个方法,它们具有相同的函数名,但参数的类型,个数和顺序可能不同。它提供了一种灵活的方式来实现相似的功能。

重写是指在子类中重新定义并覆盖父类中的方法。它使子类可以根据具体的类型调用相应的方法实现。

在 JVM 中,方法重载对应 “静态分配” 的过程,也就是 JVM 在编译期就根据参数的静态类型,决定了会使用函数的哪个重载版本。而方法重写对应 “动态分配” 的过程,具体的,重写方法的调用是由字节码中的 invokevirtual 指令实现的,而 invokevirtual 指令会在运行期间,根据方法接受者的实际类型来选择方法的执行版本。

抽象和接口的区别?

抽象在 Java 中是指被 abstract 修饰的类或方法,接口是指被 interface 修饰的类。接口中声明的方法,默认也被定义为 abstract 。

抽象主要是为了代码复用。比如一些类拥有一些通用的功能,为了不在每一类中重新写一遍这个方法的代码,就可以定义一个抽象类,这样一来,只需让每一个类继承抽象类就可以了,如果后期需要修改方法,只需要修改抽象类中的方法就行,如果子类想要自己实现不一样的行为,只需子类重写抽象类的方法。

这样一来,普通类就可以完成这个工作,为什么需要抽象类,抽象类还起了一个限制作用,比如要求每一个子类必须自己独特实现的一个方法,也是抽象类定义的抽象方法。

缺点也很明显,因为Java没有多继承,导致一个类只能继承一个父类。在表示是什么的关系时,一般使用抽象类。

接口更多的是为了解耦。比如我需要制定一套方法的规范,就可以将这套方法规范抽象为一个接口。每一个继承这个接口的类都必须实现接口中所有的方法。在表示有什么的关系时,使用接口。

谈一谈你对 final 关键字的理解?什么需要用这个关键字来定义呢?

final是Java中的一个关键字,可以用来修饰类、方法、变量。

当用final来修饰类时,表示这个类不可以被继承,可以确保一些安全性,防止类被纂改。而final用来修饰方法,表示方法不可以被重写,final修饰变量时,如果是基本数据类型,值不变。

如果是引用类型,引用指向的地址值不变,但地址的内容可能会变。final可以用来解决一些安全性问题。比如Java的一些核心类库的类的API接口不想被纂改就使用final修饰方法。

Java希望String类是不可变的,就使用final修饰String类并且修饰成员变量char[]数组,并且没有提供setter方法,这样就可以确保String类是不可变的。

另外,如果有多个线程操作同一个变量,并且这个变量的值不会变化,可以考虑使用final修饰解决线程安全问题。

异常连击,按照提问逐一回答

说一说你对异常的理解?

异常是程序在执行过程中可能发生的一些错误,它会暂时终止程序,并转到异常处理部分尝试恢复。异常的出现可以使我们把程序中可能出现错误的代码从正常代码中分离出来,单独进行处理。

异常有哪些种类,可以举几个例子吗?

类似于 Object 类,Throwable 是所有异常的父类。下一层分为 Error 类和 Exception 类。

Error 类表示严重的错误,一般由 JVM 和底层系统引发,并且不可恢复,例如内存溢出,class 没有主方法等。Exception 类是可以被程序捕获和处理的异常情况。

Exception 再往下,按照异常的性质又分为编译异常 CheckedException 和运行时异常 RuntimeException。其中检查异常在编译阶段就能被检测出,例如 IO 异常 IOException,文件找不到异常 FileNotFoundException等。运行时异常只有在程序运行时才能被检测出,例如空指针异常 NullPointerException,数组下标越界 ArrayIndexOutOfBoundsException 等。

throw 和 throws 有啥区别?直接 try catch 不好吗,为啥还要抛呢?

throw 用在方法体内,表示某个地方需要抛出一个异常,是一个具体的动作。而 throws 用在方法声明后面,表示这个方法可能会抛出哪些异常,是一个声明。

这个需要分情况对待。首先,对于能在方法内处理的异常,可以直接用 try catch,但对于在当前方法内无法处理的异常,我们只能选择抛出,将它交给方法调用者去处理。其次,有时多个方法可能会抛出相同类型的异常,如果每个方法都用 try catch 会非常冗余,这时可以在这些方法中只 throw,在调用这些方法的地方整体 try catch,统一进行处理。

try catch会影响性能吗?为什么抛出异常的时候会影响性能?

几乎不会,也就是程序没有异常时,try catch 加和不加性能几乎一样,只有在程序有异常需要捕获时才会影响性能。

原因在于,处理异常的 catch 语句在 class 文件中是用异常表实现的。异常表有四个字段,分别是 From, To, Target 和 Type。From 和 To 分别对应 try 块对应的行号,如果其中有异常抛出,会跳转到 Target 对应的行号,也就是 catch 语句对应的位置,而这个异常的类型记录在 Type 中。如果程序在 try 中没有异常抛出,最终会通过一条 goto 指令跳转到 try catch 块后的语句继续执行,这一条 goto 指令性能的消耗可以忽略不计。如果有异常,才会去查异常表,再跳转到对应的 catch 块位置去执行,这个过程可能会影响性能。

try-catch-finnally中,如果说catch中return了,finally还会执行吗?

会的,无论 catch 块中有异常还是有返回语句,最终一定会在方法真正结束之前执行 finally。但是需要考虑一种特殊情况,就是如果 try 块中抛出了异常但没有被 catch 捕获,且此时 finally 中也抛出了异常,那么 finally 中的异常会覆盖掉 try 中的异常,成为这个方法最终抛出的异常。实践中需要注意一下这个异常覆盖问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值