(三)Java 基础

Java基础

1、Java 语言特点(优势)

  • 平台无关性

    • 特点
      • 一次编译,处处执行
    • 实现方式
      • Java 代码编译后生成 .class 文件(字节码)
      • 字节码可以在任何装有 Java 虚拟机(JVM) 的系统上运行
  • 面向对象

    • 特点
      • 一切皆对象
    • 包含三大特性
      • 继承
        • 子类可以继承父类的属性或方法
        • 实现代码的复用
      • 封装
        • 将数据和操作数据的方法封装在一起
        • 隐藏内部的实现细节,提高代码的安全性
      • 多态
        • 允许不同的子类对象对相同的方法调用做出不同的响应
        • 示例:Animal 类的 makeSound() 方法,CatDog 类分别实现不同的声音
    • 继承和多态的区别
      • 继承
        • 继承是一种类与类之间的关系,用于实现代码复用
        • Cat 类继承 Animal 类的 eat() 方法
      • 多态
        • 多态是指同一个行为(方法)在不同对象中有不同的实现方式
        • CatDog 类重写 AnimalmakeSound() 方法
  • 内存管理

    • 特点
      • 自动垃圾回收机制
    • 实现方式
      • JVM 自动检测和回收不再被使用的对象所占用的内存空间
      • 开发者不需要手动管理内存

2、final 关键字

final 关键字可以修饰类、方法、变量、参数,具有不同的作用

1、修饰类:类不能被继承,通常用于创建一些不希望被拓展的类,如java中String类就是final类

final class FinalClass {
    // 类的成员和方法
}
// 以下代码将无法编译
// class SubClass extends FinalClass {} 

2、修饰方法:方法不能被子类重写,用于确保方法不会被修改

class BaseClass {
    public final void finalMethod() {
        System.out.println("This is a final method.");
    }
}
class SubClass extends BaseClass {
    // 以下代码将无法编译
    // public void finalMethod() {
    //     System.out.println("Trying to override final method.");
    // }
}

3、修饰变量:对于基本数据类型:被final修饰的变量一旦赋值,就不能再修改其值;
对于引用数据类型:一旦引用了一个对象或数组,就不能再引用其他对象或数组,但可以修改其内部状态(值)

// 修饰基本数据类型
final int number = 5;
// 以下代码将无法编译
// number = 10; 

// 修饰对象
final StringBuilder sb = new StringBuilder("Hello");
sb.append(", World"); // 允许修改对象的内部状态
// 以下代码将无法编译
// sb = new StringBuilder("Goodbye"); 

4、修饰参数:被final修饰的参数,在方法内部不能被修改

public void printValue(final int value) {
    // 以下代码将无法编译
    // value = 10; 
    System.out.println(value);
}

3、深拷贝和浅拷贝

image-20250113173815158

浅拷贝:创建一个新对象,复制原始对象的所有属性值。如果属性是基本类型,则复制值;如果是引用类型,则复制引用(即共享同一内存地址)

深拷贝:创建一个新对象,递归复制原始对象的所有属性值,包括引用类型属性的内容,确保两者完全独立。

两者一个是复制引用,共享引用类型属性;一个是递归复制所有内容,完全独立。

4、序列化和反序列化

序列化:把Java对象转换为字节序列的过程
反序列化:把字节序列恢复为Java对象的过程

为什么需要序列化?

​ 现在在网络上传输的各种类型的数据(文本、图片、音视频等)都是以二进制序列的形式在网络上传送,那么发送方就需要将这些数据序列化为字节流后传输,而接收方收到字节流数据后需要将其转化为相应的数据类型,也可以将字节流数据存储到磁盘,等到以后恢复。

故序列化和反序列化主要两种用途:

1、持久化数据到磁盘

2、方便数据在网络上传输

5、java 创建对象的方式

1、使用new关键字。直接调用类的构造方法来创建对象

MyClass obj = new MyClass();

2、使用Class类的newInstance()方法。通过反射机制,可以使用Class类的newInstance()方法创建对象。

MyClass obj = (MyClass) Class.forName("com.example.MyClass").newInstance();

3、使用Constructor类的newInstance()方法:同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象。

Constructor<MyClass> constructor = MyClass.class.getConstructor();
MyClass obj = constructor.newInstance();

4、使用clone()方法。如果类实现了Cloneable接口,可以使用clone()方法复制对象。

MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();

5、使用反序列化。通过将对象序列化到文件或流中,然后再进行反序列化来创建对象

