[java]为什么System.in/out/err值为null?

本文深入探讨了Java中System类的工作原理,特别是其in/out成员变量的初始化过程。通过解析System类的静态代码块和native方法,揭示了这些成员变量如何从初始的null状态变为实际可用的输入输出流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在查看System源码的时候会发现

public static final InputStream in = null;
public static final PrintStream out = null;

值是null也就算了竟然还被final修饰不可修改,那我们调用的System.out.println()方法算什么呢?
实际上在System类的开头有静态代码块:

    static {
        registerNatives();
    }

在加载System类的时候先调用了System类中的静态方法registerNatives().
在System类中是这样定义registerNatives()方法:

private static native void registerNatives();

使用了native修饰符,这意味着该方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
因此System类首先声明了in/out(实际上还有err)静态成员常量,然后在registerNatives()中对其进行赋值.


至于底层具体实现可以看下zhh5919大神在百度知道的回答:https://zhidao.baidu.com/question/38660871.html 引用如下:

System类里有大量的native方法,是调用本地代码的,这些代码很可能是由虚拟机来调用的.
System类的开头有一段:
static {
registerNatives();
}
这段代码会在虚拟机启动的时候就执行,它在虚拟机里注册System需要使用的一些本地代码
比如:
private static native Properties initProperties(Properties props);
private static native void setOut0(PrintStream out);
在windows下的话,它就告诉虚拟机到哪个dll文件里去找相应的实现
然而,我知道out是一个PrintStream的对象,但我查看了有关的原代码:public final static PrintStream out = nullPrintStream();
public final static InputStream in = nullInputStream();
在nullInputStream()方法里有注释解释为什么会设置为空:
//The following two methods exist because in, out, and err must be
//initialized to null. The compiler, however, cannot be permitted to
//inline access to them, since they are later set to more sensible values
//by initializeSystemClass().
private static InputStream nullInputStream() throws NullPointerException {
if (currentTimeMillis() > 0)
return null;
throw new NullPointerException();
}
也就说in, out, and err 初始化为null,然后会在后来由initializeSystemClass()方法类初始化成有意义的值
// Initialize the system class. Called after thread initialization.
private static void initializeSystemClass() {
props = new Properties();
initProperties(props);
sun.misc.Version.init();
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn)); !!!
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true)); !!!
setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true)); !!!
// Enough of the world is now in place that we can risk
// initializing the logging configuration.
try {
java.util.logging.LogManager.getLogManager().readConfiguration();
} catch (Exception ex) {
// System.err.println(“Can′t read logging configuration:”);
// ex.printStackTrace();
}
// Load the zip library now in order to keep java.util.zip.ZipFile
// from trying to use itself to load this library later.
loadLibrary(“zip”);
// Subsystems that are invoked during initialization can invoke
// sun.misc.VM.isBooted() in order to avoid doing things that should
// wait until the application class loader has been set up.
sun.misc.VM.booted();
}
in,out,err就是在以上方法以下三条语句里初始化的.
setIn0(new BufferedInputStream(fdIn)); !!!
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true)); !!!
setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true)); !!!

private static native void setIn0(InputStream in);
这是个native函数,是前面registerNatives()的时候注册了的.这个函数应该是把实际连接到输入输出设备的句柄传给虚拟机并赋值给in,out,err
至于:
InputStream是个抽象的类,怎么能使用char=(char)System.in.read()读入一个字符
我想你还没有明白什么是面向对象.
看看下面代码,我用OutputStream(也是抽象类,跟InputStream对应的输出类)以方便演示:
import java.io.IOException;
import java.io.OutputStream;
public class HelloWorld {
public OutputStream out=null;
public void setOutputStream(OutputStream out){
this.out=out;
}
public static void main(String[] args) throws IOException{
HelloWorld h=new HelloWorld();
PrintStream myOut=System.out;//System.out是一个PrintStream
h.setOutputStream(myOut);
h.out.write(“hello,world”.getBytes());//一般没人这么写的
}
}
以上代码执行后会输出hello,world
h.out是OutputStream,也是个抽象类,为什么能write(o)呢?
因为PrintStream是OutputStream的子类,所以能被"当作"OutputStream传给h.setOutputStream(myOut);
h.out.write执行的时候实际上是调用这个传进来的PrintStream实例的write方法
同样System.in和out肯定也是在initializeSystemClass()的时候被赋予了一个实际的可用的子类
要能体会到面向对象的好处,就要逐渐适应"对接口编程"的思想,相同接口的对象可以根据需要方便的替换.
比如,我刚才传了一个PrintStream,因此HelloWorld输出到了屏幕上. 我如果传给OutputStream的另一个子类FileOutputStream,就会输出到文件里
还有为什么不是说字符流:writer和reader一般用于UniCode的读写吗?为什么键盘的输入用reader类呢?
不知道你在哪里看到说writer和reader一般用于UniCode的读写
最近正在看java.io包,晕死!里面用到了很多包装器,这个包装那个,那个又包装这个的,简直要崩溃
看一下设计模式–Decorator(装饰)模式,你就明白了

