三目运算符的嵌套处理:

时间:2024-03-04 10:15:29

Java讲义第三章学习笔记

 Chapter 3 数据类型和运算符

3.1  注释

 

--3.1.1   单行注释和多行注释

  单行注释就是在程序中注释一行代码,在Java中,将双斜线(//)放在需要注释的内容之前就可以了;多行注释是指一次性地将程序中的多行代码注释掉,在Java语言中,使用“/*”和“*/”

  将程序中需要注释的内容包含起来,“/*”表示注释开始,而“*/”表示注释结束。

public class CommentTest
{
    /*    
        这里面的内容全都是多行注释
        Java
    */
    public static void main(String[] args)    
    {
        //这是一行简单的注释
        System.out.println("Hello World!");
        // System.out.println("这行代码被注释了,不能被编译,执行!");
    
    }
}

  除此之外,添加注释也是调试程序的一个重要方法。如果觉得某段代码可能有问题,可以先把这段代码注释起来,让编译器忽略这段代码,再次编译、运行,如果程序可以正常执行,则可以说明错误就是由这段代码引起的,反之则说明不是由这段代码引起的。

 

--3.1.2   文档注释

  Java语言还提供了一种功能更强大的注释形式:文档注释。如果编写Java源代码时添加了合适的文档注释,然后通过JDK提供的javadoc工具可以直接将源代码里的文档注释提取成一份系统的API文档。

  Java11的API文档下载可以在官网上下载。

  由于文档注释适用于生成API文档的,而API文档主要用于说明类、方法、成员变量的功能。因此,javadoc工具只处理文档源文件在类、接口、方法、成员变量、构造器和内部类之前的注释,忽略其他地方的文档注释。而且javadoc工具默认只处理public或protected修饰的类、接口、方法、成员变量、构造器和内部类之前的文档注释。

  文档注释以斜线后紧跟两个星号(/**)开始,以星号后紧跟一个斜线(*/)结束,中间部分全部都是文档解释,会被提到提取到API文档中。

package lee;
/**
*Description:
*网站:<a href="http://www.crazyit.org">疯狂Java联盟</a><br>
*Copyright (C),2001-2020,Yeeku.H.Lee<br>
*This program is protected by copyright laws.<br>
*Date:<br>
*@author Yeeku.H.Lee kongyeeku@163.com
*@version 5.0
*
*/
public class JavadocTest{
    /**
     * 简单测试成员变量
     */
    protected String name;
    /**
     * 主方法,程序的入口
     */
     public static void main(String[] args)
     {
         System.out.println("Hello World!");
     }
}

 

package yeeku;
/**
*Description:
*网站:<a href="http://www.crazyit.org">疯狂Java联盟</a><br>
*Copyright (C),2001-2020,Yeeku.H.Lee<br>
*This program is protected by copyright laws.<br>
*Date:<br>
*@author Yeeku.H.Lee kongyeeku@163.com
*@version 5.0
*
*/
public class Test
{
    /**
     * 简单测试成员变量
     */
    public int age;
    /**
     * Test类的测试构造器
     */
     public Test() 
     {     
     }
}

  上面Java程序中粗体字标识部分就是文档注释。编写好上面的Java程序后,就可以使用javadoc工具提取这两个程序中的文档注释来生成API文档。javadoc命令的基本算法如下:

    javadoc 选项 Java 源文件|包

  javadoc命令可以对源文件、包生成API文档,在上面的语法格式中,Java源文件可以支持通配符,例如,使用 *.java 来代表当前路径下所有的Java源文件。javadoc的常用选项由如下几个。

  •     -d<directory>:   该选项指定一个路径,用于生成的API文档放到指定目录下。
  •     -windowtitle<text>:   该选项指定一个字符串,用于设置API文档的浏览器窗口标题。
  •     -doctitle<html-code>:   该选项指定一个HTML格式的文本,用于制定描述页面的标题。
  •     -header<html-code>:   该选项指定了一个HTML格式的文本,包含每个页面的页眉。

  在命令行窗口执行如下命令来为刚刚编写的两个Java程序生成API文档:

      javadoc  -d  apidoc  -windowtitle  测试  -doctitle  学习  javadoc  工具的测试  API  文档  -header  我的类  *Test.java                                                  

  除此之外,如果希望javadoc工具生成更详细的文档信息,例如为方法参数、方法返回值等生成详细的生命信息,则可利用javadoc标记。常用的javadoc标记如下。

    •  @author:     指定Java程序的作者。
    •     @version:     指定源文件的版本。
    •     @deprecated:     不推荐使用方法。
    •     @param:     方法的参数说明信息。
    •     @return:     方法返回值说明信息。
    •     @see:     “参见”,用于指定交叉参考的内容。
    •     @exception:     抛出异常的类型
    •     @throws:     抛出异常的类型,和@exception同义。

  下面的JavadocTagTest程序包含了一个hello方法,该方法的文档注释使用了@param 和 @return 等文档标记。

package yeeku;
/**
*Description:
*网站:<a href="http://www.crazyit.org">疯狂Java联盟</a><br>
*Copyright (C),2001-2020,Yeeku.H.Lee<br>
*This program is protected by copyright laws.<br>
*Date:<br>
*@author Yeeku.H.Lee kongyeeku@163.com
*@version 5.0
*
*/
public class JavadocTagTest
{
    /**
       *一个得到打招呼字符串的方法
       *@param name 该参数指定向谁打招呼
       *@return 返回打招呼的字符串
    */
      public string hello(String name)
      {
             return name + ". 你好!";
      }
}
            

  上面程序中为了能提取到文档中的@author 和 @version 等标记信息,在使用javadoc工具时增加 -author 和 -version 两个选项,按如下格式来运行javadoc命令:

    javadoc  -d  apidoc  -windowtitle  测试  -doctitle  学习javadoc工具测试API文档  -header  我的类  -version  -author  *Test.java                           

 

3.2   标识符和关键字

 

--3.2.1   分隔符

  1. 分号:Java语言采用分号(;)作为语句的分隔。
  2. 花括号:花括号的作用就是定义一个代码块,一个代码块是指 “{” 和 “}” 所包含的一段代码。
  3. 方括号:方括号的主要作用是访问数组元素,其后通常紧跟数组变量名。
  4. 圆括号:圆括号是一个功能非常丰富的分隔符:定义方法时必须使用圆括号来包含所有的形参声明,调用方法时必须使用圆括号来传入实参值;不仅如此,圆括号还可以将表达式中的某个部分括成一个整体,保证这个部分优先计算;除此之外,圆括号还可以作为强制类型转换符。
  5. 空格:Java语言是用空格来分隔一条语句的不同部分。Java语言中的空格包含空格符(Space)、制表符(Tab)和回车(Enter)等。
  6. 圆点:圆点(.)通常用作类/对象和它的成员(包括成员变量、方法和内部类)之间的分隔符,表现调用某个类或某个实例的指定成员。

 

--3.2.2   标识符规则

  标识符就是用于给程序中变量、类、方法命名的符号。Java语言的标识符必须以字母、下划线(_)、美元($)开头,后面可以跟任意数目的字母、数字、下划线(_)和美元符($)。此处的字母不局限于26个英文字母,而且可以包含中文字符、日文字符等。

  从Java9开始,不允许使用单独的下画线(_)作为标识符。也就是说,下划线必须与其他字符组合在一起才能作为标识符。

  使用标识符的规则:

    •    标识符可以由字母、数字、下划线(_)和美元符($)组成,其中数字不能打头。
    •       标识符不能是Java关键字和保留字,但可以包含关键字和保留字。
    •       标识符不能包含空格。
    •       标识符只能包含美元符($),不能包含@、#等其他特殊字符。

 

--3.2.3   Java关键字

  Java语言中有一些具有特殊用途的单词被称为关键字(keyword),当定义标识符时,不要让标识符和关键字相同,否则将引起错误。

  Java的所有关键字都是小写的,TRUE、FALSE、NULL都不是Java关键字。

  Java一共包含51个关键字。

Java    关键字

abstract

continue

for

new

switch

assert

default

if

package

synchronized

boolean

do

goto

private

this

break

double

implements

protected

throw

byte

else

import

public

throws

case

enum

instanceof

return

transient

catch

extends

int

short

try

char

final

interface

static

void

class

finally

long

strictfp

volatile

const

float

native

super

while

_(下划线)

 

 

 

 

 

 

 

 

               

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  在上面51个关键字中,enum是从Java 5新增的关键字,用于定义一个枚举。goto和const这两个关键字也被成为保留字,保留字的意思是,Java现在还未使用这两个关键字,

  但可能在未来的Java版本中使用这两个关键字;不仅如此,Java还提供了三个特殊的直接量(literal):true、false、null;Java语言的标识符也不能使用这三个特殊的直接量。

  从Java 10开始引入的 var 并不是关键字,它相当于一个可变的类型名,因此var依然可以作为标识符。

 

3.3   数据类型分类

  Java语言是强类型语言,其包含两方面的含义:①所有的变量必须先声明、后使用;②指定类型的变量只能接受类型与之匹配的值。这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值,限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作,并确定了这些操作的含义。

  Java语言支持的类型分为两类:基本类型(Primitive Type)和引用类型(Reference Type)。

  基本类型包括boolean类型和数值类型。数值类型有整数类型和浮点类型。整数类型包括byte、short、int、long、char,浮点型类型包括 float 和 double 。

注:char 代表字符型,实际上字符型也是一种整数类型,相当于无符号整数类型。

  引用类型包括类、接口和数组类型,还有一种特殊的null类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是Java语言没有指针这个说法。

  空类型(null type)就是null值的类型,这种类型没有名称。因为null类型没有名称,所以不可能声明一个null类型的变量或者转换到null类型。空引用(null)是null类型变量唯一的值。空引用(null)可以转换为任何引用类型。

注:空引用(null)只能被转换成引用类型,不能转换成剧本类型,因此不要把一个null值赋给基本数据类型的变量。

 

3.4   基本数据类型

 

--3.4.1   整形

  通常说的整形,实际指的是下面4种类型。

    •   byte:   一个byte类型整数在内存里占8位,表数范围是-128(-27)~127(27-1)。
    •   short:   一个short类型整数在内存里占16位,表数范围是-32768(-215)~32767(215-1)。
    •   int:   一个int类型整数在内存里占32位,表数范围是-2147483648(-231)~2147483647(231-1)。
    •   long:   一个long类型整数在内存里占64位,表数范围是(-263)~(263-1)。

   两种情况:

    1. 如果直接将一个较小的整数值(在byte或short类型的表数范围内)赋给一个byte或short变量,系统会自动把这个整数值当成byte或者short变量,系统会自动把这个整数值当成byte或者short类型来处理。
    2. 如果使用一个巨大的整数值(超出了int类型的表数范围)时,Java不会自动把这个整数值当成long类型来处理。如果希望系统把一个整数值当成long类型来处理,应在这个整数值后增加 l 或者L作为后缀。通常推荐使用L,因为英文字母 l 很容易和数字 1 搞混。
//下面代码时正确的,系统会自动把56当成byte类型处理
    byte a = 56;
/*
下面代码时错误的,系统不会把9999999999999当成long类型处理
所以超出int的表数范围,从而引起错误。
*/
//    long bigValue = 9999999999999;
//    下面代码时正确的,在巨大的整数值后使用L后缀,强制使用long类型
    long bigValue2 = 92232272036854775807L;

注:可以把一个较小的整数值(在int类型的表数范围以内)直接赋给一个long类型的变量,这并不是因为Java会把这个较小的整数值当成long类型来处理,Java依然把这个整数值当成int类型来处理,只是因为int类型的值会自动类型转换到long类型。

 

  Java中整数值有4种表示方式:十进制、二进制、八进制和十六进制,其中二进制的整数以0b或0B开头;八进制的整数以0开头;十六进制的整数以0x或者0X开头,其中10~15分别以a~f(此处的a~f不区分大小写)来表示。

//以0开头的整数值是八进制的整数
int octalValue = 013;
//以0x或0X开头的整数值是十六进制的整数
int hexValue1 = 0x13;
int hexValue2 = 0Xaf;

  从 Java7 开始新增了对二进制整数的支持,二进制整数以0b或者0B开头,程序片段如下。

//定义两个8位的二进制整数
int binVal1 = 0b11010100;
byte binVal2 = 0B01101001;
//定义一个32位的二进制整数,最高位是符号位。
int binVal3 = 0B1000000000000000000000000000011;
System.out.println(binVal1); //输出212
System.out.println(binVal2); //输出105
System.out.println(binVal3); //输出-2147483645

  从上可知,当定义32位的二进制整数时,最高位其实是符号位,当符号位是1时,表明它是一个负数,负数在计算机里是以补码的形式存在的,因此还需要转换成原码。

  如果在二进制整数后添加 l 或者 L 后缀,那么这个二进制整数默认占64位,其中第64位是符号位。如下所示。

/*
定义一个8位二进制整数,该数值默认占32位,因此它是一个正数
只是强制类型转换成byte时产生了溢出,最终导致binVal4变成了-23
*/
byte binVal4 = (byte) 0b11101001;
/*
定义一个32位二进制整数,最高位是1
但由于数值后添加了L后缀,因此该整数实际占64位,第32位的1不是符号位。
因此binVal5的值等于2的31次方+2+1
*/
long binVal5 = 0B1000000000000000000000000000011;
System.out.println(binVal4); //输出-23
System.out.println(binVal5); //输出2147483651

 

--3.4.2   字符型

  字符型通常用于表示单个字符,字符型值必须使用单引号(\')括起来。Java语言使用16位的Unicode字符集作为编码方式,而Unicode被设计成支持世界上所有书面语言的字符,包括中文字符,因此Java程序支持各种语言的字符。

  字符型值有如下三种表示形式。

    •   直接通过单个字符来指定字符型值,例如 \'A\'、\'9\'等。
    •   通过转义字符表示特殊字符型值,例如\'\n\'、\'\t\'等。
    •   直接使用Unicode来表示字符型值,格式是 \'\uXXXX\',其中XXXX代表一个十六进制的整数。

  Java语言中常用的转义字符如表3.2所示。

Java语言常用的转义字符

转义字符

说    明

Unicode 表示方式

\b

退格符

\u0008

\n

换行符

\u000a

\r

回车符

\u000d

\t

制表符

\u0009

\”

双引号

\u0022

\’

单引号

\u0027

\\

反斜线

\u005c

  字符型值也可以采用十六进制编码的方式来表示,范围是\'\u0000\'~\'\uFFFF\',一共可以表示65536个字符,其中前256个(\'\u0000\'~\'\u00FF\')字符和ASCII码中的字符完全重合。

  由于计算机底层保存字符时,实际是保存该字符对应的编号,因此char类型的值也可直接作为整型值来使用,它相当于一个16位的无符号整数,表数范围是0~65535。

提示:char 类型的变量、值完全可以参与加、减、乘、除等数学运算,也可以比较大小——实际上都是用该字符对应的编号参与运算。

  如果把 0~65535 范围内的一个int整数赋给char类型变量,系统会自动把这个int整数当成char类型来处理。

public class CharTest
{
    public static void main(String[] args) {
        //直接指定单个字符作为字符值
        char aChar = \'a\';
        //使用转义字符来作为字符值
        char enterChar = \'\r\';
        //使用Unicode编码值来指定字符值
        char ch = \'\u9999\';
        //将输出一个‘香’字符
        System.out.println(ch);
        //定义一个‘疯’字符
        char zhong = \'疯\';
        //直接将一个char变量当成int类型变量使用
        int zhongValue = zhong;
        System.out.println(zhongValue);
        //直接把一个0~65535范围内的int整数赋给一个char变量
        char c = 97;
        System.out.println(c);
    }
}

Java没有提供表示字符串的基本数据类型,而是通过String类来表示字符串,由于字符串由多个字符组成,因此字符串要使用双引号括起来。如下:

//下面代码定义了一个s变量,他是一个字符串实例的引用,他是一个引用类型的变量
String s = "沧海月明珠有泪,蓝田日暖玉生烟。";

 

--3.4.3   浮点型

  Java的浮点类型有两种:float 和 double 型。Java的浮点类型有固定的表数范围和字段长度。字段长度和表数范围与机器无关。Java的浮点数遵循IEEE 754标准,采用二进制数据的科学记数法来表示浮点数,对于float型数值,第一位是符号位,接下来八位表示指数,再接下来的23位表示尾数;对于double类型数值,第一位也是符号位,接下来11位表示指数,再接下来52位表示尾数。

  double类型代表双进度浮点数,float类型代表单精度浮点数。一个double类型的数值占8字节、64位,一个float类型的数值占4字节、32位。

  Java语言的浮点数有两种表示形式。

    •   十进制数形式:这种形式就是简单的浮点数,例如:5.12、512.0、.512。浮点数必须包含一个小数点,否则会被当成int类型处理。
    •   科学记数法形式:例如 5.12e2,5.12E2。

  必须指出的是,只有浮点类型的数值才可以使用科学记数法形式表示,例如,51200是一个 int 类型的值,但512E2则是浮点类型的值。

  Java语言的浮点类型默认是double类型,如果希望Java把一个浮点类型值当成float类型处理,应该在这个浮点类型后紧跟f或F。例如,5.12代表一个double类型的值,占64位的内存空间;5.12f或者5.12F才表示一个float类型的值,占32位的内存空间。当然,也可以在一个浮点数后添加d或D后缀,强制指定是double类型,但通常没必要。

  Java还提供了三个特殊的浮点数值:正无穷大、负无穷大和非数,用于表示溢出和出错。例如,使用一个正数除以0将得到正无穷大,使用一个负数除以0将得到负无穷大,0.0除以0.0或对一个负数开方得到一个非数。正无穷大通过Double或Float类的POSITIVE_INFINITY表示;负无穷大通过Double或Float类的NEGATIVE_INFINITY表示,非数通过Double或Float类的NaN表示。

  必须指出的是,所有正无穷大数值都是相等的,所有的负无穷大数值也都是相等的;而NaN不与任何数值相等,甚至和NaN都不相等。

注意:只有浮点数除以0才可以得到正无穷大和负无穷大,因为Java语言会自动把和浮点数运算的0(整数)当成0.0(浮点数)处理。如果一个整数值除以0,则会抛出一个异常:ArithmeticException:/by zero(除以0异常)。

public class FloatTest
{
    public static void main(String[] args)
    {
        float af = 5.2345556f;
        //下面将看到af的值已经发生改变
        System.out.println(af);
        double a = 0.0;
        double c = Double.NEGATIVE_INFINITY;
        double d = Float.NEGATIVE_INFINITY;
        //看到float和double的负无穷大都是相等的
        System.out.println(c == d);
        //0.0除以0.0将出现非数
        System.out.println(a / a);
        //两个非数之间是不相等的
        System.out.println(a / a == Float.NaN);
        //所有正无穷大都是相等的
        System.out.println(6.0 / 0 == 555.0 / 0);
        //负数除以0.0得到负无穷大
        System.out.println(-8 / 0);
    }
}

 

--3.4.4   数值中使用下画线分割

 

  正如前面程序中看到的,当程序中用到的数值位数特别多时,不容易看清楚到底有多少位数。为了解决这种问题,Java 7 引入了一个新功能:程序员可以在数值中使用下划线,不管是整形数值,还是浮点型数值,都可以*地使用下画线。

public class UnderscoreTest
{
    public static void main(String[] args)
    {
        //定义一个32位的二进制数,最高位是符号位
        int binVal = 0B1000_0000_0000_0000_0000_0000_0000_0011;
        double pi = 3.14_15_92_65_36;
        System.out.println(binVal);
        System.out.println(pi);
        double height = 8_8_4_8.23;
        System.out.println(height);
    }
}

 

--3.4.5   布尔型

  布尔型只有一个boolean类型用于表示逻辑上的 “真”或 ”假“。在Java语言中boolean类型的数值只能是true或false,不能用0或者非0来代表。其他数据类型的值也不能转换成boolean类型。

  例如,下面代码定义了两个boolean类型的变量,并指定初始值。

//定义b1的值为true
boolean b1 = true;
//定义b2的值为false
boolean b2 = false;

  字符串“true”和“false”不会直接转换成boolean类型,但如果使用一个boolean类型的值和字符串进行连接运算,则boolean类型的值将会自动转换成字符串。看下面代码。

//使用boolean类型的值和字符串进行连接运算,boolean类型的值会自动转换成字符串
String str = true + "";
//下面将输出true
System.out.println(str);

  boolean类型的值或变量主要用做旗标来进行流程控制,Java语言中使用boolean类型的变量或值控制的流程主要有如下几种。

    •   if条件控制语句
    •   while循环控制语句
    •   do while 循环控制语句
    •   for循环控制语句
    •   ……

 

--3.4.6   使用 var定义变量

  为了简化局部变量的声明,从Java10开始支持使用var定义局部变量:var相当于一个动作类型,使用var定义的局部变量的类型由编译器自动推断——定义变量时分配了什么类型的初始值,那么该变量就是什么类型。因此,使用var定义局部变量时,必须在定义局部变量的同时指定初始值,否则编译器无法推断该变量的类型。

  例如,如下代码使用var定义了多个局部变量。

var a = 20;    //被赋值为20,因此a的类型是int
System.out.println(a);
//a = 1.6; 这行代码会报错:不兼容的类型
var b = 3.4;    //被赋值为3.4,因此b的类型是float
System.out.println(b);
var c = (byte)13;
System.out.println(c);
//c = a;  // 这行代码会报错:不兼容类型
//var d;  这行代码会报错:无法推断局部变量d的类型

  使用var定义的变量不仅可以是基本类型,也可以是字符串等引用类型。

var st = "Hello";   //被赋值为“Hello”,因此st的类型是String
st = 5; //这行代码会报错:不兼容的类型

  不宜使用var变量的情况:

    •   变量的类型不容易判断——比如变量的初始值是由复杂的方法调用所得到的。
    •   局部变量的适用范围很大——随着局部变量的适用范围的增大,后面的代码就更难判断该变量的类型了。

 

3.5   基本类型的类型转换

--3.5.1   自动类型转换

  Java所有的数值型变量可以相互转换,如果系统支持把某种基本类型的值直接赋给另一种基本类型的变量,则这种方式被称为自动类型转换。当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,系统可以进行自动类型转换:否则就需要强制类型转换。

  表数范围小的可以向表数范围大的进行自动类型转换。如图:

  箭头左边的数值类型可以可以自动类型转换为箭头右边的数值类型。

public class AutoConversion
{
    public static void main(String[] args) {
        int a = 6;
        //定义一个byte类型的整数变量
        byte b =9;
        //下面代码将出错,byte类型不能自动类型转换为char类型
        //char c = b;
        //byte类型变量可以自动类型转换为double类型
        double d = b;
        //下面将输出9.0
        System.out.println(d);
    }
}

  不仅如此,当把任何基本类型的值和字符串值进行连接运算时,基本类型的值将自动类型转换为字符串类型,虽然字符串类型不是不是基本类型,而是引用类型。因此,如果希望把基本类型的值转换为对应的字符串时,可以把基本类型的值和一个空字符串进行连接。

提示:+ 不仅可以作为加法运算符使用,还可作为字符串连接运算符使用。

public class PrimitiveAndString
{
    public static void main(String[] args) {
        //下面代码是错误的,因为5是一个整数,不能直接赋给一个字符串
        //String str1 = 5;
        //一个基本类型的值和字符串进行连接运算时,基本类型的值自动转换为字符串
        String str2 = 3.5f + "";
        //下面输出3.5
        System.out.println(str2);
        //下面语句输出7Hello!
        System.out.println(3 + 4 + "Hello!");
        //下面语句输出Hello!34,因为Hello! + 3会把3当成字符串处理
        //而后再把4当成字符串处理
        System.out.println("Hello!" + 3 + 4);
    }
}

 

--3.5.2   强制类型的转换

  如果希望把上图箭头右边的类型转换为左边的类型,则必须使用强制类型转换,强制类型转换的语法格式时(targetType)value,强制类型转换的运算符号是圆括号(())。

public class NarrowConversion
{
    public static void main(String[] args) {
        var iValue = 233;
        //强制把一个int类型的值转换为byte类型的值
        byte bValue = (byte) iValue;
        //将输出-23(溢出)
        System.out.println(bValue);
        var dValue = 3.98;
        //强制把一个double类型的值转换为int类型的值
        int tol = (int) dValue;
        //将输出3(截断)
        System.out.println(tol);
    }
}

  在上面程序中,把一个浮点数强制类型转换为整数时,Java直接截断浮点数小数部分。除此之外,上面程序还把233强制类型转换为byte类型的整数,从而变成了-23,这就是典型的溢出。

  从下图看出,32位int类型的233在内存中所示,强制转换为8位的byte类型,则需要截断前面的24位,只保留右边8位,最右边的1是一个符号位,此处表明这是一个负数,负数在计算机中以补码形式存在的,因此还需要换算成原码。

  将补码减1得到反码形式,再将反码取反就可以得到原码。

 

32位int类型:

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

1

1

1

0

1

0

0

1

8位byte类型(高位截断):

1

1

1

0

1

0

0

1

反码形式:

1

1

1

0

1

0

0

0

 原码形式:

1

0

0

1

0

1

1

1

拓展:6位随机字符串生成程序

public class RandomStr
{
    public static void main(String[] args) {
        //定义一个空字符串
        var result = "";
        //进行6次循环
        for(var i = 0;i < 6;i++) {
            //生成一个97~122之间的int类型整数
            var intVal = (int)(Math.random() * 26 + 97);
            //将intValue 强制转化为char类型后连接到result后面
            result = result + (char) intVal
        }
        //输出随机字符串
        System.out.println(result);
    }
}

  还有下面一行容易出错的代码:

//直接把5.6赋值给float类型的变量将出现错误,因为5.6默认是double类型
float a = 5.6;

  因为5.6默认是一个double类型的浮点数,因此将5.6赋值给一个float类型变量将导致错误,必须使用强制类型转换才可以,即将上面代码改为如下形式:

float a = (float)5.6

  在通常情况下,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。例如,把字符串转换成int类型,则可通过如下代码实现:

String a = "45";
//使用Integer的方法将一个字符串转换成int类型
int iValue = Integer.parseInt(a);

 

--3.5.3   表达式类型的自动提升

  当一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。Java定义了如下的自动提升规则。

    •   所有的byte类型、short类型和char类型将被提升到int类型。
    •   整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。
//定义一个short类型变量
short sValue = 5;
//表达式中的sValue将自动提升到int类型,则右边的表达式类型为int
//将一个int类型的值赋给short类型变量将发生错误
sValue = sValue - 2;

  上面的“sValue - 2”表达式的类型将被提升到int类型,这样就把右边的int类型值赋给左边的short类型变量,从而引起错误。

  下面代码是表达式类型自动提升的正确示例代码

byte b = 40;
var c = \'a\';
var i = 23;
var d = .314;
//右边表达式中最高等级操作数为d(double类型)
//则右边表达式的类型为double类型,故赋值一个double类型变量
double result = b + c + i * d;
//将输出144.222
System.out.println(result);

  必须指出,表达式的类型将严格保持和表达式中最高等级操作数相同的类型。下面代码中两个int类型整数进行除法运算,即使无法除尽,也将得到一个int类型结果。

var val = 3;
//右边表达式中两个操作数都是int类型,故右边表达式的类型为int类型
//虽然 23/3 不能除尽,但依然得到一个int类型整数
int intResult = 23 / val;
System.out.println(intResult); //将输出7

  从上面程序中可以看出,当两个整数进行除法运算时,如果不能整出,得到的结果将是把小数部分截断后的整数。

  如果表达式中包含了字符串,则又是另一番情景了。因为把加号(+)放在字符串和基本类型值之间时,这个加号是一个字符串连接运算符,而不是进行加法运算。

//输出字符串Hello!a7
System.out.println("Hello!"+\'a\'+7);
//输出字符串104Hello!
System.out.println(\'a\'+7+"Hello!");     

  以上结果是根据类型自动提升关系图得出的,遇到字符串直接连接。

 

3.6   直接量

  直接量是指在程序中通过源代码直接给出的值,例如在int a = 5; 这行代码中,为变量a所分配的初始值5就是一个直接量。

--3.6.1   直接量的类型

  并不是所有的数据类型都可以指定直接量,能指定直接量的通常只有三种类型:基本类型(int,long,float,double,boolean,char)、字符串类型(String)和null类型(null)。

--3.6.2   直接量的赋值

  String类型的直接量不能赋值给其他类型的变量,null类型的直接量可以直接赋值给任何引用类型的变量,包括String类型。boolean类型的直接量只能赋给boolean类型的变量不能赋给其他任何类型的变量。

  关于字符串直接量有一点需要提出,当程序第一次使用某个字符串直接量时,Java会使用常量池来缓存该字符串直接量。如果程序后面的部分需要用到该字符串直接量时,Java会直接使用常量池中的字符串直接量。

  提示:1.由于String类是一个典型的不可变类,因此String对象创建出来就不可能改变。

     2.常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括类、方法、接口中的常量,也包括字符串直接量。

var s0 = "hello";
var s1 = "hello";
var s2 = "he" + "llo";
System.out.println(s0 == s1);
System.out.println(s0 == s2);

运行结果:

true
true

  Java会确保每个字符串常量只有一个,不会产生多个副本。例子中的s0和s1中的“hello”都是字符串常量,他们在编译期就被确定了,所以s0==s1返回true;而“he”和“llo"也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它本身也是字符串常量,s2同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”hello"的引用。

 

3.7   运算符

  运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。

--3.7.1   算术运算符

  • +:加法运算符。
  • -: 减法运算符。
  • *: 乘法运算符。
  • /: 除法运算符。除法运算符的两个操作数有一个是浮点数,或者两个数都是浮点数,则计算结果也是浮点数,则计算结果也是浮点数,这个结果就是自然除法的结果。而且此时允许除数是0,或者0.0,得到结果是正无穷大或负无穷大。
  • %:求余运算符。求余运算的结果不一定总是整数,它的计算结果是使用第一个操作数除以第二个操作数,得到一个整除的结果后剩下的值就是余数。由于求余运算也需要进行除法运算,因此如果求余运算的两个操作数都是整数类型,则求余运算的第二个操作数不能是0,否则将引发除以0异常。如果求余运算的两个操作数中有一个或者两个都是浮点数,则允许第二个操作数是0或者0.0,只是求余运算的结果是非数:NaN。
  • ++:自加。该运算符有两个要点:①自加是单目运算符,只能操作一个操作数;②自加运算符只能操作单个数值型(整形、浮点型都行)的变量,不能操作常量或者表达式。运算符既可以出现在操作数的左边,也可以出现在操作数的右边。但出现在左边和右边的效果是不一样的。如果把++放在左边,则先把操作数加1,然后才把操作数放入表达式中运算;如果把++放在右边,则先把操作数放入表达式中运算,然后才把操作数加1。
  • --:自减。也是单目运算符,用法与++基本相似,只是将操作数的值减1。
public class MathTest
{
    public static void main(String[] args) {
        var a = 3.2;//定义变量a为3.2
        //求a的5次方,并将计算结果赋给b
        double b = Math.pow(a, 5);
        System.out.println(b);  //输出b的值
        //求a的平方根,并将结果赋给c
        double c = Math.sqrt(a);
        System.out.println(c);//输出c的值
        //计算随机数,返回一个0~1之间的伪随机数
        double d = Math.random();
        System.out.println(d);//输出d的值
        //求1.57的sin函数值:1.57被当成弧度数
        double e = Math.sin(1.57);
        System.out.println(e);//接近输出1
    }
}

 

--3.7.2   赋值运算符

  赋值运算符用于为变量指定变量值,与C类似,Java也使用  “=”  作为赋值运算符。通常,使用赋值运算符将一个直接量赋给变量,也可以将一个变量赋值给另一个变量。

提示:按前面关于变量的介绍,可以把变量当成一个可盛装数据的容器。而赋值运算就是将被赋得值“装入”变量的过程。赋值运算符是从右向左执行计算的,程序先计算得到=右边的值,然后将该值“装入”=左边的变量,因此赋值运算符(=)左边只能是变量。

 

--3.7.3   位运算符

  Java支持的位运算符有如下7个。

    •   &:按位与。当两位同时为1时才返回1。
    •   |:按位或。只要有1位为1就返回1.
    •   ~:按位非。单目运算符,将操作符的每个位(包括符号位)全部取反。
    •   ^:按位异或。当两位相同时返回0,不同时返回1。
    •   <<:左移运算符。
    •   >>:右移运算符。
    •   >>>:无符号右移运算符。

  一般来说,位运算符只能操作整数类型的变量或值。位运算符的运算法则如下表。

第一个操作数

第二个操作数

按位与

按位或

按位异或

0

0

0

0

0

0

1

0

1

1

1

0

0

1

1

1

1

1

1

0

    

  左移运算符是将操作数的二进制码整体左移指定位数,左移后右边空出来的位以0填充。

  下面以-5<<2为例介绍左移运算过程。

-5的补码(32位)
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

 

-5的补码左移两位后
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0

  蓝色部分被左移出去,会被截断,黄色部分为左移后末位补充的0。所得到的新值为-20。

  Java的右移运算符有两个:>> 和 >>>,对于>>运算符而言,把第一个操作数的二进制码右移指定位数后,左边空出来的位以原来的符号位填充,即如果第一个操作数原来是正数,则左边补0;如果第一个操作数是负数,则左边补1。>>>是无符号右移运算符,它把第一个操作数的二进制右移指定位数后,左边空出来的位总是以0填充。

  下面以-5>>2为例介绍右移运算过程。(结果:-2)

-5的补码(32位)
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

 

-5补码右移两位后
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

黄色表示以符号位填充,蓝色表示移出去的被截断。

下面以-5>>>2为例介绍右移运算过程。(结果:1073741822)

 -5的补码(32位)
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

 

-5补码右移两位
0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1

黄色表示以符号位填充,蓝色表示移出去的被截断。

  进行位移运算时还要遵循如下规则。

  • 对于低于int类型的操作数总是先自动类型转化为int类型后在移位。
  • 对于int类型的整数移位a>>b,当b>32时,系统先用b对32求余(因为int类型只有32位),得到的结果才是真正位移的位数。例如,a>>33和a>>1的结果完全一样,而a>>32的结果和a相同。
  • 对于long类型的整数移位a>>b,当b>64时,总是先用b对64求余(因为long类型是64位),得到的结果才是真正移位的位数。

注意:当进行移位运算时,只要被移位的二进制码没有发生有效位的数字丢失(对于正数而言,通常指被移出的位全部都是0),不难发现左移n位就相当于乘以2的n次方,右移n位则是除以2的n次方。不仅如此,进行移位运算不会改变操作数本身,只是得到了一个新的运算结果,而原来的操作数本身是不会改变的。

 

--3.7.4   扩展后的赋值运算符

 

  赋值运算符可与算术运算符、位移运算符结合,扩展成功能更加强大的运算符。扩展后的赋值运算符如下。

    •   +=:对于 x+=y ,及对应于 x = x + y。
    •    -=:对于 x-=y ,及对应于 x = x - y。
    •    *=:对于 x*=y ,及对应于 x = x * y。
    •    /=:对于 x/=y ,及对应于 x = x / y。
    •    %=:对于 x%=y ,及对应于 x = x % y。
    •    &=:对于 x&=y ,及对应于 x = x & y。
    •    |=:对于 x|=y ,及对应于 x = x | y。
    •    ^=:对于 x^=y ,及对应于 x = x ^ y。
    •    <<=:对于 x<<=y ,及对应于 x = x << y。
    •    >>=:对于 x>>=y ,及对应于 x = x >> y。
    •    >>>=:对于 x>>>=y ,及对应于 x = x >>> y。

 

--3.7.5   比较运算符

  比较运算符用于判断两个变量或者常量的大小,比较运算的结果是一个布尔值。

    •   >:大于,只支持左右两边操作数是数值类型。
    •   >=:大于等于,只支持左右两边操作数是数值类型。
    •   <:小于,只支持左右两边操作数是数值类型。
    •   <=:小于等于,只支持左右两边操作数是数值类型。
    •   ==:等于,如果进行比较的两个操作数都是数值类型,即使他们的数据类型不同,只要它们的值相等,就返回true。如果两个操作数都是引用类型,那么只有当两个引用变量的类型具有父子关系才可以比较,而且这两个引用必须指向同一个对象才会返回true。
    •   !=:不等于,如果进行比较的两个操作数都是数值类型,无论它们的数据类型是否相同,只要它们的值不相等,也都返回true。如果两个操作数都是引用类型,只有当两个引用变量的类型具有父子关系时才可以比较,只要两个引用指向的不是同一个对象就会返回true。

注意:基本类型的变量、值不能和引用类型的变量、值使用 == 进行比较;boolean类型的变量,值不能与其他任意类型的变量、值使用 == 进行比较;如果两个引用类型之间没有父子继承关系,那么它们的变量也不能使用 == 进行比较。

 

--3.7.6   逻辑运算符

  逻辑运算符用于操作两个布尔类型的变量或常量。

    •   &&:与,前后两个操作数必须都是true才返回true,否则返回false。
    •   &:不短路与,作用与&&相同,但不会短路。
    •   ||:或,只要两个操作数中有一个是true,就可以返回true,否则返回false。
    •   |:不短路或,作用与||相同,但不会短路。
    •   !:非,只需要一个操作数,如果操作数是true,则返回false;如果操作数是false,则返回true。
    •   ^:异或,当两个操作数不同时才返回true,如果两个操作数相同则返回false。

  对于 | 和 || 的区别:

//定义变量a,b,并为两个变量赋值
var a = 5;
var b = 10;
if(a > 4 | b++ > 10)
{
    //输出a的值是5,b的值是11
     System.out.println("a的值是:"+ a + ",b的值是:"+ b);
}

  执行上面程序,看到输出a的值是5,b的值为11,这表明 b++ > 10 表达式得到了计算,但实际上没有计算的必要,因为 a > 4已经返回了true,则整个表达式一定返回true。

//定义变量c,d,并为两个变量赋值
var c = 5;
var d = 10;
if(c > 4 || d++ > 10)
{
    //输出c的值是5,b的值是10
     System.out.println("c的值是:"+ c + ",d的值是:"+ d);
}

  对比两段代码,后面的代码不仅仅将不短路或改成短路或,程序最后输出的d值不再是11,这表明表达式 d++ > 10没有获得执行机会。&与&&情况与此类似。

 

--3.7.7   三目运算符

  三目运算符只有一个:? : 。其规则是:先对逻辑表达式求值,如果逻辑表达式返回true,则返回第二个操作数的值,如果逻辑表达式返回false,则返回第三个操作数的值。

    条件表达式 a;

    条件表达式 b;

    条件表达式 c;

      a ? b ? c ? 结果1 :  结果2 :  结果3 : 结果4

等同于

if(a){
    if(b){
          if(c){
              结果1;
          }else{
              结果 2;
          }  
    }else{
        结果 3
    }
}else{
   结果4
}

 

 

--3.7.8   运算符的结合性和优先级

运算符优先级
运算符说明 Java运算符
分隔符  .  []  ()  {}  ,  ;
单目运算符 ++  --  ~  !
强制类型转换运算符 (type)
乘法/除法/求余 *  /  %
加法/减法 +  -
移位运算符 <<  >>  >>>
关系运算符 <  <=  >=  >  instanceof
等价运算符 ==  !=
按位与 &
按位异或 ^
按位或 |
条件与 &&
条件或 ||
三目运算符 ? :
赋值 =  +=  -=  *=  /=  &=  |=  ^=  %=  <<=  >>=  >>>=