由于“xx”是引用封送类的字段,访问上面的成员可能导致运行时异常

本文详细解析了C#中的MarshalByRefObject机制,解释了其在远程处理应用中的作用,以及如何正确使用它来避免CS1690警告。通过示例代码展示了如何在类成员为引用封送类时正确复制对象,从而解决运行时异常的问题。

DataTable dt = fm_main_data.c_data.BJLX; 

这段代码提示了异常:

是引用封送类的字段,访问上面的成员可能导致运行时异常;

============================================================================================

解决:

DataTable dt = fm_main_data.c_data.BJLX.Copy()

=========================================

原因:http://technet.microsoft.com/zh-cn/library/x524dkh4(v=vs.90).aspx


错误消息
由于“member”是引用封送类的字段,访问上面的成员可能导致运行时异常

当您尝试对从 MarshalByRefObject 派生的类成员调用方法、属性或索引器时,并且该成员是值类型时,会出现此警告。若要解决此警告,请将该成员复制到局部变量并对此变量调用方法。

下面的示例生成 CS1690:

// CS1690.cs
using System;

class WarningCS1690: MarshalByRefObject
{
   int i = 5;

   public static void Main() 
   {
     WarningCS1690 e = new WarningCS1690();
     e.i.ToString();   // CS1690

     // OK
     int i = e.i;
     i.ToString();
     e.i = i;
   }
}

=======================================================================================================================================================================================

我的总结:

一:MarshalByRefObject

marshal

