java基础

长度

Java 中的 length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
Java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.
Java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!


JDK、JRE、JVM

image-20240725230247664

JRE是java运行时环境,是java程序运行所需的最小环境。它包含了JVM和一组java类库,用于支持java程序的执行。RE不包,含开发工具,只提供Jva程序运行所需的运行环境。

为什么说 Java 语言“编译与解释并存”?

  • 编译:源代码需要先编译为字节码(编译型语言的特性)。
  • 解释:字节码默认由JVM解释执行(解释型语言的特性)。
  • 动态编译:JIT在运行时进一步编译热点代码(二次编译优化性能)。

这种混合机制既保证了Java的跨平台性(字节码可以在任何JVM上运行),又通过JIT优化提升了运行效率,避免了纯解释型语言的性能瓶颈。

八种基本数据类型

1743671428303.png

在Java中,对象引用的存储位置取决于其作用域和上下文。以下是详细的解释:


对象存储位置

1. 对象实例的存储位置

  • 堆内存(Heap)
    所有对象实例本身(即通过 new 关键字创建的对象)都存储在堆内存中。堆是JVM中最大的一块内存区域,由垃圾回收器(GC)统一管理。

2. 对象引用的存储位置

对象引用(即变量名)存储的是对象在堆内存中的地址,但引用变量本身的位置由它的作用域决定:

(1) 局部变量(方法内的引用)

  • 存储位置栈内存(Stack) 当引用作为某个方法的局部变量时(例如方法参数或方法内部定义的变量),引用变量本身存储在栈帧的局部变量表中。

(2) 成员变量(实例变量)

  • 存储位置堆内存(Heap) 如果引用是某个类的成员变量(实例变量),则该引用作为对象的一部分,存储在堆内存的所属对象实例中。

    1
    2
    3
    class MyClass {
    Object ref; // 成员变量,引用存储在堆内存的MyClass对象实例中
    }

(3) 静态变量(类变量)

  • 存储位置方法区(Method Area / Metaspace) 如果引用是静态变量(static修饰),则该引用存储在方法区(JDK 8之后称为Metaspace)中。

    Java

1
2
3
class MyClass {
static Object staticRef; // 静态变量,引用存储在方法区
}

3. 为什么这样设计?

  • 栈内存

    用于存储方法的执行上下文(局部变量、操作数栈等),生命周期与方法调用一致,速度快但容量小。

    局部变量的引用存放在栈中,方法结束后自动释放,无需GC干预。

  • 堆内存

    用于存储对象实例,生命周期由垃圾回收器管理,容量大但访问速度较慢。

    对象实例必须存储在堆中,以便跨方法或线程共享

  • 方法区

    存储类元数据、静态变量等,生命周期与类加载一致。

总结

  • 对象实例本身:始终存储在堆内存中。
  • 对象引用的存储位置:
    • 局部变量 → 栈内存。
    • 成员变量 → 堆内存(所属对象内部)。
    • 静态变量 → 方法区。

向下转型

定义

将父类引用强制转换为子类类型(显式转换,需要强制类型转换符)。

Java

1
2
Parent parent = new Child();
Child child = (Child) parent; // 向下转型

特点

  • 需要强制转换符:必须显式使用(Child)
  • 存在风险:如果父类引用实际指向的对象不是目标子类实例,会抛出ClassCastException
  • 恢复子类能力:转换成功后,可以调用子类特有的属性和方法。

向上转型\向下转型关键区别

特性 向上转型 向下转型
转换方向 子类 → 父类 父类 → 子类
安全性 安全(自动完成) 不安全(需显式检查)
语法 隐式(无需强制转换符) 显式(需(Child)强制转换)
方法访问范围 只能访问父类方法 可访问子类特有方法
典型应用 多态、统一接口 恢复子类功能

Java抽象类和接口的区别是什么?

两者的特点:

  • 抽象类用于描述类的共同特性和行为,可以有成员变量、构造方法和具体方法。适用于有明显继承关系的场景。
  • 接口用于定义行为规范,可以多实现,只能有常量和抽象方法(Java8以后可以有默认方法和静态方法)。适用于定义类的能力或功能。

两者的区别:

  • 抽象类不能被实例化

    • 实现方式:实现接口的关键字为implements,继承抽象类的关键字为extends

    • 方法、变量:接口只有常量(即静态常量)、方法定义,不能有方法的实现,java1.8中可以定义default方法体**;抽象类可以包含实例变量和静态变量**,可以有方法定义与实现,方法可在抽象类中实现。

      Java

      1
      2
      3
      4
      5
      6
      public interface Animal{
      void makesound();
      default void sleep(){
      System.out.println("Sleeping...");
      }
      }
    • 访问修饰符:接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

浅拷贝和深拷贝

  • 浅拷贝是指只复制对象本身和其内部的值类型字段,但不会复制对象内部的引用类型字段,两个对象指向的是同一个引用对象。
  • 深拷贝是指在复制对象的同时,将对象内部的所有引用类型字段的内容也复制一份

泛型

它允许类、接口和方法在定义时使用一个或多个类型参数,这些类型参数在使用时可以被指定为具体的类型。

如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;

Java

1
2
3
4
5
6
7
8
private static double add(double a,double b){
System.out.println(a + "+"+ b + "=" +(a + b));
return a + b;
}
private static <T extends Number>double add(T a,T b){
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}

