当前位置: 首页 > article >正文

三天急速通关JAVA基础知识:Day3 基础加强

三天急速通关JAVA基础知识:Day3 基础加强

  • 0 文章说明
  • 1 接口
  • 2 函数式编程
    • 2.1 Lambda表达式
    • 2.2 方法引用
  • 3 异常
    • 3.1 异常的定义
    • 3.2 异常的分类
      • 3.2.1 检查型异常(Checked Exception)
      • 3.2.2 非检查型异常(Unchecked Exception)
      • 检查型异常与非检查型异常的区别
    • 3.3 异常的处理
      • 3.3.1 使用`try-catch`捕获异常
      • 3.3.2 使用`try-catch-finally`进行清理操作
      • 3.3.3 使用`try-with-resources`自动管理资源
      • 3.3.4 使用`throws`声明抛出异常
  • 4 泛型
    • 4.1 泛型的定义
    • 4.2 泛型类,接口,方法
    • 4.3 通配符与上下限
  • 5 集合
    • 5.1 集合的定义
    • 5.2 顶层集合接口的常见方法
  • 6 Stream流
    • 6.1 Stream的定义
    • 6.2 Stream常见方法
  • 7 IO流
  • 8 并发
    • 8.1 线程
    • 8.2 线程池
  • 9 网络编程
    • 9.1 基础知识
    • 9.2 Java网络编程的一些核心类
    • 9.3 TCP编程
    • 9.4 UDP编程
  • 10 注解
    • 10.1 注解的定义
    • 10.3 常见注解
    • 10.4 元注解
  • 11 代理
    • 11.1 代理的定义
    • 11.2 代理的形式(JDK动态代理)
  • Final 考试

0 文章说明

本文目的是为了扩展Java的基础知识体系,在后续实践中逐渐掌握加完善,内容由本人已学习的java知识为基础,kimi与gpt辅助完善。本文所提供的信息和内容仅供参考,作者和发布者不保证其准确性和完整性。