[英]['mɑ:ʃl] [美][ˈmɑrʃəl]

n.元帅; 典礼官; 执法官; 消防局长; 

vt.整理,排列,集结; 

 

Marshal 类

提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。

MarshalByRefObject

允许在支持远程处理的应用程序中跨应用程序域边界访问对象。
.MarshalByValueComponent

实现 IComponent 并提供可远程控制的组件的基实现,该组件是由值封送的(传递序列化对象的副本)。

================================================================

介绍(http://blog.youkuaiyun.com/enemyisgodlike/article/details/20391861)

remoting 是MarshByReference的,可以传变量的引用,直接对服务器对象操作。速度快,适合intranet(企业内部互联网)。//??不确定
webservice 是MarshByValue的,必须传对象的值。速度慢,可以过FIREWALL,配置比较简单,适合internet(因特网)。//??不确定

DataSet 和 DataTable 对象从 MarshalByValueComponent 继承,并支持用于 .NET Framework 远程处理的 ISerializable 接口。 它们是唯一可用于 .NET Framework 远程处理的 ADO.NET 对象。

二:问题

fm_main_data.c_data.BJLX.Copy();中c_data是结构体 值类型 而 fm_main_data 定义如下:   private FM_Main fm_main_data;

1: 在哪里使用了MarshalByRefObject?

2:  提示异常可能造成什么结果?

3:  为什么示例生成 CS1690的代码:如果不使用tostring() (比如直接赋值)  就不会提示异常?


C#这里太墨迹了 待续待续。。。。。


<think>我们正在讨论Java的NullPointerException(空指针异常)的原理。根据用户的问题,他想要理解空指针异常的原理。结合之前提供的引用内容,我们可以从以下几个方面进行解释: 1. **空指针异常的本质**:当程序试图使用一个`null`引用调用方法、访问属性或操作数组,Java虚拟机就会抛出`NullPointerException`(NPE)。这是因为`null`在Java中表示“没有对象”,即该引用没有指向任何有效的对象实例。 2. **Java的内存模型与引用**:在Java中,对象存储在堆内存中,而变量(引用)存储在栈内存中。引用变量存储的是对象在堆内存中的地址。当一个引用被赋值为`null`,表示它不指向任何有效的内存地址。如果试图通过这样的引用访问对象,就会导致空指针异常。 3. **JVM的运行检查机制**:Java虚拟机在执行每条操作引用的指令,都会检查引用是否为`null`。如果检测到`null`,JVM就会创建一个`NullPointerException`对象,并抛出这个异常。这个过程是由JVM自动完成的,不需要程序员显式地编写检查代码。 4. **空指针异常的具体触发场景**(参考引用[1]和[3]): - 调用一个`null`对象的方法(如`obj.method()`)。 - 访问或修改`null`对象的字段(如`obj.field`)。 - 获取`null`数组的长度(如`array.length`)。 - 访问或修改`null`数组的某个元素(如`array[0]`)。 - 将`null`对象作为参数传递给一个需要对象的方法(如`String.valueOf(null)`,但注意这里可能会因为方法重载而出现不同的行为,比如调用的是`String.valueOf(char[])`而不是`String.valueOf(Object)`,所以不一定会抛出NPE,但通常我们讨论的是常见情况)。 5. **自动拆箱导致的NPE**(引用[1]):当使用包装类型(如`Integer`)的变量,如果该变量为`null`,而在需要基本类型的上下文中使用(例如自动拆箱),就会抛出NPE。例如: ```java Integer num = null; int n = num; // 这里会触发自动拆箱,调用num.intValue(),但num为null,因此抛出NPE ``` 6. **级联调用中的NPE**(引用[1]):在链式调用中,如果中间某个环节返回了`null`,那么后续的调用就会抛出NPE。例如: ```java user.getAddress().getCity(); // 如果user.getAddress()返回null,那么调用getCity()就会抛出NPE ``` 7. **容器类不允许null值**(引用[1]):某些容器类(如`ConcurrentHashMap`)不允许键或值为`null`,如果试图存入`null`,就会抛出NPE。 8. **方法返回null**(引用[1]和[5]):如果方法返回了`null`,而调用方没有检查就直接使用,就可能抛出NPE。例如: ```java List<String> list = getList(); // 可能返回null list.size(); // 如果list为null,则抛出NPE ``` 9. **空指针异常的处理流程**(引用[4]):当JVM检测到空指针操作,会创建一个`NullPointerException`对象,然后沿着调用栈向上寻找异常处理代码(`catch`块)。如果当前方法没有处理,就会终止当前方法的执行,并弹出当前栈帧,继续在调用者中寻找,直到找到合适的异常处理器。如果整个调用栈都没有处理,那么线程就会终止(如果是主线程,则程序终止)。 10. **为什么空指针异常运行异常**:因为空指针异常属于`RuntimeException`的子类,它不需要在编译进行捕获或声明。这主要是因为空指针异常通常是由于程序逻辑错误引起的,应该在开发阶段通过测试和代码检查来避免。 11. **Java的null设计哲学**(引用[5]):Java允许使用`null`值,这带来了灵活性,但也带来了空指针异常的问题。有些语言(如Kotlin)通过类型系统区分可空类型和非空类型,从而在编译避免空指针异常。但在Java中,我们只能通过编码规范和工具来减少NPE。 12. **从字节码角度看NPE**:在字节码层面,当执行一个方法调用指令(如`invokevirtual`)或字段访问指令(如`getfield`),如果操作数栈顶的引用是`null`,JVM就会抛出NPE。 下面我们用一个示例代码及其字节码来展示: ```java public class NullExample { public static void main(String[] args) { Object obj = null; obj.toString(); } } ``` 对应的字节码(关键部分): ``` 0: aconst_null // 将null压入操作数栈 1: astore_1 // 将null存储到局部变量表索引1的位置(变量obj) 2: aload_1 // 将变量obj(null)压入操作数栈 3: invokevirtual #2 // 调用toString方法,此操作数栈顶为null,因此抛出NPE ``` 因此,空指针异常的原理可以总结为:**当Java虚拟机执行到需要操作对象引用的指令,发现该引用为`null`,无法完成操作,于是抛出`NullPointerException`异常**。 接下来,我们将按照要求组织回答,包括引用标注,并生成相关问题。</think>### Java 空指针异常(NullPointerException)原理详解 #### 1. **核心原理:JVM 的引用访问机制** Java 空指针异常的本质是 **JVM 对无效对象引用的安全拦截机制**。当程序试图通过值为 `null` 的引用访问对象成员(方法/属性),JVM 会执行以下操作: 1. **检测引用值**:检查操作数栈顶的引用值 2. **验证对象存在性**:发现引用值为 `null`(未指向任何对象) 3. **创建异常对象**:实例化 `NullPointerException` 4. **中断执行流**:终止当前操作,沿调用栈向上寻找异常处理器 5. **默认处理**:若无处理器,打印异常栈并终止线程[^4] ```java String str = null; // 引用赋值为null int len = str.length(); // JVM检测到null引用,抛出异常 ``` #### 2. **底层机制:JVM 字节码层面** 通过字节码分析可直观理解原理: ```java // Java源码 Object obj = null; obj.toString(); ``` 对应字节码: ``` 0: aconst_null // 将null压入操作数栈 1: astore_1 // 存储到局部变量表(位置1) 2: aload_1 // 加载局部变量(obj)到操作数栈 3: invokevirtual #2 // 调用toString() - 关键点! ``` 在 `invokevirtual` 指令执行,JVM 会检查操作数栈顶的引用: - 若为 `null` → 抛出 `NullPointerException` - 若为有效引用 → 正常调用方法[^4] #### 3. **关键触发场景** 根据引用[1][3][5],主要发生在以下操作中: | **操作类型** | **示例代码** | **异常原因** | |--------------------|---------------------------------------|----------------------------------| | 方法调用 | `obj.method()` | 对象未实例化 | | 字段访问 | `int x = obj.field;` | 对象未初始化 | | 数组操作 | `int len = array.length;` | 数组未创建 | | 自动拆箱 | `int num = integerObj;` | 包装类为null拆箱 | | 容器操作 | `map.put(null, "value");` | 容器不支持null键值[^1] | | 链式调用 | `user.getAddress().getCity();` | 中间环节返回null[^1] | | 返回null的方法 | `List list = getList(); list.size();` | 未检查返回值[^1] | #### 4. **特殊场景原理分析 ##### 4.1 自动拆箱陷阱 ```java Integer num = null; int n = num; // 触发NullPointerException ``` 等价于: ```java int n = num.intValue(); // 当num为null调用方法 ``` JVM 执行**隐式方法调用**导致 NPE[^1] ##### 4.2 链式调用中断 ```java user.getAddress().getCity(); ``` 字节码执行流程: 1. 调用 `getAddress()` → 返回 `null` 2. 对 `null` 调用 `getCity()` → 触发 NPE **级联调用中每个 "." 都是独立操作**,需单独验证[^1] ##### 4.3 容器限制原理 `ConcurrentHashMap` 等容器禁止 `null` 键值的设计原因: - 多线程环境下无法区分"键不存在"和"键值为null" - 强制 `put(null, ...)` 立即抛出 NPE 避免后续并发问题[^1] #### 5. Java 空值哲学缺陷 Java 的引用类型默认值设计导致空指针漏洞: - 类字段默认值:`null`(未显式初始化) - 局部变量:无默认值(但编译器仅部分检查) - 方法返回:允许返回 `null` 这与 Kotlin 等现代语言形成对比: ```kotlin var str: String = "abc" // 非空类型 var nullableStr: String? = null // 可空类型需显式声明 str = null // 编译错误!从源头避免NPE ``` #### 6. JVM 优化机制 现代 JVM(JDK 14+)引入**更有用的 NPE 信息**: ```java // JDK 14前 NullPointerException: null // JDK 14+(启用-XX:+ShowCodeDetailsInExceptionMessages) NullPointerException: Cannot invoke "String.length()" because "str" is null ``` 通过字节码增强精确定位 `null` 来源变量[^5] #### 7. 与其它异常的本质区别 | **异常类型** | **触发条件** | **根本原因** | |---------------------------|-------------------------------|--------------------------| | `NullPointerException` | 访问 `null` 引用成员 | 对象未初始化 | | `ArrayIndexOutOfBounds` | 访问无效数组索引 | 索引越界 | | `ClassCastException` | 错误的类型转换 | 类型不兼容 | | `IllegalArgumentException`| 传递非法参数 | 参数校验失败 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值