Java 基础篇

本文深入探讨了Java编程中的面向对象思想,包括面向过程与面向对象的区别、类与对象的关系、JDK与JRE、JVM的区别,以及final、finally、finalize的用途。此外,还讲解了Java中的异常处理、集合框架、线程安全、并发与并行的概念,以及类加载机制和线程的生命周期。最后,讨论了泛型、深拷贝与浅拷贝、内存栈和堆的使用,以及设计模式的应用。
摘要由CSDN通过智能技术生成

谈谈对面向对象思想的理解

首先,谈谈“面向过程”vs“面向对象” 我觉得这两者是思考角度的差异,面向过程更多是以“执行者”的角度来思考问题,而面向对象更多是以“组织者”的角度来思考问题,举个例子,比如我要产生一个 0-10 之间 的随机数,如果以“面向过程”的思维,那我更多是关注如何去设计一个算法,然后保证比较均衡产生 0-10 的随机数,而面向对象的思维会更多关注,我找谁来帮我们做这件事,比如 Random 类,调用其中提供的方法即可。

所以,面向对象的思维更多的是考虑如何去选择合适的工具,然后组织到一起干一件事。好比一个导演,要拍一场电影,那么首先要有男猪脚和女猪脚,然后还有其他等等,最后把这些资源组织起来,拍成一场电影。持久层 jpa mybaits

再说回我们的程序世界,这个组织者的思维无处不在,比如,我们要开发项目,以三层架构的模式来开发,那么这个时候,我们不需要重复造轮子,只需要选择市面上主流的框架即可,比如 SpringMVCSpringMyBatis,这些都是各层的主流框架。

JDK,JRE,JVM 有什么区别?

JDK:Java Development KitJava 开发工具包,提供了 Java的开发环境和运行环境。包含了编译 Java 源文件的编译器 Javac,还有调试和分析的工具。

JREJava Runtime EnvironmentJava 运行环境,包含 Java虚拟机及一些基础类库

JVMJava Virtual MachineJava 虚拟机,提供执行字节码文件的能力所以,如果只是运行 Java 程序,只需要安装 JRE 即可。

另外注意,JVM 是实现 Java 跨平台的核心,但 JVM 本身并不是跨平台的

==和 equals 的区别

equals():是超类 Object 中的方法。==:是操作符。

==判断变量对象是否指向同一个内存空间

Equals 重写后判断两个内容是否相同

运行速度不同 equals():没有==运行速度快。

final 的作用

final 修饰类,表示类不可变,不可继承     比如,String,不可变性

final 修饰方法,表示该方法不可重写      比如模板方法,可以固定我们的算法

final 修饰变量,这个变量就是常量

注意:

修饰的是基本数据类型,这个值本身不能修改

JDKJava Development KitJava 开发工具包,提供了 Java 的开发环境和运行环境。

包含了编译 Java 源文件的编译器 Javac,还有调试和分析的工具。

JREJava Runtime EnvironmentJava 运行环境,包含 Java 虚拟机及一些基础类库

JVMJava Virtual MachineJava 虚拟机,提供执行字节码文件的能力

所以,如果只是运行 Java 程序,只需要安装 JRE 即可。另外注意,JVM 是实现 Java 跨平台的核心,但 JVM 本身 并不是跨平台的

修饰的是引用类型,引用的指向不能修改

阐述 final、finally、finalize 的区别。
其实是三个完全不相关的东西,只是长的有点像。。

final 如上所示。

finally:finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使用,用于存放那些无论是否出现异常都一定会执行的代码。在实际使用中,通常用于释放锁、数据库连接等资源,把资源释放方法放到 finally 中,可以大大降低程序出错的几率。

finalize:Object 中的方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。finalize()方法仅作为了解即可,在 Java 9 中该方法已经被标记为废弃,并添加新的 java.lang.ref.Cleaner,提供了更灵活和有效的方法来释放资源。这也侧面说明了,这个方法的设计是失败的,因此更加不能去使用它。

String 是 Java 基本数据类型吗?
答:不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type)。

基本数据类型:数据直接存储在栈上

引用数据类型区别:数据存储在堆上,栈上只存储引用地址
 

String,StringBuffer,StringBuilder 区别

