1.概念以及提纲
3.数据类型[一部分]
4.操作符
5.控制流程
6.关键字清单
1.概念以及提纲
Java技术是一种高级的面向对象的编程语言,也是一个平台,Java技术是基于Java虚拟机(Java Virtual Machine,JVM)的概念——这是语言和底层软件和硬件之间的一种转换器,Java语言的所有实现都是基于JVM的,从而使Java程序可以在有JVM的任何系统上运行。
i.JVM详细介绍:
1)JVM执行原理
JVM可以称为软件模拟的计算机,它可以在任何处理器安全地兼容并且执行.class字节码。其实JVM兼容的二进制字节码和操作系统的本地机器码有一定的区别,只是针对JVM上层的调用程序而言,执行过程效果一样,所以我们一般理解就是说直接用JVM来执行二进制码,实际上二者本质有一定的差异,但是这一点可以理解JVM具有跨平台性。一般情况下,编程人员都是直接编写.java的源文件,然后用Java编译器(javac命令)对源文件进行编译,生成.class文件,生成的.class文件就是我们平时所说的包含了“机器码”的文件,实际上JVM在编译和运行过程做了两件事,先是直接将源文件编译成二进制字节码.class文件,然后进行第二次处理:解释器负责将这些二进制字节码根据本地操作系统宿主环境生成相应的本地机器码解释执行。所以可以理解的一点是为什么Java语言具有跨平台性,因为JVM提供了Java运行的一个中间层,使得操作系统和上层应用相互之间是依靠JVM中间层进行通信的,也就是说Java编写的程序是运行在JVM上的;再者尽管Java确实可以做到“一次编译,多处运行”,但是在不同结构的操作系统平台生成的.class文件真正在执行的时候是存在一定差异的,只是JVM本身会根据安装的不同版本进行不同的操作系统平台下本地机器码的生成及运行,所以虽然我们在Sun公司官方网站可以下载到很多不同操作系统版本的JDK,但是执行效果一样。而且还有一点,这一步操作针对开发人员是透明的,所以真正在开发过程可以放心的是不需要去担心这种问题,只要下载的版本是和符合我们运行的操作系统的,只管进行普通编程的编译、解释运行操作就可以了。
Java语言既是编译型语言,也是解释型语言,在.class文件生成之前,JVM通过javac命令对源代码进行编译操作,然后用JVM根据包含了二进制码的.class生成机器码并且解释执行。所以Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或者电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码进行运行。Java虚拟机的建立需要针对不同的软硬件平台来实现,既要考虑处理器的型号,也要考虑操作系统的种类。由此在SPARC结构、X86结构、MIPS和PPC等嵌入式处理芯片上,在UNIX、Linux、Windows和部分实时操作系统上都可实现Java虚拟机,这也是为了在运行过程生成本地机器码而考虑的,使得JVM可以兼容不同的软硬件平台。
2)JVM的安全检查机制【参考链接:http://galaxystar.javaeye.com/blog/225615】
JVM在执行字节码的时候需要经过下边的步骤:
- 由类加载器(Class Loader)负责把类文件加载到Java虚拟机中(.class),在这个过程需要校验该类文件是否符合类文件规范
- 字节码校验器(Bytecode Verifier)检查该类文件的代码中是否存在着某些非法操作
- 如果字节码校验器校验通过,就由Java解释器负责把该类文件解释成机器码进行执行
JVM在上边操作过程使用了“沙箱”模型,即把Java程序的代码和数据都限制起来放在一定的内存空间执行,不允许程序访问该内存空间以外的内存。这种访问过程不仅仅是本地的,也可以是远程的,最明显的体验是使用RMI的时候。
——[$]Java的“沙箱”详解——
[1]步骤一:“双亲委派类加载模型”:
双亲委派方式,指的是优先从顶层启动类加载器,自定向下的方式加载类模型,这种方式在“沙箱”安全模型里面做了第一道安全保障;而且这样的方式使得底层的类加载器加载的类和顶层的类加载器的类不能相互调用,哪怕两种类加载器加载的是同一个包里面的类,只要加载的时候不属于同一个类加载器,就是相互隔绝的,这样的操作称为JVM的“安全隔离”。
[2]步骤二:字节码校验:
字节码校验过程需要经过四个步骤:
[3]步骤三:内置于JVM的安全特性:
在执行期,除了符号引用验证,JVM还会对一些内建的安全特性进行检查
- 类型安全的引用转化
- 结构化的内存访问(非指针算法)
- GC
- 数组边界检查
- 空引用检查(NullPointer)
强制内存的结构话访问,避免了恶意用户在了解内存分布的情况下通过指针对JVM的内部结构进行破外。当然要了解JVM的内存分布也不是易事。JVM对运行时数据空间的分配是一个黑盒过程,完全由JVM自己决定如何分配,在class中没有任何相关的信息。
字节码检查的局限就是对于那些不经过字节码检查的方法(如本地方法:native method)无法验证其安全性,所以这里采用的是对动态链接库的访问控制,对于那些足有足够可信度的代码才被允许访问本地方法。具体的实现是,由安全管理器来决定代码是否有调用动态链接库的权限,因为调用本地方法必须调用动态链接库。这样一来,不可信的代码将无法通过调用本地方法来绕过字节码检查。
[4]步骤四:安全管理器SecurityManager:
这一步是编程中最接近程序的一个环节,SecurityManager存在于JavaAPI里面,是可以通过编程来完成安全策略管理的,默认情况下,Java应用是不设置SecurityManager实例的,但是这个实例却需要我们在启动的时候通过System.setSecurityManager来进行设置。一般情况下,调用了SecurityManager.checkPermission(Permission perm)来完成的,外部程序可以创建一个权限实例Permission来进行Check操作。主要应用为:
- 默认安全管理器:java.lang.SecurityManager
- 代码签名和认证
- 策略:java.security.Policy
- 权限:java.security.Permission
- 策略文件
- 保护域:CodeSource,PersimissionCollection,ProtectionDomain
- 访问控制器:java.security.AccessController
【这一步内容比较多,这里不详细讲解,带过】
[5]步骤五:Java签名/证书机制:
Java签名机制使得使用者可以安全调用外部提供的jar文件,签名必须是基于jar包的,也就是如果要使用安全模型必须要将提供的class文件打包成jar,然后使用JDK工具(jarsigner)对jar进行签名。新安全平台中对足够信任度的代码放宽了限制,要获得充分信任须通过代码签名来实现,若我们对某一签名团体足够信任,比如SUN,那么具有该团体签名的代码将被给予充分信任。一个基本的思路大致为,代码发布者创建私钥/公钥对,然后利用私钥对发布代码进行签名,代码使用方在获得代码发布者提供的公钥后对代码进行验证,确认代码确为该提供者提供以及在发布后未经非法修改。这其中存在一些潜在的危险,既是公钥是否是该代码发布者提供的,恶意用户可能替换掉合法的公钥,这会导致用户将给与恶意用户发布的代码以充分信任,目前的常见做法是通过一些权威的证书机构来发布证书而不是公钥,代码发布者被证书发布机构认证合格后,可以将自己的公钥交付证书发布机构,证书发布机构再通过私钥加密该公钥,从而生成证书序列。这样替换公钥的可能性就变得微乎其微。不过世上无绝对安全之事,通过证书机构发布的证书也不是绝对安全的途径。
证书是在签名基础上,对签名值,再进一步做一次加密。而这次加密使用的私钥和公钥都是证书机构提供的。这种方式,是为了防止,有些恶意用户,在公钥发到你手上前,就对其做了手脚,然后再发一个动过手脚的jar给你,用动过手脚的公钥解动过手脚的jar包,是可以解开的。而使用证书后,它会对已经加密的签名值,再做一层加密,这样,到你手里,你只需要通过证书机构的公钥进行解密,然后再用jar包发布者的公钥解密就行了。(只能在一定程度上,提供一些安全性)
3)JVM的内部结构:
JVM是Java平台的核心,为了让编译产生的字节码能够更好的解释和执行,JVM主要分为6个部分【这里只是带过,想要了解JVM整体执行原理的读者可以去参考《Inside JVM》】:
- JVM解释器:即这个虚拟机处理字段码的CPU。
- JVM指令系统:该系统与计算机很相似,一条指令由操作码和操作数两部分组成。操作码为8位二进制数,主要是为了说明一条指令的功能,操作数可以根据需要而定,JVM有多达256种不同的操作指令。
- 寄存器:JVM有自己的虚拟寄存器,这样就可以快速地与JVM的解释器进行数据交换。为了功能的需要,JVM设置了4个常用的32位寄存器:pc(程序计数器)、optop(操作数栈顶指针)、frame(当前执行环境指针)和vars(指向当前执行环境中第一个局部变量的指针)。
- JVM栈:指令执行时数据和信息存储的场所和控制中心,它提供给JVM解释器运算所需要的信息。
- 存储区:JVM存储区用于存储编译过后的字节码等信息。
- 碎片回收区:JVM碎片回收是指将使用过的Java类的具体实例从内存进行回收,这就使得开发人员免去了自己编程控制内存的麻烦和危险。随着JVM的不断升级,其碎片回收的技术和算法也更加合理。JVM 1.4.1版后产生了一种叫分代收集技术,简单来说就是利用对象在程序中生存的时间划分成代,以此为标准进行碎片回收。
ii.Java平台介绍:
Java平台(Java Platform)是一种纯软件平台,它可以在各种基于硬件的平台运行,它有三个版本(JavaSE,JavaME,JavaEE),它由Java应用程序接口(Java Application Programming Interface,API)和JVM组成,Java API是一个现成的软件组件集合,可以简化Applet和应用程序的开发部署,包括健壮、安全且可互操作的企业应用程序。它涵盖了从基本对象到连网和安全性,再到XML生成和Web服务的所有东西,Java API组织成相关类和接口的库,库也可以成为包。
每个包实现包括:
- 用来编译、运行、监视、调试应用程序以及简历应用程序文档的开发工具
- 用来部署应用程序的标准机制
- 用来创建复杂的图形用户界面(GUI)的用户界面工具包
- 用来启用数据库访问和操作远程对象的集成库
1)Java平台的版本:
Java平台有三个版本,使得软件开发人员、服务提供商和设备生产商可以针对特定的市场进行开发:
[1]Java SE(Java Platform,Standard Edition):Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为 Java Platform,Enterprise Edition(Java EE)提供基础。大多数 Java 开发人员使用 Java SE 5,也称为 Java 5.0 或 “Tiger”(2006 年 6 月,Java SE 6 或 “Mustang” 发布了 beta 版。)
[2]Java EE(Java Platform,Enterprise Edition):这个版本以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端 Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web 2.0 应用程序。
[3]Java ME(Java Platform,Micro Edition):这个版本以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
2)Java组件技术提纲:【写此提纲的目的是防止学习过程术语混乱,这里提供的分类不标准,但是覆盖了Java里面大部分我们开发过程会碰到的规范和术语】
- Java SE中的规范:
JavaBeans Component Architecture【JavaBeans】:一个为Java平台定义可重用软件组件的框架,可以在图形化构建工具中对这些组件进行设计
Java Foundation Classes(Swing)【JFC】:一套Java的类库,支持为基于Java的客户端应用程序构建GUI和图形化功能
JavaHelp:一个独立于平台的可扩展帮助系统,开发人员和做着可以使用它将在线帮助集成到Applet、组件、应用程序、操作系统和设备中,还可以提供Web的在线文档
Java Native Interface【JNI】:使JVM中运行的Java代码可以与用其他编程语言编写的应用程序和库进行互操作,很多时候实现部分用C来写,有兴趣的读者可以写一个简单的Java版任务管理器,嘿嘿,写这个可以收获很多哦!
Java Platform Debugger Architecture【JPDA】:用于Java SE的调试支持的基础结构
Java 2D API【Java 2D】:是一套用于高级2D图形和图像的类、一套提供了精确控制颜色空间定义和转换的类以及一套面向显示图像的操作符
Java Web Start:允许用户通过一次点击下载并启动特性完整的网络应用程序,而不需要安装,从而简化了Java的部署工作
Certification Path API:提供了一套用于创建、构建和检验认证路径(也称为 “认证链”)的 API,可以安全地建立公共密钥到主体的映射。
Java Advanced Imaging【JAI】:提供一套面向对象的接口,这些接口支持一个简单的高级编程模型,使开发人员能够轻松地操作图像。
Java Cryptography Extension【JCE】:提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现,它提供对对称、不对称、块和流密码的加密支持,它还支持安全流和密封的对象。
Java Data Objects【JDO】:是一种基于标准接口的持久化 Java 模型抽象,使程序员能够将 Java 领域模型实例直接存储进持久化存储(数据库)中,这可以替代直接文件 I/O、串行化、JDBC 以及 EJB Bean Managed Persistence(BMP)或 Container Managed Persistence(CMP)实体 bean 等方法。
Java Management Extensions【JMX】提供了用于构建分布式、基于 Web、模块化且动态的应用程序的工具,这些应用程序可以用来管理和监视设备、应用程序和服务驱动的网络。
Java Media Framework【JMF】:可以将音频、视频和其他基于时间的媒体添加到 Java 应用程序和 applet 中。
Java Secure Socket Extensions【JSSE】:支持安全的互联网通信,实现了 SSL(Secure Sockets Layer)和 TLS(Transport Layer Security)的 Java 版本,包含数据加密、服务器身份验证、消息完整性和可选的客户机身份验证等功能。
Java Speech API【JSAPI】:包含 Grammar Format【JSGF】和 Markup Language【JSML】规范,使 Java 应用程序能够将语音技术集成到用户界面中。JSAPI 定义一个跨平台的 API,支持命令和控制识别器、听写系统和语音识别器。
Java 3D API【Java 3D】:它提供一套面向对象的接口,这些接口支持一个简单的高级编程模型,开发人员可以使用这个 API 轻松地将可伸缩的独立于平台的 3D 图形集成到 Java 应用程序中。
Metadata Facility【JMI】:允许给类、接口、字段和方法标上特定的属性,从而使开发工具、部署工具和运行时库能够以特殊方式处理它们,Java元数据接口(使用Annotation实现)
Java Content Repository API:用于访问 Java SE 中独立于实现的内容存储库的 API。内容存储库是一个高级信息管理系统,它是传统数据存储库的超集。
Concurrency Utilities:一套中级实用程序,提供了并发程序中常用的功能。
Juxtapose【JXTA】:一个用来解决P2P计算的开放的网络计算平台。JXTA P2P平台使开发者在其上建立PtoP的应用。
- Java EE中的规范:
Java Database Connectivity【JDBC】:使用户能够从 Java 代码中访问大多数表格式数据源,提供了对许多 SQL 数据库的跨 DBMS 连接能力,并可以访问其他表格式数据源,比如电子表格或平面文件。
Java Naming and Directory Interface【JNDI】:为 Java 应用程序提供一个连接到企业中的多个命名和目录服务的统一接口,可以无缝地连接结构不同的企业命名和目录服务。
Enterprise JavaBeans【EJB】: J2EE技术之所以赢得某体广泛重视的原因之一就是EJB。它们提供了一个框架来开发和实施分布式商务逻辑,由此很显著地简化了具有可伸缩性和高度复杂的企业级应用的开发。EJB规范定义了EJB组件在何时如何与它们的容器进行交互作用。容器负责提供公用的服务,例如目录服务、事务管理、安全性、资源缓冲池以及容错性。但这里值得注意的是,EJB并不是实现J2EE的唯一途径。正是由于J2EE的开放性,使得有的厂商能够以一种和EJB平行的方式来达到同样的目的。
Remote Method Invoke【RMI】: 正如其名字所表示的那样,RMI协议调用远程对象上方法。它使用了序列化方式在客户端和服务器端传递数据。RMI是一种被EJB使用的更底层的协议。
Java IDL/CORBA:在Java IDL的支持下,开发人员可以将Java和CORBA集成在一起。 他们可以创建Java对象并使之可在CORBA ORB中展开, 或者他们还可以创建Java类并作为和其它ORB一起展开的CORBA对象的客户。后一种方法提供了另外一种途径,通过它Java可以被用于将你的新的应用和旧的系统相集成。
Portlet Specification:定义了一套用于 Java 门户计算的 API,可以解决聚合、个人化、表示和安全性方面的问题。
JavaMail:提供了一套对邮件系统进行建模的抽象类。
Java Message Service【JMS】:为所有与 JMS 技术兼容的消息传递系统定义一套通用的消息概念和编程策略,从而支持开发可移植的基于消息的 Java 应用程序。
JavaServer Faces【JSF】:提供一个编程模型,帮助开发人员将可重用 UI 组件组合在页面中,将这些组件连接到应用程序数据源,将客户机生成的事件连接到服务器端事件处理程序,从而轻松地组建 Web 应用程序。
JavaServer Pages【JSP】:允许 Web 开发人员快速地开发和轻松地维护动态的独立于平台的 Web 页面,并将用户界面和内容生成隔离开,这样设计人员就能够修改页面布局而不必修改动态内容。这种技术使用类似 XML 的标记来封装为页面生成内容的逻辑。
Java Servlets:提供一种基于组件的独立于平台的方法,可以构建基于 Web 的应用程序,同时避免 CGI 程序的性能限制,从而扩展并增强 Web 服务器的功能。
J2EE Connector Architecture【JCA】:为将 J2EE 平台连接到各种结构的 Enterprise Information Systems(EIS)定义了一个标准的体系结构,它定义了一套可伸缩的安全的事务性机制,使 EIS 厂商能够提供标准的资源适配器,可以将这些资源适配器插入应用服务器中。
J2EE Management Specification【JMX】:为 J2EE 平台定义了一个信息管理模型。根据其设计,J2EE Management Model 可与多种管理系统和协议进行互操作;包含模型到 Common Information Model(CIM)的标准映射,CIM 是一个 SNMP Management Information Base(MIB);还可以通过一个驻留在服务器上的 EJB 组件 —— J2EE Management EJB Component【MEJB】 —— 映射到 Java 对象模型。
Java Transaction API【JTA】:是一个独立于实现和协议的高级 API,它使应用程序和应用服务器可以访问事务。
Java Transaction Service【JTS】:JTS是CORBA OTS事务监控的基本的实现。JTS规定了事务管理器的实现方式。该事务管理器是在高层支持Java Transaction API (JTA)规范,并且在较底层实现OMG OTS specification的Java映像。JTS事务管理器为应用服务器、资源管理器、独立的应用以及通信资源管理器提供了事务服务……
Java Authentication and Authorization Service【JAAS】:实现了标准的 Pluggable Authentication Module(PAM)框架的 Java 版本并支持基于用户的授权,使服务能够对用户进行身份验证和访问控制。
JavaBeans Activation Framework【JAF】:JavaMail利用JAF来处理MIME编码的邮件附件。MIME的字节流可以被转换成Java对象,或者转换自Java对象。大多数应用都可以不需要直接使用JAF。
Java API for XML Web Service【JAX-WS】:一组XML web services的JAVA API。JAX-WS允许开发者可以选择RPC-oriented或者message-oriented 来实现自己的web services。
Java API for XML Processing【JAXP】:允许 Java 应用程序独立于特定的 XML 处理实现对 XML 文档进行解析和转换,允许灵活地在 XML 处理程序之间进行切换,而不需要修改应用程序代码。
Java API for XML Binding【JAXB】:允许在 XML 文档和 Java 对象之间进行自动的映射。
SOAP with Attachments API for Java【SAAJ】:使开发人员能够按照 SOAP 1.1 规范和 SOAP with Attachments note 生成和消费消息。
Streaming API for XML【StAX】:就提供了两种方法来处理 XML:文档对象模型(DOM)方法是用标准的对象模型表示 XML 文档;Simple API for XML (SAX) 方法使用应用程序提供的事件处理程序来处理 XML。JSR-173 提出了一种面向流的新方法:Streaming API for XML (StAX)。其最终版本于 2004 年 3 月发布,并成为了 JAXP 1.4(将包含在即将发布的 Java 6 中)的一部分。
Java API for XML Registries【JAXR】:提供了一种统一和标准的Java API,用于访问不同类型的基于XML的元数据注册中心。JAXR目前实现支持ebXML Registry2.0版和UDDI2.0版。未来可以支持更多的类似的注册中心。JAXR为客户端提供了API,与XML注册中心进行交互,同时为服务中心提供者提供了服务提供者接口(SPI),这样,注册中心的实现是可插拔的。JAXR API将应用代码与下层的注册中心机制相隔离。当编写基于JAXR的客户端浏览和操作注册中心时,当注册中心更换时,比如从UDDI更换为ebXML,代码不需要进行修改。
Java Persistence API【JPA】:通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中,一个标准的ORM框架。
Java Authorization Service Provider Contract for Containers【JACC】:在J2EE应用服务器和特定的授权认证服务器之间定义了一个连接的协约,以便将各种授权认证服务器插入到J2EE产品中去。
Java API for RESTful Web Services【JAX-RS】:定义一个统一的规范,使得 Java 程序员可以使用一套固定的接口来开发 REST 应用,避免了依赖于第三方框架。同时,JAX-RS 使用 POJO 编程模型和基于标注的配置,并集成了 JAXB,从而可以有效缩短 REST 应用的开发周期。
- Java ME中的规范:
Connected Limited Device Configuration【CLDC】:是组成资源有限的移动信息设备的 Java 运行时环境的两种配置之一。CLDC 描述最基本的库和虚拟机特性,所有包含 K 虚拟机(K virtual machine,KVM)的 J2ME 环境实现中都必须提供这些库和特性。
Mobile Information Device Profile【MIDP】:是组成资源有限的移动信息设备的 Java 运行时环境的两种配置之一。MIDP 提供核心应用程序功能,包括用户界面、网络连接、本地数据存储和应用程序生命周期管理。
Connected Device Configuration【CDC】:是一个基于标准的框架,用来构建和交付可以跨许多连接网络的消费类设备和嵌入式设备共享的应用程序。
Mobile 3D Graphics API for J2ME【M3G】:是一种轻量的交互式 3D 图形 API,它作为可选的包与 J2ME 和 MIDP 结合使用。
【*:上边这份清单基本上涵盖了Java平台所有的术语以及相关介绍,因为WebBeans是Java EE 6的新规范,而且JBI和SCA具体是为ESB开发存在的,这三个规范我没有归纳进来,有兴趣的读者可以自己去查阅一下,还有一点需要说明JXTA以前不属于Java规范里面的,也是因为该组件用起来比较方便貌似现在纳入JSR规范了,具体情况如何我没有查证,见谅,若有遗漏掉的规范提纲,也请有心的读者来Email告知,谢谢:[email protected]。这套教程我在书写的时候我只会写一部分与我自己开发相关的使用过的组件技术,其他我不熟悉的可能不会涉及到。而且需要提醒一下读者的是:SSH(Struts+Spring+Hibernate)框架虽然很流行,但是不属于Sun公司Java EE里面的官方标准规范,这三个框架是三个开源框架,所以:JavaEE的规范定义里面并不包含SSH!】
2.语言基础【*:本教材不会讲JDK的环境配置,环境配置以后一必要我在BLOG的配置分类里面单独讲解】
i.开发第一个Java程序:
步骤一:书写.java源代码
步骤二:使用javac命令编译.java源代码,将该代码编译成.class字节码
步骤三:使用java命令运行.class字节码
接着按照步骤,书写第一个Java程序:
/**
*我的第一个Java程序
**/
class Hello{
}
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
将上边的代码保存成HelloJava.java文件名,放于D盘根目录。【*:这里的放置位置根据读者爱好自己决定】
第二步:使用命令行工具:
javac HelloJava.java
第三步:运行.class文件
java HelloJava
输出应该为:
Hello Java
针对这样一个过程有几点需要说明:
[1]关于Java源文件:Java的源文件是以.java结尾的,每一个源文件里面可以定义很多class,但是只能定义一个public的class,如果不定义public的class,文件名必须和其中至少一个class的名称一致;若定义了一个public的class,文件名必须和定义的public的class的类名一致。【*:这点是Java的语言规范,但是从语言设计上来讲这是没有必要的,实际上编译器不应该把文件名和类名关联,但是Java语言是一个特殊,学过Scala和C#的读者应该就明白这一点,所以这一点在Java使用的时候需要特别小心。】
[2]关于函数入口:一个Java程序可能由一个或者多个class构成,上边使用了javac过后,同一个目录下可以生成两个.class文件,分别是Hello.class和HelloJava.class,不论怎样必须在运行的类里面(使用java命令的时候后边跟的类名的类)定义一个Java应用程序的入口:public static void main(String args[]),在这里,该定义成为Java语言的主函数入口,也是程序运行的起点,上边的程序的入口就在HelloJava类里面。main()方法的签名是不可以更改的,因此下边的函数入口的写法都是不对的:
static void main(String args[])
public void main(String args[])
public static void main(String args)
使用上边三种入口函数的定义都会使得JVM没有办法找到主函数入口,后两种定义会抛出下边的异常,第一种情况比较特殊,JVM会提示:Main method not public,也就是说第一种情况JVM能够找到主函数,但是该主函数不符合Java应用程序的运行规范。
Exception in thread "Main Thread" java.lang.NoSuchMethodError: main
[3]在上边的程序里面,HelloJava的类可以不是public的时候,这里需要说明的是,不是说因为某个类是public主函数入口就必须放在该类里面,读者可以试试下边的代码段:
class HelloTester{
public static void main(String[] args){
System.out.println("Hello World");
}
}
public class MainTester {
}
当我们使用javac MainTester.java的时候,编译是可以通过的,而且会生成两个类文件:HelloTester.class和MainTester.class。注意这里的代码结构,按照第一点提到的,这个.java文件只能保存为MainTester.java,如果这段代码保存为HelloTester.java文件,编译的时候会抛出下边的异常:
HelloTester.java:8:class MainTester is public,should be declared in a file named MainTester.java
也就是说一旦在一个.java源文件里面定义了一个类为public,那么这个源文件的名称就被限制了必须和public的类保持一致。上边的代码经过编译过后使用下边的命令读者可以测试以下:
java HelloTester【这种做法会输出:Hello World,因为在这个类里面找到了主函数的入口】
java MainTester【这种做法不会成功,因为在执行类MainTester里面,JVM找不到主函数的入口】
[4]main函数的参数:
对于java里面的主函数入口方法main,JVM有一定的限制,首先是不可以有返回值,因此返回值为void,而且必须有一个参数,类型为String[]的,这也是Java语言里面的规范,这个参数会匹配命令行里面的参数,至于参数名是里面唯一可以更改的,所以上边的定义可以为:
public static void main(String[] myInputValue)
一般情况下,约定使用args的名称,而这里的args代表了命令行的输入参数,这一点可以提供一个带命令行参数的例子:
public class MainTester {
public static void main(String args[])
{
System.out.println(args[0]);
System.out.println(args[1]);
}
}
把上边的代码保存为MainTester.java,然后使用javac命令编译,这一步之前所有的的内容都不用担心,和前边一样,但是运行的时候可能需要下边这种格式:
java MainTester hello1 hello2
上边这段代码会输出为:
hello1
hello2
也就是说,该参数读取的是在java ClassName之后的参数,按照空白为分隔符,然后输入的内容就为参数的值,这些值构成了一个String[]的参数列表,上边代码里面args.length的值为2,第一个参数为hello1,第二个参数为hello2,这就是主函数参数列表里面的参数的使用
小节一下上边几点:
- 使用javac命令的时候:如果.java里面没有public的class,直接针对class的定义进行编译操作,这种情况下,.java源文件的文件名只要和里面的任何一个类的类名一致就可以了;如果.java里面定义了一个类是public的,这种情况下,.java源文件的名称只能和这个类的类名保持一致,否则编译不会通过。
- 使用java命令的时候:在使用java命令的时候,后边敲入命令不需要填写文件全名【不需要java ClassName.class格式】,只是需要填写类名就可以了,这种情况下,JVM会在ClassName对应的类定义里面去寻找主函数入口,如果找不到就会抛出异常,找到过后就可以执行,也可以这样讲,包含主函数的类不一定在一个public的类里面可以是非public的。
- Java应用程序在执行的时候,必须有一个主函数的入口【*:这里不包括Applet的运行,因为Applet里面有特殊的入口以及特殊的运行机制】,而且该主函数的入口必须定义为:public static void main(String args[])
- 这里还有一点需要说明:main函数是可以抛异常的,JVM在检查方法签名的时候,不会检查main后边带的异常定义,比如下边这种方式也是定义函数入口的正确方式,也就是说带异常抛出的时候仅仅检测方法签名就可以了:
public static void main(String args[]) throws Exception
- 注意主函数传入参数的使用,args参数实际上就是在使用java命令的时候传入的参数的值的使用,这些使用可以通过写几个简单的程序来实现,在参数使用的过程里面,一般情况下在编写程序的时候针对参数进行一定的检测,上边使用参数的代码实际上是会抛异常的,因为会存在数组越界的问题,为了保证数据不会越界,一般可以通过代码if( args.length > 0 )来对传入的参数进行判断,而且把使用参数的代码放在try-catch块里面进行数组越界异常的捕捉。
ii.Java的注释:
在Java里面主要有三种注释:行注释、段落注释、文档注释
1)行注释:行注释也成为单行注释,行注释使用 “//注释文字”的格式来对某一行的代码进行注释或者加以说明
public class LineComment
{
//这是单行注释的范例
public static void main(String args[])
{
//这只是一个单行注释的例子
System.out.println("Single Line Comment");
}
}
上边代码里面//后边的文字就是行注释的一个例子
2)段注释:段注释也成为多行注释,通常是当说明文字比较长的时候的注释方法
public class MultiCommont
{
/*
*这是段注释的一个简单的例子
*这里是函数入口main方法
*/
public static void main(String args[])
{
System.out.println("Multi Lines Comments");
}
}
3)文档注释:文档注释是Java里面的一个比较厉害的功能,它可以用于注释类、属性、方法等说明,而且通过JDK工具javadoc直接生成相关文档,文档注释的基本格式为“/**...*/”,不仅仅如此,文档注释本身还存在语法
[1]文档和文档注释的格式化:
生成的文档是HTML格式的,而这些HTML格式的标识符并不是javadoc加的,而是我们在写注释的时候写上去的。因此在格式化文档的时候需要适当地加入HTML标签,例如:
/**
*This is first line.
*This is second line.
*This is third line.
**/
[2]文档注释的三部分:
根据在文档中显示的效果,文档注释可以分为三个部分,这里举个例子:
/**
*testDoc方法的简单描述
* testDoc方法的详细说明
*@param testInput String 打印输入的字符串
*@return 没有任何返回值
**/
public void testDoc(String testInput)
{
System.out.println(testInput);
}
简述:文档中,对于属性和方法都是现有一个列表,然后才在后面一个一个的详细说明,列表中属性名或者方法名后面那段说明都是简述。
详细说明:该部门对属性或者方法进行了详细说明,在格式上不需要特殊的要求,可以写成前边讲的HTML的格式,如同上边第二行的内容。【*:这里有个技巧就是简述和详细说明尽量不要重复,在简述中出现过的内容不需要在详细说明中进行第二次描述,可以理解为详细说明是简述的一种扩展。后边章节的概念说明代码大部分我都没有写注释,也请各位读者见谅!】
特殊说明:除开上边的两部分,最后一个部分就是特殊说明部分,特殊说明部分使用JavaDoc标记进行,这些都是JavaDoc工具能够解析的特殊标记,这一点下边章节将会讲到
[3]使用javadoc标记:
javadoc标记是java插入文档注释中的特殊标记,它们用于识别代码中的特殊引用。javadoc标记由“@”以及其后跟着的标记类型和专用注释引用组成。它的格式包含了三个部分:
@、标记类型、专用注释引用
有时候我们也可以直接理解为两个方面:@和标记类型、专用注释引用;Javadoc工具可以解析Java文档注释中嵌入的特殊标记,这些文档标记可以帮助自动从源代码生成完整的格式化API,标记用“@”符号开头,区分大小写,必须按照正确的大小写字母输入。标记必须从一行的开头开始,否则会被视为普通文本,而且按照规定应将相同名字的标记放一起,比如所有的@see标记应该放到一起,接下来看一看每一种标记的含义。
@author(1.0):语法[@author name-text]
当使用-author选项的时候,用指定的name-text在生成文档的时候添加“Author”项,文档注释可以包含多个@author标记,可以对每个@author指定一个或者多个名字。
@deprecated(1.0):语法[@deprecated deprecated-text]
添加注释,只是不应该再使用该API(尽管是可用的),Javadoc工具会将deprecated-text移动到描述前面,用斜体显示,并且在它前边添加粗体警告:“不鼓励使用”。deprecated-text的首句至少应该告诉用户什么时候开始不鼓励使用该API以及使用什么替代它。Javadoc仅将首句复制到概览部分和索引中,后面的语句还可解释为什么不鼓励使用,应该还包括一个指向替代API的{@link}标记【1.2版本用法】
- 对于Javadoc 1.2,使用{@link}标记,这将在需要的地方创建内嵌链接,如:
/**
*@deprecated 在JDK X.X中,被{@link #methodName(paramList)}取代
**/
【*:在上边的标记里面X.X指代JDK的版本,后边的链接链接的是方法的签名,methodName为方法名,paramList为参数列表】
- 对于Javadoc 1.1,标准格式是为每个@deprecated标记创建@see标记(它不可内嵌)
@exception(1.0):语法[@exception class-name description]
@throws(1.2):语法[@throws class-name description]
以上两个标记是同义词,用class-name和description文本给生成的文档添加“抛出”子标题,其中class-name是该方法可抛出的异常名。
{@link}(1.2):语法[{@link name label}]
出入指向指定name的内嵌链接,该标记中name和label的语法与@see标记完全相同,如下所述,但是产生的内嵌链接而不是在“参见”部分防止链接。该标记用花括号开始并用花括号结束,以使它区别于其他内嵌文本,如果需要在标签内使用“}”,直接使用HTML的实体表示法:};
@param(1.0):语法[@param parameter-name description]
给“参见”部分添加参数,描述可以继续到下一行进行操作,主要是提供了一些参数的格式以及描述
@return(1.0):语法[@return description]
用description本文添加“返回”部分,该文本应描述值的返回类型和容许范围
@see(1.0):语法[@see reference]
该标记是一个相对复杂的标记,添加“参见”标题,其中有指向reference的链接或者文本项,文档注释可包含任意数目的@see标记,它们都分组在相同的标题下,@see有三种格式:
- @see “string” 注:该形式在JDK 1.2中没有用,它不打印引用文本
为string添加文本项,不产生链接,string是通过该URL不可用的书籍或者其他信息引用,Javadoc通过查找第一个字符为双引号(")的情形来区分它前边的情况,比如:
@see "这是Java教材,主要是提供给初学者"
上边的注释将会生成:
参见:
“这是Java教材,主要是提供给初学者”
- @see Java某章节
添加URL#value定义的链接,其中URL#value是相对URL或者绝对URL,JavaDoc工具通过查找第一个字符小写符号(<)区分它与其他情况,比如:
@see 测试规范
上边的注释将会生成:
参见:
测试规范
- @see package.class#member label【比较常用的一种格式】
添加带可见文本label的链接,它指向Java语言中指定名字的文档。其中label是可选的,如果省略,则名字作为可见文本出现,而且嵌在HTML标记中,当想要缩写可见文本或不同于名字的可见文本的时候,可以使用label。
——package.class#member是Java语言中的任何有效名字——包名、类名、接口名、构造函数名、方法名或域名——除了用hash字符(#)取代成员名前面的点之外,如果该名字位于带文档的类中,则Javadoc将自动创建到它的链接,要创建到外部的引用链接,可使用-link选项,使用另外两种@see形式中的任何一种引用不属于引用类的名字的文档。
——label是可选文本,它是链接的可见标签,label可包含空白,如果省略label,则将显示package.class.member,并相对于当前类和包适当缩短
——空格是package.class#member和label之间的分界符,括号内的空格不表示标签的开始,因此在方法各参数之间可使用空格
@see package.class#member的典型形式 |
引用当前类的成员 @see #field @see #method(Type,Type,...) @see #method(Type argname,Type argname,...) 引用当前包或导入包中的其他类 @see Class#field @see Class#method(Type,Type,...) @see Class#method(Type argname,Type argname,...) @see Class 引用其他包(全限定) @see package.Class#field @see package.Class#method(Type,Type,...) @see package.Class#method(Type argname,Type argname,...) @see package.Class @see package |
简单说明一下:
——第一套形式(没有类和包)将导致 Javadoc 仅搜索当前类层次。它将查找当前类或接口、其父类或超接口、或其包含类或接口的成员。它不会搜索当前包的其余部分或其他包(搜索步骤 4-5)。
——如果任何方法或构造函数输入为不带括号的名字,例如 getValue,且如果没有具有相同名字的域,则 Javadoc 将正确创建到它的链接,但是将显示警告信息,提示添加括号和参数。如果该方法被重载,则 Javadoc 将链接到它搜索到的第一个未指定方法。
——对于所有形式,内部类必须指定为 outer.inner,而不是简单的 inner。
——如上所述,hash 字符(#)而不是点(.)用于分隔类和成员。这使 Javadoc 可正确解析,因为点还用于分隔类、内部类、包和子包。当 hash 字符(#)是第一个字符时,它是绝对不可少的。但是,在其他情况下,Javadoc 通常不严格,并允许在不产生歧义时使用点号,但是它将显示警告。
@see标记的搜索次序——JavaDoc将处理出现在源文件(.java)、包文件(package.html)或概述文件(overview.html)中的@see标记,在后两种文件中,必须完全限定使用@see提供的名字,在源文件中,可指定全限定或部分限定的名字,@see的搜索顺序为:
- 当前类或接口
- 任何包含类和接口,先搜索最近的
- 任何父类和超接口,先搜索最近的
- 当前包
- 任何导入包、类和接口,按导入语句的次序搜索
@since(1.1):语法[@since since-text]
用since-text指定的内容给生成文档添加“Since”标题,该文本没有特殊内部结构,该标记表示该改变或功能自since-text所指定的软件件版本后就存在了,例如:@since JDK 1.4
@serial(1.2):语法[@serial field-description]
用于缺省的可序列化域的文档注释中,可选的field-description增强了文档注释对域的描述能力,该组合描述必须解释该域的意义并列出可接受值,如果有需要描述可以多行,应该对自Serializable类的最初版本之后添加的每个可序列化的域添加@since标记。
@serialField(1.2):语法[@serialField field-name field-type field-description]
简历Serializable类的serialPersistentFields成员的ObjectStreamField组件的文档,应该对每个ObjectStreamField使用一个@serialField标记
@serialData(1.2):语法[@serialData data-description]
data-description建立数据(尤其是writeObject方法所写入的可选数据和Externalizable.writeExternal方法写入的全部数据)序列和类型的文档,@serialData标记可用于writeObject、readObject、writeExternal和readExternal方法的文档注释中
@version(1.0):语法[@version version-text]
当使用-version选项时用version-text指定的内容给生成文档添加“版本”子标题,该文本没有特殊内部结构,文档注释最多只能包含一个@version标记。
{@code}(1.5):语法[{@code text}]
该标签等同于
{@literal},里面可以直接过滤掉HTML的标签,可以不用<和>来显示(<和>),在这个代码块里面的text部分,可以直接书写代码,即使使用了Hello,在HTML里面也不会识别成为加粗的Hello,而还是原来的代码段Hello的格式输出
{@docRoot}(1.3):语法[{@docRoot}]
代表相对路径生成的文件的(目标)的根从任何产生的网页目录,当您要包括如版权页或公司徽标文件的时候它是有用的,你要引用所生成的网页,链接从每个页面底部的版权页面是常见的。(@docRoot将标记可用于在命令行,并在两个文档注释:这个标记是有效的,在所有文档注释:概述、包装类、接口、构造、方法和领域,包括任何标记文本的一部分(如@return,@param和@deprecated的使用)。
比如:
/**
**/
{@inheritDoc}(1.4):语法[{@inheritDoc}]
继承(拷贝)文档从最近的“继承类或可执行的接口”到当前在这个标签的位置的文档注释内容,这使您可以编写更多与继承相关的一般性文档
下边的情况比较适合:
- 在一个方法的主要描述块,在这种情况下,主要描述是整个继承树结构里面的类和接口的一个拷贝。
- 在文本参数返回的@return @param和@throws等方法标记,在这种情况下,文本标记是整个层次结构里面标签的拷贝。
{@linkplain}(1.4):语法[{@linkplain package.class#member label}]
和{@link}类似,除非链接的label是用普通文本的格式显示的,当文本是普通文本的时候该标签就能够起作用,例如:
Refer to {@linkplain add() the overridden method}
这个会显示成:
Refer to the overridden method
{@value}(1.4):语法[{@value package.class#field}]
主要用来显示静态字段的值:
/**
* The value of this constant is {@value}
**/
public static final String SCRIPT_START = "script";
[4]JavaDoc标记的举例:
——[$]一个使用JavaDoc标记的例子——
/**
* @author Lang Yu
* @version 1.2
*/
public class JavaDocBasic {
/**
* @see "Main Function JavaDoc"
* @since JDK 1.4
* @param args The params of console
**/
public static void main(String args[]){
System.out.println("Hello World!");
}
}
例如有这样一小段代码,在我机器上我放在了D:/Source/work下,然后进入该目录下,使用下边的命令:
D:/Source/work>javadoc -d doc JavaDocBasic.java
通过这样的命令使用,在执行该命令了过后电脑上有下边这样的输出,而且去目录下边可以看到一个doc文件夹,用浏览器打开里面的index.html就可以看到生成的文档的内容:
Loading source file JavaDocBasic.java...
Constructing Javadoc information...
Standard Doclet version 1.6.0_16
Building tree for all the packages and classes...
Generating doc/JavaDocBasic.html...
Generating doc/package-frame.html...
Generating doc/package-summary.html...
Generating doc/package-tree.html...
Generating doc/constant-values.html...
Building index for all the packages and classes...
Generating doc/overview-tree.html...
Generating doc/index-all.html...
Generating doc/deprecated-list.html...
Building index for all classes...
Generating doc/allclasses-frame.html...
Generating doc/allclasses-noframe.html...
Generating doc/index.html...
Generating doc/help-doc.html...
Generating doc/stylesheet.css...
——[$]一个使用{@link}的例子——
/**
* @author Lang Yu
* @see java.lang.String
*/
public class JavaDocLink {
private int a;
private int b;
/**
* {@link #getAdd() getAdd()} Method
* @return the result of (a + b)
**/
public int getAdd(){
return a + b;
}
}
同样的方式生成对应的文档,就可以看到@link的效果了,讲到这里Java注释相关的内容就介绍了一半了,后边一般是与JavaDoc工具相关的,这个留到命令行工具里面去介绍。
iii.JDK和JRE的区别:
JDK(Java Development Kit的缩写),主要是指Java的开发环境和运行环境的一个集成开发包,这是面向开发人员的。
JRE(Java Runtime Enviroment的缩写),是指Java的运行环境,这一部分不包括开发环境,这是面向用户的。
JDK主要目的是为了开发、修改、调试、部署作为主体用途,因为有了JDK才能使用它提供的工具进行Java程序开发以及相关操作,这一开发包主要是针对开发人员的。JRE只是一个Java的运行环境,这里有一点需要说明的是,当一个程序开发完了需要在PC上运行的时候,该PC必须要提供一个JRE,也就是Java的运行环境,否则使用Java开发的软件是不能够直接运行在PC机上的,任何一个系统如果要运行Java的应用程序是必须依赖一个JRE的,有了这个运行时,才使得Java开发的软件可以无缝运行在操作系统上,而且对于像Tomcat这种服务器而言,JRE还远远不够,必须要JDK才能起到一定的作用,【*:这里提一下,Tomcat安装在搜索JDK的时候必须是JDK的路径,而不是JRE的路径,也就是说如果依赖Tomcat进行开发不能单纯配置一个JRE环境。】
其他相关知识参考一下网上的文档:
[1]为什么Sun要让JDK安装两套相同的JRE?这是因为JDK里面有很多用Java所编写的开发工具(如javac.exe、jar.exe等),而且都放置在 /lib/tools.jar 里。从下面例子可以看出,先将tools.jar改名为tools1.jar,然后运行javac.exe,显示如下结果: Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/tools/javac /Main 这个意思是说,你输入javac.exe与输入 java -cp c:/jdk/lib/tools.jar com.sun.tools.javac.Main 是一样的,会得到相同的结果。从这里我们可以证明javac.exe只是一个包装器(Wrapper),而制作的目的是为了让开发者免于输入太长的指命。而且可以发现/lib目录下的程序都很小,不大于2 9K,从这里我们可以得出一个结论。就是JDK里的工具几乎是用Java所编写,所以也是Java应用程序,因此要使用JDK所附的工具来开发Java程序,也必须要自行附一套JRE才行,所以位于C:/Program Files/Java目录下的那套JRE就是用来运行一般Java程序用的。
[2]如果一台电脑安装两套以上的JRE,谁来决定呢?这个重大任务就落在java.exe身上。Java.exe的工作就是找到合适的JRE来运行Java程序。 Java.exe依照底下的顺序来查找JRE:自己的目录下有没有JRE;父目录有没有JRE;查询注册表: [HKEY_LOCAL_MACHINE/SOFTWARE/JavaSoft/Java Runtime Environment] 所以java.exe的运行结果与你的电脑里面哪个JRE被执行有很大的关系。
[3]介绍JVM JRE目录下的Bin目录有两个目录:server与client。这就是真正的jvm.dll所在。 jvm.dll无法单独工作,当jvm.dll启动后,会使用explicit的方法(就是使用Win32 API之中的LoadLibrary()与GetProcAddress()来载入辅助用的动态链接库),而这些辅助用的动态链接库(.dll)都必须位于jvm.dll所在目录的父目录之中。因此想使用哪个JVM,只需要设置PATH,指向JRE所在目录底下的jvm.dll。
iv.Java标识符:
标识符是指可被用来为类、变量或方法等命名的字符序列,换言之,标识符就是用户自定义的名称来标识类、变量或方法等。更简单的说,标识符就是一个名字。
标识符的选择并不是任意的,Java语言规定标识符由字母、数字、下划线和美元符号($)组成,并且第一个字符不能是数字,例如以下都是合法的标识符:
num、user3、price$、book_name、MIN_VALUE
Java标识符中的字符是区分大小写的,如name和Name是两个不同的标识符。Java中所谓的字母并不只包含英文字母、数字及一些常用符号。Java语言使用的是Unicode标准字符集中的字符,Unicode字符集最多可以识别65536个字符,其前128个字符与ASCII码表中的字符对应。其余的字符中就包含了世界上大部分语言中的“字母表”中的字母,大部分国家“字母表”中的字母都是Unicode字符集中的一个字符,如汉字中的“礼”字就是Unicode字符集中的第31036个字符。因此,Java可使用的字符不仅可以是英文字母等,也可以是汉字、朝鲜文、俄文、希腊字母以及其他许多语言中的文字。
总结起来:
[1]Java标识符只能由数字、字母、下划线“_”或“$”符号以及Unicode字符集组成
[2]Java标识符必须以字母、下划线“_”或“$”符号以及Unicode字符集开头
[3]Java标识符不可以是Java关键字、保留字(const、goto)和字面量(true、false、null)
[4]Java标识符区分大小写,是大小写敏感的
合法标识符举例:
_哈哈、哈哈、_name、name、$dollor、$123、哈1哈
非法标识符举例:
1name、1Hello、 H%llo
——[$]标识符举例——
package org.susan.java.basic;
public class IdentityTester {
public static void main(String args[]){
// 因为中文是Unicode字符集,所以是可以作为标识符的
String _哈哈 = "Hello";
String 哈哈 = "You are very good";
int _name = 0;
int name = 0;
float $dollor = 0.0f;
String 哈1哈 = "Hello One Hello";
//String 1Hello = "";
//String H%llo = "";
}
}
上边的代码段里面可以知道,合法标识符和非合法表示符的区别,这里特别需要注意的是中文汉字、朝鲜文、俄文、希腊字母以及其他文字,只要这些文字是属于Unicode字符集的,那么这些都是可以定义为标识符的,上边就是一个最简单的例子。
3.数据类型
数据类型在计算机语言里面,是对内存位置的一个抽象表达方式,可以理解为针对内存的一种抽象的表达方式。接触每种语言的时候,都会存在数据类型的认识,有复杂的、简单的。各种数据类型都需要在学习初期去了解,Java是强类型语言,所以Java对于数据类型的规范相对严格。数据类型是语言的抽象原子概念,可以说是语言中最基本的单元定义,在Java里面,本质上数据类型分为两种:简单类型和复杂类型。
简单类型:简单数据类型是不能简化的、内置的数据类型、由编程语言本身定义,它表示了真实的数字、字符和整数。
复杂类型:Java语言本身不支持C++中的结构(struct)或联合(union)数据类型,它的复合数据类型一般都是通过类或接口进行构造,类提供了捆绑数据和方法的方式,同时可以针对程序外部进行信息隐藏。
i.Java中的基本类型:
1)概念:
Java中的简单类型从概念上分为四种:实数、整数、字符、布尔值。但是有一点需要说明的是,Java里面有八种原始类型,其列表如下:
实数:double、float
整数:byte、short、int、long
字符:char
布尔值:boolean
复杂类型和基本类型的内存模型本质上不一样,简单数据类型的存储原理:所有的简单数据类型不存在“引用”概念,简单数据类型直接存储在内存中的内存栈上,数据本身的值存储在栈空间里面,而Java语言里面只有这八种数据类型是这种存储模型;而其他的只要是继承于Object类的复杂数据类型都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”是存储在有序的内存栈上的,而对象本身的值存储在内存堆上的。
2)原始类型特征:
Java的简单数据讲解列表如下:
int:int为整数类型,在存储的时候,用4个字节存储,范围为-2,147,483,648到2,147,483,647,在变量初始化的时候,int类型的默认值为0。
short:short也属于整数类型,在存储的时候,用2个字节存储,范围为-32,768到32,767,在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。
long:long也属于整数类型,在存储的时候,用8个字节存储,范围为-9,223,372,036,854,775,808到9,223,372,036, 854,775,807,在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。
byte:byte同样属于整数类型,在存储的时候,用1个字节来存储,范围为-128到127,在变量初始化的时候,byte类型的默认值也为0。
float:float属于实数类型,在存储的时候,用4个字节来存储,范围为32位IEEEE 754单精度范围,在变量初始化的时候,float的默认值为0.0f或0.0F,在初始化的时候可以写0.0。
double:double同样属于实数类型,在存储的时候,用8个字节来存储,范围为64位IEEE 754双精度范围,在变量初始化的时候,double的默认值为0.0。
char:char属于字符类型,在存储的时候用2个字节来存储,因为Java本身的字符集不是用ASCII码来进行存储,是使用的16位Unicode字符集,它的字符范围即是Unicode的字符范围,在变量初始化的时候,char类型的默认值为'u0000'。
boolean:boolean属于布尔类型,在存储的时候不使用字节,仅仅使用1位来存储,范围仅仅为0和1,其字面量为true和false,而boolean变量在初始化的时候变量的默认值为false。
——[$]提供一个字面量赋值的例子——
package org.susan.java.basic;
public class AssignTester {
public static void main(String args[]){
int x,y;//定义x,y变量
float f = 12.34f; //定义float类型的变量并赋值
double w = 1.234;//定义double类型变量并且赋值
boolean flag = true ; //指定变量flag为boolean型,且赋初值为true
char c ; //定义字符型变量c
String str ; //定义字符串变量str
String str1 = " Hi " ; //指定变量str1为String型,且赋初值为Hi
c = 'A' ; //给字符型变量c赋值'A'
str = " bye " ; //给字符串变量str赋值"bye"
x = 12 ; //给整型变量x赋值为12
y = 300; //给整型变量y赋值为300
}
}
3)自动拆箱(AutoBox):
Java里面,每一种原始类型都对应着相应的包装类型,在JDK1.5之前(不包含JDK1.5),当包装类和原始类型进行相互转换的时候,需要调用包装类型的方法进行转换,不能通过操作符进行直接的计算。下边是一个原始类型和包装类型的一个对应表:
原始类型 |
对应的包装类型 |
默认值 |
存储格式 |
数据范围 |
short |
java.lang.Short |
0 |
2个字节 |
-32,768到32767 |
int |
java.lang.Integer |
0 |
4个字节 |
-2,147,483,648到2,147,483,647 |
byte |
java.lang.Byte |
0 |
1个字节 |
-128到127 |
char |
java.lang.Character |
/u0000 |
2个字节 |
Unicode的字符范围 |
long |
java.lang.Long |
0L或0l |
8个字节 |
-9,223,372,036,854,775,808到9,223,372,036, 854,775,807 |
float |
java.lang.Float |
0.0F或0.0f |
4个字节 |
32位IEEEE 754单精度范围 |
double |
java.lang.Double |
0.0或0.0D(d) |
8个字节 |
64位IEEE 754双精度范围 |
boolean |
java.lang.Boolean |
false |
1位 |
true(1)或false(0) |
简单看看下边这段代码:
package org.susan.java.basic;
public class AutoBoxTester {
public static void main(String args[]){
Integer integer = new Integer(12);
int integer2 = 33;
System.out.println(integer + integer2);
}
}
这段代码在JDK 1.5版本以上可以通过编译,而且不会报错,运行结果如下输出:
45
但是如果这段代码在JDK 1.4上边编译就会有问题了,因为在JDK 1.4的规范里面Integer属于一个包装类型,而int是原始类型,如果一个包装类型和原始类型要进行想对应的运算的时候,需要进行转换操作,直接将Integer类型转换称为原始类型操作,否则二者是不允许相加的,可以试试将上边代码用1.4版本进行编译:
javac -source 1.4 AutoBoxTester.java
就会收到下边的异常:
AutoBoxTester.java:5: operator + cannot be applied to java.lang.Integer,int
System.out.println(integer + integer2);
为什么呢?其实编译器给的信息很明显,使用JDK 1.5进行编译可以直接通过而且不会报错,是因为JDK 1.5提供了自动拆箱和自动装箱的功能,而JDK 1.4里面如果要使得上边的代码段可以编译通过,必须做一个简单的修改:
public class AutoBoxTester {
public static void main(String args[]){
Integer integer = new Integer(12);
int integer2 = 33;
System.out.println(integer.intValue() + integer2);
}
}
改成上边代码段了过后,在JDK 1.4平台下就可以得到输出:
45
从上边的例子可以看出,在JDK 1.5之前,如果要针对包装类进行数值计算,必须要将包装类直接转化称为原始类型,否则操作符本身是不会支持包装类的操作的,但是在JDK 1.5以及以后就没有这个限制了。
【简单总结:自动拆箱的意思就是不需要经过用户手工编程,编译器会直接识别包装类和原始类型相互之间的转换以及运算,并且把包装类型拆成原始类型进行代码里面规定的数值运算或者其他操作,这功能JDK的最低支持版本是1.5。其实对Java语言本身而言,Integer这种封装类实际上就是Java里面继承于Object的类的对象实例,只是在1.4之前,必须调用方法xxxValue()来完成手工拆箱的操作,只是这个在JDK 1.5不会有此限制。】
4)类型转换:
Java里面的类型转换包括两种:自动转换(隐式转换);强制转换(显示转换)
[1]自动转换:
条件:A.这两种类型是兼容的;B.目的类型数的范围(位数)比来源类型的大
当以上2个条件都满足的时候,拓宽转换(widening conversion)就会自动发生,例如,int类型范围比所有byte类型的合法范围大,因此不要求显示的强制转换语句。对于拓宽转换,兼容程度可以看下边的继承树:
java.lang.Object
|—java.lang.Boolean
|—java.lang.Character
|—java.lang.Number
|—java.lang.Byte
|—java.lang.Float
|—java.lang.Integer
|—java.lang.Long
|—java.lang.Short
|—java.lang.Double
从上边的继承树可以可以看到,Boolean类型、Character类型、Number类型是两两不兼容的,所以在隐式转换的过程,不兼容的类型是不能进行自动类型转换的
[2]强制转换:
尽管自动类型转换是很有帮助的,但并不能满足所有的编程需要。如果2种类型是兼容的,那么Java 将自动地进行转换。例如,把int 类型的值赋给long 类型的变量,总是可行的。然而,不是所有的类型都是兼容的,因此,不是所有的类型转换都是可以隐式实现的。例如,没有将double 型转换为byte 型的定义。幸好,获得不兼容的类型之间的转换仍然是可能的。要达到这个目的,你必须使用一个强制类型转换,它能完成两个不兼容的类型之间的显式变换。它的通用格式如下:
(target-type)value
[3]关于类型的自动提升,遵循下边的规则:
所有的byte、short、char类型的值将提升为int类型;
如果有一个操作数是long类型,计算结果是long类型;
如果有一个操作数是float类型,计算结果是float类型;
如果有一个操作数是double类型,计算结果是double类型;
自动类型转换图如下:
byte->short(char)->int->long->float->double
如果是强制转换的时候,就将上边的图反过来
[4]转换附加:
当两个类型进行自动转换的时候,需要满足条件:【1】这两种类型是兼容的,【2】目的类型的数值范围应该比源转换值的范围要大。(上边已经讲过了)而拓展范围就遵循上边的自动类型转换树,当这两个条件都满足的时候,拓展转换才会发生,而对于几个原始类型转换过程,根据兼容性boolean和char应该是独立的,而其他六种类型是可以兼容的,在强制转换过程,唯独可能特殊的是char和int是可以转换的,不过会使用char的ASCII码值比如:
int a = (int)'a';
a的值在转换过后输出的话,值为97;
[5]基础类型的几个常见的代码例子:
——[$]附加转换和精度丢失——
package org.susan.java.basic;
public class CastingNumbers {
public static void main(String args[]){
double above = 1.7;
double below = 0.4;
System.out.println("above:" + above);
System.out.println("below:" + below);
System.out.println("(int)above:" + (int)above);
System.out.println("(int)below:" + (int)below);
System.out.println("(char)('a' + above):" + (char)('a' + above));
System.out.println("(char)('a' + below):" + (char)('a' + below));
}
}
可以分析上边这段代码的输出:
above:1.7
below:0.4
(int)above:1
(int)below:0
(char)('a' + above):b
(char)('a' + below):a
从上边的输出结果可以知道,在double到int的强制转换中,会发现精度丢失的情况,而且这种精度丢失是不考虑四舍五入的,是直接将浮点部分舍弃掉,从上边的输出就可以知道了;而且当int以及double转化称为char的时候,会使用上边提及的附加转换的结果,当'a'加了1过后进行char转换会生成b。
——[$]进制转换——
package org.susan.java.basic;
/**
*进制相互转换的例子,以十进制为基础
**/
public class SystemTester {
public static void main(String args[]){
//二进制和十进制相互转换
int integer = 117;
String binary = Integer.toBinaryString(integer);
System.out.println("Binary value of " + integer + " is " + binary);
String inputBinary = "100010011";
int bResult = Integer.parseInt(inputBinary,2);
System.out.println("Integer value of '" + inputBinary + "' is " + bResult);
//十进制和八进制的相互转换
int integer2 = 1024;
String octal = Integer.toOctalString(integer2);
System.out.println("Octal value of " + integer2 + " is " + octal);
String inputOctal = "712364";
int oResult = Integer.parseInt(inputOctal, 8);
System.out.println("Integer value of " + inputOctal + " is " + oResult);
//十进制和十六进制的相互转换
int integer3 = 4096;
String hex = Integer.toHexString(integer3);
System.out.println("Hex value of " + integer3 + " is " + hex);
String inputHex = "FEDD34";
int hResult = Integer.parseInt(inputHex, 16);
System.out.println("Integer value of " + inputHex + " is " + hResult);
}
}
上边这段代码的输出为:
Binary value of 117 is 1110101
Integer value of '100010011' is 275
Octal value of 1024 is 2000
Integer value of 712364 is 234740
Hex value of 4096 is 1000
Integer value of FEDD34 is 16702772
——[$]字符类型例子——
package org.susan.java.basic;
/**
*字符类型的例子
**/
public class CharTester {
public static void main(String args[]){
//Char和整数之间的相互转换
char ch1 = 'a';
char ch2 = 66;
System.out.println("---------------");
System.out.println("ch1 is " + ch1);
System.out.println("ch2 is " + ch2);
//Character里面的方法
System.out.println("---------------");
System.out.println("isLowerCase:Z——" + Character.isLowerCase('Z'));
System.out.println("isUpperCase:A——" + Character.isUpperCase('A'));
System.out.println("isLetter:A——" + Character.isLetter('A'));
System.out.println("isLetter:2——" + Character.isLetter('3'));
System.out.println("isLetterOrDigit:%——" + Character.isLetterOrDigit('%'));
System.out.println("isDigit:%——" + Character.isDigit('%'));
System.out.println("isDigit:0——" + Character.isDigit('0'));
System.out.println("isWhitespace: ' '——" + Character.isWhitespace(' '));
System.out.println("isWhitespace: A——" + Character.isWhitespace('A'));
//从字符串里面抽取Char
System.out.println("---------------");
String inputValue = "123456";
System.out.println("Get 3 " + inputValue.charAt(2));
System.out.println(containsOnlyNumbers("123456"));
System.out.println(containsOnlyNumbers("123abc456"));
}
//检测某个字符串是否全部是数字
private static boolean containsOnlyNumbers(String str){
for(int i = 0; i < str.length(); i++ ){
if(!Character.isDigit(str.charAt(i))){
return false;
}
}
return true;
}
}
上边这段代码提取了Char类里面的一部分方法,其他的方法可以去查阅API的内容,输出为下:
---------------
ch1 is a
ch2 is B
---------------
isLowerCase: Z——false
isUpperCase: A——true
isLetter: A——true
isLetter: 2——false
isLetterOrDigit: %——false
isDigit: %——false
isDigit: 0——true
isWhitespace: ' '——true
isWhitespace: A——false
---------------
Get 3 3
true
false
Java的基本类型里面有几点需要说明:
[1]char类型是无符号16位整数,子面值必须用单引号括起来,如:'a'
[2]String在Java里面是类,直接父类是java.lang.Object,所以String不属于Java里面的原始类型
[3]长整数字有一个后缀为“L”或者“l”,八进制数字前缀为“0”,十六进制的前缀为“0x”
[4]默认的基本浮点类型为double
[5]float数据类型有一个后缀为“F”或“f”,Double数据类型后边可以跟“D”或者“d”,也可以不跟
[6]char类型可以使用通用的转义字符,但是不是ASCII码,应该是Unicode格式的如'/u0000'
5)Java的浮点精度:
[1]精确的浮点运算:
在Java里面,有时候为了保证数值的准确性需要精确的数据,先提供一个例子就可以发现问题了:
package org.susan.java.basic;
public class FloatNumberTester {
public static void main(String args[]){
System.out.println(0.05+0.01);
System.out.println(1.0 - 0.42);
System.out.println(4.015 * 100);
System.out.println(123.3 / 100);
}
}
按照我们的期待,上边应该是什么结果呢,但是看输出我们就会发现问题了:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
这样的话这个问题就相对严重了,如果我们使用123.3元交易,计算机却因为1.2329999999999999而拒绝了交易,岂不是和实际情况大相径庭
[2]四舍五入:
另外的一个计算问题,就是四舍五入。但是Java的计算本身是不能够支持四舍五入的,比如:
package org.susan.java.basic;
public class GetThrowTester {
public static void main(String args[]){
System.out.println(4.015 * 100.0);
}
}
这个输出为:
401.49999999999994
所以就会发现这种情况并不能保证四舍五入,如果要四舍五入,只有一种方法java.text.DecimalFormat:
package org.susan.java.basic;
import java.text.DecimalFormat;
public class NumberFormatMain {
public static void main(String args[]){
System.out.println(new DecimalFormat("0.00").format(4.025));
System.out.println(new DecimalFormat("0.00").format(4.024));
}
}
上边代码输出为:
4.02
4.02
发现问题了么?因为DecimalFormat使用的舍入模式,关于DecimalFormat的舍入模式在格式化一章会讲到
[3]浮点输出:
Java浮点类型数值在大于9999999.0就自动转化成为科学计数法,看看下边的例子:
package org.susan.java.basic;
public class FloatCounter {
public static void main(String args[]){
System.out.println(9969999999.04);
System.out.println(199999999.04);
System.out.println(1000000011.01);
System.out.println(9999999.04);
}
}
输出结果为:
9.96999999904E9
1.9999999904E8
1.00000001101E9
9999999.04
但是有时候我们不需要科学计数法,而是转换成为字符串,所以这样可能会有点麻烦。
[4]BigInteger类和BigDecimal介绍:
BigInteger用法:
package org.susan.java.basic;
import java.math.BigInteger;
public class BigIntTester {
public static void main(String args[]){
BigInteger leftInteger = new BigInteger("1234567890123400");
BigInteger rightInteger = BigInteger.valueOf(123L);
//大数加法
BigInteger resultInteger = leftInteger.add(rightInteger);
System.out.println("Add:" + resultInteger);
//大数除法
resultInteger = leftInteger.divide(rightInteger);
System.out.println("Divide:" + resultInteger);
//大数减法
resultInteger = leftInteger.subtract(rightInteger);
System.out.println("Substract:" + resultInteger);
//大数乘法
resultInteger = leftInteger.multiply(rightInteger);
System.out.println("Multiply:" + resultInteger);
}
}
上边的输出为:
Add:1234567890123523
Divide:10037137318076
Substract:1234567890123277
Multiply:151851850485178200
在BigInteger里面有很多运算,这里只是提供了普通的+、-、*、/的运算,可以参考以下BigInteger的方法列表:
BigInteger abs():返回BigInteger的绝对值
BigInteger add(BigInteger val):返回为(this + val)的BigInteger,做加法
BigInteger and(BigInteger val):返回为(this & val)的BigInteger,进行位与运算
BigInteger andNot(BigInteger val):返回为(this & ~val)的BigInteger
int bitCount():返回此BigInteger的二进制补码表示形式中与符号不同的位的常量
int bitLength():返回此BigInteger的最小的二进制补码表示形式的位数,不包括符号位
BigInteger clearBit(int n):返回其值与清除了指定位的此BigInteger等效的BigInteger
int compareTo(BigInteger val):将此BigInteger与指定的BigInteger进行比较
BigInteger divide(BigInteger val):返回值为(this/val)的BigInteger,做除法
BigInteger[] divideAndRemainder(BigInteger val):包含返回(this/val)后跟(this & val)的两个BigInteger的数组
double doubleValue():将此BigInteger转换为double
boolean equals(Object s):比较此BigInteger与指定的Object的相等性
BigInteger flipBit(int n):返回其值与对此BigInteger进行指定位翻转过后的值等效的BigInteger
float floatValue():将此BigInteger转换为float
BigInteger gcd(BigInteger val):返回一个BigInteger,值为abs(this)和abs(val)的最大公约数
int getLowestSetBit():返回此 BigInteger 最右端(最低位)1 比特的索引(即从此字节的右端开始到本字节中最右端 1 比特之间的 0 比特的位数)
int hashCode():返回此BigInteger的哈希码
int intValue():将此BigInteger转换为int
boolean isProbablePrime(int certaintry):如果此 BigInteger 可能为素数,则返回 true,如果它一定为合数,则返回 false
long longValue():将此BigInteger转换为long
BigInteger max(BigInteger val):返回this和val的最大值
BigInteger min(BigInteger val):返回this和val的最小值
BigInteger mod(BigInteger val):返回this mod val的结果,取模运算
BigInteger modInverse(BigInteger val):返回其值为 (this-1 mod val) 的 BigInteger
BigInteger modPow(BigInteger exponent,BigInteger val):返回其值为 (thisexponent mod val) 的 BigInteger
BigInteger multiply(BigInteger val):返回值为(this*val)的BigInteger
BigInteger negate():返回值是(-this)的BigInteger
BigInteger nextProbablePrime():返回大于此BigInteger的可能为素数的第一个整数
BigInteger not():返回其值为(~this)的BigInteger
BigInteger or(BitInteger val):返回值为(this|val)的BigInteger
BigInteger pow(int exponent):返回其值为 (thisexponent) 的 BigInteger
static BigInteger probablePrime(int bitLength,Random rnd):返回有可能是素数的、具有指定长度的正 BigInteger
BigInteger remainder(BigInteger val):返回其值为 (this % val) 的 BigInteger
BigInteger setBit(int n):返回其值与设置了指定位的此 BigInteger 等效的 BigInteger
BigInteger shiftLeft(int n):返回其值为 (this << n) 的 BigInteger
BigInteger shiftRight(int n):返回其值为 (this >> n) 的 BigInteger
int signum():返回此 BigInteger 的正负号函数
BigInteger substract(BigInteger val):返回其值为 (this - val) 的 BigInteger
BigInteger xor(BigInteger val):返回其值为 (this ^ val) 的 BigInteger
BigDecimal用法:
BigDecimal是Java提供的一个不变的、任意精度的有符号十进制数对象。它提供了四个构造器,有两个是用BigInteger构造,在这里我们不关心,我们重点看用double和String构造的两个构造器:
BigDecimal(double val):把一个double类型十进制数构造为一个BigDecimal对象实例
BigDecimal(String val):把一个以String表示的BigDecimal对象构造为BigDecimal对象实例
用一段代码来说明两个构造函数的区别:
package org.susan.java.basic;
import java.math.BigDecimal;
public class BigDecimalMain {
public static void main(String args[]){
System.out.println(new BigDecimal(123456789.01).toString());
System.out.println(new BigDecimal("123456789.01").toString());
}
}
看了输出结果过后,两种构造函数的区别一目了然:
123456789.01000000536441802978515625
123456789.01
BigDecimal舍入模式介绍:
舍入模式在java.math.RoundingMode里面:
RoundingMode.CEILING:向正无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN。注意,此舍入模式始终不会减少计算值
输入数字 |
使用CEILING舍入模式将数字舍入为一位数 |
5.5 |
6 |
2.5 |
3 |
1.1 |
2 |
1.0 |
1 |
-1.0 |
-1 |
-1.1 |
-1 |
-1.6 |
-1 |
-2.5 |
-2 |
-5.5 |
-5 |
RoundingMode.DOWN:向零方向舍入的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值
输入数字 |
使用DOWN舍入模式将数字舍入为一位数 |
5.5 |
5 |
2.5 |
2 |
1.1 |
1 |
-1.0 |
-1 |
-1.6 |
-1 |
-2.5 |
-2 |
-5.5 |
-5 |
RoundingMode.FLOOR:向负无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP。注意,此舍入模式始终不会增加计算值
输入数字 |
使用FLOOR舍入模式将输入数字舍入为一位 |
5.5 |
5 |
2.3 |
2 |
1.6 |
1 |
1.0 |
1 |
-1.1 |
-2 |
-2.5 |
-3 |
-5.5 |
-6 |
RoundingMode.HALF_DOWN:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN
输入数字 |
使用HALF_DOWN输入模式舍入为一位 |
5.5 |
5 |
2.5 |
2 |
1.6 |
2 |
1.0 |
1 |
-1.1 |
-1 |
-1.6 |
-2 |
-2.5 |
-2 |
-5.5 |
-5 |
RoundingMode.HALF_EVEN:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN。注意,在重复进行一系列计算时,此舍入模式可以在统计上将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。此舍入模式类似于 Java 中对 float 和 double 算法使用的舍入策略
输入数字 |
使用HALF_EVEN舍入模式将输入舍为一位 |
5.5 |
6 |
2.5 |
2 |
1.6 |
2 |
1.1 |
1 |
-1.0 |
-1 |
-1.6 |
-2 |
-2.5 |
-2 |
-5.5 |
-6 |
RoundingMode.HALF_UP:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN。注意,此舍入模式就是通常学校里讲的四舍五入
输入数字 |
使用HALF_UP舍入模式舍入为一位数 |
5.5 |
6 |
2.5 |
3 |
1.6 |
2 |
1.0 |
1 |
-1.1 |
-1 |
-1.6 |
-2 |
-2.5 |
-3 |
-5.5 |
-6 |
RoundingMode.UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException
输入数字 |
使用UNNECESSARY模式 |
5.5 |
抛出 ArithmeticException |
2.5 |
抛出 ArithmeticException |
1.6 |
抛出 ArithmeticException |
1.0 |
1 |
-1.0 |
-1.0 |
-1.1 |
抛出 ArithmeticException |
-1.6 |
抛出 ArithmeticException |
-2.5 |
抛出 ArithmeticException |
-5.5 |
抛出 ArithmeticException |
RoundingMode.UP:远离零方向舍入的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值
输入数字 |
使用UP舍入模式将输入数字舍入为一位数 |
5.5 |
6 |
1.6 |
2 |
1.1 |
2 |
1.0 |
1 |
-1.1 |
-2 |
-1.6 |
-2 |
-2.5 |
-3 |
-5.4 |
-6 |
——[$]示例代码:——
package org.susan.java.basic;
import java.math.BigDecimal;
import java.text.DecimalFormat;
/**
*使用舍入模式的格式化操作
**/
public class DoubleFormat {
public static void main(String args[]){
DoubleFormat format = new DoubleFormat();
System.out.println(format.doubleOutPut(12.345, 2));
System.out.println(format.roundNumber(12.335, 2));
}
public String doubleOutPut(double v,Integer num){
if( v == Double.valueOf(v).intValue()){
return Double.valueOf(v).intValue() + "";
}else{
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(num,BigDecimal.ROUND_HALF_UP).toString();
}
}
public String roundNumber(double v,int num){
String fmtString = "0000000000000000"; //16bit
fmtString = num>0 ? "0." + fmtString.substring(0,num):"0";
DecimalFormat dFormat = new DecimalFormat(fmtString);
return dFormat.format(v);
}
}
这段代码的输出为:
ii.数组、复杂类型
1)数组的定义、初始化:
在Java语言里面使用下边的方式定义数组:
Type[] arrayone;
Type arrayone[];
这里的Type可以是基本数据类型,也可以是Object类的子类的引用,arrayone在这里为一个Java合法的标识符
——[$]这里提供一个参考代码进行数组的定义和初始化——
package org.susan.java.basic;
import java.util.Arrays;
/**
*数组的定义和初始化过程
**/
public class ArraysMain {
public static void main(String args[]){
String[] arrays1 = new String[3];
String[] arrays2 = new String[]{"A","B","C"};
String[] arrays3 = {"A","B","C","D"};
System.out.println("Array 1:" + Arrays.asList(arrays1));
System.out.println("Array 2:" + Arrays.asList(arrays2));
System.out.println("Array 3:" + Arrays.asList(arrays3));
}
}
先看上边代码段的输出:
Array 1:[null, null, null]
Array 2:[A, B, C]
Array 3:[A, B, C, D]
这里简单总结一下Java里面定义、创建、初始化数组的一些细节问题:
- 定义数组一般用Type[] arrays或者Type arrays[]两种格式,Java里面推荐使用Type[] arrays这种方式定义数组
- 创建数组的时候可以使用三种方式创建,上边的arrays1创建了一个长度为3的字符串数组,但是未被初始化
- 初始化过程有两种方式,上边arrays2和arrays3是直接赋值,这种情况不仅仅创建了数组,而且进行了初始化的操作,还有一种方式像下边这样针对每个元素初始化
——[$]单项初始化——
package org.susan.java.basic;
import java.util.Arrays;
public class ArrayItemMain {
public static void main(String args[]){
String[] arrayStrings = new String[3];
for(int i = 0; i < arrayStrings.length; i++){
arrayStrings
= new String("String" + i);
}
System.out.println(Arrays.asList(arrayStrings));
}
}
上边的输出为:
[String0, String1, String2]
由此可以知道,在这样的循环里面已经将定义的长度为3的字符串数组的每一项都初始化了,而且值分别为String0,String1,String2,而且从上边可以知道,数组的元素访问使用索引进行访问,格式为:
arrays[index],这里需要特殊说明的是索引的范围:0 <= index < arrays.length;关于索引的范围需要特别注意,最小的index是0,最大的index值是length-1
}
System.out.println(Arrays.asList(arrayStrings));
}
}
上边的输出为:
[String0, String1, String2]
由此可以知道,在这样的循环里面已经将定义的长度为3的字符串数组的每一项都初始化了,而且值分别为String0,String1,String2,而且从上边可以知道,数组的元素访问使用索引进行访问,格式为:
arrays[index],这里需要特殊说明的是索引的范围:0 <= index < arrays.length;关于索引的范围需要特别注意,最小的index是0,最大的index值是length-1
2)多维数组:
Java里面不仅仅可以定义一纬数组,还可以定义多维数组,多维数组的定义如下:
int[][] arrays1 = new int[3][2];
int[][] arrays2 = new int[3][];
不合法的定义方式为:
int[][] arrays3 = new int[][2];
【*:这里定义的多维数组为两维数组,不仅仅如此,根据需要可以定义三维数组或者多维数组,其实在Java里面,二维数组就是数组的数组,也就是说在一纬数组的基础之上每一项又保存了一个数组的引用。】
——[$]提供一个多维数组的例子——
package org.susan.java.basic;
import java.util.Arrays;
import java.util.Random;
public class ArrayMultiMain {
public static void main(String args[]){
// 二维规则数组
int[][] arrays = new int[3][3];
Random random = new Random();
for( int i = 0; i < arrays.length; i++){
for( int j = 0; j < arrays
.length; j++){
arrays
[j] = random.nextInt(40);
}
}
printArray(arrays);
System.out.println("------------------------");
// 二维不规则数组
int[][] arrays1 = new int[3][];
for( int i = 0; i < arrays1.length; i++){
int innerLength = random.nextInt(6) + 1;
arrays1
= new int[innerLength];
for( int j = 0; j < arrays1
.length; j++){
arrays1
[j] = random.nextInt(40);
}
}
//System.out.println(Arrays.asList(arrays1))
printArray(arrays1);
System.out.println("------------------------");
int[][] arrays2 = {{12,33},{45,44,46,47},{56,12,9}};
printArray(arrays2);
}
private static void printArray(int [][] arrays){
for(int[] array:arrays){
for(int item:array){
System.out.print("[" + item + "],");
}
System.out.println();
}
}
}
这里运行的输出为:
[8],[16],[4],
[36],[2],[34],
[20],[35],[37],
------------------------
[4],
[8],[38],[37],[7],
[18],[17],[36],
------------------------
[12],[33],
[45],[44],[46],[47],
[56],[12],[9],
针对输出内容需要说明的有几点:
- 第一个数组的行列是定死的,但是里面的数据是随机的,所以每次运行可能里面盛放的项不一样
- 第二个数组为不规则的二维数组,行市定死的,但是每行的列数十不定的,每次运行可能都不一样
- 第三个数组在创建和初始化的时候因为是直接赋值,是一个死数组,所以第三那个数组每次输出都一样
3)Java中的动态数组和静态数组:【摘录自:http://developer.51cto.com/art/200905/122774.htm】
我们学习的数组都是静态数组,其实在很多的时候,静态数组根本不能满足我们编程的实际需要,比方说我需要在程序运行过程中动态的向数组中添加数据,这时我们的静态数组大小是固定的,显然就不能添加数据,要动态添加数据必须要用到动态数组,动态数组中的各个元素类型也是一致的,不过这种类型已经是用一个非常大的类型来揽括—Object类型。Object类是java.lang包中的顶层超类。所有的类型都可以与Object类型兼容,所以我们可以将任何Object类型添加至属于Object类型的数组中,能添加Object类型的的集合有ArrayList、Vector及LinkedList,它们对数据的存放形式仿造于数组,属于集合类,下面是他们的特点:
特点一、容量扩充性
从内部实现机制来讲ArrayList和Vector都是使用Objec的数组形式来存储的。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
特点二、同步性
ArrayList,LinkedList是不同步的,而Vestor是的。所以如果要求线程安全的话,可以使用ArrayList或LinkedList,可以节省为同步而耗费开销。但在多线程的情况下,有时候就不得不使用Vector了。当然,也可以通过一些办法包装ArrayList,LinkedList,使他们也达到同步,但效率可能会有所降低。
特点三、数据操作效率
ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置。所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作,最好选择LinkedList、ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快