反射机制大揭秘-进阶Java技巧,直击核心!


反射在Java中扮演着重要的角色,掌握了反射,就等于掌握了框架设计的钥匙。本文将为您逐步讲解反射的基本概念、获取Class对象的三种方式、使用反射实例化对象并操作属性和方法,还有解析包的相关内容。跟随我一起探索反射的奥秘,提升编码技能!


一、反射基本概念


Java 反射是一个强大的特性,它允许程序在运行时查询、访问和修改类、接口、字段和方法的信息,以及创建和操作对象。通过反射,我们可以在运行时动态地创建对象,调用方法,修改字段值,这些在传统的面向对象编程中是难以实现的。
1、运行时类型识别 RTTI(Run-Time Type Identification)

RTTI 即运行时类型识别,是许多编程语言中用于在程序执行期间确定对象类型的一种机制。在Java这种强类型语言中,RTTI提供了一种方式来获取对象的实际类型,这在处理多态性、动态类型转换和反射时尤为重要。


Java中RTTI的主要组成部分:

(1)、instanceof运算符

instanceof关键字用于在运行时检查对象是否是特定类的实例,或者是否实现了特定的接口。它返回一个布尔值,如果对象是指定类型的实例,则返回true,否则返回false

Object obj = new MyClass();
boolean isMyClassInstance = obj instanceof MyClass; // true

(2)、Class类

java.lang.Class类是反射机制的核心类,它代表类的元数据。每个Java类在加载时都会创建一个Class对象。Class对象包含了类的名称、字段、方法、构造函数等信息。

Class<?> clazz = obj.getClass(); // 获取obj的Class对象
String className = clazz.getName(); // 获取类名

(3)、反射API

Java的反射API允许程序在运行时查询和操作类的结构。通过反射,你可以获取类的信息,创建对象实例,调用方法,访问字段等。

Class<?> clazz = Class.forName("MyClass");
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();

(4)、类型转换

在Java中,类型转换分为自动类型转换(向上转型)和强制类型转换(向下转型)。向上转型不需要显式操作,因为子类可以自动转换为父类类型。向下转型需要显式操作,并且通常需要instanceof检查以确保转换的安全性。

// 向上转型
MyClass myClass = new SubClass();

// 向下转型,需要先检查类型
if (myClass instanceof SubClass) {
    SubClass subClass = (SubClass) myClass;
}

(5)、动态方法分派

在Java中,方法调用是基于对象的实际类型进行分派的,这称为动态绑定或晚期绑定。这意味着即使方法调用在编译时是未知的,JVM在运行时也能确定调用哪个方法。

class Base {
    void show() { System.out.println("Base"); }
}
class Derived extends Base {
    void show() { System.out.println("Derived"); }
}

Base base = new Derived();
base.show(); // 输出 "Derived",因为base实际上是Derived类型

RTTI的优点:

  • 灵活性:RTTI提供了在运行时处理不同类型的灵活性,使得代码更加通用。
  • 多态性:RTTI支持多态性,允许将子类对象视为父类类型,而JVM在运行时确定正确的方法实现。
  • 动态行为:通过反射,RTTI允许程序在运行时动态地改变其行为。

RTTI的缺点:

  • 性能开销:使用RTTI,特别是反射,可能会引入额外的性能开销。
  • 安全问题:RTTI可能会破坏封装性,允许访问私有成员,这可能导致安全问题。
  • 复杂性:过度依赖RTTI可能会使代码难以理解和维护。

RTTI是Java中一个强大的特性,它使得程序能够在运行时识别对象的实际类型,并执行相应的操作。正确使用RTTI可以提高程序的灵活性和动态性,但开发者需要权衡其性能和安全性的影响。


2、Class类

Java中的每个类都隐式地继承自java.lang.Object类,而java.lang.ClassObject的一个子类。

Class类是反射的核心,它代表了一个类或接口的静态类型信息。

每个Java类型(类、接口、数组等)都有一个对应的Class对象。

数组同样也被映射为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。