分别是 String,StringBuffer和  StringBuilder.这三个类都是以 char[]的形式保存的字符串,但是String类型的字符串是不可变的,对String类型的字符串做修改操作都是相当于重新创建对象.而对 StringBuffer和 StringBuilder进行增删操作都是对同一个对象做操作.StringBuffer中的方法大部分都使用 synchronized关键字修饰,所以  StringBuffer是线程安全的,StringBuilder中的方法则没有,线程不安全,但是 StringBuilder因为没有使用使用  synchronized关键字修饰,所以性能更高,在单线程环境下我会选择使用  StringBuilder,多线程环境下使用 StringBuffer.如果生成的这个字符串几乎不做修改操作,那么我就直接使用 String,因为不调用 new关键字声明 String类型的变量的话它不会在堆内存中创建对象,直接指向  String的常量池,并且可以复用.效率更高.

final 与 static 的区别

都可以修饰类、方法、成员变量。

都不能用于修饰构造方法。

static 可以修饰类的代码块,final 不可以。

static 不可以修饰方法内的局部变量,final 可以。

static

static 修饰表示静态或全局,被修饰的属性和方法属于类,可以用类名.静态属性 / 方法名 访问

static 修饰的代码块表示静态代码块,当 Java 虚拟机(JVM)加载类时,就会执行该代码块,只会被执行一次

static 修饰的属性,也就是类变量,是在类加载时被创建并进行初始化,只会被创建一次

static 修饰的变量可以重新赋值

static 方法中不能用 this super 关键字

static 方法必须被实现,而不能是抽象的 abstract

static 方法不能被重写

final

final 修饰表示常量、一旦创建不可改变

final 标记的成员变量必须在声明的同时赋值,或在该类的构造方法中赋值,不可以重新赋值

final 方法不能被子类重写

final 类不能被继承,没有子类,final 类中的方法默认是 final

接口和抽象类的区别

JDK1.8 之前:

语法:

抽象类:方法可以有抽象的,也可以有非抽象, 有构造器

接口:方法都是抽象,属性都是常量,默认有 public static final 修饰

设计:

抽象类:同一类事物的抽取,比如针对 Dao 层操作的封装,如,BaseDaoBaseServiceImp

接口:通常更像是一种标准的制定,定制系统之间对接的标准

例子:

1,单体项目,分层开发,interface 作为各层之间的纽带,在 controller 中注入 IUserService

Service 注入 IUserDao

2,分布式项目,面向服务的开发,抽取服务 service,这个时候,就会产生服务的提供者和

服务的消费者两个角色

这两个角色之间的纽带,依然是接口

JDK1.8 之后:

接口里面可以有实现的方法,注意要在方法的声明上加上 default 或者 static

最后区分几个概念:

多继承,多重继承,多实现

多重继承:A->B->C(爷孙三代的关系)

多实现:Person implements IRunable,IEatable(符合多项国际化标准)

多继承:接口可以多继承,类只支持单继承

抽象类(abstract class)和接口(interface)有什么区别?
抽象类只能单继承,接口可以多实现。

抽象类可以有构造方法,接口中不能有构造方法。

抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)

抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。

抽象类中的方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。

设计思想的区别:

接口是自上而下的抽象过程,接口规范了某些行为,是对某一行为的抽象。我需要这个行为,我就去实现某个接口,但是具体这个行为怎么实现,完全由自己决定。

抽象类是自下而上的抽象过程,抽象类提供了通用实现,是对某一类事物的抽象。我们在写实现类的时候,发现某些实现类具有几乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有一些差异点,则可以提供抽象方法来支持自定义实现。

我在网上看到有个说法,挺形象的:

普通类像亲爹 ,他有啥都是你的。

抽象类像叔伯,有一部分会给你,还能指导你做事的方法。

接口像干爹,可以给你指引方法,但是做成啥样得你自己努力实现。
 

方法的重写和重载的区别

重载:发生在一个类里面,方法名相同,参数列表不同(混淆点:跟返回类型没关系)

重写:发生在父类子类之间的,方法名相同,参数列表相同

Collection 和 Collections 有什么区别

Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式。

Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。

集合

Collection 接口是集合类的根接口,java 中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口就是 Set ListSet 中不能包含重复的元素。List 是一个有序的集合,提供了按索引访问的方式。

Map Java.util 包中的另一个接口,它和 Collection 接口没有关系,是互相独立的,但是都属于集合类的一部分。Map 包含了 Key-value 对。Map 不能包含重复的 Key,但是可以包含相同的 value

1List(有序,可重复)

List 里存放的对象是有序的,同时也是可以重复的,List 关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往 list 集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

ArrayList LinkedList

ArrayList LinkedList 在用法上没有区别,但是在功能上还是有区别的。LinkedList 经常

用在增删操作较多而查询操作很少的情况下,ArrayList 则相反。