获取 Class 对象的四种方式

1.类.class

知道具体类的情况下可以使用:

Java

1
Class alunbarClass = TargetObject.class;

2. 通过 **Class.forName()**传入类的全路径获取:

Java

1
2
3
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");

TargetObject targetObject = (TargetObject) targetClass.newInstance();

3.通过对象实例instance.getClass()获取:**

Java

1
2
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

4.通过类加载器xxxClassLoader.loadClass()传入类路径获取:

Java

1
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");

通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行

获取指定方法并调用

Java

1
2
3
4
5
 public void publicMethod(String s) {      
System.out.println("I love " + s);
}
Method publicMethod = targetClass.getDeclaredMethod("publicMethod", String.class);
publicMethod.invoke(targetObject, "JavaGuide");

反射在你平时写代码或者框架中的应用场景有哪些?

1.加载数据库驱动

2.加载配置文件

Spring通过XML配置模式装载Bean的过程:

  • 将程序中所有XML或properties配置文件加载入内存
  • Java类里面解析xml或者properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
  • 使用反射机制,根据这个字符串获得某个类的Class实例

注解的解析方法有哪几种?

注解只有被解析之后才会生效,常见的解析方法有两种:

  • 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的

== 与 equals 有什么区别?

== 在基本数据类型:值内容, 引用类型时:地址

equals 重写:值内容 , equals不重写:地址


hashcode和equals方法有什么关系?

在Java中,对于重写equals方法的类,通常也需要重写hashcode方法,并且需要遵循以下规定:

  • 一致性:如果两个对象使用equa1s方法比较结果为 true,那么它们的hashCode值必须相同。也就是说,如果obj1.equals(obj2)返回true,那么obj1.hashCode()必须等于 obj2.hashCode()。
  • 非一致性:如果两个对象的 hashcode值相同,它们使用equals方法比较的结果不一定为 true。即obj1.hashCode()=obj2.hashcode()时, obj1.equals(obj2)可能为false,这种情况称为哈希冲突。

hashCode和equals方法是紧密相关的,重写equals方法时必须重写hashcode方法,以保证在使用哈希表等数据结构时,对象的相等性判断和存储查找操作能够正常工作。而重写hashCode方法时,需要确保相等的对像具有相同的哈希码,但相同哈希码的对象不一定相等


String、StringBuffer、StringBuilder的区别和联系

JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动到了Java 堆中。

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// s1 指向字符串常量池中的 "Java" 对象
String s1 = "Java";
// s2 也指向字符串常量池中的 "Java" 对象,和 s1 是同一个对象
String s2 = s1.intern();//如果常量池中已经存在相同内容的字符串,则返回常量池中已有对象的引用;否则,将该字符串添加到常量池并返回其引用。
// 在堆中创建一个新的 "Java" 对象,s3 指向它
String s3 = new String("Java");
// s4 指向字符串常量池中的 "Java" 对象,和 s1 是同一个对象
String s4 = s3.intern();
// s1 和 s2 指向的是同一个常量池中的对象
System.out.println(s1 == s2); // true
// s3 指向堆中的对象,s4 指向常量池中的对象,所以不同
System.out.println(s3 == s4); // false
// s1 和 s4 都指向常量池中的同一个对象
System.out.println(s1 == s4); // true
========================================
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//编译器会给你优化成 String str3 = "string";
String str4 = str1 + str2;// stringbuilder,新对象
String str5 = "string";
String str6=str4.intern();
==========================================
System.out.println(str3 == str6);//true
System.out.println(str3 == str5);//true
System.out.println(str3 == str5&&str6 == str5);//true
System.out.println(str3 == str4);//false

SPI 和 API12

1. 什么是 SPI 和 API?34

SPI(Service Provider Interface) 是一种 Java 的扩展机制,用于实现模块化开发。它允许应用程序定义接口,并通过配置文件来加载具体的实现类。56

API(Application Programming Interface) 是一组预定义的函数、方法或协议,用于在软件系7统中进行交互。API 定义了如何使用8某个软件库或框架提供的功能。

2. SPI 和 API 的区别是什么?

  • 定义方式不同:API 是由开发者主动编写并公开给其他开发者使用的,而 SPI 是由框架或库提供方定义的接口,供第三方开发者实现。
  • 调用方式不同:API 是通过直接调用接口的方法来使用功能,而 SPI 是通过配置文件来指定具体的实现类,然后由框架或库自动加载和调用。
  • 灵活性不同:API 的实现类必须在编译时就确定,无法动态替换;而 SPI 的实现类可以在运行时根据配置文件的内容进行动态加载和替换。
  • 依赖关系不同:API 是被调用方依赖的,即应用程序需要引入 API 所在的库才能使用其功能;而 SPI 是调用方依赖的,即框架或库需要引入第三方实现类的库才能加载和调用。

3. SPI 的优点

  • 扩展性强:SPI 允许第三方开发者在不修改框架或库源码的情况下,通过实现接口来扩展功能。
  • 解耦合:SPI 将接口与具体实现类分离,降低了模块之间的依赖关系,提高了代码的可维护性和可测试性。
  • 动态加载:SPI 支持在运行时根据配置文件的内容动态加载和替换实现类,使系统更加灵活。