每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取class对象)。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.   //私有构造器,只有JVM才能调用创建Class对象
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

由上可知:

  • Class类也是类的一种,与class关键字是不一样的。

  • 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)

  • 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。

  • Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载。

  • Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要。

  • 再来看看 Class类的方法

方法名说明
forName()(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。
(2) 为了产生Class引用,forName()立即就进行了初始化。
Object-getClass()获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName()取全限定的类名(包括包名),即类的完整名字。
getSimpleName()获取类名(不包括包名)
getCanonicalName()获取全限定的类名(包括包名)
isInterface()判断Class对象是否是表示一个接口
getInterfaces()返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss()返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance()返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields()获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

3、类加载

类加载机制和类字节码技术,感兴趣的朋友请前往查阅。

  • 探索Java的DNA-JVM字节码深度解析
  • Java 类加载机制解密一探到底

其中,这里我们需要回顾的是,类加载机制流程:

包括5个阶段:加载、验证、准备、解析和初始化。其中加载、验证、准备、初始化这4个阶段的顺序是确定的,只有解析阶段在特定情况下可以在初始化之后再开始。

在这里插入图片描述


二、反射组件及使用方法


在Java中,Class类是反射机制的一部分,它代表了一个类或接口的静态类型信息。使用Class类,你可以获取类的信息,包括构造函数、方法、字段等。

以下示例代码,展示如何获取和使用Class类对象。


1、获取Class对象

要获取一个Class对象,你可以使用以下方法之一:

  • 使用.class语法。
  • 使用Class.forName()静态方法。

(1)、使用.class语法
public class MyClass {
    public void myMethod() {
        System.out.println("Hello, World!");
    }
}

public class Main {
    public static void main(String[] args) {
        Class<MyClass> myClassClass = MyClass.class; // 获取MyClass的Class对象
        System.out.println(myClassClass.getName()); // 打印类名
    }
}

(2)、使用Class.forName()
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> myClassClass = Class.forName("com.example.MyClass"); // 获取MyClass的Class对象
        System.out.println(myClassClass.getName()); // 打印类名
    }
}

(3)、对象实例获取
Object obj = new Object();
Class clazz = obj.getClass();

2、获取类的构造函数
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Constructor<?> constructor = myClassClass.getConstructor(); // 获取无参构造函数
        System.out.println(constructor);
    }
}

3、创建类的实例
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Constructor<?> constructor = myClassClass.getConstructor();
        Object myClassInstance = constructor.newInstance(); // 创建MyClass的实例
        myClassInstance.getClass().getMethod("myMethod").invoke(myClassInstance); // 调用方法
    }
}

4、获取类的方法
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Method method = myClassClass.getMethod("myMethod"); // 获取myMethod方法
        System.out.println(method);
    }
}

5、调用方法
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Constructor<?> constructor = myClassClass.getConstructor();
        Object myClassInstance = constructor.newInstance();
        Method method = myClassClass.getMethod("myMethod");
        method.invoke(myClassInstance); // 调用myMethod方法
    }
}

6、获取类的字段

反射可以获取某个类的所有属性信息,包括私有属性。

public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Field field = myClassClass.getField("myField"); // 获取public字段
        System.out.println(field);
    }
}

7、访问字段的值
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> myClassClass = Class.forName("com.example.MyClass");
        Constructor<?> constructor = myClassClass.getConstructor();
        Object myClassInstance = constructor.newInstance();
        Field field = myClassClass.getField("myField");
        field.setAccessible(true); // 如果字段是private,需要设置为可访问
        Object fieldValue = field.get(myClassInstance); // 获取字段值
        System.out.println(fieldValue);
    }
}

请注意,以上示例代码中的com.example.MyClass需要替换为实际的类路径。此外,如果类、方法或字段是私有的,你可能需要调用setAccessible(true)来允许反射访问它们。使用反射时要小心,因为它可能会破坏封装性,并带来性能开销。


8、反射获取包信息

Package类提供了一些与包相关的实用方法和信息。比如获取包名、包版本等。示例:

