Java基础
阅读提示
先掌握“概念定义 + 对比区别 + 常见坑”三种回答模板。
如果时间紧,优先看:面向对象、== 与 equals、String、接口 vs 抽象类。
Java基础
回答提示:是一种比较通俗的解释,比较好理解,如果‘参考答案’不太好记住,可以直接复习回答提示也可以。
1、面向对象和面向过程的区别是什么?
回答提示:
- 面向过程就像是做菜时按步骤来,先洗菜、切菜、炒菜,一步步完成。它关注解决问题的步骤和流程,直接用函数实现每个步骤,然后依次调用。
- 而面向对象则更像是把做菜看成一个整体,关注构成这个问题的各个“对象”,比如“锅”、“铲子”、“食材”等。每个对象有自己的属性和行为(方法),如“锅”有“加热”的方法。面向对象通过创建对象并调用它们的方法来解决问题,更强调事物的属性和行为,以及它们之间的关系。
- 总结来说,面向过程更直接,关注怎么做;面向对象更抽象,关注做什么和由谁来做。面向对象具有更高的复用性、扩展性和维护性,但可能性能稍低;面向过程则性能较高,但复用和维护可能较复杂。
参考回答:
- 面向过程和面向对象是两种不同的处理问题的角度。
- 面向过程更注重事情的每一个步骤及顺序。主要是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用
- 面向对象更注重事情有哪些参与者(对象)、及各自需要做什么。主要是把事物对象化,包括其属性和行为。通过调用不同对象之间的方法,以组合的方式解决问题。
2、面向对象的三大基本特征
回答提示:
- 封装:就是把数据和操作数据的方法“打包”在一起,形成一个独立的单元(类)。外界不需要知道内部的具体实现细节,只需要通过接口(类提供的方法)与之交互。这就像是一个黑盒子,我们只知道它的输入和输出,但不知道里面是怎么工作的。
- 继承:子类可以继承父类的属性和方法,这样就可以在不需要重新编写原有代码的基础上,扩展新的功能。比如,我们可以定义一个“动物”类,然后让“猫”和“狗”类继承它,从而复用“动物”类中的属性和方法。
- 多态:是同一个接口(方法)在不同的子类中有不同的实现。这样,我们就可以用同一个父类类型的引用来调用不同子类的方法,实现不同的功能。这增加了程序的灵活性和可扩展性。
继承,方法重写,父类引用指向子类对象
父类类型 变量名 = new 子类对象 ;
变量名.方法名();
参考回答:
- 三大基本特征:封装、继承、多态。
- 封装
- 封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,提供对应的函数给外部进行调用,内部实现对外部调用不可见,外部调用无需修改或关心内部实现。
- 继承
- 继承基类的方法,并做出自己的改变和/或扩展,子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的。
- 多态
- 基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同,父类中定义的属性和方法被子类继承后或者接口定义的方法被子类实现,通过重写使得父类和子类具有不同的实现,从而同一个属性或方法在父类及其各个子类中具有不同的含义。无法调用子类特有的功能。
4、==和equals区别(了解)
回答提示:
==操作符:它主要用于比较基本数据类型的值是否相等,比如两个整数是否相等。对于引用数据类型,==比较的是两个对象的内存地址是否相同,即它们是否是同一个对象的引用。简单来说,==看的是“是不是同一个东西”。equals方法:它是Object类的一个方法,用于比较两个对象的内容是否相等。默认情况下,equals方法的行为和==对于引用类型是一样的,即比较内存地址。但是,很多类(如String、Integer等)都重写了equals方法,使其能够比较对象的内容是否相等,而不是内存地址。简单来说,equals看的是“内容是否一样”。
参考回答:
- ==:对于基本数据类型,直接比较值;对于引用数据类型,比较引用(内存地址)。
- equals():首先检查两个对象是否为同一个对象的引用(这通常是通过==运算符完成的,但具体实现可能有所不同),如果不是,则根据类的具体实现来比较对象的内容。许多类(如String、Integer等)都重写了equals()方法以提供更有意义的比较逻辑。
5、equals() 既然已经能实现对比的功能了,为什么还要 hashCode() 呢?(了解)
回答提示:
- 想象一下你有一堆对象,你想要快速地找出里面是否有某个特定的对象。如果只用
equals()方法逐个比较,效率会很低,因为equals()可能会比较对象的很多内部细节。 - 而
hashCode()方法则是用来快速判断两个对象在逻辑上是否可能相等的一个“捷径”。如果两个对象的hashCode()值不同,那么根据hashCode()的设计原则,这两个对象在逻辑上一定不相等,我们甚至不需要调用equals()方法就可以知道结果。这样,我们可以先将对象存储或查找在基于哈希值的集合中(如HashSet、HashMap),大大减少了不必要的equals()调用,提高了效率。 **hashCode()**方法通过提供一个快速的初步判断,帮助我们缩小了可能相等的对象的范围,与**equals()**方法一起工作,实现了高效的对象比较和查找。
参考回答:
- 因为重写的 equals() 里一般比较的比较全面比较复杂,这样效率就比较低,而利用 hashCode() 进行对比,则只要生成一个 hash 值进行比较就可以了,效率很高,那么 hashCode() 既然效率这么高为什么还要 equal() 呢?
- 因为 hashCode() 并不是完全可靠,有时候不同的对象他们生成的 hashcode 也会一样(生成 hash 值得公式可能存在的问题),所以 hashCode() 只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出结论:
- equals() 相等的两个对象他们的 hashCode() 肯定相等,也就是用 equals() 对比是绝对可靠的。
- hashCode() 相等的两个对象他们的 equals() 不一定相等,也就是 hashCode() 不是绝对可靠的。
6、String、StringBuilder和StringBuffer的区别?
回答提示:
- String是final修饰的,不可变,每次操作都会产生新的String对象.
- StringBuffer是在原对象上操作,是线程安全的,是synchronized修饰的。
- StringBuilder是在原对象上操作,是线程不安全的。
- 性能:StringBuilder > StringBuffer > String
参考回答:
- 字符串类型
String是不可变的,即一旦创建后,其值不可被修改。相比之下,StringBuilder和StringBuffer是可变的,允许对其内容进行修改。其中,StringBuffer是线程安全的,而StringBuilder是非线程安全的。
7、String str=new String("IT枫斗者")创建了几个对象?
参考回答:
- 1个或者2个
new String("IT枫斗者");- 相信很多小伙伴们听到的答案很多,有说创建了1个对象,也有说创建了2个对象。答案对,也不对,关键是要知道问题底层的原理。
String str1 = "IT枫斗者"; // 在常量池中
String str2 = new String("IT枫斗者"); // 在堆上- 当直接赋值时,字符串“IT枫斗者”会被存储在常量池中,只有1份,此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“IT枫斗者”,那么不会再创建对象,直接将引用赋值给str1;如果常量池中没有“IT枫斗者”,那么创建一个对象,并将引用赋值给str1。
- 那么,通过new String(“IT枫斗者”);的形式又是如何呢?答案是1个或2个。
- 当JVM遇到上述代码时,会先检索常量池中是否存在“IT枫斗者",IT枫斗者"这个字符串,则会先在常量池中创建这个一个字符串。然后再执行new操作,会在堆内存中创建一个存储“IT枫斗者”的String对象,对象的引用赋值给str2。此过程创建了2个对象。
- 当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。
8、接口和抽象类的区别?(最少要回答出1,2,3,5,7)
参考回答:
1. 定义与目的
- 接口:是一种行为规范或约定,它定义了对象应该做什么,而不关心对象具体如何做。接口只包含方法的声明,不包含方法的实现。
- 抽象类:是一种不能被实例化的类,它提供了一种模板,用于被其他类继承并实现其中的抽象方法。抽象类可以包含方法的实现,也可以不包含(即抽象方法)。
2. 成员变量
- 接口:成员变量默认都是静态常量(
static final),不能被修改。 - 抽象类:可以包含字段、属性、方法(包括抽象方法和非抽象方法)、构造器等。
3. 方法
- 接口:方法默认是公开的(
public),且不能有具体实现(Java 8及之后允许接口定义默认方法和静态方法,但仍以抽象方法为主)。接口中的所有方法都是抽象的。 - 抽象类:方法可以是抽象的,也可以是非抽象的。抽象方法没有具体实现,需要在子类中重写;非抽象方法可以有具体实现。抽象类中的方法可以有不同的访问级别。
4. 实例化
- 接口:不能被实例化,它只能被类实现。
- 抽象类:不能被直接实例化,但可以通过其子类(如果子类不是抽象的)来实例化。
5. 继承与实现
- 接口:一个类可以实现多个接口,从而获取多个接口中的方法。这是接口实现多态性的一个重要方式。
- 抽象类:一个类只能继承一个抽象类(在大多数面向对象的编程语言中,如Java)。这种单继承的特性限制了抽象类的使用范围。
6. 扩展性
- 接口:具有很好的扩展性,可以很容易地添加新的方法,而不需要修改现有的实现类。
- 抽象类:扩展性相对较差,如果添加新的抽象方法,所有子类都需要实现这个方法,这可能会影响到现有的代码。
7. 设计层面
- 抽象类:是对类的抽象,是一种模板式设计。它对整个类进行抽象,包括属性和行为,子类必定是抽象类的一种类型,继承是“是不是”的关系。
- 接口:是对行为的抽象,是一种辐射式设计。它是对类局部(行为)的抽象,接口实现是“有没有”的关系,比如一个类是否能实现某个接口,取决于它是否具备该接口所定义的行为
9、浅拷贝和深拷贝?
回答提示:
- 浅拷贝仍然指向原始对象的内存地址。这意味着,如果原始对象中的子对象发生了变化,浅拷贝得到的对象也会受到影响。浅拷贝适用于对象结构较简单,不包含复杂数据结构或不需要独立修改子对象的情况。
- 深拷贝递归地复制了对象内部的所有子对象或属性,创建全新的内存空间来存储这些对象。因此,深拷贝得到的对象与原始对象是完全独立的,对其中一个对象的修改不会影响到另一个对象。深拷贝适用于需要保持数据独立性和完整性的场景,如数据备份、多线程编程等。
参考答案:
1. 定义与本质
- 浅拷贝(Shallow Copy):
- 创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行逐位复制;如果字段是引用类型的话,则复制引用但不复制引用的对象。
- 因此,原始对象及其副本引用同一个对象。
BeanUtils.copyProperties方法用于将一个bean的属性值复制到另一个bean中,前提是这两个bean之间有相同的属性名和兼容的属性类型。这种方法在处理JavaBean之间的数据转换时非常有用,尤其是在不同层(如数据访问层和服务层)之间传递数据时。 --- 浅拷贝- 深拷贝(Deep Copy):
- 创建一个新对象,然后递归地将当前对象的所有非静态字段复制到该新对象,同时复制这些非静态字段引用的所有对象。
- 这意味着副本与原始对象完全独立,修改副本不会影响原始对象,反之亦然。
你通常会使用 JSONArray.parseArray 方法来将一个 JSON 字符串解析成一个 JSONArray 对象,这个对象包含了 JSON 数组中的所有元素。 -- 将对象转为json字符串后,通过JSONArray.parseArray转为对象后,就会出现一个新的对象,并且他们的内存地址不一样,对象中的内容相同2. 内存中的表现
- 浅拷贝:新对象与原始对象在内存中部分重叠,因为它们共享引用类型的字段。
- 深拷贝:新对象与原始对象在内存中完全独立,它们之间没有共享的字段或引用。
3. 修改的影响
- 浅拷贝:修改浅拷贝对象的引用类型字段可能会影响原始对象,因为它们共享同一个引用。
- 深拷贝:修改深拷贝对象不会影响原始对象,因为它们是完全独立的。
10、Java 8 新特性?(能回答出其中四五点即可)
参考回答:
Lambda表达式:是Java 8最重要的特性之一,允许以更简洁的方式编写匿名函数,特别是与函数式接口结合使用时,可以极大地简化代码。
例子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.forEach(item -> System.out.println(item));函数式接口:
描述:函数式接口是只包含一个抽象方法声明的接口。Java 8引入了一个注解
@FunctionalInterface,用来表示一个接口是函数式接口。例子:
@FunctionalInterface
public interface GreetingService {
void sayMessage(String message);方法引用:
描述:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。方法引用和Lambda表达式配合使用,可以使语言的构造更紧凑简洁,减少冗余代码。
例子:
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.forEach(System.out::println);默认方法:
描述:默认方法允许你在接口中有默认实现,这样即使实现的类没有实现该方法,也会有默认的行为。
例子:
public interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}Stream API:
描述:新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
例子:
List<String> myList = Arrays.asList("apple", "banana", "cherry");
myList.stream().filter(s -> s.contains("a")).forEach(System.out::println);Optional类:
描述:Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
例子:
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"新的日期时间API:
描述:Java 8引入了新的日期时间API,在
java.time包下,改进了时间日期的处理。例子:
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.of(12, 20);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.DECEMBER, 12, 12, 20);
dateTime.plusDays(12); // 日期加12天11、反射机制在项目中的实际应用?(了解)
参考回答:
- 自定义注解里有反射的应用
- 写上注解类,注解本身是一种特殊的接口,可以在接口里面设置属性,然后通过反射对属性进行处理就可以完成注解的功能,注解的功能是我们自己进行实现的
- 写完注解类之后,我们可以把这个注解写到其他的类或者属性上,我们通过
Class<T>clazz 来获取具体类的属性和方法,然后通过获取属性或者方法上的注解值,进行逻辑操作实现注解的功能 - 通过反射记录日志
- 比较两个VO对象的值是否发生变化,用于记录日志或审批
12、Java是值传递还是引用传递?(了解)
参考回答:
- Java是值传递。
- 当传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,因此可以修改原变量中的值;当传的是String类型时,虽然拷贝的也是引用地址,指向的是同一个数据,但是String的值不能被修改,因此无法修改原变量中的值。
13、Java的内部类
回答提示:
- 成员内部类:定义在外部类中的类,与外部类的成员变量和方法并列。成员内部类可以访问外部类的所有成员(包括私有成员),但需要通过外部类的实例来创建。
- 静态内部类:使用static修饰的内部类称为静态内部类。它不需要外部类的实例即可创建,因此只能访问外部类的静态成员(变量和方法)。
- 局部内部类:这是定义在外部类的方法或代码块中的类。它的作用域仅限于所在的方法或代码块,且不能添加访问修饰符(如public、private等)。局部内部类可以访问外部类的所有成员(包括私有成员),以及所在方法或代码块中的局部变量(但变量必须是final的)。
- 匿名内部类:没有具体名称的内部类,通常用于实现接口或继承其他类,并立即创建该类的实例。它通常用在只需要一个类的实例,且这个类本身不会重用的场景中。匿名内部类可以访问外部类的所有成员,但无法定义构造方法。
参考回答:
成员内部类
成员内部类:最普通的一种内部类,定义在类中方法外的一个普通的类(没有static修饰),可用任意的修饰符修饰。
内部类可以访问外部类所有的属性和方法(包括私有)。但是外部类要访问成员内部类的属性和方法,必须要先实例化内部类。
在其他的类中使用内部类时,需要先有外部类的对象(成员内部类是依附外部类而存在的。
静态内部类
静态内部类:静态内部类就是在成员内部类加了一个 static 关键字,静态内部类就像外部类的一个静态成员一样。
静态内部类不能直接访问外部类的非静态成员,但可以通过创建外部类的对象访问。
外部类的静态成员与内部类的成员名称相同,可通过(类名.静态成员) 访问外部类的静态成员。
静态内部类创建对象无需依赖外部类对象,可以直接创建。
局部内部类
局部内部类:使用的比较少,声明在方法体或一段代码块的内部。
仅在定义的方法、代码块内部使用,其他地方无法使用(定义在方法内则只能在方法中使用,定义在for循环中则只能在for循环中使用),作用范围类似于局部变量。
局部内部类不可使用权限修饰符 静态(static)修饰符进行修,但可以使用final 或 abstract修饰。
局部内部类可以直接访问方法中的属性。
可以访问外部类的的属性和方法(包括私有的)。
匿名内部类
匿名内部类:很常用。它是一种没有名字的内部类,只能使用一次,通常是某个类在实现时只会使用一次,所以为了简化,不需要去专门写一个实现类,而是直接使用内部类完成。
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,匿名内部类没有名字,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。(只能有一个对象)。
匿名内部类必须继承父类或实现接口,使用多态形式引用。
14、什么关键字阻止该变量被序列化到文件中?
- 在变量声明前加上 Transient 关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
- 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串 等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在 客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的 数据安全。