List 接口:元素按进入先后有序保存,可重复

ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全

LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全

Vector 接口实现类 数组, 同步, 线程安全(Stack Vector 类的实现类)

2Set(无序,不能重复)

Set 里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单

地把对象加入集合中。

HashSet 集合

底层数据结构是哈希表(是一个元素为链表的数组) 不能保证元素的顺序。

HashSet 不是线程同步的,如果多线程操作 HashSet 集合,则应通过代码来保证其同步。

集合元素值可以是 null

影响哈希冲突的条件,首先看哈希值是否相等,然后判断 equals 是否相等(内容是否相等)

TreeSet 集合

A:底层数据结构是红黑树(是一个自平衡的二叉树)

B:保证元素的排序方式(自然排序),实现 Comparable 接口

LinkedHashSet 集合 

A::底层数据结构由哈希表和链表组成。

原来存储是什么顺序,就是什么顺序

Set 接口: 仅接收一次,不可重复,并做内部排序

HashSet 使用 hash 表(数组)存储元素

LinkedHashSet 链表维护元素的插入次序

TreeSet 底层实现为二叉树,元素排好序

3Map(键值对,建唯一,值不唯一)

Map 集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对 map 集合遍历时先得到键的 set 集合,对 set 集合进行遍历,得到相应的值。

实现类:HashMapHashtableLinkedHashMap TreeMap

HashMap

HashMap 是最常用的 Map,它根据键的 HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以 HashMap 最多只允许一条记录的键为 Null,允许多条记录的值为 Null,是非同步的

Hashtable

Hashtable HashMap 类似,是 HashMap 的线程安全版,它支持线程的同步,即任一

时刻只有一个线程能写 Hashtable,因此也导致了 Hashtale 在写入时会比较慢,它继承自

Dictionary 类,不同的是它不允许记录的键或者值为 null,同时效率较低。

ConcurrentHashMap

线程安全,并且锁分离。ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部 分,每个段其实就是一个小的 hash table,它们有自己的锁。只要多个修改操作发生在不同 的段上,它们就可以并发进行。

LinkedHashMap

LinkedHashMap 保存了记录的插入顺序,在用 Iteraor 遍历 LinkedHashMap 时,先得到的 记录肯定是先插入的,在遍历的时候会比 HashMap 慢,有 HashMap 的全部特性。

TreeMap 

TreeMap 实现 SortMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排 序(自然顺序),也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。不允许 key 值为空,非同步的;

serialVersionUID 的作用是什么

当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号 ID当反序列化时,程序会比较磁盘中的序列化版本号 ID 跟当前的类结构生成的版本号 ID是否一致,如果一致则反序列化成功,否则,反序列化失败

加上版本号,有助于当我们的类结构发生了变化,依然可以之前已经序列化的对象反序

列化成功

请描述下 Java 的异常体系

Error 是虚拟机内部错误

栈内存溢出错误:StackOverflowError(递归,递归层次太多或递归没有结束)没有足够的内存空间供其使用。

堆内存溢出错误:OutOfMemoryError(堆创建了很多对象)无法释放已申请的内存空间,

内存泄露堆积会导致内存被占光。

Exception 是我们编写的程序错误

RuntimeException:也称为 LogicException

为什么编译器不会要求你去 try catch 处理?

本质是逻辑错误,比如空指针异常,这种问题是编程逻辑不严谨造成的

应该通过完善我们的代码编程逻辑,来解决问题

RuntimeException

编译器会要求我们 try catch 或者 throws 处理

本质是客观因素造成的问题,比如 FileNotFoundException 写了一个程序,自动阅卷,需要读取答案的路径(用户录入),用户可能录入是一个错误的路径,所以我们要提前预案,写好发生异常之后的处理方式,这也是 java 程序健壮性的一种体现

5 个运行时异常

算数异常,空指针,类型转换异常,数组越界,

NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)

5 个非运行时异常

IOExceptionSQLExceptionFileNotFoundException,NoSuchFileException

NoSuchMethodException

并发和并行有什么区别?
并发:两个或多个事件在同一时间间隔发生。

并行:两个或者多个事件在同一时刻发生。

并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。

网上有个例子挺形象的:

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
 

创建线程的方式

我们常说的方式有以下三种:继承 Thread 实现   Runable 接口

实现 Callable 接口(可以获取线程执行之后的返回值)

但实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,还需 要通过创建 Thread 对象来执行,比如 new Thread(new Runnable(){}).start();这样的方式来执

行。 在实际开发中,我们通常采用线程池的方式来完成 Thread 的创建,更好管理线程资源。