Object instance1 = MyClass.class.getConstructor().newInstance();

// SerializedObject.java
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.ser"));
out.writeObject(obj);
out.close();

// DeserializedObject.java
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.ser"));
MyClass obj = (MyClass) in.readObject();
in.close();

// 被序列化的类必须实现 Serializable 接口
class MyClass implements Serializable {
    public void myMethod(String message) {
        System.out.println("Method called with: " + message);
    }
}

6、Java 反射

反射机制是程序在运行时能够动态获取类的信息(如类名、方法、属性等),并操作类或对象的能力。

关键类和API:

  1. Class<?>:表示类的元数据
  2. Constructor:用于创建对象
  3. Method:表示类的方法,可用于调用方法
  4. Field:表示类的属性,可用于访问或修改属性值
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取类的Class对象
        Class<?> clazz = Class.forName("com.example.MyClass");

        // 创建对象
        Object instance = clazz.getDeclaredConstructor().newInstance();

        // 获取方法
        Method method = clazz.getMethod("myMethod", String.class);

        // 调用方法
        method.invoke(instance, "Hello, Reflection!");
    }
}

class MyClass {
    public void myMethod(String message) {
        System.out.println("Method called with: " + message);
    }
}

7、String|StringBuffer|StringBuilder

处理字符串的三个常用类,区别主要体现在 可变性、线程安全性 和 性能

  • String
    • 不可变
      • String 对象一旦创建,其内容就不能被修改
      • 任何对 String 的操作(如拼接、替换)都会生成一个新的 String 对象
    • 线程安全
      • 由于 String 是不可变的,因此它是线程安全的
    • 性能
      • 频繁修改字符串时性能较差,因为每次操作都会生成新对象
    • 适用场景
      • 适用于字符串内容不经常变化的场景
  • StringBuffer
    • 可变
      • 可变的字符序列,可动态修改其内容
    • 线程安全
      • StringBuffer 的方法都使用了 synchronized 关键字,保证了线程安全
    • 性能
      • 由于线程安全的开销,性能比 StringBuilder 稍差
    • 适用场景
      • 多线程场景
  • StringBuilder
    • 可变
      • 可变的字符序列,可动态修改其内容
    • 非线程安全
      • StringBuilder 没有使用 synchronized 关键字,因此不是线程安全的
    • 性能
      • 由于没有线程安全的开销,性能比 StringBuffer 更好
    • 适用场景
      • 单线程场景

8、BIO | NIO | AIO

BIO和NIO都是同步IO,区分是阻塞/非阻塞
AIO是异步IO

- 同步 | 异步
	- 同步:应用程序需要主动检查 I/O 操作的状态
	- 异步:I/O 操作完成后 (操作系统) 会通过回调通知应用程序
- 阻塞 | 非阻塞
	- 可以理解为阻塞IO和非阻塞IO的意思
  • BIO
    • 定义
      • 传统的基于流模型实现,采用同步阻塞的方式
    • 特点
      • 同步阻塞:线程在读写操作完成之前会一直阻塞,无法执行其他任务
      • 一对一模型:每个连接需要一个独立的线程处理,适合连接数较少的场景
    • 类比
      • 就像在食堂点餐,厨师做好之前你必须在窗口等着,其他人也得排队
  • NIO
    • 定义
      • 基于通道(Channel)和缓冲区(Buffer)实现,支持多路复用和非阻塞操作
    • 特点
      • 同步非阻塞:线程在读写操作时不会被阻塞,可以处理其他任务
      • 多路复用:通过 Selector 机制,一个线程可以管理多个连接
      • 事件驱动:基于事件(如可读、可写)触发操作
    • 类比
      • 就像在食堂点餐后拿到小票,你可以去做其他事情,等餐好了再来取
  • AIO
    • 定义
      • 基于事件和回调机制实现,支持异步非阻塞操作
    • 特点
      • 异步非阻塞:应用程序发起 I/O 操作后立即返回,内核完成操作后通过回调通知应用程序
      • 事件驱动:基于回调机制,内核主动通知应用程序
    • 类比
      • 就像在食堂点餐后,你可以完全离开,厨师做好后会主动通知你来取餐