1 接口

  • 接口的定义

    • 使用interface关键字定义接口,是抽象方法和常量的集合,方法默认为public abstract,字段默认为public static final
      // 定义一个接口
      public interface Animal {
          // 抽象方法 默认是public abstract
          void makeSound();
      
          // 接口中的字段默认是 public static final
          int LEGS = 4;
      }
      
  • 接口的特性

    • 不能被实例化,抽象方法不能有具体实现
    • 可以包含抽象方法、默认方法、静态方法和私有方法,一个接口可以继承多个接口
  • 接口的实现

    • 使用implements关键字实现接口,一个类可以实现多个接口的同时继承一个其他类,语法是先继承后实现
    • 实现接口的类必须实现接口中的所有抽象方法(除非该类是抽象类
      // 实现接口的类
      public class Dog implements Animal {
          @Override
          public void makeSound() {
              System.out.println("Woof woof!");
          }
      
          public static void main(String[] args) {
              Dog dog = new Dog();
              // 输出:Woof woof!
              dog.makeSound(); 
              // 输出:Number of legs: 4
              System.out.println("Number of legs: " + Animal.LEGS); 
          }
      }
      
  • 接口中的方法

    • 抽象方法
      • 方法默认是抽象方法,没有方法体,只有方法签名
    • 默认方法
      • 使用default关键字定义,可以有具体实现
      • 解决了接口方法扩展时的兼容性问题
    • 静态方法
      • 使用static关键字定义,可以有具体实现,只能通过接口名调用
    • 私有方法
      • 使用private关键字定义,只能在接口内部使用
      • 用于辅助默认方法或静态方法的实现
      // 定义接口
      public interface Calculator {
          // 抽象方法
          int add(int a, int b);
      
          // 默认方法
          default int subtract(int a, int b) {
              return a - b;
          }
      
          // 静态方法
          static int multiply(int a, int b) {
              return a * b;
          }
      
          // 私有方法(用于辅助默认方法或静态方法)
          private int addHelper(int a, int b) {
              return a + b;
          }
      }
      
  • 接口中的字段

    • 接口中的字段默认是public static final,即常量,必须赋初值,且不能被修改
  • 接口的用途

    • 定义规范,规定实现类的行为
    • 实现多态,通过接口引用调用实现类的方法
    • 解耦合,降低类与类之间的依赖关系
    • 提供回调机制,用于事件处理等场景
  • 接口与抽象类

    • 类单继承类多实现接口,接口多继承接口
    • 接口继承自多个接口,存在方法签名冲突时,编译器会报错。
    • 类继承自类并实现接口,父类和接口中存在同名方法时,会优先使用父类的方法。为了避免歧义,显式使用super来调用父类方法。
    • 类实现多个接口,存在同名的默认方法时,需要在实现类中重写该方法以解决冲突。如果要分别调用两个父接口的方法,可以通过接口引用来实现。
      interface Foo {
             default void bar() {
                 System.out.println("Foo.bar");
             }
         }
      
         interface Baz {
             default void bar() {
                 System.out.println("Baz.bar");
             }
         }
      
         class Qux implements Foo, Baz {
             // 重写默认方法
             @Override
             public void bar() {
                 // 通过接口引用分别调用两个接口中的同名方法
                 Foo.super.bar(); // 调用Foo接口中的bar方法
                 Baz.super.bar(); // 调用Baz接口中的bar方法
                 System.out.println("Qux.bar");
             }
         }
      

2 函数式编程

2.1 Lambda表达式

  • Lambda表达式定义
    Java 8引入了Lambda表达式,替代匿名内部类用于简化函数式接口(接口中只有一个抽象方法)的实现,Lambda表达式的语法如下:
    (参数列表) -> 表达式或语句块
    
  • Lambda表达式的简化
    • 省略参数类型
      如果Lambda表达式的参数类型可以从上下文中推断出来,则可以省略参数类型。
      // 显式类型
      Comparator<Integer> comparator = (Integer a, Integer b) -> a.compareTo(b);  
      // 省略类型
      Comparator<Integer> comparator = (a, b) -> a.compareTo(b);
      
    • 省略括号(单个参数)
      如果Lambda表达式只有一个参数,可以省略参数的括号。
      // 显式括号  
      Consumer<String> consumer = (String s) -> System.out.println(s);  
      // 省略括号  
      Consumer<String> consumer = s -> System.out.println(s);
      
    • 省略花括号(单个表达式)
      如果Lambda表达式体只包含一个表达式,可以省略花括号{},并且表达式的结果会自动返回。
      // 显式花括号  
      Function<Integer, Integer> function = (Integer x) -> { return x * 2; };  
      
      // 省略花括号  
      Function<Integer, Integer> function = x -> x * 2;
      
    • 省略return关键字(单个表达式)
      如果Lambda表达式体只包含一个表达式,可以省略return关键字,表达式的结果会自动作为返回值。
      // 显式返回  
      Function<Integer, Integer> function = (Integer x) -> { return x * 2; };  
      
      // 省略`return`关键字  
      Function<Integer, Integer> function = x -> x * 2;
      

2.2 方法引用

  • 方法引用的定义
    方法引用是Lambda表达式的简化形式,它使得代码更加简洁、易读,同时保持了功能的完整性。
    类名::方法名
    对象名::方法名
    类名::new
    
  • 方法引用的分类
    • 静态方法引用
    // 定义一个静态方法
    public class Utils {
        public static int add(int a, int b) {
            return a + b;
        }
    }
    // 使用静态方法引用
    BinaryOperator<Integer> adder = Utils::add;
    System.out.println(adder.apply(3, 4)); 
    
    • 实例方法引用
    // 定义一个实例方法
    public class Person {
        private String name;
        public Person(String name) {
            this.name = name;
        }
        public void greet() {
            System.out.println("Hello, my name is " + name);
        }
    }
    // 使用实例方法引用
    Person person = new Person("Alice");
    Runnable task = person::greet;
    task.run();  // 输出:Hello, my name is Alice
    
    • 类方法引用
    // 使用类方法引用
    Comparator<String> comparator = String::compareTo;
    System.out.println(comparator.compare("apple", "banana"));  // 输出:负数
    
    • 构造器引用
    // 定义一个类
    public class Box<T> {
        private T value;
        public Box(T value) {
            this.value = value;
        }
        public T getValue() {
            return value;
        }
    }
    // 使用构造器引用
    Function<String, Box<String>> boxFactory = Box::new;
    Box<String> box = boxFactory.apply("Hello");
    System.out.println(box.getValue());  // 输出:Hello
    

3 异常

3.1 异常的定义

  • 异常是一种特殊的对象,用于表示程序运行时出现的错误或异常情况。当程序运行过程中遇到无法正常执行的代码时,就会抛出一个异常对象。
  • 所有异常类都继承自java.lang.Throwable类。Throwable类有两个主要的子类:
    • Exception:表示程序运行时可能遇到的异常情况,这些异常通常可以通过合理的代码逻辑来避免或处理。
    • Error:表示程序运行时出现的严重错误,通常是系统级的错误,无法通过代码逻辑来处理,例如内存不足(OutOfMemoryError)或虚拟机错误(VirtualMachineError)。

3.2 异常的分类

Java中的异常主要分为两大类:检查型异常(Checked Exception)非检查型异常(Unchecked Exception)

3.2.1 检查型异常(Checked Exception)

检查型异常是指那些在编译时会被编译器检查的异常。如果方法中可能抛出检查型异常,那么必须显式地处理这些异常,否则编译器会报错。处理方式有两种:

  1. 使用try-catch语句捕获并处理异常。
  2. 使用throws关键字将异常向上抛出,让调用者处理。

检查型异常通常用于那些可以通过合理逻辑处理的错误情况,例如:

  • IOException:表示输入输出操作中可能出现的错误,如文件未找到、磁盘空间不足等。
  • SQLException:表示数据库操作中可能出现的错误,如SQL语句错误、数据库连接失败等。
  • ClassNotFoundException:表示类加载器无法找到指定的类。

3.2.2 非检查型异常(Unchecked Exception)

非检查型异常是指那些在编译时不会被编译器检查的异常。这些异常通常是由程序逻辑错误引起的,可以选择性处理。非检查型异常又分为两类:

  • 运行时异常(RuntimeException):这些异常通常是由程序逻辑错误引起的,例如:
    • NullPointerException:尝试访问空对象的成员时抛出。
    • ArrayIndexOutOfBoundsException:数组索引越界时抛出。
    • ArithmeticException:如除以零时抛出。
  • 错误(Error):这些异常通常表示系统级的严重错误,程序无法处理,例如:
    • OutOfMemoryError:内存不足时抛出。
    • StackOverflowError:栈溢出时抛出。
    • NoClassDefFoundError:运行时找不到类定义时抛出。

检查型异常与非检查型异常的区别

特性检查型异常(Checked Exception)非检查型异常(Unchecked Exception)
编译时检查是,必须处理(捕获或向上抛出)否,可以选择性处理
常见类型IOException, SQLExceptionNullPointerException, ArrayIndexOutOfBoundsException
是否可恢复通常可以通过合理逻辑处理通常是程序逻辑错误,难以恢复
用途外部环境引起的错误(如文件、网络)程序内部逻辑错误或系统级错误

3.3 异常的处理

Java提供了多种机制来处理异常,主要包括try-catchtry-catch-finallytry-with-resourcesthrowsthrow

3.3.1 使用try-catch捕获异常

try-catch是最基本的异常处理方式。try块中包含可能抛出异常的代码,catch块用于捕获并处理异常,捕获不同类型的异常,捕获顺序应从子类到父类。

try {
    // 可能抛出多个异常的代码
    FileInputStream fis = new FileInputStream("file.txt");
    int result = 10 / 0;
} catch (FileNotFoundException e) {
    System.out.println("文件未找到:" + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("算术异常:" + e.getMessage());
}

3.3.2 使用try-catch-finally进行清理操作

finally块用于在trycatch块执行后执行清理操作,无论是否捕获到异常,finally块中的代码都会执行。finally块通常用于释放资源,如关闭文件流、释放数据库连接等。

FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // 使用文件流读取文件
} catch (FileNotFoundException e) {
    System.out.println("文件未找到:" + e.getMessage());
} finally {
    // 清理操作:关闭文件流
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            System.out.println("关闭文件流时发生异常:" + e.getMessage());
        }
    }
}
  • 如果trycatch块中没有return语句,则finally块会在方法返回之前执行。
  • 如果trycatch块中有return语句,则finally块会在return之前执行,不会改变return的返回值。
  • 如果trycatch块中抛出了异常且未被捕获,则finally块会在异常向上抛出之前执行。