线程的生命周期

newrunnableblockedwaitingtimed waitingterminated

1,当进入 synchronized 同步代码块或同步方法时,且没有获取到锁,线程就进入了 blocked

状态,直到锁被释放,重新进入 runnable 状态

2,当线程调用 wait()或者 join 时,线程都会进入到 waiting 状态,当调用 notify notifyAll

时,或者 join 的线程执行结束后,会进入 runnable 状态

3,当线程调用 sleep(time),或者 wait(time)时,进入 timed waiting 状态,当休眠时间结束后,或者调用 notify notifyAll 时会重新 runnable 状态。

4,程序执行结束,线程进入 terminated 状态

谈谈 Sleep 和 wait 的区别

1,所属的类不同:

sleep 方法是定义在 Thread     wait 方法是定义在 Object

2,对于锁资源的处理方式不同

sleep 不会释放锁                 wait 会释放锁

3,使用范围:

sleep 可以使用在任何代码块       wait 必须在同步方法或同步代码块执行

4,与 wait 配套使用的方法

void notify()         唤醒在此对象监视器上等待的单个线程

void notifyAll()       唤醒在此对象监视器上等待的所有线程

void wait( )  导致当前的线程等待,直到其他线程调用此对象的 notify( ) 方法或 notifyAll( ) 方法

生命周期 

1,当线程调用 wait()或者 join 时,线程都会进入到 waiting 状态,当调用 notify notifyAll

时,或者 join 的线程执行结束后,会进入 runnable 状态

2,当线程调用 sleep(time),或者 wait(time)时,进入 timed waiting 状态,

为什么 wait 要定义在 Object 中,而不定义在 Thread 中?

来解释下,我们回想下,在同步代码块中,我们说需要一个对象锁来实现多线程的互斥效果, 也就是说,Java 的锁是对象级别的,而不是线程级别的。

为什么 wait 必须写在同步代码块中?

原因是避免 CPU 切换到其他线程,而其他线程又提前执行了 notify 方法,那这样就达不到我们的预期(先 wait 再由其他线程来唤醒),所以需要一个同步锁来保护

谈谈你对线程安全的理解

如果这个是面试官直接问你的问题,你会怎么回答?

一个专业的描述是,当多个线程访问一个对象时,如果不用进行额外的同步控制或其他

的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

那么我们如何做到线程安全?

实现线程安全的方式有多种,其中在源码中常见的方式是,采用 synchronized 关键字给

代码块或方法加锁,比如 StringBuffer

那么,我们开发中,如果需要拼接字符串,使用 StringBuilder 还是 StringBuffer

场景一:

如果是多个线程访问同一个资源,那么就需要上锁,才能保证数据的安全性。

这个时候如果使用的是非线程安全的对象,比如 StringBuilder,那么就需要借助外力,给他

synchronized 关键字。或者直接使用线程安全的对象 StringBuffer

场景二:

如果每个线程访问的是各自的资源,那么就不需要考虑线程安全的问题,所以这个时候,

我们可以放心使用非线程安全的对象,比如 StringBuilder

比如在方法中,创建对象,来实现字符串的拼接。

看场景,如果我们是在方法中使用,那么建议在方法中创建 StringBuilder,这时候相当

是每个线程独立占有一个 StringBuilder 对象,不存在多线程共享一个资源的情况,所以我们

可以安心使用,虽然 StringBuilder 本身不是线程安全的。

什么时候需要考虑线程安全?

1,多个线程访问同一个资源

2,资源是有状态的,比如我们上述讲的字符串拼接,这个时候数据是会有变化的

类的加载机制

请问,我现在编写一个类,类全名如下:java.lang.String,

我们知道 JDK 也给我们听过了一个 java.lang.String

那么,我们编写的这个 String 类能否替换到 JDK 默认提供,也就是说程序实际运行的时候,

会加载我们的 String 还是 JDK String?为什么?

1,首先,什么是类的加载机制?

JVM 使用 Java 类的流程如下:

1Java 源文件----编译---->class 文件

2,类加载器 ClassLoader 会读取这个.class 文件,并将其转化为 java.lang.Class 的实例。有了该实例,JVM 就可以使用他来创建对象,调用方法等操作了。

那么 ClassLoader 是以一种什么机制来加载 Class 的?

这就是我们要谈的类的加载机制!

2,搞清楚这个问题,首先要知道,我们用到的 Class 文件都有哪些来源?

1Java 内部自带的核心类,位于$JAVA_HOME/jre/lib,其中最著名的莫过于 rt.jar