下午7:18:05: 正在执行 ':StudentManagementSystem.main()'… > Task :compileJava > Task :processResources UP-TO-DATE > Task :classes > Task :StudentManagementSystem.main() java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=gbk at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706) at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229) at StudentManagementSystem.initDatabase(StudentManagementSystem.java:39) at StudentManagementSystem.<init>(StudentManagementSystem.java:24) at StudentManagementSystem$5.run(StudentManagementSystem.java:299) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "java.sql.Connection.createStatement()" because "this.conn" is null at StudentManagementSystem.loadStudentData(StudentManagementSystem.java:267) at StudentManagementSystem.<init>(StudentManagementSystem.java:30) at StudentManagementSystem$5.run(StudentManagementSystem.java:299) at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318) at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722) at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86) at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741) at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
最新发布
06-12
<think>我们要解决的是Java程序中连接MySQL数据库时出现的"java.sql.SQLException:Nosuitabledriverfoundforjdbc:mysql"错误。根据引用的资料,该错误通常由以下几个原因引起:1.连接URL格式错误2.驱动字符串错误(名不正确)3.缺少合适的JDBC驱动jar包,或者jar包没有被正确加载解决方法如下:1.**检查JDBC连接URL格式**正确的URL格式应该是:`jdbc:mysql://主机名:端口/数据库名?参数`。例如:`jdbc:mysql://localhost:3306/mydb`。在引用[1]的例子中,错误信息是`jdbc:mysql//localhost:3306/mldn`(缺少冒号,应该是`jdbc:mysql://...`)。因此请确保URL中`jdbc:mysql:`后面有两个斜杠,并且主机名、端口和数据库名正确。2.**检查驱动名**MySQL的JDBC驱动名是`com.mysql.jdbc.Driver`(5.x版本)或`com.mysql.cj.jdbc.Driver`(6.0及以上版本)。在代码中加载驱动时,应使用:```javaClass.forName("com.mysql.cj.jdbc.Driver");//推荐使用8.0及以上的驱动```或者,如果你使用的是较老版本的MySQL驱动(5.x):```javaClass.forName("com.mysql.jdbc.Driver");```注意:从JDBC4.0开始(Java6及以上),通常不需要显式调用`Class.forName()`,因为驱动会自动注册。但是,在某些环境中(如Tomcat),可能还是需要显式加载。3.**确保JDBC驱动jar包在路径中**-如果使用普通的Java应用,将`mysql-connector-java-xxx.jar`文件添加到项目的构建路径中(如在IDE中,将jar包添加到项目的lib目录,并加入buildpath)。-如果使用Web应用(如部署在Tomcat中),则需要将jar包放在`WEB-INF/lib`目录下。-另外,引用[3]和[4]提到,有些情况下可能需要将jar包复制到JRE的`lib/ext`目录下(例如,`C:\ProgramFiles\Java\jre1.8.0_121\lib\ext`)。但这种方式通常不推荐,因为它会影响所有使用该JRE的应用程序。更好的做法是将其放在应用的路径中。4.**检查依赖作用域(如果使用Maven等构建工具)**如果使用Maven,确保依赖配置正确,不要将作用域设置为`provided`(除非容器会提供)。正确的依赖配置如下:```xml<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version><!--使用最新稳定版本--></dependency>```使用`provided`作用域表示希望容器在运行时提供该依赖,但如果没有提供,就会导致上述错误。5.**检查JAR包冲突**确保没有多个不同版本的MySQL驱动jar包存在于路径中,这可能导致冲突。6.**重新启动服务器**如果更改了路径(如添加了新的jar包),请重启应用服务器(如Tomcat)以确保更改生效。总结步骤:1.检查URL是否书写正确(特别是冒号和斜杠)。2.检查驱动名是否正确(根据驱动版本)。3.确保驱动jar包在路径中(项目lib目录或WEB-INF/lib)。4.若使用Maven/Gradle,检查依赖配置。5.重启应用服务器。如果以上步骤都确认无误,通常可以解决该问题。§§相关问题§§1.如何检查JDBC连接URL的正确性?2.MySQLJDBC驱动不同版本之间有什么主要区别?3.在Tomcat中部署Web应用时,如何确保数据库驱动被正确加载?4.为什么在JDBC4.0之后不需要显式调用`Class.forName()`来加载驱动?</think> ### java.sql.SQLException: No suitable driver found for jdbc:mysql 解决方法 这个错误表明Java程序无法找到合适的JDBC驱动来连接MySQL数据库,通常由以下原因导致。请按照以下步骤排查: #### 🔧 1. **检查JDBC连接URL格式** - **错误示例**:`jdbc:mysql//localhost:3306/mydb`(缺少冒号) - **正确格式**:`jdbc:mysql://主机名:端口/数据库名?参数` ✅ 正确示例: ```java String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC"; ``` - 确认URL中不包含非法字符(如中文空格),冒号`:`和斜杠`/`位置正确[^1][^3]。 #### 📦 2. **添加MySQL驱动JAR包到路径** - **驱动名称**:`mysql-connector-java-x.x.xx.jar` - **添加方式**: - **普通Java项目**:将JAR包放入项目`lib`目录并添加到构建路径 - **Web项目(Tomcat)**:放入`WEB-INF/lib`目录 - **Maven项目**:在`pom.xml`添加: ```xml <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <!-- 使用最新版本 --> </dependency> ``` - 📌 关键点:驱动版本需与MySQL服务器版本兼容(建议MySQL 5.x用5.1.x驱动,MySQL 8.x用8.0.x驱动) #### ⚙️ 3. **正确加载驱动** ```java // 显式加载驱动(推荐) Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 8.x+ // 或 Class.forName("com.mysql.jdbc.Driver"); // MySQL 5.x ``` - 即使JDBC 4.0+支持自动加载,但某些容器仍需显式调用[^1][^2] #### 🧪 4. **解决常见配置问题** - **Tomcat特殊配置**: - 将驱动JAR复制到`Tomcat/lib`目录 - 重启Tomcat服务 - **Java环境修复**(慎用): - 仅当加载器隔离失效时,将JAR放入JRE的`lib/ext`目录 (如:`C:\Program Files\Java\jre1.8.0_xxx\lib\ext`)[^3][^4] - **排除JAR冲突**:检查是否存在多个版本的驱动JAR #### 🚨 5. **异常情况处理** **连接报错但驱动已加载**: ```java // 手动注册驱动(终极方案) DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); ``` > 适用于加载器隔离导致`Class.forName`失效的场景 #### ✅ 验证步骤 1. 确认驱动JAR在运行时路径中 ```java System.out.println(this.getClass().getClassLoader().getResource("com/mysql/cj/jdbc/Driver.class")); ``` 2. 测试最小连接代码: ```java try (Connection conn = DriverManager.getConnection(url, user, pwd)) { System.out.println("连接成功"); } ``` --- ### 完整解决方案示例 ```java public class MySQLConnector { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC"; String user = "root"; String password = "123456"; try { // 1. 加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 2. 建立连接 try (Connection conn = DriverManager.getConnection(url, user, password)) { System.out.println("数据库连接成功!"); } } catch (ClassNotFoundException e) { System.err.println("未找到驱动: " + e.getMessage()); } catch (SQLException e) { System.err.println("连接失败: " + e.getMessage()); } } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值