前言
第一章、计算机语言发展史
第一代语言:机器语言
- 主要编写二进制码,以打孔机为代表
第二代语言:低级语言
- 主要以汇编语言为代表
- 在低级语言中已经引入了一些英语单词,例如变量赋值
第三代语言:高级语言
- 几乎和人类的语言完全相同,即使是没有学习过计算机编程,只要看到这段代码就知道该代码完成什么功能,例如:c语言,c++,Java
总之,大家看到的编程语言的发展方向是:向着人类更加容易理解的方向发展。
第二章、Java概述
一、Java语言发展史
Java语言诞生于1995年
Java包括三大块
--JavaSE:Java标准版
--JavaEE:Java企业版
--JavaME:Java微型版
其中JavaSE是基础,以后主攻JavaEE方向
二、Java语言的特性(开源免费,纯面向对象,跨平台)
- 简单性
- 相对而言的,例如Java中不再支持多继承,C++是支持多继承的,多继承比较复杂
- C++中有指针,Java中屏蔽了指针的概念
- 所以相对来说Java是简单的
- Java语言底层是C++实现的,不是C语言
- 面向对象
- Java是纯面向对象的,更符合的人的思维模式,更容易理解
- 可移植性
- Java程序可以做到一次编译,到处运行,也就是说Java程序可以在windows操作系统上运行,不做任何修改,也可以放到别的操作系统上运行,也叫做跨平台
- 多线程
- 多线程threading,实现并发,效率更高
- 健壮性
- 自动垃圾回收机制,GC机制
- Java语言运行过程中产生的垃圾是自动回收的,不需要程序员关心
- 安全性
1、Java跨平台原理(可移植性)
Windows操作系统内核和Linux操作系统的内核肯定不同,他们这两个操作系统执行指令的方式是不一样的。一般情况下同一个Java程序是不能在不同的操作系统上运行的。
显然Java程序不能直接和操作系统打交道,因为Java程序只有一份,操作系统执行原理都不同。SUN团队很聪明,他们想了一个办法,他们让Java程序运行在一台虚拟的计算机中,这个虚拟的计算机叫做Java虚拟机(JVM:Java Virtual Machine),给不同的操作系统安装不同版本的JVM,这样Java程序就可以运行在JVM中,相当于JVM屏蔽了操作系统之间的差异。因此可以实现Java的跨平台
2、java的设计哲学
Java 是一种通用的、面向对象的编程语言,由 Sun Microsystems(现在是 Oracle Corporation)的詹姆斯·高斯林(James Gosling)等人开发。Java 的设计哲学主要包括以下几个方面:
-
简单性(Simplicity):Java 设计追求简单性,尽可能简化语言的规范和语法,使得初学者能够更轻松地学习和掌握它。Java 的语法借鉴了 C 和 C++,但是去除了一些容易引起错误和混乱的特性,使得代码更易于阅读和理解。
-
面向对象(Object-Oriented):Java 是一种纯粹的面向对象编程语言,它支持封装、继承和多态等面向对象的概念。这使得 Java 代码更具有灵活性和可扩展性,便于编写复杂的软件系统。
-
平台无关性(Platform-Independence):Java 被设计为一种具有平台无关性的语言,这意味着编写的 Java 程序可以在不同的操作系统和硬件平台上运行。这是通过 Java 虚拟机(JVM)实现的,它允许 Java 程序在不同的平台上以相同的方式运行。
-
安全性(Security):Java 的设计注重安全性,它提供了许多安全特性来保护系统免受恶意代码的攻击。Java 的安全模型包括字节码验证、安全沙箱、安全类库等特性,这些特性使得 Java 成为一种安全可靠的编程语言。
-
可移植性(Portability):由于 Java 的平台无关性,它具有很高的可移植性。Java 程序可以在任何支持 Java 虚拟机的平台上运行,而不需要针对特定的操作系统或硬件进行重新编译。
-
高性能(Performance):尽管 Java 是一种解释型语言,但是它通过即时编译器(Just-In-Time Compiler,JIT)提高了代码的执行效率。Java 虚拟机通过动态优化技术提高了代码的执行速度,使得 Java 程序可以达到接近原生代码的性能水平。
Java 的设计哲学使得它成为一种功能强大、易学易用、安全可靠且具有高性能的编程语言,广泛应用于企业级应用开发、移动应用开发、云计算等各个领域。
三、安装java
1、JDK8安装
2、JDK19安装
3、java历史版本
四、JRE和JDK
1、JRE和JDK和JVM之间的关系
在 Java 中,JVM(Java Virtual Machine)和 JRE(Java Runtime Environment)是两个不同但密切相关的概念。
JVM 是 Java 虚拟机,它是 Java 运行程序的核心。它负责将 Java 字节码(.class 文件)翻译成机器码,并在计算机上执行 Java 程序。它提供了内存管理、垃圾回收、线程管理等功能。
而 JRE 是 Java 运行时环境,它包含 JVM 和 Java 类库等运行 Java 程序所需的核心组件。JRE 还包括了一些工具和库,使得 Java 应用程序能够在计算机上运行,例如 Java 核心类库、Java 程序所需的运行时库等。
简而言之,JVM 是运行 Java 程序的虚拟机,而 JRE 则是包含 JVM 和其他运行时所需组件的环境。在运行 Java 程序时,JRE 提供了必要的资源和库,JVM 则负责将 Java 代码转换成可执行的机器码并执行。
所以只需要安装jdk就可以了,安装JDK教程
2、java版本说明
"java version 1.8.0_111" 表示 JDK 8 版本的第111个更新版本。在 Java 的版本号规则中,第一个数字表示主版本号,第二个数字表示次版本号,第三个数字表示更新版本号。因此,在这个版本号中,“1.8.0” 表示 JDK 8 版本,“111” 表示这个版本的更新次数。
比如说 Java SE Development Kit 11.0.2,其中:
- 主版本号为 11,表示这是 JDK 11 版本。
- 次版本号为 0,表示这是 JDK 11 的初始版本。
- 更新版本号为 2,表示这是 JDK 11 的第二个更新版本。
那为什么8版本是这样写的
Java SE Development Kit 8 版本之前的版本号规则与 Java SE 8 及以后的版本略有不同。在早期的版本中,版本号中的第一个数字表示主要版本号,后面的数字表示次要版本号和更新版本号。例如,在 "java version 1.7.0_80" 中,1 表示主要版本号,7 表示次要版本号,0_80 表示更新版本号。
但是从 Java SE Development Kit 8 开始,版本号规则被调整为与后续版本一致。主要版本号位于版本号的第一个位置,次要版本号位于第二个位置,更新版本号位于第三个位置。因此,Java SE Development Kit 8 版本的完整版本号是 1.8.0_xxx。
3、Windows操作系统搜索硬盘上某个命令原理
- 首先会从当前目录下搜索
- 当前目录搜索不到的话,会从环境变量path制定的路径中搜索
- 如果都搜索不到则会报错
一般都在系统变量中配置path,配置变量后一定要重启DOS窗口
4、JDK目录介绍
JDK默认安装在:C:\Program Files\Java
JDK/bin:该目录下存放了很多命令,例如javac.exe和java.exe
在 Java 的安装目录中,常见的目录结构及其作用如下:
-
bin 目录:包含了可执行的二进制文件,如
java
和javac
等。这些命令是用来运行 Java 程序和编译 Java 源代码的。 -
lib 目录:包含 Java 运行时所需的核心类库文件。这些文件是 Java 程序运行所必需的基本类库。
-
conf 目录:存放了 Java 配置文件,可以用来配置 Java 运行时的各种参数和选项。这些配置文件可以用来控制 Java 虚拟机的行为,如内存管理、类加载器设置等。
-
include 目录:主要包含了一些用于本地方法调用(JNI)的头文件,这些头文件用于在 Java 中调用本地系统的一些特定功能。
-
jre 目录:这是 Java Runtime Environment(JRE)的子目录,包含了用于在计算机上运行 Java 程序所需的类库和虚拟机。在 JDK 安装中,该目录通常是一个子模块,而在 JRE 安装中则是主要部分。
-
jmods目录:这是在Java 9及更高版本中引入的目录,它包含了Java平台模块的文件。Java平台模块是用于将Java SE分解为互不依赖的一组模块的概念。在
jmods
目录中,您可能会找到一些以.jmod
为扩展名的文件,这些文件包含了Java模块的信息。 -
legal目录:该目录通常包含了法律方面的信息,比如许可协议和版权信息等。这些文件列出了Java平台及相关组件的使用条款和条件,以及版权声明。这对于了解和遵守Java平台的法律限制非常重要。
这些目录提供了 Java 开发和运行所需的基本组件和配置文件。通过对这些目录的管理和设置,您可以对 Java 环境进行调整和定制,以满足特定的应用需求。
4、JVM介绍
Java虚拟机(Java Virtual Machine,JVM)是Java平台的核心组成部分之一,它是一个虚拟的计算机,它的任务是执行Java字节码(bytecode)。
Java编译器将Java源代码编译成Java字节码,这种字节码可以在任何平台上运行,只要该平台有一个安装了Java虚拟机的运行环境。Java虚拟机在运行Java应用程序时,会将Java字节码转换成该平台的本地机器指令执行。
Java虚拟机的主要优点是,它提供了一种独立于平台的执行环境,使得Java程序可以跨平台运行。此外,Java虚拟机还提供了一些内存管理机制,例如自动垃圾回收等,这些机制可以使Java程序员更容易编写高效且可靠的代码。
Java虚拟机的实现有很多种,其中最著名的是Oracle公司开发的HotSpot虚拟机,其他的实现包括IBM公司的J9虚拟机和Azul Systems公司的Zing虚拟机等。
五、Java的加载与执行
Java程序的运行包括两个非常重要的阶段
编译阶段只检查语法,不会进行运算,不会赋值,不会开辟内存空间
运行阶段会进行运算,赋值和开辟内存空间
六、java运行原理
java程序运行是以类为基本单位的,方法为一般单位,成员变量为最小单位
1、源码编译
是指在Java程序编译的时候,就将所需的类文件编译到了可执行文件中,这些类文件在程序运行时不需要再次加载。又称编译期加载。
当Java程序被编译成字节码文件(.class文件)时,编译器会将所有的依赖类都编译到字节码文件中。这样,在程序运行时,只需要将字节码文件加载到方法区内存中即可,无需再次加载依赖的类文件。这种方式也称为静态链接或静态绑定。
不同的Java源文件都会被编译成不同的.class文件,而不是同一个。每个Java源文件都应该被编译成一个独立的.class文件,这个文件包含了该源文件中的类和接口的字节码。
Java源代码编译过程:
1、编译成员变量,
2、在编译构造方法
java源代码编译时默认执行默认的构造方法(无参),如果没有的话则执行自定义的构造方法
3、编译方法
class字节码文件内容
Java中的.class文件是Java程序的编译结果,其中包含了Java代码的字节码和一些元数据。以下是Java中.class文件中包含的内容:
魔数(Magic Number):用于标识该文件是否为Java字节码文件。在Java中,该值为0xCAFEBABE。
版本号:用于标识Java版本。
常量池(Constant Pool):包含了字节码中用到的各种常量,比如字符串常量、类常量、方法常量等。
访问标志(Access Flags):用于标识该类的访问级别和属性。
类信息(Class Information):包含了该类的名称、父类、接口、字段和方法等信息。
字段信息(Field Information):包含了该类中定义的字段的名称、类型和访问标志等信息。
方法信息(Method Information):包含了该类中定义的方法的名称、参数、返回值类型和访问标志等信息。
属性信息(Attribute Information):包含了该类中定义的各种属性的名称、类型和值等信息。
Java中的.class文件是平台无关的二进制文件,可以在任何Java虚拟机上运行。
源码编译可以提高程序的运行效率,因为它避免了在程序运行时动态加载类所产生的开销。但是,编译期加载的缺点是,它限制了程序的灵活性,因为在编译时就需要确定所有的依赖关系,无法动态地加载新的类。
Java不允许在类体中方法体外直接编写可执行代码,包括输出语句等。
因为Java是一种面向对象的编程语言,它的主要思想是将程序分解成对象,每个对象包含数据和方法,通过这些方法来操纵数据。因此,在类体中只能定义数据和方法,而不能直接编写可执行代码,因为这样会违反面向对象的设计原则。
另外,Java的编译过程中会将类体中的成员变量和方法提取出来,生成类的字节码文件。如果在类体中编写可执行代码,编译器就无法将其提取出来,也就无法生成可执行的字节码文件。
如果您需要在类体中编写可执行代码,可以使用静态块或实例块等机制来实现。但是,这种做法并不是很常见,一般情况下还是建议将可执行代码放到方法体内部执行。
2、JVM启动
虚拟机启动是指当我们启动一个Java应用程序时,操作系统会首先启动JVM,然后JVM会负责解释和执行Java代码。在JVM启动的过程中,会进行一系列的初始化操作,例如加载核心类库、设置系统属性、创建线程等等。
必须加载核心类库
在JVM启动的过程中,会执行一系列的初始化操作,包括设置系统属性、加载java核心类库 如Object、Class、String等类,这些类被称为启动类(Bootstrap Classes)、创建主线程等等。在初始化完成之后,JVM会启动应用程序的主线程,并且通过类加载器加载应用程序的类。类加载器会根据需要加载类,并将这些类加载到JVM的方法区内存中。
需要注意的是,类加载器的加载顺序是有规定的,首先会使用启动类加载器(Bootstrap ClassLoader)加载Java核心类库到JVM方法区内存,然后使用扩展类加载器(Extension ClassLoader)加载Java扩展类库到JVM方法区内存,最后使用应用程序类加载器(Application ClassLoader)加载应用程序的类到JVM方法区内存。这个加载顺序保证了类的唯一性,也保证了类的正确性和安全性。
2.1、java核心类库(只有java.lang包自动加载 )
java核心类库是指:rt.jar
核心类库中的核心是指:rt.jar/java.base/java/lang
在Java中,java.lang
包下的类会自动加载,这是因为它们被视为Java语言本身的一部分,属于语言的核心部分。这些类包括Object
、String
、Exception
等。Java编译器会自动导入这些类,因此您无需手动导入它们就可以直接使用。
其他核心类库中的类,如java.util
、java.io
等,由于不是语言本身的一部分,因此需要显式地导入才能使用。这是为了避免不必要的资源浪费,因为Java标准库中包含了大量的类和方法,如果所有类都自动加载,会对系统资源造成不必要的负担。此外,这种显式导入的方式也提供了更好的可读性和代码的清晰性,使得开发人员能够清楚地知道代码所使用的类来自哪个包。
Java核心类库中包含了很多常用的类和接口,以下是一些常用的类的例子:
java.lang:全部自动加载,无需显式导入,是Java语言的一个核心包(package),其中包含了Java语言的基本类和接口。这个包在Java程序中自动导入,无需手动导入。(只是java.lang包中的类自动加载,java.lang子包中的类需要显示导入)
- java.lang.String:用于处理字符串的类,提供了字符串的各种操作,如连接、截取、替换、大小写转换等。
- java.lang.System:是一个包含一些有用的类成员和类方法的工具类,它提供了一些系统级别的操作和信息,比如标准输入输出流、获取系统属性、垃圾回收等。
java.util
:部分自动加载,需显式导入,提供了Java语言中常用的数据结构和算法,如集合框架、日期和时间处理、随机数生成等。
java.util.ArrayList:用于存储和操作集合元素的类,它可以动态地增加或减少元素,并提供了各种访问、排序、遍历等操作。
java.util.concurrent.ExecutorService:用于管理多线程的执行,可以并发地执行多个任务,并提供了线程池等机制
java.io
:部分自动加载,需显式导入,提供了Java语言中用于输入和输出的类和接口,如文件处理、网络通信、对象序列化等。
java.io.File:用于操作文件和文件夹的类,提供了创建、读取、写入、删除等文件操作方法。
java.net
:部分自动加载,需显式导入,提供了Java语言中用于网络编程的类和接口,如Socket、ServerSocket、URL等。
java.net.URL:用于表示统一资源定位符(URL),可以进行网络连接和通信。
java.sql
:部分自动加载,需显式导入,提供了Java语言中用于数据库编程的类和接口,如JDBC API,用于连接和操作各种关系型数据库。
java.awt
:部分自动加载,需显式导入,提供了Java语言中的基本图形界面组件和功能,如窗口、按钮、标签、绘图等。
javax.swing
:部分自动加载,需显式导入,提供了Java语言中的高级图形界面组件和功能,如表格、树形控件、弹出式菜单等。
2.2、java扩展类库(手动加载)
Java扩展类库(Java Extension Libraries)是Java平台提供的一组功能强大、可定制的类库,它们不像Java核心类库那样必须被加载,而是通过Java平台的扩展机制进行加载和使用。可以通过Java的类加载机制进行加载。扩展类库是一些独立的Java库,它们扩展了Java SE API中的功能,例如Java邮件API和Java数据库连接API。如果要使用扩展类库中的类,需要将相应的库文件添加到Java类路径中。
Java扩展类库中包含了很多有用的类和接口,例如:
Java Database Connectivity (JDBC) 扩展类库:用于连接和操作各种关系型数据库,提供了一组标准的接口,可以通过这些接口访问和管理数据库。
Java Cryptography Extension (JCE) 扩展类库:用于实现加密和解密算法,提供了多种加密方式,如对称加密、非对称加密、数字签名等。
Java Servlet API:用于实现基于Java的Web应用程序的开发,提供了一组标准的接口和类,可以处理HTTP请求和响应,构建Web页面和应用程序等。
Java Messaging Service (JMS) 扩展类库:用于实现分布式应用程序之间的消息传递,提供了一组标准的接口和协议,可以在不同的应用程序之间进行消息传递。
Java Naming and Directory Interface (JNDI) 扩展类库:用于实现命名和目录服务,提供了一组标准的接口和协议,可以将分布式应用程序中的资源进行命名和查找。
Java扩展类库提供了丰富的功能和灵活的扩展机制,可以满足不同应用程序的需求。开发者可以通过加载和使用Java扩展类库中的类和接口,快速构建高效、可靠的应用程序。
3、类加载器启动
在Java程序运行的过程中,类的加载时机可以分为二种,分别是虚拟机启动时加载和运行时加载。它们的含义如下:
-
虚拟机启动时加载:在Java虚拟机启动时就将类加载到内存中。这种方式主要是针对一些核心类,如Java核心类库、Java扩展类库等。
-
运行时加载:在Java程序运行时动态地将类加载到内存中。这种方式主要是针对用户自定义的类和库中的类。在运行时加载类可以提高程序的灵活性和可扩展性,因为程序可以在运行时根据需要动态地加载和卸载类。
需要注意的是,类的加载只会发生一次,也就是说,一个类只会被加载一次,不管这个类被引用了多少次。在类被加载之后,JVM会将这个类的信息存放在方法区中,并且在内存中创建一个Class对象,这个Class对象可以用来获取类的信息和访问类的成员。
Class对象也是保存在方法区中的,Class对象是保存在方法区中的,它描述了一个类的各种信息,并且可以在程序运行时动态地获取和操作类的信息。而类的实例则是在堆中创建的,它保存了这个类的实例数据和相关的成员方法。
类加载原理:
JVM在加载类时,并不会一次性地把整个包中的所有类文件全部加载进来。相反,它是按需动态加载的,即在需要使用某个类的时候,才会加载该类所对应的类文件。这个过程中,JVM会根据类的继承关系和依赖关系来确定需要加载哪些类文件,以及它们的加载顺序。
当一个类第一次被使用时,JVM会尝试查找该类的类文件,如果该类文件还没有被加载,则会触发类加载过程。在类加载过程中,JVM会执行以下步骤:
加载:在文件系统或网络中查找并读取该类的二进制字节流,将其转换为JVM内部的表示形式,并创建该类的Class对象。
链接:将该类的二进制字节流合并到JVM的运行时数据区域中,并进行验证、准备和解析等操作,生成符号引用和直接引用。
初始化:执行该类的静态代码块和初始化语句,为该类的静态变量赋初值。
在类加载过程中,JVM只会加载必要的类文件,并且只会加载一次。如果在后续的执行过程中,再次需要使用该类,则会直接使用已经加载的Class对象,而不会重新加载该类文件。这样可以避免重复加载和浪费内存。
3.1、关于JDK中自带的类加载器(以JDK8说明)
类加载器:ClassLoader,是专门负责加载类的命令/工具
JDK中自带了3个类加载器
- 启动类加载器:rt.jar
- 扩展类加载器:ext/*.jar
- 应用类加载器:CLASSPATH环境变量指定目录
假设有这样一段代码:
String s = "abc";
代码在开始执行之前,会将所需的类全部加载到JVM中。
通过类加载器加载,看到以上代码类加载器会找String.class字节码文件
找到就加载,那么是怎么进行加载的?
- 首先通过“启动类加载器”加载。
- 注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar中的class文件
- rt.jar中都是JDK中最核心的类库
- 如果通过启动类加载器加载不到的时候,会通过“扩展类加载器”加载
- 注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\*.jar
- 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载
- 注意:应用类加载器专门加载:CLASSPATH在环境变量中指定的路径加载其中的jar包(class文件)
3.2、双亲委派机制
java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”,“父”无法加载时,再从扩展类加载器中加载,这个称为“母”,双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。
4、main主方法执行
一个Java程序中只能有一个main
方法,这是程序的入口点,JVM会从这个方法开始执行程序。java程序中执行一个类时,JVM(Java虚拟机)会寻找这个类的main方法作为程序的入口点。因此,在Java程序执行过程中,首先执行的是main方法。JVM会自动调用main方法,
public类不是必须的
main
方法必须写在一个类中,这个类的名称可以任意取,如果写在一个public类中,该文件名必须和这个类名相同,并且文件扩展名为.java
。
类是一个封装了属性和行为(方法)的单位,main
方法是这个类的一个静态方法,它可以访问这个类的静态成员(如静态变量和静态方法),也可以创建这个类的对象来访问它的非静态成员(如非静态变量和非静态方法)。
因此,为了让main
方法能够访问这个类的属性和行为,它必须写在这个类中。同时,Java程序的运行也是从这个类的main
方法开始的
当我们在Java程序中执行一个类时,JVM会先加载这个类对应的.class
文件,并且创建一个对应的Class对象。然后,JVM会搜索这个类中是否有一个名为main
的静态方法,如果找到了就执行这个方法。因此,main
方法所在的类必须已经被加载到方法区内存中,并且可以通过Class对象来访问这个类的静态方法。
七、编译阶段
- 编译阶段主要的任务是检查Java源程序是否符合Java语法,符合Java语法则能够生产正常的字节码文件(xxx.class),不符合Java语法规则则无法生产字节码文件
- 字节码文件中不是纯粹的二进制,这种文件无法在操作系统中直接执行
- 编译阶段的过程:
- 程序员需要在硬盘的某个位置(任意位置)新建一个.java扩展名的文件,该文件被称为Java源文件,源文件当中编写的是Java源代码/源程序。而这个源程序是不能随意编写,必须符合Java语法规则(语法规则是需要记忆的)
- Java程序员需要使用JDK当中自带的Javac.exe(Java编译器)命令进行Java程序的编译
- Javac使用规则:javac java源文件的路径
- 在DOS命令窗口中使用
- javac是一个java编译器工具/命令
- 一个java源文件可以编译生产多个.class文件,一个class类就会有一个.class字节码文件
- 字节码文件/.class文件是最终要执行的文件,所以说.class文件生成之后,java源文件删除并不会影响java程序的执行(一般不删除,因为.class文件最终执行效果可能不理想,就要修改源文件,重新编译后重新执行)
- 编译结束之后,可以将.class文件拷贝到其他操作系统上运行(跨平台)
八、运行阶段
JDK安装之后,除了自带一个javac.exe文件之外,还有一个自带的文件java.exe文件
java.exe命令主要负责运行阶段
java.exe使用:
- 在DOS窗口中使用
- java.exe使用规则:java 类名
- 例如:
- 硬盘上有一个A.class,则使用java A; A为类名
- 硬盘上有一个B.class,则使用java B;
- 硬盘上有一个C.class,则使用java C
- 不能写成java A.class
- 例如:
- 运行阶段的过程
- 打开DOS命令窗口
- 输入 java A
- java.exe命令会启动Java虚拟机(JVM),JVM会启动类加载器(ClassLoader)
- ClassLoader会去硬盘上搜索A.class文件,找到该文件后则将该字节码文件装在到JVM中
- JVM将A.class字节码文件解释成二进制数据
- 操作系统执行二进制数据并和底层硬件平台进行交互
1、命令行参数运行java
Java 程序需要接受命令行参数,可以在运行时在类名后面添加参数,例如:
java MyProgram arg1 arg2
其中 MyProgram
是你的 Java 类名,arg1
和 arg2
是传递给程序的命令行参数。
九、hello world
public class HelloWorld{
public static void main(String[] args){
System.out.println("HelloWorld!");
}
}
1、编译Java程序
绝对路径:打开DOS窗口,javac 直接将源文件拖进去 回车即可
相对路径:在源文件所在文件夹打开DOS窗口 javac 文件名.java
2、运行Java程序
首先需要在字节码文件所在目录处打开DOS窗口
一定注意:java命令后面跟的不是文件路径,是一个类名
【JAVA】错误: 找不到或无法加载主类 HelloWorld_GokuCode的博客-CSDN博客
CLASSPATH变量是为类加载器指路的
CALSSPATH是类加载器特有的
3、编码问题
//public表示公开的
//class表示定义一个类
//HelloWorld表示一个类名
public class HelloWorld{ //表示定义一个公开的类,起名为HelloWorld
//类体,类体中不允许直接编写java语句,(除声明变量外)
/*
public表示公开的
static表示静态的
void表示空
main表示方法名是main
(String[] args)是一个main方法的形式参数列表
以下是程序的主方法,是程序的执行入口,固定写法
*/
public static void main(String[] args){ //表示定义一个公开的静态的主方法,是一个程序的入口
//方法体
//java语句,必须以;结尾,必须是半角的双引号
//java中所有的字符串必须用双引号括起来
System.out.println("Hello World!"); //向控制台输出消息
System.out.println("Hello 贾卓群!");
//程序员的双引号是全角的
//这里的全角的双引号只是一个普通的字符串
System.out.println("我是一个“程序员”");
}
}
十、Java中的注释
- 出现在java源程序中,对java源代码的解释说明
- 注释不会被编译到.class字节码文件当中
- 增加程序的可读性
1、单行注释
// 单行注释:只注释当前行
2、多行注释
/*......*/ 中间为注释
3、javadoc注释(专业注释)
- /**
- * javadoc注释
- * javadoc注释
- */
十一、public class 和class的区别
- 一个java源文件当中可定义多个class
- 一个java源文件中不一定有public的class (public不是必须得)
- 一个class会对用生成一个*.class字节码文件
- 一个java源文件中定义公开的(public)的类的话,public的class只能有一个,并且该类名必须和java源文件名称一致
- 每一个class当中都可以编写main方法,都可以设定程序的入口,想执行哪个class文件中main方法,就只java 后跟该类名
- 注意:当在命令窗口中执行java Hello,那么要求Hello.class文件中必须有主方法,否则会报错
十二、java中常用命令
在Java开发中,有一些常用的命令可用于编译、运行、调试和管理Java应用程序。以下是这些命令的详细说明:
1、javac
Java编译器命令,用于将Java源代码文件(以
.java
为扩展名)编译成Java字节码文件(以.class
为扩展名)。示例用法:javac HelloWorld.java
2、
java
Java虚拟机(JVM)命令,用于运行Java应用程序。它接受一个类名作为参数,该类包含应用程序的入口点(即
public static void main(String[] args)
方法)。示例用法:java HelloWorld
3、
javadoc
Java文档生成器命令,用于根据代码中的注释生成API文档。通过在源代码中添加特定格式的注释,可以生成详细的API文档,包括类、方法、字段的说明和文档。示例用法:
javadoc MyClass.java
4、
jar
Java归档工具命令,用于创建、查看和提取Java归档文件(JAR文件),这些文件可以包含一组Java类文件和相关资源。JAR文件通常用于分发和打包Java应用程序。示例用法:
- 创建JAR文件:
jar cf jar-file input-file(s)
- 解压缩JAR文件:
jar xf jar-file
- 查看JAR文件内容:
jar tf jar-file
5、默认以当前DOS窗口所在位置为命令起始位置,文件中所有路径都是相对于命令执行位置处开始
java源文件路径使用 / 表示
class类路径使用 . 表示
javac命令:编译文件
- 编译java源文件,.java文件,必须加.java后缀
- .java文件可以是相对路径或者绝对路径,使用 / 分隔
- .java文件中的路径必须是相对路径,不能是绝对路径
java命令:运行类
- 运行java字节码文件,.class文件,不能加.class后缀
- 必须使用相对路径 使用 . 分隔
6、将中文换行为unicode编码
public class UnicodeConverter {
public static void main(String[] args) {
String chineseCharacter = "中文";
String unicode = convertToUnicode(chineseCharacter);
System.out.println(unicode);
}
/**
* public static String convertToUnicode(String input): 这是一个公共静态方法,接受一个字符串参数input,用于将输入的字符串转换为Unicode编码。方法返回一个字符串,表示转换后的Unicode编码结果。
*
* StringBuilder unicodeBuilder = new StringBuilder();: 创建一个StringBuilder对象,用于构建最终的Unicode编码结果。
*
* for (char ch : input.toCharArray()): 遍历输入字符串input中的每个字符。
*
* unicodeBuilder.append(String.format("\\u%04x", (int) ch));: 对每个字符执行以下操作:
*
* (int) ch: 将字符转换为其对应的整数值,即Unicode编码的十进制表示。
* String.format("\\u%04x", (int) ch): 将整数值格式化为\\u加上四位十六进制的Unicode转义序列。例如,如果字符的Unicode编码为65,则格式化结果为\u0041。
* unicodeBuilder.append(...): 将转义序列追加到unicodeBuilder中。
* return unicodeBuilder.toString();: 将最终构建的Unicode编码结果转换为字符串,并作为方法的返回值。
*
* 该方法通过遍历字符串中的每个字符,将每个字符转换为其对应的Unicode转义序列,并将转义序列追加到字符串构建器中。最后,返回拼接完成的Unicode编码结果字符串
*
* \\u%04x 是一个格式化字符串,其中包含以下部分:
*
* \\u:表示 Unicode 转义序列的起始标记。
* %04x:是一个格式化指示符,用于指定输出的格式。在这里,%04x 表示将整数值转换为四位十六进制数,并在不足四位时使用零进行填充。
* 例如,如果要将整数值 65 转换为 Unicode 转义序列,String.format("\\u%04x", 65) 将返回 \u0041,其中 0041 是 65 的十六进制表示形式。
* 所以,unicodeBuilder.append(String.format("\\u%04x", (int) ch)) 这行代码将把每个字符的整数值转换为四位的十六进制形式,并追加到 unicodeBuilder 字符串构建器中。
*
* @param input
* @return
*/
public static String convertToUnicode(String input) {
StringBuilder unicodeBuilder = new StringBuilder();
for (char ch : input.toCharArray()) {
unicodeBuilder.append(String.format("\\u%04x", (int) ch));
}
return unicodeBuilder.toString();
}
}
第三章、Java默认是同步的(sync)
代码运行规则:java是同步(sync)的,类体和方法体中的代码,都是遵守自上而下的顺序一次执行的,逐行执行。上一行的代码必须完整的结束之后,下一行的代码才会执行
第四章、IDEA
IDEA 开发工具_idea开发工具_W_chuanqi的博客-CSDN博客
一、格式化代码设置
二、包层级显示设置
三、IDEA 中,Java出现无效的源发行版 解决办法
四、模块导入jar包
五、设置模版
IDEA设置类模板、方法模板_idae怎么设置类初始化模板-CSDN博客
六、不显示工程目录,解决办法
七、常用快捷键
1、Ctrl + F12
查看类中的属性和方法
2、Ctrl + h
查看当前类的继承关系
3、Alt + Shift + insert
多行选择
4、Ctrl + r
批量替换代码
第五章、变量与常量
一、标识符
1、定义
- 标识符又称变量名
- 在java源程序中凡是程序员有权利自己命名的单词都是标识符(在notepad++中以黑色字体高亮显示)
- 标识符可以标识:类名,方法名,变量名,接口名,常量名
2、命名规则:(不遵守规则,编译会报错)
- 一个合法的标识符只能有数字,字母,下划线_,美元符号$ 组成,不能含有其他符合
- 不能以数字开头
- 严格区分大小写
- 关键字不能做标识符(在notepad++中以蓝色字体高亮显示)
- 理论上无长度限制,最好不要太长
3、命名规范:(只是一种规范,不属于语法,不遵守规范,编译不会报错)
- 最好见名知意
- 遵守驼峰命名方式
- 类名,接口名,首字母大写,后面每个单词首字母大写(大驼峰)
- 变量名,方法名,首字母小写,后面每个单词首字母大写(小驼峰)
- 常量名,全部大写,单词间用下划线隔开
合法标识符 | 不合法标识符 |
_123Test | 123Test |
HelloWorld!(不能有!) | |
HellpWorld#(不能有#) | |
Hello World(不能有空格) | |
public(不能为关键字) |
二、关键字:
- 关键字在java中全部小写
- 常见的关键字:public class static void if for while do default byte short int long float double boolean char private protected switch true false throw throws try catch
在 Java 中,没有直接的方法可以类似于 Python 中的 help("keywords")
来查看关键字。然而,您可以通过查看 Java 的官方文档或一些在线资源来了解关于 Java 语言的关键字。关键字是 Java 语言的基本构建模块,因此了解它们是编写有效 Java 代码的关键。
以下是一些常用的 Java 关键字:
-
数据类型关键字:例如
int
、double
、char
等。 -
控制流关键字:例如
if
、else
、for
、while
等。 -
访问修饰符关键字:例如
public
、private
、protected
等。 -
类和对象关键字:例如
class
、extends
、implements
等。 -
异常处理关键字:例如
try
、catch
、finally
等。
如果您使用的是集成开发环境(IDE),通常可以通过 IDE 提供的代码补全和代码提示功能来查看和了解 Java 关键字。此外,您可以参考 Java 官方文档以及相关的 Java 编程书籍,这些资源中通常包含了关于 Java 关键字的详细解释和用法说明
三、字面值(常量,变量值)
- 字面值就是数据,例如:10 100 3,14 “abc” ‘aaa’ true false
- 字面值是java源程序的组成部分
- 数据在现实世界中是分门别类的,所以数据在计算机编程语言中也是有类型的(数据类型)
- 10,100属于整数型字面值
- 3.14属于浮点型字面值
- true false 属于布尔型字面值
- “axc” “中国人”属于字符串型字面值
- ‘a’ ‘人’ 属于字符型字面值
- 注意:java语言中所有的字符串型字面值必须使用双引号括起来,双引号是半角
- 注意:java语言中所有的字符型字面值必须使用单引号括起来,单引号是半角
四、变量概述
不同的数据类型在内存开辟的空间是不一样的,数据类型的作用就是指导在内存中开辟多大的空间
1、定义:
- 变量本质上说是内存中的一块空间,这块空间中包含变量值数据类型,变量名,字面值,变量本身在内存中所占的内存空间的内存地址信息
- 变量是内存中存储数据的最基本单元
2、变量的组成
- 变量值数据类型
- 变量名称
- 变量中保存的值
- 变量本身的内存地址信息
3、数据类型的作用
- 不同的数据有不同的类型,不同的数据类型底层会分配不同大小的空间
- 数据类型是指导程序在运行阶段应该分配多大的内存空间
五、声明变量
1、声明变量要求(静态语言)
变量中存储的具体的数据必须和变量的数据类型一致,当不一致时编译会报错
2、声明或定义变量的语法
数据类型 变量名;
数据类型:int float 等
变量名:符合语法规则的标识符(首字母小写,后面的每个单词首字母大写)
3、变量声明之后赋值
变量名 = 字面值;
要求:字面值的数据类型必须和变量的数据类型保持一致
= 等号是一个运算符,叫做赋值运算符,赋值运算符先运算等号右边的表达式,表达式执行结束之后的结果赋值给左边的变量
4、变量声明与赋值
数据类型 变量名 = 字面值;
注意:
- 声明变量时会开辟内存空间,但是变量赋值时不会,在原内存空间中进行修改
- 在同一个作用域中同一个变量名只能声明一次,不能重名
- 在不同的作用域中,变量名可以相同,可以重复声明相同的变量名
5、变量赋值之后,可以重新赋值,变量的值可变化
有了变量的概念之后,内存空间得到了重复的使用
6、变量在一行上可以声明多个
//a和b尚未初始化,c赋值10 int a,b,c = 10; a = 30; b = 50; System.out.println(a); //30 System.out.println(b); //50 System.out.println(c); //10
六、访问变量
1、get形式
第一种:读取变量中保存的具体数据 get
2、set形式
第二种:修改变量中保存的具体数据 set
变量必须先声明在赋值,才能访问
只声明变量,内存空间并未开辟出来,变量并未初始化,所以在赋值之前无法调用
七、变量的作用域
1、定义
变量的作用域其实就是描述变量的有效范围,在什么范围之内是可以被访问的,只要出了这个范围该变量就无法访问了
出了大括号就不认识了
2、作用域注意事项
- 在一个方法中声明并赋值了一个变量,在另一个方法中不能访问
- 在类体中声明并赋值一个变量,在所有方法中都可以访问
- 循环中的声明的变量作用域只在循环内,循环结束后,变量的内存就释放了,循环外不能访问;在循环外声明的变量,在循环外可以访问
3、变量的分类
根据变量声明的位置来分类
1、局部变量(包括:块级变量,函数级变量)
在方法体内声明的变量叫做局部变量,作用域该方法体内
2、成员变量(包括:类级变量,包级变量,模块变量)
在方法体外声明的变量叫做成员变量,作用域类体内
- java遵循就近原则:在方法体内访问变量,相同的变量名以局部变量为准
- 成员变量没有手动赋值系统会默认赋值,但是局部变量不会
4、作用域分类
作用域指一个变量的作用的范围,在java中一共有四种作用域:
- 模块作用域(Module Scope)
- 包级作用域(Package Scope)
- 类级作用域 (Class Scope)
- 函数作用域(局部作用域)Function Scope
- 块级作用域(Block Scope)
在 Java 中,作用域的大小(即变量的可访问范围)由其声明方式和声明位置决定。一般来说,作用域从小到大排列如下:
1、块级作用域
这意味着它们对于代码块(如 if、for、while 等)中定义的变量可见,但对于包含该块的函数或文件中定义的变量不可见。
2、函数作用域
变量在函数内部声明,但在函数外部无法访问。这也被称为函数作用域。
3、类级作用域
变量只能在类内访问,类外不能访问
4、包级作用域
变量只能包内访问,包外不能访问
5、模块作用域
使用模块化方式组织代码时,每个模块都具有自己的作用域。模块中声明的变量只能在该模块内访问,不会影响其他模块或全局命名空间。
综上所述,作用域的大小从小到大为:块级作用域 < 函数作用域 <类级作用域< 包级作用域 < 模块作用域。
除了函数作用域和块级作用域的变量可直接声明在作用域中,不需要访问权限控制符外,其他三个作用域均需通过访问权限控制符确定是在哪个作用域中
5、作用域链
在 Java 中,作用域链(Scope Chain)是指在嵌套的代码块中,变量的查找顺序。当在一个代码块中访问一个变量时,Java 编译器会按照特定的顺序查找变量的值。作用域链通常按照以下顺序进行查找:
-
当前代码块的局部变量(Local Variables):Java 首先查找当前代码块中声明的局部变量。如果变量在当前代码块中找到,则使用该变量的值。
-
外层代码块的局部变量(Enclosing Blocks' Local Variables):如果在当前代码块中没有找到变量,Java 会继续查找外层代码块中声明的局部变量,直到找到该变量或查找到最外层的代码块。
-
成员变量(Instance Variables):如果在所有的局部变量中都没有找到变量,Java 将查找当前对象的成员变量(实例变量)。成员变量属于对象级别的,在对象的生命周期内一直存在,直到对象被销毁。
-
静态变量(Static Variables):如果在当前对象的成员变量中没有找到变量,Java 会继续查找当前类的静态变量。静态变量属于类级别的,在程序运行期间一直存在。
-
父类的成员变量和静态变量:如果当前类没有找到变量,Java 会继续在父类中查找变量。如果父类中也没有找到变量,则会继续在父类的父类中查找,直到找到变量或到达继承层次的顶部。
-
全局作用域:如果在继承层次中都没有找到变量,Java 将查找全局作用域中声明的变量,例如
import
的包、java.lang
包等。
如果在以上所有的作用域中都没有找到变量,则 Java 编译器会抛出 variable not found
错误。
需要注意的是,在嵌套的代码块中,内层代码块可以访问外层代码块中的变量,但外层代码块不能访问内层代码块中的变量。作用域链确保了变量的查找顺序,使得 Java 程序能够正确访问不同作用域的变量。
第六章、数据类型
输出在控制台的都是字符串类型
数据类型的作用:程序当中有很多数据,每一个数据都是有相关类型的,不同数据类型的数据占用空间大小不同。数据类型的作用就是指导JVM在运行程序的时候给改数据分配多大的内存空间
一、数据类型分类
1、基本数据类型
基本数据类型包括四大类和八小种
基本数据类型不同于引用数据类型一样在内存中开辟内存空间,有内存地址。基本数据类型是由二进制直接组成的空间地址,并不是在堆内存中。
2、引用数据类型:
类,接口,数组,字符串,自定义类等
2.1、引用数据类型声明:
数据类型名 变量名 = new 类名(实参列表);
//数据类型名和类名默认是一致的,也可向上转型(多态)
演示默认情况:
先声明变量类型并进行初始化和直接初始化的类型一样
基本数据类型:int a = 10; 将a传入方法参数;和直接将10传入方法参数中一样
引用数据类型:String str = new String() 将str传入方法参数,和直接将new String()传入方法参数是一样的
3、可变性分析
在Java中,基本数据类型全都是不可变的,而引用数据类型则有些是可变的,有些是不可变的。以下是Java中常见的数据类型及它们的可变性:
基本数据类型(Primitive Data Types):包括byte、short、int、long、float、double、char和boolean。它们都是不可变的,一旦赋值后不能再被改变。
字符串(String):字符串是引用数据类型,但是它们是不可变的。当一个字符串被创建后,它的值不能被改变。如果想要改变一个字符串的值,需要创建一个新的字符串对象。
数组(Array):数组也是引用数据类型,但是数组的长度是固定的,一旦数组被创建后,它的长度不能被改变。但是可以改变数组中的元素的值。
集合类(Collection):Java中有很多集合类,包括List、Set、Map等。其中,List和Set和Map都是可变的,都有相应的增删改查方法CRUD。当往List中添加、删除元素时,List的大小会发生变化。Map也是一种可变的数据类型,可以添加、删除键值对。
总之,在Java中,基本数据类型是不可变的,而引用数据类型中有些是可变的,有些是不可变的。需要根据具体情况来选择使用哪种数据类型。
3.1、基本数据类型都是不可变的
在Java中,基本数据类型(primitive data types)是不可变的,主要是出于以下几个原因:
-
性能优化: Java的基本数据类型是在底层直接表示为原始二进制数据,不需要额外的内存分配和回收,因此具有非常高的性能。由于它们是不可变的,不会涉及修改操作,这使得在处理基本数据类型时更加高效。基本数据类型不涉及内存,和底层二进制有关
-
线程安全: 不可变的基本数据类型在多线程环境中具有天然的线程安全性。因为不可变对象无法被修改,多个线程可以安全地共享它们而无需额外的同步措施。这有助于避免竞态条件和并发问题。
-
简单性: 不可变性使得基本数据类型的使用更简单,因为你不需要考虑是否需要保护或复制它们。它们在使用时不会发生意外的变化,从而简化了代码的编写和调试。
-
预测性: 基本数据类型的不可变性使得它们的行为更加可预测。例如,如果你将一个基本数据类型的变量传递给一个方法,你可以确保它的值在方法内部不会被修改。
尽管基本数据类型是不可变的,但Java还提供了包装类(wrapper classes)来处理基本数据类型的对象表示。这些包装类(例如Integer
、Double
、Boolean
等)允许我们在需要时将基本数据类型转换为对象,以便在集合类和泛型中使用。包装类是不可变的,并且提供了一些额外的功能,但在性能和内存占用方面通常不如基本数据类型。
总结起来,Java中的基本数据类型是不可变的,这样做有助于提高性能、线程安全性,并简化代码的使用和编写。
3.2、引用数据类型部分可变,部分不可变
在Java中,引用数据类型(Reference data types)的可变性与数据类型本身的特性和设计有关。Java中的引用数据类型可以分为可变和不可变两类。
可变引用数据类型:
-
数组(Array): 数组是一种可变的引用数据类型。一旦创建后,数组的长度是固定的,但数组中的元素可以被修改。你可以通过索引访问和修改数组中的元素,这使得数组在处理一系列变化的数据时非常方便。
-
集合类(Collection classes): Java中的集合类,如ArrayList、LinkedList、HashSet等,都是可变的引用数据类型。这些集合类可以动态地增加、删除和修改元素。它们提供了丰富的方法用于在集合中操作数据,使得数据的存储和处理更加灵活。
不可变引用数据类型:
-
字符串(String): 字符串是一种不可变的引用数据类型。一旦创建后,字符串的内容不能被修改。任何字符串的修改操作都会生成一个新的字符串对象。这是为了保护字符串的数据完整性,使得字符串在多线程环境下更加安全。
-
包装类(Wrapper classes): Java中的包装类,如Integer、Double、Boolean等,也是不可变的引用数据类型。一旦创建后,包装类的值不能被修改。如果需要修改包装类对象的值,只能创建一个新的包装类对象。
可变和不可变引用数据类型的设计是为了在Java中提供更好的数据安全性和线程安全性。不可变数据类型在多线程环境中通常更加安全,因为它们不会发生竞态条件。同时,不可变数据类型也有助于代码的稳定性和可维护性,因为它们避免了意外的数据修改。
在使用Java时,根据数据的性质和需求,选择合适的可变或不可变引用数据类型非常重要。对于那些不需要频繁修改的数据,使用不可变类型可以带来很多好处。而对于需要频繁修改的数据,可变类型则提供了更高的灵活性和性能。
二、第一类:整数型 byte short int long
1、原码、反码、补码
计算机在任何情况下寻出整数型都是以补码的形式
正数的补码:与原码相同
负数的补码:负数的绝对值对应的二进制码中所有的二进制位取反,再加1
强制类型转换后都为补码形式,以byte强制转换为例:
案例1:(负数)
补码:1000 0000 转换后最高位为1
计算过程:10000000 -1 --》01111111--取反--》10000000--》128--》-128
案例二:(正数)
补码:0101 1001 转换后最高位为0
计算过程:直接补码就是原码,0101 1001--》89
计算机原码,反码,补码_机器数的原码反码补码_chenchao2017的博客-CSDN博客
什么是原码、反码和补码_原码反码补码_莪是男神的博客-CSDN博客
2、整数型字面值默认为int类型
- java语言中的“整数型字面值”被默认当做int类型来处理,要让这个“整数型字面值”被当做long类型来处理的话,需要在“整数型字面值”后面添加 L
- 自动类型转换机制:小容量向大容量转换
- 强制类型转换机制:大容量向小容量转换 须加强制类型转换符
- (以long类型为例 long a = 100L;)
- 原始数据:
- 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
- 强制转换后的数据:
- 00000000 00000000 00000000 01100100
- 将左边的二进制砍掉(所有的数据强转都是这样实现的)
- (以long e = 2147483648L;)
- 原始数据
- 00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000
- 强制转换后的数据
- 10000000 00000000 00000000 00000000,目前存储在计算机内部,计算机存储数据都是采用补码的形式存储
- 所以现在10000000 00000000 00000000 00000000是一个补码形式,将该补码转换到原码就是最终的结果
- 在java语法中,虽然整数型字面值默认为int类型,但是当一个整数型字面值没有byte,short,char类型取值范围的话,该字面值可以直接赋值给对应类型的变量(超出范围才会精度损失,因为超出了取值范围需要强制类型转换)
-
byte a = 127; System.out.println(a); //127 short b = 32767; System.out.println(b); //32767 char c = 122; System.out.println(c); //z 转换为对应的ASCII编码输出
- byte r = (byte)128;
- 原始数据:
- 00000000 00000000 00000000 10000000
- 强制转换之后的数据
- 10000000 这是一个补码
- (以long类型为例 long a = 100L;)
3、java中的整数型字面值有三种表示方式
- 第一种方式:十进制(缺省默认方式)
- 第二种方式:八进制(在编写八进制整数型字面值的时候需要以0开始)
- 第三种方式:十六进制(在编写十六进制整数型字面值的时候需要以0x开始)
DataTypeTestInteger.java
public class DataTypeTestInteger {
public static void main(String[] args) {
int a = 10;
int b = 010; //整数型字面值以0开头,后面那一串数字就是八进制
int c =0x10; //整数型字面值以0x开头,后面那一串数字就是十六进制
//以十进制方式输出
System.out.println(a); //10
System.out.println(b); //8
System.out.println(c); //16
System.out.println(a+b+c); //34
//123这个整数型字面值默认是int类型
//i变量声明的时候也是int类型
//int类型的123赋值给int类型的变量i,不存在类型转换
int i = 123;
System.out.println(i);
//456整数型字面值被当做int类型,占用4个字节
//x变量在声明的时候是long类型,占用8个字节
//int类型的字面值456赋值给long类型的变量x。存在类型转换
//int类型是小容量
//long类型是大容量
//小容量可以自动转换成大容量,称为自动类型转换机制
long x = 456;
System.out.println(x);
//2147483647字面值是int的最大值,占4个字节
//y是long类型,占用8个字节,自动类型转换
long y = 2147483647;
System.out.println(y);
//编译错误:过大的整数: 2147483648
//long z = 2147483648;
//System.out.println(z);
//2147483648上来就当做long类型来处理,在字面值后面加L
//2147483648是个8字节的long类型
//z是long类型变量,所以不存在转换
long z = 2147483648L;
System.out.println(z);
//100L是long类型的字面值
//q是long类型变量
//不存在类型转换,直接赋值
long q = 100l;
//q是long类型,8个字节
//w是int类型,4个字节
//编译报错,大容量不能直接赋值给小容量
//int w =q;
//大容量转换成小容量,需要进行强制类型转换
//强制类型转换需要加“强制类型转换符”
//加上强制类型转换符后,编译会通过,但是运行阶段仍然可能精度损失很大,所以谨慎使用,
int w = (int) q;
System.out.println(w);
long e = 2147483648L;
int e1 = (int) e;
System.out.println(e1); //精度损失严重,结果是负值
int e2 = (int) 2147483648L;
System.out.println(e2);
System.out.println("==================================");
//在java语法中,当一个整数型字面值没有超出byte类型取值范围的话,该字面值可以直接赋值给byte类型的变量
byte r = 50;
int r1 = r;
System.out.println(r1); //50
byte r2 = 127;
//byte r3 = 128; //编译报错:128这个int类型的字面值已经超出了byte类型的取值范围,不能直接赋值给byte类型的变量
//强制类型转换
byte r3 = (byte) 128;
System.out.println(r3); //-128
byte r5 = (byte) 345;
System.out.println(r5); //89
byte r4 = (byte) 456;
System.out.println(r4); //-56
byte r6 = (byte) 567;
System.out.println(r6); //55
short t = 32767;
System.out.println(t); //32767
char u = 65535;
System.out.println(u); //输出为空
}
}
三、第二类:浮点型 float double
float:单精度(4个字节)
double:双精度(8个字节,精度较高)
double的精度相对比较低,不适合做财务工作,财务设计到钱的问题,要求精度很高,所以SUN在基础SE类库中为程序员准备了精确度更高的类型,是一种引用数据类型,不属于基本数据类型,它是:java.math.BigDecimal
1、java源码位置
其实java程序中SUN提供了一套庞大的类库,java程序员是基于这套基础的类库来进行开发的,
- java中SE类库字节码位置:C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar
- java中SE类库源码位置:C:\Program Files\Java\jdk1.8.0_111\src.zip
例如:String.java和String.class 文件
在main方法中 String[ ] args 中的String使用的就是String.class字节码文件
2、浮点型字面值默认为double类型
在java中所有的浮点型字面值,默认当做double类型来处理,要想该字面值当做float类型来处理,需要在字面值后面添加F/f
3、浮点型数据在计算机内部以近似值存储
注意:
- double和float在计算机内部二进制存储的时候都是近似值
- 在现实世界中有一些数字是无线循环的,例如:3.3333333....
- 计算机的资源是有限的,用有限的资源存储无线的数据只能存储近似值。
- 浮点型数据强制类型转换为int类型后只取整数位,不进行四舍五入
DataTypeTestFloat.java
public class DataTypeTestFloat {
public static void main(String[] args) {
//3.0是double类型的字面值
//a 是double类型的变量,不存在类型转换
double a = 3.0;
System.out.println(a);
//4.2是double类型的字面值,b是float类型的变量,大容量转换成小容量需要加强制类型转换符,所以以下编译错误
//float b = 4.2;
//解决方案
//第一种:没有类型转换
float b = 4.2F;
System.out.println(b);
//第二种:强制类型转换
float b1 = (float) 4.2;
System.out.println(b1);
}
}
四、第三类:布尔型 boolean
boolean 占1个字节
1、只有true和false两个值,没有其他值(不支持其他类型自动转换)
- 在java语言中boolean类型只有两个值,true和false,没有其他值,不想c语言中,0和1可以表示假和真
- 在底层存储的时候boolean类型占用1个字节,false底层是0,true底层是1
- 布尔类型在实际开发中非常重要,经常使用逻辑运算和条件控制语句中
DataTypeTestBoolean.java
public class DataTypeTestBoolean {
public static void main(String[] args) {
//编译错误:不兼容的类型
//boolean flag = 1;
boolean loginSuccess = true;
if(loginSuccess){
System.out.println("登录成功!");
}else {
System.out.println("登录失败");
}
}
}
五、第四类:字符型 char
char类型只能存储一个字符,多个字符是字符串,就不是字符了
在 Java 中,
char
类型表示一个16位的 Unicode 字符。尽管一个中文字符在 UTF-8 编码下通常占用3个字节,但在 Unicode 编码中,大多数汉字(包括"尼")都被编码为一个16位的字符。Unicode 是一种字符编码标准,它为世界上几乎所有的字符(包括不同语言的字符、符号、表情等)分配了唯一的代码点。Unicode 的目标是提供一个统一的字符编码,以便不同的文本处理系统和编程语言可以正确地处理各种字符。
因此,在 Java 中,
char
类型是用于表示 Unicode 字符的,而不是特定的字节编码(如 UTF-8)。尽管一个字符在不同的编码下可能占用不同数量的字节,但char
类型的特点是能够表示任何 Unicode 字符,包括汉字。这意味着char
类型可以用来存储"尼"这个汉字,因为它在 Unicode 中是一个合法的字符。需要注意的是,当你处理文本时,尤其是在涉及到不同的字符编码时,要小心字符编码的转换,以确保正确地处理不同编码的文本数据。在 Java 中,通常使用
String
类型来处理文本,因为它是基于 Unicode 编码的,并且提供了许多用于处理文本的方法。如果需要将字符编码转换为字节编码,可以使用getBytes
方法。
基本使用:
DataTypeTestCharBasicUse.java
public class DataTypeTestCharBasicUse {
public static void main(String[] args) {
char c ='a';
System.out.println(c);
//一个中文占用2个字节,char类型正好是2个字节,所以java中的char类型变量可以存储一个中文字符
char x = '中';
System.out.println(x);
//编译错误,ab是字符串不能使用单引号括起来
//char y = 'ab';
//类型不兼容,编译错误
//char a = "a";
char e;
e = 'f';
e = 'g';
System.out.println(e);
}
}
字符串“abc”不属于基本数据类型,属于引用数据类型,字符属于基本数据类型
1、转义字符 \
一个 \ 表示转义 ,两个 \\ 不会转义
转义字符出现在特殊字符之前,会将特殊字符转换成普通字符
System.out.println()方法和System.out.print()区别
println()表示输出之后换行,print()表示输出后不换行
\n:换行符
\t:制表符
\':普通的单引号
\”:普通的双引号
\\:普通的反斜杠
DataTypeTestEscapeCharacter.java
public class DataTypeTestEscapeCharacter {
public static void main(String[] args) {
char a = 'n';
System.out.println(a);
//这是一个“换行符”,属于char的数据,换行符
char a1 = '\n';
System.out.print("a");
System.out.print(a1);
System.out.println("b");
//制表符Tab 有时和空格很像,但是ASCII码是不一样的
char b = '\t';
System.out.print("A");
System.out.print(b);
System.out.println("B");
//输出反斜杠,需要两个斜杠,第一个反斜杠具有转义功能,将后面的的反斜杠转义为普通的反斜杠字符
//java中的两个反斜杠代表一个普通的反斜杠字符
char c = '\\';
System.out.println(c);
System.out.println('\"'); //输出一个普通的双引号
System.out.println('\''); //输出一个普通的单引号
System.out.println("'Hello World!'");
System.out.println("“Hello World!”"); //里面的双引号为中文的,全角的
System.out.println("\"Hello World!\""); //里面的双引号为英文的,半角的,采用\转义
char m = '中';
System.out.println(m);
System.out.println('\u4e2d'); //使用unicode编码输出
System.out.println('\u0000'); //默认为空
System.out.println('\u0020'); //表示空格
System.out.println('b'+1); //使用ascii码相加
}
}
public static String convertToUnicode(String input) {
StringBuilder unicodeBuilder = new StringBuilder();
for (char ch : input.toCharArray()) {
unicodeBuilder.append(String.format("\\u%x", (int) ch));
}
return unicodeBuilder.toString();
}
System.out.println(convertToUnicode("中文")); //\u4e2d\u6587
System.out.println((int) '中'); //20013
System.out.println((int) 'a'); //97
System.out.println((char) 97); //a
System.out.println('a' + 1); //98
System.out.println("a" + 1); //a1
2、回车换行符
回车符(Carriage Return)、换行符(Line Feed)和回车换行符是用于控制文本换行和光标移动的特殊字符序列。
-
回车符(Carriage Return)通常表示为
\r
,它是一个控制字符,将光标移动到当前行的开头位置,使得后续输出会覆盖当前行的内容。 -
换行符(Line Feed)通常表示为
\n
,它也是一个控制字符,将光标移动到下一行的开头位置,使得后续输出会在新的一行开始。 -
回车换行符通常表示为
\r\n
,它是回车符和换行符的组合。在某些操作系统中,例如Windows,回车换行符被视为一对,表示换行操作。它用于表示一行文本的结束,并移动光标到下一行的开头位置。
需要注意的是,不同的操作系统在处理回车、换行和回车换行符方面可能有所不同。一些常见的规则是:
- 在Unix/Linux和MacOS系统中,通常使用换行符
\n
表示换行。 - 在Windows系统中,通常使用回车换行符
\r\n
表示换行。
在Java中,使用转义字符的形式来表示这些特殊字符,例如\r
表示回车符,\n
表示换行符。
示例代码:
System.out.print("Hello\rWorld"); // 输出结果为:World
System.out.print("Hello\nWorld"); // 输出结果为:
// Hello
// World
System.out.print("Hello\r\nWorld"); // 输出结果为:
// World
总结:
- 回车符(
\r
)将光标移动到当前行的开头。 - 换行符(
\n
)将光标移动到下一行的开头。 - 回车换行符(
\r\n
)在某些操作系统中表示换行,是回车符和换行符的组合。
3、回车换行符的ASCII字节值
在Java中,回车换行符的表示方式是使用转义字符 \r
和 \n
。
对应的ASCII编码值如下:
- 回车符(Carriage Return):
\r
,ASCII编码值为 13。 - 换行符(Line Feed):
\n
,ASCII编码值为 10。
这两个字符通常一起使用,表示一个完整的回车换行操作,例如在文本文件中的换行操作。
请注意,不同的操作系统和文本编辑器可能对回车换行符的处理方式有所不同。在Windows操作系统中,通常使用回车换行符 \r\n
表示换行;而在Unix/Linux和MacOS中,通常只使用换行符 \n
来表示换行。在Java中,使用 \r
和 \n
的组合可以兼容处理这两种情况。
4、byte字节值和char字符互相转换
要将数字97转换为字符'a',可以使用类型转换。在Java中,可以使用类型转换操作符(char)
来将一个整数转换为对应的字符。
以下是将数字97转换为字符'a'的示例代码:
System.out.println((char) 97); // 输出结果为:a
System.out.println((int) 'a'); //输出结果为:97
通过在要转换的数字前面加上(char)
,将其强制转换为char
类型。这样,System.out.println()
方法就会打印出相应的字符。
需要注意的是,根据ASCII编码表,97对应的字符是小写字母'a'。因此,上述代码将输出字符'a'而不是字符'A'。
请注意,在将整数转换为字符时,确保要转换的整数值在字符范围内,否则可能会得到意外的结果。
六、八种基本数据类型各自所占的空间大小
基本数据类型 | 占用空间大小(单位:字节) | 取值范围 |
byte | 1 | -128~127 |
short | 2 | |
int | 4 | |
long | 8 | 默认为0L |
float | 4 | |
double | 8 | |
boolean | 1 | |
char | 2 |
注意:
1、short和char所表示的种类总数是一样的,只不过char可以表示更大的正整数,因为char没有负数
2、八种数据类型的默认值一切向0看齐
关于java中的数字类型,数字都是有正负之分的,所以在数字的二进制当中有一个二进制位被称为“符号位”。并且这个“符号位”在所有二进制位的最左边,0表示正数,1表示负数
计算机在任何情况下只能识别二进制数据,现代的计算机底层采用交流电的方式,接通和断开就两种状态,计算机只识别0和1,其他不认识
二进制、八进制、十进制、十六进制的前缀和后缀_二进制前缀_sabrikii的博客-CSDN博客
搞搞字节,byte的小知识_byte如何表示正负_阿伦Java的博客-CSDN博客
七、基本数据类型之间的互相转换
转换规则:
- 八种基本数据类型中,除boolean类型外,其余七中基本类型都可以互相转换
- 小容量向大容量转换,称为自动类型转换,容量从小到大排序:byte < short(char) < int < long < float < double
- 任何浮点型逐渐不管占多少字节,都比整数型容量大
- char和short可表示的种类数量相同,但是char可以取更大的正整数
- 只要按照上述的顺序,小容量的数据类型向大容量的数据类型转换,都为自动类型转换
- 大容量转换成小容量,称为强制类型转换,需要加强制类型转换符,程序才能编译通过,但是在运行阶段可能会损失精度,所以谨慎使用
- 当整数字面值没有超出byte ,short,char的取值范围,可以直接赋值给byte,short,char类型的变量 char a = 97;(转换为ASCII码 输出a)ASCII编码对照表
- byte,short,char混合运算的时候,各自先转换成int类型,再做运算
int i = 10;
byte a = (byte) (i/3);
System.out.println(a); //3
// 贾 unicode编码为\u8d3e 8d3e 为十六进制,转为十进制为;36158,字符a转为十进制ASCII编码为97,所以为36255
System.out.println('a' + '贾'); //36255
System.out.println('a' + "贾"); //a贾
- 多种数据做混合运算,先转换成容量最大的那种类型再做运算,最终结果也为容量最大的那种类型
注意:
byte a = 3; 可以编译通过,3没有超出byte类型的取值范围
int i = 10; byte a = i/3; 编译报错,编译器只检查语法,不计算 i/3
//BasicDataTypeTransition.java
public class BasicDataTypeTransition {
public static void main(String[] args) {
//出现错误,超出byte的取值范围
//byte a = 1000;
//正确,因为20没有超出byte类型的取值范围
byte a1 = 20;
//在同一作用域中变量不能重名
//short a1 = 1000;
//正确,因为1000没有超出short类型的取值范围
short b = 1000;
//正确,因为1000没有超出int类型的取值范围,且默认是按int类型存储
int c = 1000;
//正确,1000没有超出long类型的取值范围,小容量--》大容量,自动类型转换
long d = 1000;
//错误,大容量--》小容量,需强制类型转换,可能精度损失
//int e = d;
//正确,因为java中的运算会转换成容量最大的类型
//10和3 默认是int,运算后的最大类型也为int
//int类型和int类型做运算,结果也为int类型,结果取整
int e = 10/3;
System.out.println(e); //结果为3
double e1 = 10/3; //算出结果为3,由于是double类型,转为3.0
System.out.println(e1); //结果为3.0
e1 = 10.0/3; //double类型运算
System.out.println(e1); //结果为3.3333333333333335
//正确,自动类型转换
long g = 10;
//错误,g/3为long类型,需强制类型转换
//int h = g/3;
//正确,强制类型转换
int h = (int) g/3;
//正确,本身就是long类型,不存在转换
long h1 = g/3;
//错误,出现精度损失,主要考虑优先级的问题
//将g转出成int,然后又将int类型的g转换成byte类型,最后byte类型的g和3运算
//它的结果是int类型,将int类型赋值个byte类型需要强制类型转换
//byte h2 = (byte)(int)g/3;
//正确,整体做转换
byte h2 = (byte) (int)(g/3);
//错误,有优先级,应将g/3括起来
//byte h3 = (byte) g/3;
//正确,因为运算结果没有超出byte的范围
byte h4 = (byte) (g/3);
//正确,因为运算结果没有超出short的范围
short h5 = (short) (g/3);
short i = 10;
byte j = 5;
//错误,因为i+j的结果为int类型,需强制转换
//short k = i+j;
//正确,进行强制类型转换
short k = (short) (i+j);
//正确
int k1 = i + j;
char f = 'a';
System.out.println(f); //输出结果为a
System.out.println((byte)f); //输出结果为97,转换为a的ascii码值
int m = f +100;
System.out.println(m); //输出结果为197,因为取得了a的ascii码值,在和100相加运算
}
}
第七章、运算符
运算符都是对变量名中保存的值进行运算,而非对变量进行运算
一、运算符分类
-
算术运算符:包括加(+)、减(-)、乘(*)、除(/)、取模(%)等,用于进行基本的算术运算。
-
关系运算符:包括等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)等,用于比较两个值的大小或关系。
-
逻辑运算符:包括逻辑与(&&)、逻辑或(||)、逻辑非(!)等,用于进行逻辑运算。
-
位运算符:包括按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、右移(>>)、无符号右移(>>>)等,用于操作二进制数的每一位。
-
赋值运算符:包括简单赋值(=)、加等于(+=)、减等于(-=)、乘等于(*=)、除等于(/=)、模等于(%=)、与等于(&=)、或等于(|=)、异或等于(^=)、左移等于(<<=)、右移等于(>>=)和无符号右移等于(>>>=)等,用于给变量赋值。
-
条件运算符:也叫三元运算符,形式为:条件 ? 表达式1 : 表达式2,用于根据条件的真假来选择不同的表达式。
-
instanceof 运算符:用于判断一个对象是否是某个类的实例。
-
优先级运算符:用于改变运算符的优先级,如小括号()。
注意:以上列出的运算符只是Java中的部分运算符,还有一些其他的运算符。
二、算符运算符
注意:一个表达式当中有多个运算符,运算符有优先级,不确定的加小括号,优先级得到提升,没有必要去专门记忆运算符的优先级
+:求和 -:相减 *:相乘 /:相除 %:求余数 都是二元运算符
public class ArithmeticOperator {
public static void main(String[] args){
int i = 10;
int j = 3;
System.out.println(i + j); //13
System.out.println(i - j); //7
System.out.println(i * j); //30
System.out.println(i / j); //3
System.out.println(i % j); //1 取余
}
}
++:自增1 - -:自减1 都是一元运算符
JavaScript中的自增和自减_js 自增_前端小草籽的博客-CSDN博客
- ++运算符可以出现在变量前,也可以出现在变量后,无论出现在变量前还是变量后,只要++运算符结束,该变量中的值一定会自加1
int a = 100;
System.out.println(a++); //100
System.out.println(a); //101
System.out.println("=======================");
int b = 100;
System.out.println(++b); //101
System.out.println(b); //101
System.out.println(--b); //100
System.out.println(b--); //100
System.out.println(++b); //100
System.out.println(b); //100
/**
* 都只是赋值一次
* i++ 是指先对外赋值一次,在自身加1
* ++i 是指先自身加1,在对外赋值一次
*/
int i = 10;
int a = i++; //先将i = 10赋值给a,然后i自己在加1,成了11,11并不会赋值给a
System.out.println(i); //11
System.out.println(a); //10
int x = 10;
int b = ++x; //先x自己加1,成了11,将11赋值给b,此时x和b都是11
System.out.println(x); //11
System.out.println(b); //11
println底层源码
源码在src中
三、关系运算符
> :大于
>=:大于等于
<:小于
<=:小于等于
==:等于
!=:不等于
二元运算符
关系运算符的运算结果一定是boolean类型:true/false
关系运算符的运算原理:
int a = 10;
int b = 10;
a和b都占内存空间,也有内存地址(栈)
a>b 比较的是a中保存的10这个值和b中保存的10这个值之间的大小比较,和内存无关
a==b 也是如此
同一个字面值的内存地址是一样的
int a = 10;
int b = 10;
String hexString = Integer.toHexString(b);
int hashCode = Integer.valueOf(a).hashCode();
System.out.println("b的内存地址为:" + hexString); //b的内存地址为:a
System.out.println("a的哈希值为:" + hashCode); //a的哈希值为:10
int a = 10;
int b = 10;
System.out.println(a > b); //false
System.out.println(a >= b); //true
System.out.println(a < b); //false
System.out.println(a <= b); //true
System.out.println(a == b); //true
System.out.println(a != b); //false
四、逻辑运算符
&:逻辑与 (两边的算子都是true,结果才是true)
|:逻辑或 (两边的算子只要有一个是true,结果就是true)
!:逻辑非 (取反,!false就是true,!true就是false,这是一个单目运算符)
^:逻辑异或 (两边的算子只要不一样,结果就是true)
System.out.println(true ^ false); //true
System.out.println(true ^ true); //false
&&:短路与
- 两个值中只要有一个值为false,就返回false,只有两个值都为true时,才会返回true
- 如果第一个值为false,则不会检查第二个值
- 非布尔值时:如果两个都为true,则返回第二个值,如果两个值中有false,则返回靠前的false的值
- 第一个表达式执行结果为false,会发生短路与
||:锻炼或
- 两个值中只要有一个true,就返回true,只有两个值都为false,才会返回false
- 短路的或,如果第一个值为true,则不会检查第二个值
- 非布尔值时:如果两个都为false ,则返回第二个值,如果两个值中有true,则返回靠前的true的值
- 第一个表达式执行结果为true,会发生短路或
int x = 10;
int y = 8;
System.out.println(x < y & ++x < y); //false
System.out.println(x); //11
int x1 = 10;
int y1 = 8;
System.out.println(x1 < y1 && ++x1 < y1); //false
System.out.println(x1); //10
/*
* 短路与:
* x < y 结果是false,整个表达式结果已经确定为false
* 所有后面的表达式没有在执行,这种现象被称为短路现象
* 短路与才会有短路现象,逻辑与是不会存在短路现象的
*
* 从某个角度看,短路与更智能,由于后面的表达式可能不执行
* 所以执行效率更高,这种方式在实践的开发中使用更多,短路与比逻辑与使用的多,短路与更常用
* 但是,在某些特殊的业务逻辑中,要求运算符两边的算子必须全部执行,此时必须使用逻辑与,不能使用短路与,使用短路与可能导致右边的表达式不执行
*
* */
二元运算符
1、逻辑运算符要求两边的算子都是boolean类型,并且逻辑运算符最终的运算结果也是一个boolean类型
2、短路与和逻辑与最终的运算结果是相同的,只不过短路与存在短路现象
3、短路或和逻辑或最终的运算结果是相同的,只不过短路或存在短路现象
五、位运算符
二进制位的运算
比如:如何将2快速变成8
2的二进制位为:10
8的二进制位为:1000
只需要将2的二进制位的1向左移动2位即可
右移运算符:原数据 >> n
左移运算符:原数据 << n // n为移动位数
右移运算:即原数据除以 (2 * n)
左移运算:即原数据乘以 (2 * n)
package com.javase.collection;
/**
* 位运算符 >>
*/
public class BinaryTest {
public static void main(String[] args) {
/**
* >> 1 二进制右移一位
* 10的二进制位:00001010 【10】
* 10的二进制右移一位是:00000101 【5】
*/
System.out.println(10 >> 1); //5 右移一位就是除以2
System.out.println(20 >> 2); //5
/**
* << 1 二进制左移一位
* 10的二进制位:00001010 【10】
* 10的二进制左移一位是:00010100 【20】
*/
System.out.println(10 << 1); //20 左移一位就是乘2
System.out.println(10 << 2); //40 左移二位就是乘4
}
}
六、赋值运算符
每次赋值后,原变量的内存会变化,若是不再引用原来的内存地址,则原来的内存会被销毁
赋值类运算符包括两种:
- 基本的赋值运算符 =
- 扩展的赋值运算符 += -= *= /= %=
赋值类的运算符优先级:先执行等号右边的表达式,将执行结果赋值给左边的变量
基本使用:
//基本的赋值运算符
int a = 10;
System.out.println(a); //10
a = a + 5;
System.out.println(a); //15
//扩展的赋值运算符 (+= 运算符可以翻译为“累加/追加”)
a+=5; //运算结果等同于 a=a+5
System.out.println(a); //20
a-=5; //运算结果等同于 a=a-5
System.out.println(a); //15
a*=2; //运算结果等同于a = a* 2;
System.out.println(a); //30
a/=4; //运算结果等同于 a = a / 4;
System.out.println((double) a); //7.0
a%=2; //运算结果等同于 a = a % 2;
System.out.println(a); //1
int b = 30;
System.out.println((double)b/4); //7.5
特殊情况
实际中x+=5和x=x+5不一样,真正等同于(byte)(x+5)
扩展类的赋值运算符会自动进行强制类型转换,以初始声明的类型为准;
扩展类的赋值运算符不改变运算结果类型,假设最初这个变量的类型是byte类型,无论怎么进行追加或追减,最终该变量的数据类型还是byte类型
- byte i = 10;
- i+=5; 等同于 i = (byte) (i+5);
- int k = 10;
- k+=5; 等同于 k = (int) (k+5);
long m = 10L; int n = 10; n+=m; //等同于 n = (int)(n+m);
System.out.println("============================");
//实际中x+=5和x=x+5不一样,真正等同于(byte) (x+5)
byte c = 10;
//编译器只检查语法,不进行运算
//编译报错,因为会编译器会将c转换为int类型和int类型的5,最终结果是int类型,而声明的c是byte类型
//c = c+5;
//纠正错误
c = (byte) (c+5);
System.out.println(c); //15
byte x = 10;
x+=5; //等同于x=(byte)(x+5);
System.out.println(x); //15
System.out.println("==========================");
byte y = 0;
y+=128; //等同于x=(byte)(y+128);
System.out.println(y); //-128 损失精度
y+=10000; //等同于x=(byte)(y+10000);
System.out.println(y); //-112 损失精度
long m = 10L;
int n = 10;
n+=m; //等同于 n = (int)(n+m);
System.out.println(n); //20
七、字符串连接运算符
关于java中的“+”运算符
- 1、“+”运算符在java中有两个作用
- 加法运算,求和
- 字符串的连接运算
- 2、当“+”运算符两边的数据都是数字的话,一定是进行加法运算
- 3、当“+”运算符两边的数据只要有一个数据是字符串或字符,一定会进行字符串连接运算,并且,连接运算之后的结果返回一个字符串类型
- 数字 + 数字 ---> 数字(求和)
- 数字 + 字符串 ---> 字符串 (字符串连接)
- 4、在一个表达式中可以出现多个加号,在没有添加小括号的前提下,遵循自左向右的顺序依次运算
public class Operator01 {
public static void main(String[] args) {
System.out.println(10+30+"30"); //4030 字符串拼接
System.out.println(10+(30+"30")); //103030 字符串拼接
int a = 30;
int b = 50;
//要求以动态方式输出
System.out.println("10 + 20 =" + a + b); //10 + 20 =1020 加法执行顺序,从左向右
System.out.println("10 + 20 =" + (a + b)); //10 + 20 =30 加法执行顺序,从左向右,优先执行括号中的
System.out.println(a + " + 20 =" + (a + b));
//最终的结果
System.out.println(a + " + " + b + " = " + (a + b));
//引入引用类型:
//String是SUN在javaSE中提供的字符串类型
//String.class字节码文件
//int是基本数据类型,i是变量名,10是int类型的字面值
//int i = 10;
//String是引用数据类型,s是变量名,“abc是String类型的字面值”
String s = "abc";
//编译错误,类型不兼容
//String ss = 10;
//定义一个String类型的变量,起名username,赋值“zhangsan”
String username = "zhangsan";
System.out.println("登录成功,欢迎" + username + "回来");
username = "lisi";
System.out.println("登录成功,欢迎" + username + "回来");
}
}
八、三元运算符/三目运算符/条件运算符
语法: 布尔表达式?表达式1:表达式2
执行原理:
当布尔表达式的结果true的时候,选择表达式1作为整个表达式的执行结果,并返回
当布尔表达式的结果false的时候,选择表达式2作为整个表达式的执行结果,并返回
public class Operator02 {
public static void main(String[] args) {
boolean sex = false;
//编译报错:因为它不是一个完整的java语句
//sex? '男':'女';
sex = true;
char a = (sex? '男':'女');
System.out.println(a);
//编译报错:结果可能是String,也可能是char,但是前边不能用char来接受数据
// 类型不兼容
//char b = (sex? "男":'女');
String b = (sex? "男":"女");
System.out.println(b);
System.out.println(sex? "男":'女');
}
}
第八章、控制语句
一、概述
java控制语句分为7种:
- 控制选择结构语句:
- if,if else
- switch
- 控制循环结构语句:
- for
- while
- do while
- 改变控制语句顺序
- break
- continue
所有的控制语句都是可以嵌套使用的,只要合理嵌套就行(嵌套使用的时候,代码格式要保证完美,该缩进的时候必须缩进,大部分情况下使用大括号包围的需要缩进)
二、if,if else
关于java中的if语句,属于选择结构,if语句又称为分支语句/条件控制语句
if语句中只要有一个分支执行,整个if语句全部结束(if语句最多只有一个分支会执行)
if语句的分支中只有一条java语句的话,大括号可以省略不写
if(boolean表达式){
一条java语句;
}
等同于
if(boolean表达式)一条java语句;
if语句的语法结构:四种编写方式
第一种:
if(boolean表达式){
java语句;
java语句;
java语句;
}
第二种:
if(boolean表达式){
java语句; (可多条)
}else{
java语句; (可多条)
}
第三种:
if(boolean表达式){
java语句; (可多条)
}else if (boolean表达式) { (可多条else if)
java语句; (可多条)
}
第四种:
if(boolean表达式){
java语句; (可多条)
}else if (boolean表达式) { (可多条else if)
java语句; (可多条)
}else{
java语句; (可多条)
}
第二种和第四种编写方式带有else,这两种方式可以保证有一个分支会执行
import java.util.Scanner;
public class IfTest {
public static void main(String[] args) {
double score = 90;
String text = "该考生的考试成绩等级是:E等级"; //给定一个默认值
if (score < 0 || score > 100){
text = "对不起,您提供的考生成绩不合法!";
}else if (score >= 90) {
text = "该考生的考试成绩等级是:A等级";
}else if (score >= 80) {
text = "该考生的考试成绩等级是:B等级";
}else if (score >= 70) {
text = "该考生的考试成绩等级是:C等级";
}else if (score >= 60) {
text = "该考生的考试成绩等级是:D等级";
}
System.out.println(text);
java.util.Scanner scanner = new java.util.Scanner(System.in);
System.out.print("请输入您的年龄:");
int age = scanner.nextInt();
String text_age = "老年";
if (age < 0 || age > 150){
text_age = "您输入的年龄不合法!";
} else if (age <= 5) {
text_age = "幼儿";
} else if (age <= 10 ) {
text_age = "少年";
} else if (age <= 18) {
text_age = "青少年";
} else if (age <= 35) {
text_age = "青年";
} else if (age <= 55) {
text_age = "中年";
}
if (age < 0 || age > 150){
System.out.println(text_age);
}else {
System.out.println("您处于生命周期的" + text_age + "阶段");
}
//天气状况:1表示下雨,0表示晴天
//温度直接输入数字
//性别:1表示男,0表示女
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入天气情况(1表示下雨,0表示晴天):");
int weather = input.nextInt();
if(weather != 1 && weather != 0){
System.out.println("您输入的天气情况有误!");
}else {
System.out.print("请输入您的性别(1表示男,0表示女):");
int sex = input.nextInt();
if (sex != 1 && sex != 0) {
System.out.println("您输入的性别不合法!");
} else {
if (weather == 1) {
if (sex == 1) {
System.out.println("请带一把大黑伞!");
} else {
System.out.println("请带一把小花伞!");
}
}else {
System.out.print("请输入当前温度:");
int temperature = input.nextInt();
if(temperature >= 30){
if (sex == 1) {
System.out.println("请带墨镜!");
} else {
System.out.println("请擦防晒霜!");
}
}else {
System.out.println("天气适宜,无需防护!");
}
}
}
}
if(true) System.out.println(123); else System.out.println(333);
if(true)
System.out.println(111); //默认只有一条语句,在if分支中
System.out.println(222); //这是在if语句之外
//else //此处else有问题
System.out.println(555);
}
}
三、switch
语法结构:分别匹配case中的条件,若都无法匹配,则执行default中的语句
switch(int或String类型的字面值或变量 ) {
case int或String类型的字面值或变量 : (可多条)
java语句; (可多条)
break
default :
java语句; (可多条)
}
switch语句的执行原理:
- switch后面小括号内的数据和case后面的数据进行一 一匹配,匹配成功的分支执行,按照自上而下的顺序依次匹配。
- 匹配成功的分支执行,分支中最后有break语句的话,整个switch语句终止
- 匹配成功的分支执行,分支中最后没有break语句的话,直接进入下一个分支执行(不进行匹配),这种现象被称为case穿透现象【提供break语句可以避免穿透】
- 所有分支如果都没有匹配成功的话,当有default语句的话,会执行default分支中的语句
- switch和case后面的只能是int或者String类型的数据,不能是其他类型
- byte,short,char也可以直接写到switch和case后面,因为他们可以进行自动类型转换,byte,short,char可以自动转换成int类型
- JDK 6的,switch和case后面只能是int类型
- JDK 7之后包括JDK7版本在内,引入新特性,switch关键字和case关键字后面可以探测int或String类型的数据
- case可以合并
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入数字:");
int i = input.nextInt();
switch (i){
case 1: case 2: case 3:
System.out.println("Test Code!");
}
基本使用:
import java.util.Scanner;
public class SwitchTest {
public static void main(String[] args) {
//编译报错:类型不兼容,精度损失
//long x = 100L;
//switch (x){}
//修正:加强制类型转换符
long x = 100L;
switch ((int) x){}
byte a = 10;
switch (a){}
short b = 10;
switch (b){}
char c = 'a';
switch (c){}
char cc = 97;
switch (cc){}
//编译报错:不兼容的类型: boolean无法转换为int
//switch (true){}
String username = "zhangsan";
switch (username){}
//用户输入:
//1:星期一
//2;星期二
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入数字(1~7):");
int week = input.nextInt();
String text ;
switch (week){
case 1:
text = "星期一";
break;
case 2:
text = "星期二";
break;
case 3:
text = "星期三";
break;
case 4:
text = "星期四";
break;
case 5:
text = "星期五";
break;
case 6:
text = "星期六";
break;
case 7:
text = "星期日";
break;
default:
text = "您输入的数字非法!";
}
System.out.println(text);
System.out.println("==============================");
//case穿透
switch (week){
case 1:
System.out.println("星期一");
// break
case 2:
System.out.println("星期二");
// break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("您输入的数字非法!");
}
System.out.println("==============================");
//case穿透 从头穿到尾
switch (week){
case 1:
System.out.println("星期一");
// break
case 2:
System.out.println("星期二");
// break;
case 3:
System.out.println("星期三");
// break;
case 4:
System.out.println("星期四");
// break;
case 5:
System.out.println("星期五");
// break;
case 6:
System.out.println("星期六");
// break;
case 7:
System.out.println("星期日");
// break;
default:
System.out.println("您输入的数字非法!");
}
System.out.println("==============================");
//case合并
switch (week){
case 1: case 0: System.out.println("星期一"); break;
case 2: System.out.println("星期二"); break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("您输入的数字非法!");
}
System.out.println("==============================");
//switch可以探测字符串的文本
System.out.print("请输入字符串:");
String str = input.next();
switch (str){
case "星期一":
System.out.println(1);
break;
}
char m = 'A';
char m = 65;
char m = 66;
char m = 'C';
switch (m){
case 'A':
System.out.println("高级");
break;
case 'B':
System.out.println("中级");
break;
case 67:
System.out.println("初级");
break;
default:
System.out.println("出错了");
}
实现计算器当中的 + - * / %
System.out.println("欢迎使用简单计算器系统!");
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入第一个数字:");
int num1 = input.nextInt();
System.out.print("请输入运算符(+ - * / %):");
String operator = input.next();
System.out.print("请输入第二个数字:");
int num2 = input.nextInt();
int result = 0;
switch (operator){
case "+" :
result = num1 + num2;
break;
case "-" :
result = num1 - num2;
break;
case "*" :
result = num1 * num2;
break;
case "/" :
result = num1 / num2;
break;
case "%" :
result = num1 % num2;
break;
default:
System.out.println("您输入的运算符不合法");
}
System.out.println("运算结果为:" + num1 + operator + num2 + '=' + result);
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入学生成绩:");
double score = input.nextDouble();
int grade = (int) (score / 10);
String result = "A";
switch (grade){
case 9: case 10:
break;
case 8:
result = "B";
break;
case 7:
result = "C";
break;
case 6:
result = "D";
break;
default:
result = "E";
}
System.out.println(result);
}
}
四、for
用于一些需要反复的,重复的执行的代码
for语句也是循环控制语句,我们也称它为for循环。大部分循环都会有一个计数器用以控制循环执行的次数, 计数器的三个关键操作是初始化、检测和更新。for语句 就将这三步操作明确为了语法的一部分。
for(初始化表达式 ; 条件表达式 ; 更新表达式){
语句...
}
初始化表达式,条件表达式,更新表达式都不是必须得(但是两个分号是必须的)
初始化表达式最先执行,并且在整个for循环中只执行一次
条件表达式的返回值必须是true/false,不能是其他值
for循环的执行过程:
死循环:条件表达式为空时,默认为true
for(;;){
System.out.println(123);
}
public class ForTest {
public static void main(String[] args) {
for(int i = 1; i <= 10; i++){
System.out.println(i);
}
//死循环
// for(;;){
// System.out.println(123);
// }
int m = 0;
for (;m <= 10;m++){
System.out.println(m);
}
System.out.println(m);
for (int i = 10;i >= 0;i--){ //10 9 8 7 6 5 4 3 2 1
System.out.println(i);
}
System.out.println("====================");
for (int i = 10;i >= 0;){ //10 9 8 7 6 5 4 3 2 1
System.out.println("计数器==》" + i);
i--;
}
System.out.println("====================");
for (int i = 10;i >= 0;){ //9 8 7 6 5 4 3 2 1 -1
i--;
System.out.println("计数器==》" + i);
}
System.out.println("====================");
for(int i = 1;i < 10;){ //1 2 3 4 5 6 7 8 9
System.out.println("计数器==》" + i);
i++;
}
System.out.println("====================");
for(int i = 1;i < 10;){ // 2 3 4 5 6 7 8 9 10
i++;
System.out.println("计数器==》" + i);
}
// 此两种写法一样,因为 i++ 和 ++i 并没有赋值使用,只是用于计数
for (int i = 1 ; i < 10 ; i++){
System.out.println(i);
}
System.out.println("===========================");
for (int i = 1 ; i < 10 ; ++i){
System.out.println(i);
}
// 循环语句和条件判断语句嵌套使用(for循环和if语句嵌套使用)
// 找出1~100所有的奇数
for (int i = 1 ; i < 100 ; i+=2){
System.out.println(i);
}
System.out.println("=========================");
for (int i = 1 ; i <= 100 ; i++){
if (i % 2 != 0){
System.out.println("奇数:" + i);
}
}
//1~100内所有奇数的和
int result = 0; //累加器
for (int i = 1 ; i < 100 ; i +=2){
result += i;
}
System.out.println("1~100内所有奇数的和为" + result);
//归零
result = 0;
for (int i = 1 ; i <= 100 ; i++){
if (i % 2 != 0){
result += i;
}
}
System.out.println("1~100内所有奇数的和为" + result);
for (int i = 1 ; i < 10 ; i++){
for (int j = 0 ; j < 3 ; j++ ){
System.out.println("j==>" + j);
}
}
// 贾 unicode编码为\u8d3e 8d3e 为十六进制,转为十进制为;36158,字符a转为十进制ASCII编码为97,所以为36255
System.out.println('a' + '贾'); //36255
System.out.println('a' + "贾"); //a贾
for (int i = 1 ; i <= 5 ; i++){
System.out.println("Begin!");
for(int j = 1 ; j <= 5 ; j++){
System.out.println(i * j);
}
System.out.println("Over!");
}
//九九乘法表
for (int i = 1 ; i <= 9 ; i++){
for (int j = 1 ; j <= i ; j++){
if (i == 3 && j == 2){
System.out.print(i + "*" + j + "=" + i * j + " "); //3个空格
}else {
if (i == 4 && j == 2){
System.out.print(i + "*" + j + "=" + i * j + " "); //3个空格
}else {
System.out.print(i + "*" + j + "=" + i * j + " "); //2个空格
}
}
}
System.out.println();
}
System.out.println("==================================================================================");
for (int i = 1 ; i <= 9 ; i++){
for (int j = 1 ; j <= i ; j++){
if (i == 3 && j == 2){
System.out.print(j + "*" + i + "=" + i * j + " "); //3个空格
}else {
if (i == 4 && j == 2){
System.out.print(j + "*" + i + "=" + i * j + " "); //3个空格
}else {
System.out.print(j + "*" + i + "=" + i * j + " "); //2个空格
}
}
}
System.out.println();
}
}
}
1、增加版的for循环
在Java 5中引入了增强的for循环语法,也称为for-each循环,用于遍历数组或集合中的元素,提供了一种更简洁和直观的方式来进行循环迭代操作。
增强的for循环语法的基本形式如下:
for (elementType element : iterable) {
// 循环体
}
其中,elementType
是集合或数组中元素的类型,element
是当前迭代的元素变量,iterable
是要遍历的数组或集合。
使用增强的for循环,可以逐个访问集合或数组中的元素,而无需使用索引或迭代器。循环会自动在每次迭代中将集合或数组中的下一个元素赋值给 element
变量,并执行循环体中的代码。
以下是使用增强的for循环遍历数组和集合的示例:
- 遍历数组:
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
System.out.print(number + " ");
}
System.out.println();
System.out.println("==================");
for (int i = 0; i < numbers.length; i++) {
System.out.print(numbers[i] + " ");
}
增强的for循环适用于需要遍历整个数组或集合,并且不需要访问索引或对元素进行修改的情况。如果需要在循环过程中修改数组或集合的元素,或者需要访问元素的索引位置,可以选择传统的for循环语法。
五、while
while循环的结构语法:
定义计数器; (可选)
while (boolean表达式){
循环体;
改变计数器; (可选)
}
while循环的循环次数:
0~n次,while循环的循环体可能一次都不执行
public class WhileTest {
public static void main(String[] args) {
// int i = 1;
// while (i <= 5){
// System.out.println(i);
// i++;
// }
int j = 10 ;
while (j > 0){
System.out.println(--j);
System.out.println("j==>" + j);
}
System.out.println("===========");
System.out.println(j);
}
}
六、do...while
do...while循环的语法结构
do{
循环体;
} while(boolean表达式);
注意事项:
- do...while循环的执行次数:
- do...while循环的循环体执行次数为 1~n次,至少1次
- do...while循环语句最终有一个分号
int k = 10;
do{
System.out.println(k);
}while (k > 100);
System.out.println("====================");
while (k > 100){
System.out.println(k);
}
七、break
注意事项:
- break是java语言中的关键字,被翻译为“中断/折断”
- break + ‘;’可以成为一个单独的完整的java语句:break;
- break语句使用在switch语句中,用来终止switch的语句执行
- break语句使用在所有循环语句中,用于终止循环,当程序循环到某个条件的时候,后续的循环没必要执行了,在执行也是耗费资源,所以可以终止循环,提高程序的执行效率
- 默认情况下,break语句终止的是离它最近的循环语句。当然也可以指定终止某个循环,需要给循环起名,采用这种语法:break 循环名称
break只有在最外层for中时,待会终止所有循环及相关(for else组合),否则只能终止最近层for循环,其他继续,break正下面不直接写代码,没有意义
for (int i = 0 ; i <= 10 ; ){
i++;
if (i == 5){
break;
}
System.out.println("i==>" + i);
}
System.out.println("Hello World!");
//终止内层for循环
for (int j = 1 ; j <= 5 ; j++){
for (int i = 0 ; i <= 10 ; ){
i++;
if (i == 5){
//当前的break语句终止的是内层的for循环,因为这个for循环离它最近,所以不会影响到外层for循环
break;
}
System.out.println("i==>" + i);
}
System.out.println("j==>" + j);
}
System.out.println("============================================");
//可终止外层for循环,这种语法使用较少
A:for (int j = 1 ; j <= 5 ; j++){
B:for (int i = 0 ; i <= 10 ; ){
i++;
if (i == 5){
//当前的break语句终止的是内层的for循环,因为这个for循环离它最近,所以不会影响到外层for循环
break A;
}
System.out.println("i==>" + i);
}
System.out.println("j==>" + j);
}
System.out.println("Hello World!");
八、continue
注意事项:
- continue表示:继续/go on/下一个
- continue关键字加‘;’构成一个完整的java语句,主要出现在循环语句中用来控制循环的执行
- 默认情况下,continue语句跳出的是离它最近的循环语句。当然也可以指定跳出某个循环,需要给循环起名,采用这种语法:continue 循环名称
break和continue的区别:
- break表示循环结束
- continue表示跳过本次循环(continue后面的代码均不执行),直接进入下一次循环继续执行
for (int i = 0 ; i < 10 ; i++){
if (i == 5){
break; //直接结束循环
}
System.out.println("i ==>" + i);
}
System.out.println("Hello World!");
System.out.println("===========================");
for (int i = 0 ; i < 10 ; i++){
if (i == 5){
continue; //会跳过continue后面的所有语句,当前本次循环终止,直接进入下一次询价“继续”执行
}
System.out.println("i ==>" + i);
}
System.out.println("Hello World!");
System.out.println("===========================");
A:for (int j = 1 ; j <= 5 ; j++){
B:for (int i = 0 ; i <= 10 ; ){
i++;
if (i == 5){
//当前的break语句终止的是内层的for循环,因为这个for循环离它最近,所以不会影响到外层for循环
continue A;
}
System.out.println("i==>" + i);
}
System.out.println("j==>" + j);
}
System.out.println("Hello World!");
案例1:求1~100以内的素数,每8位换行输出
在程序中添加标记思想,统计计数思想,添加break或continue提高代码效率
// //使用for循环找出1~100内的素数
// int i, j;
// boolean isPrime;
//
// // 找出1~100内的素数
// for (i = 2; i <= 100; i++) {
// isPrime = true;
// for (j = 2; j <= i - 1 ; j++) {
// if (i % j == 0) {
// isPrime = false;
// break;
// }
// }
// if (isPrime) {
// System.out.print(i + " ");
// }
// }
//编写for循环找出1~100中所有的素数,每8个换行输出
int i, j;
boolean isPrime;
int sum = 0;
// 找出1~100内的素数
for (i = 2; i <= 100; i++) {
isPrime = true;
for (j = 2; j <= i - 1 ; j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
System.out.print(i + " ");
sum += 1; //计数器
if (sum % 8 == 0){
System.out.println();
}
}
}
第九章、方法
方法在参数传递和返回值时,永远传递和返回的都是变量中的值,而非变量的内存地址
变量由四部分组成
- 变量类型
- 变量名
- 变量值
- 变量自身内存地址
一、概述
- 方法对应的英语单词:Method
- 方法在C语言中叫做函数:function
方法是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用(invoke调用方法),向函数传递参数之后,函数可以返回一定的值。可以重复调用,实现代码复用
通常情况下,java代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
在 Java 中,方法不是数据类型,而是行为或操作。方法是一个包含代码块的结构,用于执行某些任务或操作。方法可以有一个或多个参数,它们可以是引用数据类型或基本数据类型。
二、main(入口)方法详解
public static void main(String[] args){ }
这是Java语言中的一个程序入口点(entry point),被称为main方法。它是程序的起点,在程序执行时,JVM会首先寻找main方法并调用它。在Java中,所有的应用程序都需要一个main方法。
main方法的写法是固定的,它的声明必须包含public、static、void和main这四个关键字,同时要带一个String类型的参数数组args。其中public表示该方法是公共的,可以被其他类访问;static表示该方法是静态的,可以通过类名直接调用;void表示该方法没有返回值;main是方法名,是固定的。
String类型的数组是一种数组类型,它的元素类型是字符串(String)。它可以存储一组字符串对象,并且可以动态地扩展或缩小数组的大小。
args参数是传递给main方法的命令行参数,它是一个String类型的数组,可以在执行Java程序时通过命令行传递参数给程序。
main方法的参数args是一个字符串数组(String[]),是因为它用于接收在命令行中传递给Java应用程序的参数。当我们在命令行中执行Java应用程序时,可以在命令行中输入参数,这些参数可以被Java应用程序读取并使用。
例如,我们可以在命令行中执行以下命令来运行一个Java程序并传递两个参数:
java MyProgram arg1 arg2
在这个例子中,MyProgram是Java程序的类名,arg1和arg2是两个传递给程序的参数。当Java程序启动时,JVM会将这些参数作为字符串数组传递给main方法的args参数,Java程序可以通过args参数读取并使用这些参数。
因此,main方法的参数args是一个字符串数组,它用于接收命令行参数,让Java程序可以根据这些参数做出相应的处理。
三、方法定义
1、原则
- 方法定义在类体当中,在一个类中可以定义多个方法,方法编写的位置没有先后顺序,可以随意
- 方法体(主方法,入口方法)中不能再定义方法
- 方法体有java语句构成,方法体中的代码遵守自上而下的顺序依次执行
2、语法结构
[ 修饰符列表 ] 返回值类型 方法名 (形式参数列表) {
方法体
}
3、修饰符列表:可选,非必须
- public static
- 有static修饰符的,调用: 类名.方法名(实际参数列表)
- 没有static修饰符的,调用:引用.方法名(实际参数列表)
4、返回值类型
- 一个方法是可以完成某个特定功能的,这个功能结束后大多数都是需要返回最终的执行结果的,执行结果可能是一个具体存在的数据。而这个具体存在的数据就是返回值
- 返回值是一具体存在的数据,数据都是有类型的,此处需要指定的是返回值的具体类型
- java中的任意一种数据类型都可以,包括基本数据类型和引用数据类型
- 也可能这个方法执行结束后不返回任何数据,java中规定,当一个方法执行结束后不返回任何数据的话,返回值类型位置必须编写:void关键字
- 返回值是void的时候,在方法体中不能编写“return 值;”这样的语句,但是能编写“return;(相当于 return null;)”这样的语句
- 返回值类型可以是:byte short int long float double boolean char void ...
- 返回值类型若不是void,表示这个方法执行结束之后必须返回一个具体的数据,当方法执行结束的时候没有返回任何类型的数据的话编译报错,
- return 数据;返回值和返回值类型必须一致
- 只要带有return 关键字的语句执行,return语句所在的方法结束
public class MethodTest04 {
/*
* 方法返回值类型不是void的时候:
* 1、返回值类型不是void的时候
* 要求方法必须保证百分百的执行“ return 值 ” 这样的语句来完成值的返回;
* 没有这个语句编译器会报错
* 2、一个方法有返回值的时候,当我们调用这个方法,该方法返回了一个值,
* 对于调用者来说,这个返回值可以选择接受,也可以选择不接受。
* 但是大部分情况下,我们都选择接受
* */
public static void main(String[] args) {
System.out.println(divide(5,2));
divide(5,2);
//编译报错:类型不兼容
//boolean i = divide(2,3);
//自动类型转换:int --》long
long j = divide(4,2);
}
//编译报错:缺少返回语句
// public static int divide(int a , int b){
// int c = a % b;
// System.out.println(c);
// }
//编译报错:缺少返回语句
// public static int divide(int a , int b){
// int c = a % b;
// return;
// }
//编译报错:不兼容的类型
// public static int divide(int a , int b){
// int c = a % b;
// return true;
// }
public static int divide(int a , int b){
System.out.println("Hello!");
return a / b;
}
}
5、方法名
- 只要是合法的标识符就行
- 方法名最好见名知意
- 方法名最好是动词
- 方法名首字母要求小写,后面每个单词首字符大写
6、 形式参数列表:形参
- 形参是局部变量,只在方法内部作用域
- 形参的个数可以是0~n个
- 多个形参之间用 ,号隔开
- 形参中起决定作用的是形参的数据类型,形参的名字就是局部变量的名字
- 方法在调用的时候,实际给这个方法传递的真实数据被称为:实际参数,简称实参
- 实参列表和形参列表必须满足:数量相同,类型对应相同
- 方法形参中可以是基本数据类型也可以是引用数据类型,起决定作用的是数据类型
-
在Java中,方法参数是引用传递的,这意味着方法中的参数是一个对象的引用,而不是对象本身。当你在方法中定义一个引用数据类型的参数时,并未实例化该对象,但是你仍然可以使用该参数调用该类中的实例方法。
这是因为该参数实际上是一个引用,它指向的是堆内存中已经实例化的对象。因此,当你在方法中使用该参数调用类中的实例方法时,实际上是在使用该对象的引用调用该方法。
需要注意的是,如果你尝试在一个没有实例化的对象上调用一个实例方法,例如在
null
引用上调用该方法,将会抛出NullPointerException
异常。因此,在使用方法参数调用实例方法之前,一定要确保该参数所引用的对象已经被实例化为一个对象。 -
Java中的方法参数定义为引用数据类型时,传递给方法的是对象的引用,而不是对象本身。这使得在方法内部可以通过引用访问该对象的实例变量和实例方法,并且对该对象的任何修改都会反映在原始对象上。
public class MethodTest01 {
public static void main(String[] args){
//编译报错,参数数量不够
//MethodTest01.sum();
//编译报错:不兼容的类型
//MethodTest01.sum(true,false);
MethodTest01.sum(10L,20L);
//自动类型转换:int--long
MethodTest01.sum(10,20);
//自动类型转换:char--long
MethodTest01.sum('a','b');
//不兼容的类型
//MethodTest01.sum(3.1,3);
MethodTest01.sum((long) 5.1,5);
//自动类型转换:long--float
MethodTest01.sum1(50L,70L);
}
public static void sum(long a,long b){
System.out.println(a + "+" + b + "=" + (a + b));
}
public static void sum1(float a,float b){
System.out.println(a + "+" + b + "=" + (a + b));
}
}
7、方法体
- 必须由大括号括起来,方法体当中的代码有顺序,遵循自上而下的顺序依次执行,并且方法体有java语句构成,每一个java语句由 ; 结尾
四、方法调用
方法只定义,不去调用的时候不会执行,只有在调用的时候才会执行
1、语法规则
- 方法的修饰符列表中有static ==》 类名 . 方法名(实参列表)完整的调用方式
- 省略类名 就会默认在当前类中寻找该方法
- 若是调用其他类(非本类)中的方法,则必须加 类名 . 方法名(实参列表)
main方法是程序的入口,方法既可以在main方法或者其他方法中调用
参数传递原则:
方法调用的时候,涉及到参数传递的问题,传递的时候,java只遵循一种语法机制,就是将变量中保存的“值”传递过去了,只不过有的时候这个值是一个字面值10,有的时候这个值是另一个java对象的内存地址0x1234
案例比较:
package com.参数传递.javase;
/**
* java中方法调用的时候涉及到参数传递的问题,实际上传递的是变量中保存的具体值
*
* int i = 10;
* add(i) 实际上等同于 add(10)
*/
public class Test01 {
public static void main(String[] args) {
int i = 10;
Test01.add(i); //11
System.out.println("main -->" + i); //10 作用域不一样
}
public static void add(int i){
i++;
System.out.println("add -->" + i);
}
}
package com.参数传递.javase;
public class Test02 {
public static void main(String[] args) {
User u = new User(20,30);
//传递u给add方法的时候,实际上传递的是u变量中的值(对象的内存地址)
add(u);
System.out.println("main age--> " + u.getAge()); //20
System.out.println("main age1--> " + u.age1); //31
}
private static void add(User u) {
int age = u.getAge();
age++;
System.out.println("add age--> " + age); //21
u.age1++;
System.out.println("add age1--> " + u.age1); //31
}
}
class User{
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private int age;
public int age1;
public User(int age,int age1) {
this.age = age;
this.age1 = age1;
}
}
案例一:
public class MethodTest {
//都是从main方法入口进入执行代码
public static void main(String[] args) {
//一个方法可以被重复调用
//注意:方法体中的代码是有顺序的,遵循自上而下的顺序依次执行,上一行代码不结束,下一行代码不会执行
MethodTest.sumInt(10,20);
int x = 100;
MethodTest.sumInt(x,500);
int m = 200;
int n = 300;
MethodTest.sumInt(m,n);
//静态方法,不加类名也可以调用
doSome();
}
//方法的调用不一定在main方法中,可以在其他方法中
//只要是程序可以执行到的位置,都可以去调用其他方法
public static void sumInt(int a,int b){
int c = a + b;
System.out.println(a + "+" + b + "=" + c);
doSome();
}
public static void doSome(){
System.out.println("Do some!");
}
}
案例二:
//一个java源文件中一般只定义一个class
public class MethodTest02 {
public static void main(String[] args){
MethodTest02.m();
//省略类名不写,则默认从当前类中调用
m();
//调用其他类(非本类)中的方法
A.doOthers();
//编译报错:未找到该方法
//doOthers();
}
public static void m(){
System.out.println("m method execute!");
//同一个类中可以省略类名
m2();
//不同的类不能省略类名
A.m2();
}
public static void m2(){
System.out.println("m2 method executed!");
}
}
class A{
public static void doOthers(){
System.out.println("A 's doOthers method invoke!");
}
public static void m2(){
System.out.println("A 's m2 method executed!");
}
}
案例三
public class MethodTest03 {
/*
main begin!
m1 begin!
m2 begin!
m3 begin!
m3 over!
m2 over!
m1 over!
main over!
对于当前的程序来说,main方法最先被调用,main方法也是最后一个结束
一般最先调用的方法,最后结束,最后调用的方法,最先结束!(栈数据结构)
单线程 同步执行
* */
public static void main(String[] args) {
System.out.println("main begin!");
m1();
System.out.println("main over!");
}
public static void m1(){
System.out.println("m1 begin!");
m2();
System.out.println("m1 over!");
}
public static void m2(){
System.out.println("m2 begin!");
m3();
System.out.println("m2 over!");
}
public static void m3(){
System.out.println("m3 begin!");
System.out.println("m3 over!");
}
}
案例四
public class MethodTest05 {
/*
* 深入return语句:
* 带有return关键字的java语句只要执行,所在方法执行结束
* 在“同一个作用域”中,return语句下面不能编写任何代码,因为这些语句永远都不会执行到,编译报错
* */
public static void main(String[] args) {
System.out.println(m(10));
m1();
for (int i = 100 ; i > 90 ; i--){
if (i == 98){
return;
}
System.out.println("i == >" + i);
}
System.out.println("Execute Here!");
m2();
}
//编译报错:缺少返回语句,因为return有可能执行,有可能不执行,无法保证return肯定会执行
// public static int m (int a){
// if (a > 3){
// return 123;
// }
// }
//这样才能保证return肯定会执行
// public static int m (int a){
// if (a > 3){
// return 1;
// }else {
// return 0;
// }
// }
// public static int m (int a){
// if (a > 3){
// return 1;
// //编译报错:无法访问的语句
// //System.out.println("Hello!");
// }
// return 0;
// }
public static int m (int a){
return a > 3 ? 1 : 0;
}
public static void m1(){
m2();
return;
}
public static void m2(){
for (int i = 10 ; i <= 20 ; i++){
if (i == 15){
return; //不是终止的for循环,而是终止的方法
}
System.out.println("i ==>" + i);
}
System.out.println("Hello World!");
}
}
五、方法的栈内存分析
1、方法只定义,不调用,是不会执行的,并且在JVM中也不会给该方法分配“运行所属”的内存空间,只有在调用这个方法的时候,才会动态的给这个方法分配所属的内存空间
2、在JVM内存划分上有这样三块主要的内存空间(除了这三块之外,还有其他内存空间)
- 方法区内存
- 栈内存
- 堆内存
3、栈数据结构
- 栈:stack 是一种数据结构
- 数据结构反应的是数据的存储形态
- 数据结构是独立的学科,不属于任何编程语言的范畴,只不过在大多数编程语言中要使用数据结构
- 作为程序员需要提前精通 数据结构 和 算法 (计算机专业必修课)
- 常见的数据结构:数组 队列 栈 链表 二叉树 哈希表/散列表 ......
4、方法代码片段存在位置
方法代码片段属于 .class 字节码文件的一部分,字节码文件在类加载的时候,将其放到了方法区当中。所以JVM中的三块主要的内存空间中,方法区内存最先有数据,存放了代码片段。
5、方法执行的时候执行过程的内存位置
代码片段虽然在方法区内存中只有一份,但是可以被重复调用,每一次调用该方法的时候需要分配独立的活动场所,在栈内存中分配
栈内存中分配方法运行的所属内存空间,方法在调用瞬间,会给该方法分配内存空间,会在栈中发生压栈动作,方法执行结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作
压栈:给方法分配内存空间
弹栈:释放该方法的内存空间
6、局部变量存储位置
局部变量在“方法体”中声明,局部变量运行阶段内存在栈中存储,这也就是局部变量不能再其他方法内访问的原因。
方法调用的时候,在传输传递的时候,实际上传递的是变量中保存的那个 值 ,而不是变量名
案例一:
public class MethodTest06 {
public static void main(String[] args) {
int a = 10;
int b = 20;
int result = sumInt(a,b);
System.out.println("结果为:" + result);
}
public static int sumInt(int i , int j){
int result = i + j;
int num = 3;
return divide(result,num);
}
public static int divide(int x , int y){
return x / y;
}
}
内存图分析:
案例二:
public class MethodTest02 {
public static void main(String[] args) {
int i = 10;
method(i);
System.out.println("main ->" + i); //11
}
public static void method(int i){
i++;
System.out.println("method ->" + i); //10
}
}
内存图分析
六、方法重载机制 overload
不使用方法重载:
/*
* 以下代码不是用“方法重载机制”,不使用overload
*
* 缺点如下:
* 1、三种方法虽然功能不同,但是功能是相似的,都是求和;
* 2、多个功能相似的方法,分别起了不同的名字,这对于程序员来说调用的时候不方便,需要记忆更多的方法名
* 3、代码不美观
* */
public class OverLoadTest01 {
public static void main(String[] args) {
//调用方法
int result1 = sumInt(1,2);
System.out.println(result1);
long result2 = sumLong(1L,2L);
System.out.println(result2);
double result3 = sumDouble(1.0,2.0);
System.out.println(result3);
}
//定义一个方法,可以计算两个int类型数据的和
public static int sumInt(int a , int b){
return a + b;
}
//定义一个方法,可以计算两个long类型数据的和
public static long sumLong(long a , long b){
return a + b;
}
//定义一个方法,可以计算两个double类型数据的和
public static double sumDouble(double a , double b){
return a + b;
}
}
//使用方法重载机制,最终希望达到的效果是:程序员在使用上面三个相似的方法的时候,就像在使用一个方法一样
//java支持这种语法(JavaScript不支持方法重载)
方法重载又被称为overload
1、方法重载机制的使用条件
- 功能相似的时候,尽可能让方法名相同
- 功能不同的时候,尽可能让方法名不同
2、方法重载机制的构成条件
- 在同一个类中
- 方法名相同
- 参数列表不同
- 参数数量不同
- 参数顺序不同
- 参数类型不同
方法重载和方法名,参数类型有关,和返回值类型,修饰符列表无关
方法名必须相同,参数类型必须不同,返回值类型和修饰符列表可以相同也可以不同
public class OverLoadTest03 {
public static void main(String[] args) {
m1();
m1(2);
m2(2.0,2);
m2(2,2.0);
m3(2);
m3(3.0);
}
//以下两种方法构成重载
public static void m1(){}
public static void m1(int a){}
//以下两种方法构成重载
public static void m2(int a , double b){}
public static void m2(double a , int b){}
//以下两种方法构成重载
public static void m3(int x){}
public static void m3(double y){}
//编译报错:以下不是方法重载,是方法重复了
//public static void m4(int a , int b){}
//public static void m4(int b , int a){}
//编译报错:以下不是方法重载,是方法重复了 因为参数类型相同了
//public static void m5(){}
//public static int m5(){}
//编译报错:以下不是方法重载,是方法重复了
//public static void m6(){}
//void m6(){}
}
案例一:
/*
* 程序员在调用方法的时候,比较方便,虽然调用的是不同的方法,但是就感觉像在调用一个方法一样。
* 不需要记忆更多的方法名
*
* 前提:功能相似的时候,方法名可以相同
*
* 但是:功能不同的时候,尽可能让这两个方法的名字不同
* */
public class OverLoadTest02 {
public static void main(String[] args) {
//调用方法的时候就像在调用一个方法一样
//参数的类型不同,对应调用的方法不同
//此时区分方法不再依靠方法名了,依靠的是参数的数据类型
System.out.println(sum(1,2));
System.out.println(sum(1L,2L));
System.out.println(sum(1.0,2.0));
}
//以下三种方法构成了方法重载机制
public static int sum (int a , int b){
System.out.println("sum(int,int)");
return a + b;
}
public static long sum (long a , long b){
System.out.println("sum(long,long)");
return a + b;
}
public static double sum (double a , double b){
System.out.println("sum(double,double)");
return a + b;
}
}
案例二:
//Print.java
public class Print {
public static void p(byte data){
System.out.println(data);
}
public static void p(short data){
System.out.println(data);
}
public static void p(int data){
System.out.println(data);
}
public static void p(long data){
System.out.println(data);
}
public static void p(float data){
System.out.println(data);
}
public static void p(double data){
System.out.println(data);
}
public static void p(boolean data){
System.out.println(data);
}
public static void p(char data){
System.out.println(data);
}
public static void p(String data){
System.out.println(data);
}
}
//OverLoadTest04.java
public class OverLoadTest04 {
public static void main(String[] args) {
Print.p("hello"); //hello
}
}
七、方法递归调用
定义:方法自身调用自身
赋值运算符和return语句,都是要在左边的语句执行完成后再执行
方法递归算法:
- 使用if else语句控制结束条件,if控制结束条件,else控制调用条件
- 递归从左向右依次展开,结果从右向左依次运算
递归是很耗费栈内存的,递归算法可以不用的时候尽量别用
递归必须有结束条件,没有结束条件一定会发生栈内存溢出错误
Exception in thread "main" java.lang.StackOverflowError
递归即使有了结束条件,结束条件是正确的,也有可能会发生栈内存溢出错误,因为递归的太深了
/*
* 以下代码报错:Exception in thread "main" java.lang.StackOverflowError
* 栈内存溢出
* 错误发生无法挽回,只有一个结果,JVM停止工作
*
*
* */
public class RecursionTest01 {
public static void main(String[] args) {
System.out.println("main begin!");
doSome();
System.out.println("main over!");
}
public static void doSome(){
System.out.println("doSome begin!");
doSome(); //这行代码不结束,下一行程序是不能执行的
System.out.println("doSome over!");
}
}
案例:
import java.util.Scanner;
public class RecursionTest02 {
public static void main(String[] args) {
// System.out.println(sum());
// System.out.println(sum1(10));
// System.out.println(sum2(10));
// System.out.println(sum3(1,10));
System.out.println(factorial(3));
System.out.println(factorial1(3));
System.out.println(factorial2(1,3));
System.out.println(fibonacciSequence(8));
}
//计算1~n的和,使用for循环
public static int sum(){
java.util.Scanner input = new Scanner(System.in);
System.out.print("请输入数字:");
int n = input.nextInt();
int result = 0;
for (int i = 1 ; i <= n ; i++){
result += i;
}
return result;
}
//计算1~n的和,原理 3+2+1
public static int sum1(int n){
int result;
if (n == 1){
result = 1;
}else {
result =n + sum1(n-1);
}
return result;
}
//计算1~n的和,原理 3+2+1
public static int sum2(int n){
if (n ==1){
return 1;
}
return n + sum2(n -1);
}
//计算1~n的和,原理 1+2+3
public static int sum3(int current , int n){
if (current == n){
return n;
}
return current + sum3(current + 1,n);
}
//求n的阶乘
public static int factorial(int n){
int result = 1;
for (int i = 1 ; i <= n ; i++){
result *= i;
}
return result;
}
public static int factorial1(int n){
if (n == 1){
return 1;
}else {
return n*factorial1(n-1);
}
}
public static int factorial2(int current , int n){
if (current == n){
return n;
}else {
return current * factorial2(current+1,n);
}
}
//斐波那契数列:1,1,2,3,5,8,13,21......
public static int fibonacciSequence(int n){
if (n == 1 || n == 2){
return 1;
}else {
return fibonacciSequence(n-2) + fibonacciSequence(n-1);
}
}
}
八、方法初始化
1、方法中局部变量不会默认初始化赋默认值,需要手动赋值
2、未初始化的局部变量,无论其类型是基本数据类型还是引用类型,编译器都会报错。
3、无论局部变量的类型是什么,都应该在使用前手动进行初始化
九、方法体内不能再定义方法
在 Java 中,方法是一段封装了特定功能的可重用代码块,用于执行特定的任务。Java 中的方法定义必须在类的方法区中进行,而方法体内部只能包含语句和表达式,而不能包含方法定义。
这是因为 Java 中的方法定义必须满足一些特定的规范,如方法的返回类型、方法名、参数列表、修饰符等。在方法体内部定义方法会破坏这些规范,使得代码难以理解和维护。此外,在方法内部定义方法还可能导致代码的逻辑混乱,影响程序的性能和可读性。
因此,为了保持代码的清晰度和可读性,Java 不允许在方法体内部定义方法。如果需要在一个类中定义多个方法,应该将这些方法定义在类的方法区中,以便其他方法可以调用它们。
十、方法的返回值类型是什么则必须使用该类型声明变量接收,不能使用其子类型声明
//编译报错:toArray方法返回值类型为Object[] 一维数组,则必须使用Object[]类型声明,不能使用其子类型
//Integer[] objs = collection.toArray();