2Java 的扩展类,位于$JAVA_HOME/jre/lib/ext 目录下

3,我们自己开发的类或项目开发用到的第三方 jar 包,位于我们项目的目录下,比如

WEB-INF/lib 目录

3,那么,针对这些 ClassJDK 是怎么分工的?谁来加载这些 Class

针对不同的来源,Java 分了不同的 ClassLoader 来加载

1Java 核心类,这些 Java 运行的基础类,由一个名为 BootstrapClassLoader 加载器负责加载。

这个类加载器被称为“根加载器或引导加载器”

注意:BootstrapClassLoader 不继承 ClassLoader,是由 JVM 内部实现。法力无边,所以你通过 java 程序访问不到,得到的是 null

2Java 扩展类,是由 ExtClassLoader 负责加载,被称为“扩展类加载器”。

3,项目中编写的类,是由 AppClassLoader 来负责加载,被称为“系统类加载器”。

4, 那凭什么,我就知道这个类应该由老大 BootStrapClassLoader 来加载?

这里面就要基于双亲委托机制?

所谓双亲委托机制,就是加载一个类,会先获取到一个系统类加载器 AppClassLoader 的实例,然后往上层层请求,先由 BootstarpClassLoader 去加载,如果 BootStrapClassLoader 发现没有,再下发给 ExtClassLoader 去加载,还是没有,才由 AppClassLoader 去加载。如果还是没有,

则报错

5,所以,上述问题的答案你清楚了吗?

JDK 提供 java.lang.String 类,默认在 rt.jar 这个包里面,所以,默认会由 BootstarpClassLoader 加载,所以,我们自己编写的 java.lang.String,都没有机会被加载到

什么是泛型?为什么要使用泛型?

泛型:

"参数化类型",将类型由具体的类型参数化,把类型也定义成参数形式(称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

JDK 5 中引入的一个新特性,提供了编译时类型安全监测机制,该机制允许程序员在编

译时监测非法的类型。

泛型的本质是把参数的类型参数化,也就是所操作的数据类型被指定为一个参数,这种参数 类型可以用在类、接口和方法中。

为什么要用泛型?

使用泛型编写的程序代码,要比使用 Object 变量再进行强制类型转换的代码,具有更好的安全性和可读性。

多种数据类型执行相同的代码使用泛型可以复用代码。

比 如 集 合 类 使 用 泛 型 , 取 出 和 操 作 元 素 时 无 需 进 行 类 型 转 换 , 避 免 出 现

java.lang.ClassCastException 异常

深拷贝和浅拷贝的区别是什么?

深拷贝:除了对象本身被复制外,对象所包含的所有成员变量都会被复制,包括引用类

型的成员对象

浅拷贝:只复制对象其中包含的值类型的成员变量,而引用类型的成员对象没有被复制

解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM的启动参数来进行调整,栈空间用光了会引发 StackOverflowError,而堆和常量池空间不足则会引发 OutOfMemoryError。

两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

不对,如果两个对象 x 和 y 满足 x.equals(y) == true,它们的哈希码(hash code)应当相同。Java 对于 eqauls 方法和 hashCode 方法是这样规定的:

(1)如果两个对象相同(equals 方法返回 true),那么它们的 hashCode 值一定要相同;

(2)如果两个对象的 hashCode 相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

补充:equals 方法的:首先 equals 方法必须满足自反性(x.equals(x)必须返回 true)、

对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)、传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)和一致性(当 x 和 y 引用的对象信息没有被修改时,多次调用 x.equals(y)应该得到同样的返回值),而且对于任何非 null 值的引用 x,x.equals(null)必须返回 false。

实现高质量的 equals 方法的诀窍包括:

(1) 使用==操作符检查”参数是否为这个对象的引用”;

(2) 使用 instanceof 操作符检查”参数是否为正确的类型”;

(3) 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;

(4) 编写完 equals方法后,问自己它是否满足对称性、传递性、一致性;

(5) 重写 equals 时总是要重写 hashCode;

(6) 不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉@Override 注解。
是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,在调用静态方法时可能对象并没有被初始化。

除了单例模式,你在生产环境中还用过什么设计模式?

这需要根据你的经验来回答。一般情况下,你可以说依赖注入,工厂模式,装饰模式或者观察者模式,随意选择你使用过的一种即可。不过你要准备回答接下的基于你选择的模式的问题。

什么是双亲委派模型?
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

呐喊2954

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

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

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

打赏作者

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

抵扣说明:

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

余额充值