3.3.3 使用try-with-resources自动管理资源

Java 7开始,try-with-resources语句用于自动关闭实现了AutoCloseable接口的资源(如文件流、数据库连接等)。这种方式可以简化资源管理,避免忘记关闭资源导致的资源泄漏。

try (FileInputStream fis = new FileInputStream("input.txt");
     BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
     FileOutputStream fos = new FileOutputStream("output.txt")) {
    String line;
    while ((line = reader.readLine()) != null) {
        fos.write(line.getBytes());
    }
} catch (IOException e) {
    e.printStackTrace();
}
  • try-with-resources语句会在try块执行完毕后自动调用资源的close()方法,即使发生异常也会确保资源被关闭。
  • 资源必须实现AutoCloseable接口或其子接口Closeable,这些接口定义了close()方法。
  • try-with-resources中声明的资源仅在try块的作用域内有效。
  • 如果try块中和close()方法中都抛出了异常,则close()方法抛出的异常会被抑制(suppressed),主异常会被抛出。可以通过Throwable.getSuppressed()方法获取被抑制的异常。

3.3.4 使用throws声明抛出异常

  • throws关键字用于在方法签名中声明该方法可能抛出的异常。如果方法中可能抛出检查型异常(Checked Exception),则必须通过throws声明抛出异常,否则编译器会报错。对于非检查型异常(Unchecked Exception)throws声明是可选的。
    public void processFile() throws IOException, SQLException {
        // 方法实现
    }
    
  • 使用throw手动抛出异常,throw关键字用于手动抛出一个异常对象,通常用于业务逻辑校验。抛出的异常可以是系统提供的异常类,也可以是自定义异常类。
    // 通过继承Exception或其子类来创建自定义异常类。
    public class MyCustomException extends Exception {
        public MyCustomException(String message) {
            super(message);
        }
    }
    public void checkValue(int value) throws MyCustomException {
        if (value < 0) {
            throw new MyCustomException("值不能小于0");
        }
    }
    

4 泛型

4.1 泛型的定义

  • 定义:泛型(Generics)是Java语言中的一种特性,允许在定义类、接口和方法时引入类型参数,从而实现类型安全和代码复用。
  • 主要作用
    • 类型安全:在编译时检查类型错误,避免运行时的ClassCastException
    • 代码复用:通过泛型可以编写通用的类和方法,适用于多种类型。
    • 减少类型转换:避免显式的类型转换,使代码更简洁。
  • 泛型的编译原理:
    泛型是通过类型擦除实现的。编译器在编译时会将泛型类型替换为Object类型(或其父类),并在需要时插入类型转换代码。运行时,泛型信息会被擦除,因此泛型的类型检查仅在编译时进行
      // 类型参数用尖括号`<>`表示,例如`<T>`、`<E>`等。
      // 示例:`ArrayList<T>`,其中`T`是类型参数。
      // 需要强制类型转换
      List list = new ArrayList();
      list.add("Hello");
      String s = (String) list.get(0); 
      // 不需要强制类型转换
      List<String> list = new ArrayList<>();
      list.add("Hello");
      String s = list.get(0);
        ```
    

4.2 泛型类,接口,方法

  • 综合示例
        // 泛型类是在类定义中使用类型参数的类,允许在实例化时指定具体的类型。
        class MyClass<T> {
            private T data;
            public MyClass(T data) {
                this.data = data;
            }
            public T getData() {
                return data;
            }
            public void setData(T data) {
                this.data = data;
            }
        }
        MyClass<String> stringClass = new MyClass<>("Hello");
        MyClass<Integer> intClass = new MyClass<>(123);
        // 泛型接口是在接口定义中使用类型参数的接口,允许在实现接口时指定具体的类型。
        interface MyInterface<T> {
          T getData();
          void setData(T data);
        }
        // 泛型方法是指在方法定义中使用类型参数的方法,允许在调用方法时指定具体的类型。
        public <T> T methodName(T param) {
            // 方法体
        }
        ```
    