Package pkg = Class.class.getPackage();
System.out.println("包名: " + pkg.getName());
System.out.println("包说明: " + pkg.getSpecificationTitle());
System.out.println("包版本: " + pkg.getSpecificationVersion());

三、反射的优缺点

  • 性能开销:反射操作通常比直接代码调用要慢,因为它涉及到类型解析和动态调用。
  • 安全问题:反射可以破坏封装性,允许代码访问私有成员,这可能导致安全问题。
  • 难以优化:由于反射操作的动态性,JVM难以对其进行优化。

四、反射的使用场景

1、框架开发

许多Java框架(如Spring、Hibernate)使用反射来实现依赖注入、ORM映射等。


(1)、依赖注入(DI)

场景:依赖注入是一种设计模式,用于实现控制反转(IoC),允许框架在运行时自动装配对象的依赖关系。

案例:Spring框架使用反射来实现依赖注入。Spring容器在启动时会扫描指定的包,查找带有特定注解(如@Component@Service等)的类,并为这些类创建实例和管理它们的生命周期。

示例代码

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        MyService myService = context.getBean(MyService.class);
        myService.doWork();
    }
}

// AppConfig.java
@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

// MyService.java
public interface MyService {
    void doWork();
}

// MyServiceImpl.java
@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doWork() {
        System.out.println("Doing work...");
    }
}

(2)、对象关系映射(ORM)

场景:ORM框架允许开发者使用面向对象的方式来操作数据库,而不是使用SQL语句。

案例:Hibernate是一个流行的ORM框架,它使用反射来将Java对象映射到数据库表中。

示例代码

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class Main {
    public static void main(String[] args) {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        Session session = sessionFactory.openSession();
        try {
            session.beginTransaction();
            Employee employee = new Employee(1, "John Doe", "Developer");
            session.save(employee);
            session.getTransaction().commit();
        } finally {
            session.close();
        }
    }
}

// Employee.java
@Entity
@Table(name = "employees")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String jobTitle;

    // Constructors, getters and setters
}

(3)、动态代理

场景:动态代理允许在运行时创建一个实现了一组接口的新类,而不需要事先编写具体的类代码。

案例:Java的java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口可以用来创建动态代理。

示例代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        MyService myService = (MyService) Proxy.newProxyInstance(
            MyService.class.getClassLoader(),
            new Class<?>[]{MyService.class},
            new MyServiceHandler(new MyServiceImpl())
        );
        myService.doWork();
    }
}

interface MyService {
    void doWork();
}

class MyServiceImpl implements MyService {
    public void doWork() {
        System.out.println("Doing work...");
    }
}

class MyServiceHandler implements InvocationHandler {
    private MyService myService;