9、Java 21 新特性

  • 语言特性增强

    • Switch 语句的模式匹配

      • 允许在 switchcase 标签中使用模式匹配,使代码更灵活、类型安全

      •   Object value = "Hello";
          switch (value) {
              // 匹配String类型,并获取长度
              case String s -> System.out.println(s.length()); 
              // 匹配Integer类型,并乘以2
              case Integer i -> System.out.println(i * 2); 
              // 匹配null值
              case null -> System.out.println("Value is null"); 
              // 未知类型
              default -> System.out.println("Unknown type"); 
          }
        
    • 数组模式匹配

      • 将模式匹配扩展到数组,支持在条件语句中解构和检查数组内容

      •   if (arr instanceof int[] {1, 2, 3}) {
              System.out.println("匹配成功!");
          }
        
    • 字符串模板(预览版)

      • 提供了一种更简洁、可读性更高的字符串拼接方式

      •   String name = "Alice";
          String message = `Hello {name}, welcome to Java 21!`;
        
  • 并发特性改进

    • 虚拟线程

      • 引入轻量级线程,显著降低内存消耗,提高并发性能

      •   Thread.startVirtualThread(() -> {
              System.out.println("Running on a virtual thread!");
          });
        
    • Scoped Values(范围值)

      • 提供了一种在线程间共享不可变数据的新方式,替代传统的线程局部存储(ThreadLocal)

      •   ScopedValue<String> userSession = ScopedValue.newInstance();
          ScopedValue.runWhere(userSession, "session123", () -> {
              System.out.println("User session: " + userSession.get());
          });
        

10、静态内部类 | 匿名内部类

特性 静态内部类 匿名内部类
定义 使用 static 关键字修饰的内部类。 没有名字的内部类,通常在创建对象时定义。
访问权限 只能访问外部类的静态成员。 可以访问外部类的所有成员(包括静态和非静态)。
生命周期 与外部类的静态成员相似,独立于外部类实例。 取决于使用场景,可能与方法或外部类对象一致。
实例化 通过 外部类名.静态内部类名 创建实例。 在定义的同时实例化,且只能创建一个实例。
适用场景 与外部类有紧密关联但不依赖外部类实例的场景。 临时实现接口或继承抽象类,且只使用一次的场景。
// 静态内部类
class OuterClass {
    private static int outerStaticVar = 10;
    static class StaticInnerClass {
        public void printOuterStaticVar() {
            System.out.println(outerStaticVar);
        }
    }
}
// 匿名内部类
interface MyInterface {
    void myMethod();
}
class Main {
    public static void main(String[] args) {
        MyInterface anonymousClass = new MyInterface() {
            @Override
            public void myMethod() {
                System.out.println("This is an anonymous class implementing MyInterface");
            }
        };
        anonymousClass.myMethod();
    }
}

11、装箱 | 拆箱

Java 引入包装类的原因主要是为了弥补基本数据类型在面向对象编程中的局限性,同时满足某些特定场景的需求。

  • 面向对象特性

    • Java 是一门面向对象的编程语言,而基本数据类型(如 intchar)不是对象,无法直接参与面向对象的操作。包装类的作用就是将基本数据类型封装成对象,从而使其具备面向对象的特性。

    • 使用场景

      • 泛型编程:Java 的泛型(如 List<T>Map<K, V>)只能接受对象类型,不能接受基本数据类型。例如:

        •   List<Integer> list = new ArrayList<>(); // 正确
            List<int> list = new ArrayList<>();     // 错误,int 不是对象类型
          
      • 方法参数:某些方法需要传递对象类型,而不是基本数据类型。

  • null 值的支持

    • 基本数据类型(如 intchar)不能为 null,而包装类可以表示 null 值。这在某些场景下非常有用,例如:

      • 数据库查询结果可能为 null,如果用 int 接收会导致错误,而 Integer 可以正确处理。
      • 表示“未初始化”或“无效值”的状态。
    •   Integer result = getFromDatabase(); // 可能返回 null
        if (result == null) {
            System.out.println("No data found.");
        } else {
            System.out.println("Data: " + result);
        }
      
  • 包装类提供了很多实用静态方法和常量

    • Integer.parseInt(String s):将字符串转换为整数
  • 自动装箱和拆箱

    • Java 5 引入了 自动装箱(Autoboxing)自动拆箱(Unboxing) 机制,使得基本数据类型和包装类之间的转换更加方便。

    • 自动装箱

      • 将基本数据类型自动转换为对应的包装类对象。

      •   Integer num = 10; // 自动装箱,相当于 Integer num = Integer.valueOf(10);
        
    • 自动拆箱

      • 将包装类对象自动转换为基本数据类型。

      •   int value = num; // 自动拆箱,相当于 int value = num.intValue();
        
    • 使用场景

      • 在集合类中存储基本数据类型。
      • 在方法调用时自动转换参数类型
  • 缓存机制

    • Java 对部分包装类(如 IntegerCharacter)实现了缓存机制,以提高性能。例如:

      • Integer 缓存了 -128 到 127 之间的值
      • Character 缓存了 0 到 127 之间的值
        • 在这个范围内的 Integer 对象会被缓存,重复使用同一个对象
        • 不在这个缓存范围内的,则创建一个新的 Integer 对象
        • 引用一致则值一定相等
          • 如果两个对象的引用一致(== 返回 true),那么它们的值一定相等。
    •   Integer a = 100;
        Integer b = 100;
        System.out.println(a == b); // true,因为 100 在缓存范围内
        
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d); // false,因为 200 不在缓存范围内
      
      • 如何正确比较 Integer 对象

        • 使用 equals 方法来比较 Integer 对象的值

          • equals 方法会比较两个 Integer 对象的值是否相等,而不是比较引用
        •   Integer c = 200;
            Integer d = 200;
            System.out.println(c.equals(d)); // true,比较的是值
          
  • 性能权衡

    • 虽然包装类提供了更多的功能,但它们也有一些缺点:
      • 内存开销
        • 包装类是对象,存储在堆内存中,而基本数据类型存储在栈内存中。包装类的内存开销更大。
      • 性能开销
        • 装箱和拆箱操作会带来额外的性能开销