4.3 通配符与上下限

  • 定义:通配符(Wildcards)是泛型中用于表示未知类型的符号,通常用于方法参数或泛型类的实例化中。
  • 无界通配符(?
    • ?)任意类型,无法添加,只能读取
    • 不能用于泛型类的定义,因为无法在类定义中明确指定具体的类型参数。
       List<?> list = new ArrayList<>();
       Object obj = list.get(0); // 可以读取,但只能作为Object类型
       class MyClass<?> { // 错误:不能在类定义中使用无界通配符
       }
      
  • 上限通配符(<? extends T>
    • 类型参数是T的子类或T本身,无法添加,只能读取
    • 编译器无法确定添加的数据是否符合集合的实际类型,因此不允许添加数据(除了null,因为null可以赋值给任何类型)。
      import java.util.ArrayList;
      import java.util.List;
      
      public class UpperBoundWildcardExample {
          public static void main(String[] args) {
              // 创建一个具体的泛型集合
              List<Integer> intList = new ArrayList<>();
              intList.add(1);
              intList.add(2);
              intList.add(3);
      
              // 使用上限通配符
              printNumbers(intList); // 正确:intList是Number的子类
          }
      
          // 方法参数使用上限通配符
          public static void printNumbers(List<? extends Number> list) {
              // 遍历并打印集合中的数据
              for (Number num : list) {
                  System.out.println(num);
              }
      
              // 尝试添加数据(会报错)
              // list.add(4); // 错误:无法确定具体类型
              list.add(null); // 正确:null可以添加
          }
      }
      
  • 下限通配符(<? super T>
    • 类型参数是T的父类或T本身,无法读取,只能添加数据,编译器无法确定读取的数据的实际类型,因此只能将读取的数据视为Object类型。
      	import java.util.ArrayList;
      	import java.util.List;
      	
      	public class LowerBoundWildcardExample {
      	    public static void main(String[] args) {
      	        // 创建一个具体的泛型集合
      	        List<Integer> intList = new ArrayList<>();
      	        intList.add(1);
      	        intList.add(2);
      	
      	        // 使用下限通配符
      	        addNumbers(intList); // 正确:intList是Integer的父类或自身
      	
      	        // 输出结果
      	        System.out.println(intList); // 输出:[1, 2, 3]
      	    }
      	
      	    // 方法参数使用下限通配符
      	    public static void addNumbers(List<? super Integer> list) {
      	        // 可以向集合中添加Integer及其子类的数据
      	        list.add(3);
      	        list.add(4);
      	
      	        // 尝试读取数据(只能视为Object类型)
      	        Object obj = list.get(0); // 正确:只能将读取的数据视为Object类型
      	        // Integer num = list.get(0); // 错误:无法确定具体类型
      	    }
      	}
      
  • 注意事项
    • 使用通配符时,尽量遵循“PECS”原则(Producer Extends, Consumer Super):
      • 如果需要从集合中读取数据(生产者),使用<? extends T>
      • 如果需要向集合中添加数据(消费者),使用<? super T>
    • 通配符的类型参数在运行时会被擦除(类型擦除)。

5 集合

5.1 集合的定义

Java集合是Java语言中用于存储和管理对象的框架,它提供了一系列的接口和实现类,用于存储和操作一组对象。

  • 集合(Collection): 存储一组对象,可以是单个元素的集合(截图来自B站黑马教程)
    在这里插入图片描述
  • 映射(Map): 存储键值对(key-value)关系,键唯一,值可以重复。(截图来自B站黑马教程)
    在这里插入图片描述

5.2 顶层集合接口的常见方法

  • Collection:
方法签名描述
boolean add(E e)将指定元素添加到集合中(如果支持此操作)。
boolean addAll(Collection<? extends E> c)将指定集合中的所有元素添加到此集合中。
void clear()移除集合中的所有元素(如果支持此操作)。
boolean contains(Object o)检查集合是否包含指定元素。
boolean containsAll(Collection<?> c)检查集合是否包含指定集合中的所有元素。
boolean isEmpty()检查集合是否为空。
Iterator<E> iterator()返回集合的迭代器。
boolean remove(Object o)从集合中移除指定元素(如果支持此操作)。
boolean removeAll(Collection<?> c)移除集合中所有与指定集合相同的元素。
boolean retainAll(Collection<?> c)仅保留集合中包含在指定集合中的元素。
int size()返回集合中的元素数量。
Object[] toArray()返回包含集合中所有元素的数组。
<T> T[] toArray(IntFunction<T[]> generator)返回包含集合中所有元素的数组,类型由生成器指定。
  • Map<K,V>:
方法签名描述
void clear()移除 Map 中的所有键值对。
boolean containsKey(Object key)检查 Map 是否包含指定的键。
boolean containsValue(Object value)检查 Map 是否包含指定的值。
Set<Map.Entry<K, V>> entrySet()返回 Map 中所有键值对的集合视图。
V get(Object key)返回指定键所映射的值,如果键不存在则返回 null
V getOrDefault(Object key, V defaultValue)返回指定键所映射的值,如果键不存在则返回默认值。
boolean isEmpty()检查 Map 是否为空。
Set<K> keySet()返回 Map 中所有键的集合视图。
V put(K key, V value)将指定的键值对插入到 Map 中,如果键已存在,则替换其值。
void putAll(Map<? extends K, ? extends V> m)将指定 Map 中的所有键值对复制到当前 Map 中。
V remove(Object key)移除指定键的键值对,并返回其值。
void replace(K key, V value)替换指定键的值(如果键存在)。
int size()返回 Map 中键值对的数量。
Collection<V> values()返回 Map 中所有值的集合视图。
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)如果指定键不存在,则使用给定的映射函数计算其值并插入。
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)如果指定键存在,则使用给定的函数重新计算其值。
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)计算指定键的值,无论键是否存在。
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)如果键不存在,则插入键值对;如果键存在,则使用给定的函数重新计算其值。

6 Stream流

6.1 Stream的定义

Stream 是一个来自 java.util.stream 包的接口,它表示一个元素的序列。这个序列可以是有限的,也可以是无限的。Stream 的设计灵感来源于函数式编程语言中的流式操作,它支持一系列操作,如过滤、映射、排序、归并等,这些操作可以组合起来形成复杂的流水线。

  • Stream 本身不是数据结构,它不会存储数据,而是对数据源(如集合、数组)的抽象表示。
  • Stream 只有在终端操作被调用时,才会真正执行中间操作。
  • Stream 执行了终端操作,就不能再次被使用
  • Stream 的中间操作可以链式调用,终端操作结束
    List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
    // 输出:["BANANA", "CHERRY"]
    List<String> result = fruits.stream()
                                .filter(s -> s.length() > 5)
                                .map(String::toUpperCase)
                                .sorted()
                                .collect(Collectors.toList());
    

6.2 Stream常见方法

方法名称功能描述示例代码
filter筛选满足条件的元素java numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList());
map对每个元素应用函数java fruits.stream().map(String::length).collect(Collectors.toList());
flatMap将每个元素映射为流并扁平化java nestedList.stream().flatMap(List::stream).collect(Collectors.toList());
sorted对元素排序java numbers.stream().sorted().collect(Collectors.toList());
distinct去除重复元素java numbers.stream().distinct().collect(Collectors.toList());
forEach对每个元素执行操作java numbers.stream().forEach(System.out::println);
collect收集结果到集合java numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList());
reduce对元素进行归并操作java numbers.stream().reduce(0, Integer::sum);
count返回元素数量java numbers.stream().filter(n -> n % 2 == 0).count();
minmax返回最小值或最大值java numbers.stream().min(Integer::compare).get();
anyMatchallMatchnoneMatch检查条件是否满足java numbers.stream().anyMatch(n -> n % 2 == 0);
toArray收集到数组java numbers.stream().toArray(Integer[]::new);
groupingBy按属性分组java fruits.stream().collect(Collectors.groupingBy(String::length));
joining连接字符串元素java fruits.stream().collect(Collectors.joining(", "));