    public MyServiceHandler(MyService myService) {
        this.myService = myService;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        method.invoke(myService, args);
        System.out.println("After method: " + method.getName());
        return null;
    }


2、插件系统

在Java中,反射可以用来实现一个插件系统,这允许主应用程序在运行时加载和使用插件,而无需在编译时知道插件的具体实现。这种机制非常有用,因为它提供了极大的灵活性和可扩展性。

假设我们有一个文本编辑器应用程序,我们希望允许用户通过插件来扩展编辑器的功能,比如添加语法高亮、拼写检查等。


步骤 1: 定义插件接口

首先,我们定义一个插件接口,所有插件都必须实现这个接口。

public interface TextEditorPlugin {
    void apply(String text);
}

步骤 2: 创建具体插件

然后,我们创建具体的插件实现。

public class SpellCheckPlugin implements TextEditorPlugin {
    @Override
    public void apply(String text) {
        // 实现拼写检查逻辑
        System.out.println("Spell check applied: " + text);
    }
}

public class SyntaxHighlightPlugin implements TextEditorPlugin {
    @Override
    public void apply(String text) {
        // 实现语法高亮逻辑
        System.out.println("Syntax highlighted: " + text);
    }
}

步骤 3: 加载和使用插件

最后,我们使用反射来加载和使用插件。

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

public class PluginLoader {
    public static void loadAndApplyPlugins(String pluginPath, String text) {
        // 创建类加载器
        URL[] urls = new URL[] { new File(pluginPath).toURI().toURL() };
        URLClassLoader classLoader = new URLClassLoader(urls);

        // 获取插件JAR中的所有类
        List<Class<?>> pluginClasses = new ArrayList<>();
        try {
            String[] entries = ((String) new File(pluginPath).listFiles()[0]).split(" ");
            for (String entry : entries) {
                Class<?> clazz = Class.forName(entry, true, classLoader);
                if (TextEditorPlugin.class.isAssignableFrom(clazz)) {
                    pluginClasses.add(clazz);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 实例化插件并应用
        for (Class<?> pluginClass : pluginClasses) {
            try {
                TextEditorPlugin plugin = (TextEditorPlugin) pluginClass.newInstance();
                plugin.apply(text);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        // 关闭类加载器
        try {
            classLoader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String text = "This is a sample text for the editor.";
        loadAndApplyPlugins("path/to/plugin/folder", text);
    }
}

在这个例子中,PluginLoader类负责加载插件。它首先创建一个URLClassLoader来加载插件JAR文件。然后,它获取JAR文件中的所有类,并检查这些类是否实现了TextEditorPlugin接口。如果是,它将这些类实例化并调用apply方法。


请注意,这个例子假设插件JAR文件中的类名存储在一个文本文件中,并且这个文本文件位于插件文件夹中。这只是一个简化的示例,实际应用中可能需要更复杂的机制来发现和加载插件。


通过这种方式,我们可以实现一个灵活的插件系统,主应用程序可以在运行时加载和使用插件,而无需在编译时知道插件的具体实现。这为应用程序的扩展和定制提供了极大的便利。


3、运行时配置

反射在运行时配置中非常有用,它允许应用程序根据配置文件在运行时动态加载类和创建对象。这种机制使得应用程序能够灵活地适应不同的环境和需求,而无需重新编译或部署。

运行时配置的使用场景
  • 环境适应性:应用程序可以根据不同的环境(开发、测试、生产)加载不同的配置。
  • 模块化:应用程序可以由多个模块组成,每个模块都可以在运行时动态加载。
  • 可扩展性:应用程序可以设计为可扩展的,允许在运行时添加新功能。
  • 插件支持:如前所述,插件系统可以利用运行时配置来加载和集成插件。

假设我们有一个简单的应用程序,它需要根据配置文件来决定使用哪个服务类来处理请求。我们将使用一个配置文件来指定服务类的全限定名,并使用反射来动态加载和实例化这个类。


步骤 1: 创建服务接口

首先,我们定义一个服务接口,所有的服务类都将实现这个接口。

public interface Service {
    void execute();
}

步骤 2: 创建具体的服务实现

然后,我们创建具体的服务实现。

public class ServiceImplA implements Service {
    @Override
    public void execute() {
        System.out.println("Executing Service A");
    }
}

public class ServiceImplB implements Service {
    @Override
    public void execute() {
        System.out.println("Executing Service B");
    }
}

步骤 3: 创建配置文件

接下来,我们创建一个配置文件(例如config.txt),它包含服务类的全限定名。

com.example.ServiceImplA

步骤 4: 使用反射加载和使用服务

最后,我们使用反射来根据配置文件加载和使用服务。

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class RuntimeConfigDemo {
    public static void main(String[] args) {
        // 从配置文件中读取服务类的名称
        String className;
        try {
            className = new String(Files.readAllBytes(new File("config.txt").toPath()));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        // 使用反射加载并实例化服务类
        Service service = loadService(className);
        if (service != null) {
            service.execute();
        } else {
            System.out.println("Service class could not be loaded or instantiated.");
        }
    }

    private static Service loadService(String className) {
        try {
            Class<?> serviceClass = Class.forName(className);
            return (Service) serviceClass.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

在这个例子中,RuntimeConfigDemo类负责根据配置文件加载服务。它首先从config.txt文件中读取服务类的全限定名,然后使用Class.forName来加载类,接着通过getDeclaredConstructor().newInstance()来创建类的实例。最后,它调用服务的execute方法。

通过这种方式,应用程序可以根据配置文件在运行时动态地加载和使用不同的服务实现,而无需事先知道具体的服务类。这为应用程序提供了极大的灵活性和可配置性。


以上就是本文的主要内容,希望您在阅读过程中有所收获。敬请期待下一期,我将分享更多反射的实战技巧和使用场景,让您的Java之旅更上一层楼!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/662728.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习Java的日子 Day48 函数,DOM

Day48 1.流程控制语句 if else for for-in(遍历数组时&#xff0c;跟Java是否一样) While do while break 语句用于跳出循环 continue 用于跳过循环中的一个迭代 2.函数 2.1 JavaScript 函数语法 函数就是包裹在花括号中的代码块&#xff0c;前面使用了关键词 function funct…

数据分析必备:一步步教你如何用Pandas做数据分析(11)

1、Pandas 自定义选项 Pandas 自定义选项操作实例 Pandas因为提供了API来自定义行为&#xff0c;所以被广泛使用。 自定义API中有五个相关功如下&#xff1a; get_option() set_option() reset_option() describe_option() option_context() 下面我们一起了解下这些方法。 1.…

【移动云】主机ECS搭建项目——具体步骤教程

目录 一、什么是移动云 二、移动云有什么优势 三、移动云使用 1.注册账号 2.云主机ECS创建 3.管理云主机 4.连接配置云主机 5.搭建服务器提示与建议 四、使用感受 一、什么是移动云 移动云是中国领先的云服务品牌之一&#xff0c;它以强大的资源优势、技术实力和品牌价…

母婴商城购物网站,基于 SpringBoot+Vue+MySQL 开发的前后端分离的母婴商城购物网站设计实现

目录 一. 前言 二. 功能模块 2.1. 前台功能 2.2. 用户信息管理 2.3. 商品分类管理 2.4. 商品信息管理 2.5. 商品资讯管理 三. 部分代码实现 四. 源码下载 一. 前言 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&a…

Compose第一弹 可组合函数+Text

目标&#xff1a; 1.Compose是什么&#xff1f;有什么特征&#xff1f; 2.Compose的文本控件 一、Compose是什么&#xff1f; Jetpack Compose 是用于构建原生 Android 界面的新工具包。 Compose特征&#xff1a; 1&#xff09;声明式UI&#xff1a;使用声明性的函数构建一…

File name ‘xxxx‘ differs from already included file name ‘xxxx‘ only in casing.

一、报错信息 VSCode报错如下&#xff1a; File name ‘d:/object/oral-data-management/src/components/VisitLogPopup/Info.vue’ differs from already included file name ‘d:/object/oral-data-management/src/components/VisitLogPopup/INfo.vue’ only in casing. The…

树莓派4B 学习笔记1:TF卡系统盘烧录_初次启动_远程端连接配置

今日开始学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; TF卡系统盘烧录_初次启动_远程端连接配置 目录 格式化SD卡&#xff1a; 烧录系统Win32DiskImager&#xff1a; Raspberry Pi Imager镜像烧写&#xff1a; 树莓派官网资料…

fyne widget小部件1

fyne widget小部件1 label标签 Label 小部件是其中最简单的——它向用户呈现文本。不像canvas.Text它可以处理一些简单的格式&#xff08;例如\n)。 package mainimport ("fyne.io/fyne/v2/app""fyne.io/fyne/v2/widget" )func main() {myApp : app.New…

【因果推断python】1_因果关系初步1

目录 为什么需要关心因果关系&#xff1f; 回答不同类型的问题 当关联确实是因果时 为什么需要关心因果关系&#xff1f; 首先&#xff0c;您可能想知道&#xff1a;它对我有什么好处&#xff1f;下面的文字就将围绕“它”展开&#xff1a; 回答不同类型的问题 机器学习目…

MOS管开关电路简单笔记

没错&#xff0c;这一篇还是备忘录&#xff0c;复杂的东西一律不讨论。主要讨论增强型的PMOS与NMOS。 PMOS 首先上场的是PMOS,它的导通条件&#xff1a;Vg-Vs<0且|Vg-Vs>Vgsth|&#xff0c;PMOS的电流流向是S->D,D端接负载&#xff0c;S端接受控电源。MOS管一般无法…

opencascade 笔记

opencascade 画一个无限大的面 在 OpenCascade 中&#xff0c;要绘制一个无限大的面&#xff0c;你可以使用 gp_Pln 类来定义一个平面&#xff0c;然后将其绘制出来。这里是一个示例代码&#xff0c;演示如何在 OpenCascade 中绘制一个无限大的平面&#xff1a; #include <…

STM32-12-OLED模块

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 文章目录 1. OLED显示屏介绍2. OLED驱动原理3. OLED驱动芯片简介4…

pytorch笔记:torch.nn.Flatten()

1 介绍 torch.nn.Flatten(start_dim1, end_dim-1) 将一个连续的维度范围扁平化为一个张量 start_dim (int)要开始扁平化的第一个维度&#xff08;默认值 1&#xff09;end_dim (int)要结束扁平化的最后一个维度&#xff08;默认值 -1&#xff09; 2 举例 input torch.ra…

过去的六年,教会了我很多事

目录 过去六年的风风雨雨android缘起爱情缘灭顿悟收拾心情&#xff0c;再次启航面试阿里大起大落 如今时光&#xff0c;刺激且美好未来展望 过去六年的风风雨雨 android缘起 2018年&#xff0c;我从北京联合大学毕业&#xff0c;跟随着学长一起创业&#xff0c;从此开始了我的…

DeFi的历程与未来:探寻去中心化金融的前行路

随着区块链技术的不断演进和加密货币市场的持续繁荣&#xff0c;DeFi&#xff08;去中心化金融&#xff09;作为一种新兴领域正迅速崛起&#xff0c;其发展历史和未来前景备受关注。 过去&#xff1a;DeFi 的发展历史 DeFi 并非一夜之间出现&#xff0c;而是经历了一系列的发展…

【信息学奥赛】字典的键和值对换

【信息学奥赛】字典的键和值对换 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 输入一个字典&#xff0c;要求将该字典的键和值对换。&#xff08;注意&#xff0c;字典中有键的值是重复的&#xff09; 输入&#xff1a; 一行&#xff0…

【ai】livekit服务本地开发模式2:模拟1个发布者

是一个会议用软件:LiveKit is an open source project that provides scalable, multi-user conferencing based on WebRTC. It’s designed to provide everything you need to build real-time video audio data capabilities in your applications.LiveKit’s server is wr…

DiffBIR论文阅读笔记

这篇是董超老师通讯作者的一篇盲图像修复的论文&#xff0c;目前好像没看到发表在哪个会议期刊&#xff0c;应该是还在投&#xff0c;这个是arxiv版本&#xff0c;代码倒是开源了。本文所指的BIR并不是一个single模型对任何未知图像degradation都能处理&#xff0c;而是用同一个…

网络模型—BIO、NIO、IO多路复用、信号驱动IO、异步IO

一、用户空间和内核空间 以Linux系统为例&#xff0c;ubuntu和CentOS是Linux的两种比较常见的发行版&#xff0c;任何Linux发行版&#xff0c;其系统内核都是Linux。我们在发行版上操作应用&#xff0c;如Redis、Mysql等其实是无法直接执行访问计算机硬件(如cpu&#xff0c;内存…

剪画小程序:视频伪原创怎么制作?视频伪原创的几种制作方法分享!

什么是视频伪原创&#xff1f; 视频伪原创是指对已有的视频内容进行一定程度的修改和处理&#xff0c;使其在形式或部分细节上与原始视频有所不同&#xff0c;但保留了核心内容或主题。 视频伪原创包括以下一些常见操作&#xff1a; 剪辑重组&#xff1a;对原始视频进行剪辑…