12、Java异常

Java的异常体系主要基于两大类:Throwable类及其子类。Throwable有两个重要的子类:Error和Exception,它们分别代表了不同类型的异常情况。

  • Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。

  • Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:

    • 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
      • 运行时异常:这类异常包括运行时异常(RuntimeException)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。

image-20250313102248797

13、Java8 新特性|Lambda

Lambda 表达式它是一种简洁的语法,用于创建匿名函数,主要用于简化函数式接口(只有一个抽象方法的接口)的使用。其基本语法有以下两种形式:

  • (parameters) -> expression:当 Lambda 体只有一个表达式时使用,表达式的结果会作为返回值。
  • (parameters) -> { statements; }:当 Lambda 体包含多条语句时,需要使用大括号将语句括起来,若有返回值则需要使用 return 语句。

传统的匿名内部类实现方式代码较为冗长,而 Lambda 表达式可以用更简洁的语法实现相同的功能。比如,使用匿名内部类实现 Runnable 接口

public class AnonymousClassExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running using anonymous class");
            }
        });
        t1.start();
    }
}

使用 Lambda 表达式实现相同功能:

public class LambdaExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> System.out.println("Running using lambda expression"));
        t1.start();
    }
}

可以看到,Lambda 表达式的代码更加简洁明了。

还有,Lambda 表达式能够更清晰地表达代码的意图,尤其是在处理集合操作时,如过滤、映射等。比如,过滤出列表中所有偶数

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ReadabilityExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        // 使用 Lambda 表达式结合 Stream API 过滤偶数
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0)
                                           .collect(Collectors.toList());
        System.out.println(evenNumbers);
    }
}

通过 Lambda 表达式,代码的逻辑更加直观,易于理解。

还有,Lambda 表达式使得 Java 支持函数式编程范式,允许将函数作为参数传递,从而可以编写更灵活、可复用的代码。比如定义一个通用的计算函数。

interface Calculator {
    int calculate(int a, int b);
}

publicclass FunctionalProgrammingExample {
    public static int operate(int a, int b, Calculator calculator) {
        return calculator.calculate(a, b);
    }

    public static void main(String[] args) {
        // 使用 Lambda 表达式传递加法函数
        int sum = operate(3, 5, (x, y) -> x + y);
        System.out.println("Sum: " + sum);

        // 使用 Lambda 表达式传递乘法函数
        int product = operate(3, 5, (x, y) -> x * y);
        System.out.println("Product: " + product);
    }
}

虽然 Lambda 表达式优点蛮多的,不过也有一些缺点,比如会增加调试困难,因为 Lambda 表达式是匿名的,在调试时很难定位具体是哪个 Lambda 表达式出现了问题。尤其是当 Lambda 表达式嵌套使用或者比较复杂时,调试难度会进一步增加。

posted @ 2025-04-09 13:26  墨羽寻觅  阅读(24)  评论(0)    收藏  举报