7 IO流

  • Java的IO流是Java语言中用于处理输入输出的核心机制,它提供了一套丰富的类和接口来操作数据的读取和写入。IO流分为两大类:字节流(InputStreamOutputStream)和字符流(Reader Writer
  • 常见IO流:
流类型继承关系用途特点
InputStream抽象类字节输入流的基类用于从源读取字节数据,是所有字节输入流的父类。
OutputStream抽象类字节输出流的基类用于向目标写入字节数据,是所有字节输出流的父类。
Reader抽象类字符输入流的基类用于从源读取字符数据,支持字符编码,是所有字符输入流的父类。
Writer抽象类字符输出流的基类用于向目标写入字符数据,支持字符编码,是所有字符输出流的父类。
FileInputStreamInputStream从文件读取字节数据提供文件的字节输入流,适用于二进制文件。
FileOutputStreamOutputStream向文件写入字节数据提供文件的字节输出流,适用于二进制文件。
FileReaderReader从文件读取字符数据提供文件的字符输入流,适用于文本文件,支持字符编码。
FileWriterWriter向文件写入字符数据提供文件的字符输出流,适用于文本文件,支持字符编码。
BufferedInputStreamInputStream缓冲字节输入流提供缓冲功能,提高字节输入流的读取效率。
BufferedOutputStreamOutputStream缓冲字节输出流提供缓冲功能,提高字节输出流的写入效率。
BufferedReaderReader缓冲字符输入流提供缓冲功能,支持按行读取,提高字符输入流的读取效率。
BufferedWriterWriter缓冲字符输出流提供缓冲功能,支持批量写入,提高字符输出流的写入效率。
DataInputStreamInputStream读取基本数据类型从输入流中读取基本数据类型(如int、double等)。
DataOutputStreamOutputStream写入基本数据类型向输出流中写入基本数据类型(如int、double等)。
ObjectInputStreamInputStream反序列化对象从输入流中读取序列化的Java对象。
ObjectOutputStreamOutputStream序列化对象向输出流中写入可序列化的Java对象。
InputStreamReaderReader字节流转字符流将字节输入流转换为字符输入流,支持字符编码。
OutputStreamWriterWriter字节流转字符流将字节输出流转换为字符输出流,支持字符编码。
ByteArrayInputStreamInputStream从字节数组读取数据从内存中的字节数组读取数据,常用于测试。
ByteArrayOutputStreamOutputStream向字节数组写入数据将数据写入内存中的字节数组,常用于缓冲或数据转换。
CharArrayReaderReader从字符数组读取数据从内存中的字符数组读取数据,常用于测试。
CharArrayWriterWriter向字符数组写入数据将数据写入内存中的字符数组,常用于缓冲或数据转换。
PipedInputStreamInputStream管道输入流用于线程间通信,数据从管道输出流流向管道输入流。
PipedOutputStreamOutputStream管道输出流用于线程间通信,数据从管道输出流流向管道输入流。
PipedReaderReader管道字符输入流用于线程间通信,字符数据从管道字符输出流流向管道字符输入流。
PipedWriterWriter管道字符输出流用于线程间通信,字符数据从管道字符输出流流向管道字符输入流。
SequenceInputStreamInputStream合并多个输入流将多个输入流按顺序连接成一个输入流。
PrintStreamOutputStream格式化输出流提供格式化输出功能,常用于控制台输出(如System.out)。
PrintWriterWriter格式化字符输出流提供格式化字符输出功能,支持自动刷新。
FilterInputStreamInputStream输入流过滤器提供输入流的扩展功能,是其他过滤输入流的基类。
FilterOutputStreamOutputStream输出流过滤器提供输出流的扩展功能,是其他过滤输出流的基类。
FilterReaderReader字符输入流过滤器提供字符输入流的扩展功能,是其他过滤字符输入流的基类。
FilterWriterWriter字符输出流过滤器提供字符输出流的扩展功能,是其他过滤字符输出流的基类。
  • 字节流文件读写:
    import java.io.*;
    
    public class FileIOExample {
        public static void main(String[] args) {
            String filePath = "example.txt";
    
            // 写入文件
            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                String content = "Hello, Java IO!";
                fos.write(content.getBytes());
                System.out.println("File written successfully.");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // 读取文件
            try (FileInputStream fis = new FileInputStream(filePath)) {
                byte[] buffer = new byte[1024];
                int length;
                while ((length = fis.read(buffer)) != -1) {
                    System.out.println(new String(buffer, 0, length));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 字符缓冲流
    import java.io.*;
    
    public class BufferedExample {
        public static void main(String[] args) {
            String filePath = "example.txt";
    
            // 写入文件
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {
                bw.write("Hello, Java Buffered IO!");
                bw.newLine(); // 写入换行符
                bw.write("This is a new line.");
                System.out.println("File written successfully.");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // 读取文件
            try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 数据流
    import java.io.*;
    
    public class DataStreamExample {
        public static void main(String[] args) {
            String filePath = "data.bin";
    
            // 写入数据
            try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filePath))) {
                dos.writeInt(123);
                dos.writeDouble(98.6);
                dos.writeUTF("Hello, Data Stream!");
                System.out.println("Data written successfully.");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // 读取数据
            try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
                int i = dis.readInt();
                double d = dis.readDouble();
                String s = dis.readUTF();
                System.out.println("Read int: " + i);
                System.out.println("Read double: " + d);
                System.out.println("Read string: " + s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 对象流
    import java.io.*;
    
    public class ObjectStreamExample {
        public static void main(String[] args) {
            String filePath = "object.bin";
    
            // 序列化对象
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
                Person person = new Person("John Doe", 30);
                oos.writeObject(person);
                System.out.println("Object serialized successfully.");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // 反序列化对象
            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
                Person person = (Person) ois.readObject();
                System.out.println("Object deserialized: " + person);
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    // 可序列化的Person类
    class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private String name;
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
    

8 并发

Java的并发是指在Java程序中同时执行多个任务的能力。并发编程可以让程序更高效地利用多核处理器的资源,提高程序的性能和响应能力。

8.1 线程

线程是Java并发编程的基本单位,是程序执行的最小单元。在Java中,线程的创建和管理由java.lang.Thread类和java.lang.Runnable接口支持。

  • 创建线程的三种方式:继承Thread,实现Runnable ,实现Callable <T>
    // 继承Thread类
    // 特点:Java不支持多继承,继承Thread类后无法再继承其他类。
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Running in thread: " + Thread.currentThread().getName());
        }
    }
    
    public class ThreadExample {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start(); // 启动线程
        }
    }
    // 实现Runnable接口
    // 特点:符合面向对象的设计原则,避免了多继承问题。
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Running in thread: " + Thread.currentThread().getName());
        }
    }
    
    public class ThreadExample {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start(); // 启动线程
        }
    }
    // 实现Callable接口
    // 特点:Callable接口与Runnable类似,但支持返回值和抛出异常。通常与Future一起使用,适用于需要获取异步任务结果的场景。
    import java.util.concurrent.*;
    
    class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("Running in thread: " + Thread.currentThread().getName());
            return 42; // 返回值
        }
    }
    
    public class CallableExample {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<Integer> future = executor.submit(new MyCallable());
            System.out.println("Result: " + future.get()); // 获取结果
            executor.shutdown();
        }
    }
    
  • 线程的生命周期:
    • 新建(New):线程被创建,但尚未调用start()方法。
    • 运行(Runnable):线程已启动,正在运行或等待CPU时间片。
    • 阻塞(Blocked):线程尝试获取对象锁时,锁被其他线程持有。
    • 等待(Waiting):线程调用wait()join()LockSupport.park()方法进入等待状态。
    • 超时等待(Timed Waiting):线程调用sleep()wait(timeout)LockSupport.parkNanos()等方法进入超时等待状态。
    • 终止(Terminated):线程执行完毕或因异常退出。
      在这里插入图片描述
  • 线程安全:
    当多个线程访问共享资源时,可能会出现数据竞争(多个线程同时修改共享数据,导致结果不确定),线程不安全(有些集合类在多线程环境下可能会出现ConcurrentModificationException
    • synchronized关键字是Java中最基本的同步机制,用于确保同一时间只有一个线程可以访问共享资源。
      // 同步方法:整个方法加锁。
      public synchronized void increment() {
          count++;
      }
      // 同步代码块:对特定对象加锁,更细粒度
      public void increment() {
          synchronized (this) { // 对当前对象加锁
              count++;
          }
      }
      
    • 锁(Lock),java.util.concurrent.locks包提供了更灵活的锁机制,如ReentrantLock
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class Counter {
          private int count = 0;
          private final Lock lock = new ReentrantLock();
      
          public void increment() {
              lock.lock(); // 加锁
              try {
                  count++;
              } finally {
                  lock.unlock(); // 释放锁
              }
          }
      }
      

8.2 线程池

线程池的主要目的是减少线程创建和销毁的开销,提高程序性能。线程池会预先创建一定数量的线程,并将它们放入一个池中。当有任务需要执行时,线程池分配一个空闲线程来执行任务,而不是每次都创建新线程。任务执行完成后,线程会返回线程池,等待下一个任务。

  • java.util.concurrent.Executors 快速创建线程池
    // 固定大小线程池(newFixedThreadPool)
    // 固定大小线程池会创建一个固定数量的线程,并且线程数量不会自动扩展。
    // 创建4个线程的线程池
    ExecutorService executor = Executors.newFixedThreadPool(4); 
    
    // 单线程线程池(newSingleThreadExecutor)
    // 单线程线程池确保所有任务都在同一个线程中按顺序执行,适用于需要保证任务顺序的场景。
    ExecutorService executor = Executors.newSingleThreadExecutor();
    
    // 缓存线程池(newCachedThreadPool)
    // 缓存线程池会根据需要创建线程,但空闲线程会在60秒后被回收。适合执行大量短期异步任务。
    ExecutorService executor = Executors.newCachedThreadPool();
    
    // 可调度线程池(newScheduledThreadPool)
    // 可调度线程池支持任务的定时执行和周期性执行。
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
    executor.schedule(() -> System.out.println("Hello"), 5, TimeUnit.SECONDS); // 5秒后执行
    executor.scheduleAtFixedRate(() -> System.out.println("Tick"), 0, 2, TimeUnit.SECONDS); // 每2秒执行一次
    
  • java.util.concurrent.Executors 创建自定义线程池
    • corePoolSize:核心线程数。
    • maximumPoolSize:最大线程数。
    • keepAliveTime:空闲线程存活时间。
    • unit:存活时间的单位。
    • workQueue:任务队列,用于存储等待执行的任务。
    • threadFactory:线程工厂,用于创建线程。
    • handler:拒绝策略,当任务过多时的处理方式。
    import java.util.concurrent.*;
    
    public class CustomThreadPoolExample {
        public static void main(String[] args) {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    2, // 核心线程数
                    4, // 最大线程数
                    60, // 空闲线程存活时间
                    TimeUnit.SECONDS, // 时间单位
                    new LinkedBlockingQueue<>(10), // 任务队列
                    Executors.defaultThreadFactory(), // 线程工厂
                    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
            );
    
            for (int i = 0; i < 20; i++) {
                executor.submit(() -> {
                    System.out.println("Task executed by: " + Thread.currentThread().getName());
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
    
            executor.shutdown(); // 关闭线程池
        }
    }
    
  • 线程池的关闭
    • shutdown():尝试关闭线程池,不再接受新任务,但会等待已提交的任务完成。
    • shutdownNow():立即关闭线程池,尝试中断正在执行的任务,并返回尚未执行的任务列表。

9 网络编程

Java网络编程主要涉及基于TCP/IPUDP协议的网络通信,通过Java的网络API实现客户端与服务器之间的数据交互。

9.1 基础知识

  • TCP/IP协议栈

    • 应用层:如 HTTPFTPSMTP 等,用于具体的应用程序通信。
    • 传输层:如 TCPUDP,负责数据的传输和可靠性。
    • 网络层:如 IP,负责数据包的路由和转发。
    • 链路层:负责物理链路上的数据传输。
  • TCP协议:面向连接,提供可靠的字节流服务

    • 优缺点:可靠性强,数据完整性和顺序有保障,但连接建立和维护成本高,传输效率相对较低。
    • 应用场景:Web服务器(HTTP/HTTPS),数据库通信(MySQLPostgreSQL),远程桌面(SSH
  • UDP协议:不需要建立连接,不保证数据的可靠性和顺序

    • 优缺点:传输速度快,延迟低,但不保证数据的可靠性和顺序,可能丢失数据。
    • 应用场景:实时音视频通信(VoIP、视频会议),游戏(需要低延迟的场景),DNS查询
  • 网络编程模型

    • C/S模型(客户端/服务器模型):客户端主动发起请求,服务器被动监听并响应请求。
    • P2P模型(对等网络模型):每个节点既可以作为客户端,也可以作为服务器,适用于分布式文件共享等场景。

9.2 Java网络编程的一些核心类

Java的java.net包提供了丰富的类和接口,用于实现网络编程。

  • InetAddress

    • 用途:表示IP地址,用于获取主机信息和域名解析。
    • 常用方法
      • static InetAddress getLocalHost():获取本地主机的IP地址。
      • static InetAddress getByName(String host):根据主机名获取IP地址。
      • String getHostName():获取主机名。
      • byte[] getAddress():获取IP地址的字节数组。
  • Socket

    • 用途:表示TCP客户端套接字,用于与服务器建立连接并进行通信。
    • 常用方法
      • Socket(String host, int port):连接到指定主机和端口。
      • InputStream getInputStream():获取输入流,用于读取服务器发送的数据。
      • OutputStream getOutputStream():获取输出流,用于向服务器发送数据。
      • void close():关闭套接字。
  • ServerSocket

    • 用途:表示TCP服务器套接字,用于监听客户端连接请求。
    • 常用方法
      • ServerSocket(int port):在指定端口监听客户端连接。
      • Socket accept():接受客户端连接请求,返回一个与客户端连接的Socket对象。
      • void close():关闭服务器套接字。
  • DatagramSocket

    • 用途:表示UDP客户端或服务器套接字,用于发送和接收UDP数据报。
    • 常用方法
      • DatagramSocket(int port):在指定端口监听UDP数据报。
      • void send(DatagramPacket p):发送UDP数据报。
      • void receive(DatagramPacket p):接收UDP数据报。
      • void close():关闭套接字。
  • DatagramPacket

    • 用途:表示UDP数据报,封装了发送或接收的数据。
    • 常用方法
      • DatagramPacket(byte[] buf, int length):构造一个用于接收数据报的包。
      • DatagramPacket(byte[] buf, int length, InetAddress address, int port):构造一个用于发送数据报的包。
      • byte[] getData():获取数据报的内容。
      • int getLength():获取数据报的长度。
      • InetAddress getAddress():获取发送方或接收方的IP地址。
      • int getPort():获取发送方或接收方的端口号。
  • URL

    • 用途:表示统一资源定位符,用于访问网络资源。
    • 常用方法
      • URL(String spec):根据URL字符串构造一个URL对象。
      • URLConnection openConnection():打开到该URL的连接。
      • String getProtocol():获取协议类型(如httphttpsftp等)。
      • String getHost():获取主机名。
      • int getPort():获取端口号。
      • String getPath():获取资源路径。
  • URLConnection

    • 用途:表示与URL的连接,用于读取或写入网络资源。
    • 常用方法
      • InputStream getInputStream():获取输入流,用于读取资源。
      • OutputStream getOutputStream():获取输出流,用于写入资源。
      • void connect():建立连接。
      • int getContentLength():获取资源的内容长度。
      • String getContentType():获取资源的内容类型(如text/htmlapplication/json等)。
  • HttpURLConnection

    • 用途:表示HTTP协议的连接,用于实现HTTP请求和响应。
    • 常用方法
      • void setRequestMethod(String method):设置请求方法(如GETPOSTPUT等)。
      • void setRequestProperty(String key, String value):设置请求头。
      • int getResponseCode():获取HTTP响应码。
      • String getResponseMessage():获取HTTP响应消息。
      • InputStream getInputStream():获取响应输入流。
      • OutputStream getOutputStream():获取请求输出流。

9.3 TCP编程

  • TCP服务端
    • ServerSocket:用于监听客户端的连接请求。
      • Socket accept():接受客户端连接请求,返回一个与客户端通信的Socket对象。
      • void close():关闭服务器套接字,停止监听。
    • Socket:用于与客户端建立连接并进行通信。
      • InputStream getInputStream():获取输入流,用于读取客户端发送的数据。
      • OutputStream getOutputStream():获取输出流,用于向客户端发送数据。
      • void close():关闭与客户端的连接。
      import java.io.*;
      import java.net.*;
      
      public class TCPServer {
          public static void main(String[] args) {
              int port = 8888;
      
              try (ServerSocket serverSocket = new ServerSocket(port)) {
                  System.out.println("Server is running on port " + port);
      
                  while (true) {
                      // 等待客户端连接
                      Socket clientSocket = serverSocket.accept();
                      System.out.println("Client connected: " + clientSocket.getInetAddress());
      
                      // 获取输入输出流
                      try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                           PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
      
                          // 读取客户端消息
                          String message = in.readLine();
                          System.out.println("Received from client: " + message);
      
                          // 向客户端发送响应
                          out.println("Hello from server!");
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
        ```
      
  • TCP客户端
  • Socket:用于与服务器建立连接并进行通信。
    • InputStream getInputStream():获取输入流,用于读取服务器发送的数据。
    • OutputStream getOutputStream():获取输出流,用于向服务器发送数据。
    • void close():关闭与服务器的连接。
    import java.io.*;
    import java.net.*;
    
    public class TCPClient {
        public static void main(String[] args) {
            String host = "localhost";  // 服务器地址
            int port = 8888;            // 服务器端口
    
            try (Socket socket = new Socket(host, port);
                 PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
    
                // 向服务器发送消息
                out.println("Hello, server!");
    
                // 读取服务器响应
                String response = in.readLine();
                System.out.println("Server response: " + response);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

9.4 UDP编程

  • UDP服务端
    import java.io.*;
    import java.net.*;
    
    public class UDPServer {
        public static void main(String[] args) {
            int port = 8888;
    
            try (DatagramSocket socket = new DatagramSocket(port)) {
                System.out.println("UDP Server is running on port " + port);
    
                byte[] buffer = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
    
                while (true) {
                    // 接收客户端数据报
                    socket.receive(packet);
                    String message = new String(packet.getData(), 0, packet.getLength());
                    System.out.println("Received from client: " + message);
    
                    // 构造响应数据报
                    String response = "Hello from UDP server!";
                    byte[] responseBytes = response.getBytes();
                    DatagramPacket responsePacket = new DatagramPacket(responseBytes, responseBytes.length,
                            packet.getAddress(), packet.getPort());
                    socket.send(responsePacket);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  • UDP客户端
    import java.io.*;
    import java.net.*;
    
    public class UDPClient {
        public static void main(String[] args) {
            String host = "localhost";
            int port = 8888;
    
            try (DatagramSocket socket = new DatagramSocket()) {
                // 构造发送的数据报
                String message = "Hello, UDP server!";
                byte[] data = message.getBytes();
                DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(host), port);
                socket.send(packet);
    
                // 接收服务器响应
                byte[] buffer = new byte[1024];
                DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
                socket.receive(responsePacket);
                String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
                System.out.println("Server response: " + response);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

10 注解

10.1 注解的定义

注解(Annotation)是Java语言中的一种特殊类型的接口,用于为程序代码提供元数据信息。它并不直接改变代码的逻辑,而是为代码添加额外的说明或指令。注解可以应用于类、方法、字段、参数等程序元素上。

  • 注解的语法:注解以@符号开头,后跟注解名称。例如:@Override
  • 注解本质上是一种接口,但它不能被直接实例化。注解的定义使用@interface关键字。

10.3 常见注解

注解名称作用范围说明
@Override方法表示当前方法覆盖父类中的方法,用于确保方法签名的正确性。
@Deprecated类、方法、字段标记类、方法或字段已过时,不推荐使用。IDE通常会发出警告。
@SuppressWarnings类、方法、字段、局部变量抑制编译器警告,常用于忽略特定的编译器警告(如未使用的变量、泛型警告等)。
@SafeVarargs方法、构造器用于标记使用变长参数的方法或构造器是安全的,避免泛型相关的警告。
@FunctionalInterface接口标记一个接口为函数式接口,确保接口中只有一个抽象方法。

10.4 元注解

  • 元注解是用于修饰注解的注解
  • 常见元注解:
元注解名称作用范围说明
@Retention注解类型定义注解的保留策略(SOURCECLASSRUNTIME)。
@Target注解类型定义注解可以应用的目标范围(如类、方法、字段等)。
@Documented注解类型表示注解将被包含在JavaDoc文档中。
@Inherited注解类型表示注解可以被子类继承(仅对类有效)。
import java.lang.annotation.*;

// 自定义注解 @MyAnnotation
@Retention(RetentionPolicy.RUNTIME)  // 指定注解的保留策略为运行时
@Target({ElementType.TYPE, ElementType.METHOD})  // 指定注解可以应用于类和方法
@Documented  // 指定注解将被包含在JavaDoc文档中
@Inherited  // 指定注解可以被子类继承
@interface MyAnnotation {
    String value() default "default value";  // 注解的元素,提供默认值
}

// 使用自定义注解
@MyAnnotation("This is a class annotation")
class MyClass {
    @MyAnnotation("This is a method annotation")
    public void myMethod() {
        System.out.println("This is my method.");
    }
}

// 子类继承父类的注解
class MySubClass extends MyClass {
}

// 测试类
public class AnnotationExample {
    public static void main(String[] args) {
        // 检查类注解
        MyAnnotation classAnnotation = MyClass.class.getAnnotation(MyAnnotation.class);
        if (classAnnotation != null) {
            System.out.println("Class annotation value: " + classAnnotation.value());
        }

        // 检查方法注解
        MyAnnotation methodAnnotation = MyClass.class.getMethod("myMethod").getAnnotation(MyAnnotation.class);
        if (methodAnnotation != null) {
            System.out.println("Method annotation value: " + methodAnnotation.value());
        }

        // 检查子类是否继承了父类的注解
        MyAnnotation subClassAnnotation = MySubClass.class.getAnnotation(MyAnnotation.class);
        if (subClassAnnotation != null) {
            System.out.println("SubClass annotation value: " + subClassAnnotation.value());
        }
    }
}

11 代理

11.1 代理的定义

代理是一种设计模式,允许通过一个代理对象控制目标对象的访问。代理对象作为目标对象的“中间人”,在不改变目标对象代码的情况下,添加额外的功能或行为

11.2 代理的形式(JDK动态代理)

通过 java.lang.reflect.ProxyInvocationHandler 动态生成代理类。
逻辑:目标类实现接口再生成一个对象,利用Proxy.newProxyInstance生成代理对象,对象需要三个参数,一个是类加载器,一个是代理类需要实现的接口数组,还有就是调用处理器InvocationHandler。匿名类的方式重写调用处理器的invoke方法。

// 定义接口
public interface HelloService {
    void sayHello();
}

// 实现接口
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello, world!");
    }
}

// 实现 InvocationHandler
import java.lang.reflect.*;

public class MyInvocationHandler implements InvocationHandler {
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}

// 创建代理对象并调用
import java.lang.reflect.Proxy;

public class JDKProxyExample {
    public static void main(String[] args) {
        // 目标对象
        HelloService target = new HelloServiceImpl();

        // 创建调用处理器
        MyInvocationHandler handler = new MyInvocationHandler(target);

        // 创建代理对象
        HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),// 类加载器,一般就用target的
            new Class<?>[]{HelloService.class},// 接口数组
            handler// 调用处理器
        );
        // 调用代理对象的方法
        proxyInstance.sayHello();
    }
}

Final 考试

自己练去吧!


http://www.kler.cn/a/515259.html

相关文章:

  • QT:控件属性及常用控件(3)-----输入类控件(正则表达式)
  • Picsart美易照片编辑器和视频编辑器
  • Qt Creator 15.0.0如何更换主题和字体
  • 网络安全等级保护基本要求——等保二级
  • Swift 中 Codable 和 Hashable 的理解
  • 大华前端开发面试题及参考答案 (下)
  • Python FastAPI 实战应用指南
  • WordPress Hunk Companion插件节点逻辑缺陷导致Rce漏洞复现(CVE-2024-9707)(附脚本)
  • Nginx:通过upstream进行代理转发
  • vue request 发送formdata
  • 【Python运维】Python与网络监控:如何编写网络探测与流量分析工具
  • vue3中使用render函数以及组合式写法实现一个配置化生成的表单组件
  • 数论问题61一一各种进位制
  • leetcode hot100(3)
  • 1561. 你可以获得的最大硬币数目
  • Qt实践:一个简单的丝滑侧滑栏实现
  • Java 大视界 -- 深度洞察 Java 大数据安全多方计算的前沿趋势与应用革新(52)
  • 在Debian系统中安装Debian(Linux版PE装机)
  • 正向代理与反向代理的主要区别
  • 极速、免费、体积小,一款PDF转图片软件
  • 微信小程序1.1 微信小程序介绍
  • leetcode——轮转数组(java)
  • leetcode_字符串 409. 最长回文串
  • 什么是IP地址、子网掩码、网关、DNS
  • AI刷题-策略大师:小I与小W的数字猜谜挑战
  • Matlab 亥姆霍兹谐振器的吸声特性