Java程序基础基础

Java程序基础基础

注释

编写注释的原因

编写程序时总需要为程序添加一些注释,用以说明某段代码的作用,或者说明某个类的用途、某个方法的功能,以及该方法的参数和返回值的数据类型及意义等。

编写注释的原因及意义如下

  1. 为了更好的阅读自己编写的代码,建议添加这注释。自己写的代码,可能过一段时间回顾的时候,就变得不熟悉。这个时候,注释就起到了很好的帮助作用。

  2. 可读性第一,效率第二。一个软件一般都是一个团队协同作战开发出来的。因此,一个人写的代码,需要被整个团队的其他人所理解。

  3. 代码即文档。程序源代码是程序文档的重要组成部分。

注释的语法规则

编写Java中的注释不会出现在可执行程序中。因此,可以在源程序中根据需要添加任意多的注释,而不必担心可执行代码会膨胀。在 Java 中,有三种书写注释的方式。

  1. 单行注释——注释一行

    以双斜杠“//”标识,只能注释一行内容,用在注释信息内容少的地方。

    image-20210106195133866

  2. 多行注释——注释一段

    包含在“/* ”和“ */”之间,能注释很多行的内容。为了可读性比较好,一般首行和尾行不写注释信息(这样也比较美观好看)。

    20201231160059

    注意:多行注释可以嵌套单行注释,但是不能嵌套多行注释和文档注释。

  3. 文档注释

    包含在“/** ”和“ */”之间,也能注释多行内容,一般用在类、方法和变量上面,用来描述其作用。注释后,鼠标放在类和变量上面会自动显示出我们注释的内容。

    20201231160110

    注意:文档注释能嵌套单行注释,不能嵌套多行注释和文档注释,一般首行和尾行也不写注释信息。

标识符和关键字

Java标识符定义

  1. 包名、类名、方法名、参数名、变量名等,这些符号被称为标识符。

  2. 标识符可以由字母、数字、下划线()和美元符号($)组成

  3. 标识符不能以数字开头,不能是java中的关键字。例如:

    • 正确的标识符
      Username、username123、user_name、_userName、$username

    • 不正确的标识符:
      123username、class、87.2、Hello World、num*123

  4. 首字符之后可以是字母(A­Z 或者 a­z),美元符($)、下划线()或数字的任何字符。

  5. 标识符是大小写敏感。

Java标识符规则

  1. 包名所有字母必须小写。例如:cn.itcast.test
  2. 类名和接口名每个单词的首字母都要大写。例如:ArrayList
  3. 常量名所有的字母都大写,单词之间用下划线连接。例如:DAY_OF_MONTH
  4. 变量名和方法名的第一个单词首字母小写,从第二个单词开始,每个单词首字母大写。例如:lineName、getLingNumber
  5. 在程序中,应该尽量使用有意义的英文单词来定义标识符,使得程序便于阅读。例如:使用userName表示用户名,password表示密码。

Java 关键字

  • 下面列出了 Java 关键字。这些保留字不能用于常量、变量、和任何标识符的名称。
  • 所有的关键字都是小写
  • 程序中的标识符不能以关键字命名
  • const和goto是保留字关键字,虽然在java中还没有任何意义,但在程序中不能用来作为自定义的标识符。
  • true、false和null不属于关键字,它们是一个单独标识类型,不能直接使用
类别 关键字 说明
访问控制 private 私有的
访问控制 protected 受保护的
访问控制 public 公共的
类、方法和变量修饰符 abstract 声明抽象
类、方法和变量修饰符 class
类、方法和变量修饰符 extends 扩充,继承
类、方法和变量修饰符 final 最终值,不可改变的
类、方法和变量修饰符 implements 实现(接口)
类、方法和变量修饰符 interface 接口
类、方法和变量修饰符 native 本地,原生方法(非 Java 实现)
类、方法和变量修饰符 new 新,创建
类、方法和变量修饰符 static 静态
类、方法和变量修饰符 strictfp 严格,精准
类、方法和变量修饰符 synchronized 线程,同步
类、方法和变量修饰符 transient 短暂
类、方法和变量修饰符 volatile 易失
程序控制语句 break 跳出循环
程序控制语句 case 定义一个值以供 switch 选择
程序控制语句 continue 继续
程序控制语句 default 默认
程序控制语句 do 运行
程序控制语句 else 否则
程序控制语句 for 循环
程序控制语句 if 如果
程序控制语句 instanceof 实例
程序控制语句 return 返回
程序控制语句 switch 根据值选择执行
程序控制语句 while 循环
错误处理 assert 断言表达式是否为真
错误处理 catch 捕捉异常
错误处理 finally 有没有异常都执行
错误处理 throw 抛出一个异常对象
错误处理 throws 声明一个异常可能被抛出
错误处理 try 捕获异常
包相关 import 引入
包相关 package
基本类型 boolean 布尔型
基本类型 byte 字节型
基本类型 char 字符型
基本类型 double 双精度浮点
基本类型 float 单精度浮点
基本类型 int 整型
基本类型 long 长整型
基本类型 short 短整型
变量引用 super 父类,超类
变量引用 this 本类
变量引用 void 无返回值
保留关键字 goto 是关键字,但不能使用
保留关键字 const 是关键字,但不能使用
保留关键字 null

数据类型

强类型语言

强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。

Java、.Net和C++等一些语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。

例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。

弱类型语言

弱类型语言也称为弱类型定义语言,与强类型定义相反。像VB,PHP等一些语言就属于弱类型语言。

简单理解就是一种变量类型可以被忽略的语言。

比如VBScript是弱类型定义的,在VBScript中就可以将字符串'12'和整数3进行连接得到字符串'123',然后可以把它看成整数123,而不用显示转换。但其实他们的类型没有改变,VB只是在判断出一个表达式含有不同类型的变量之后,自动在这些变量前加了一个clong()或(int)()这样的转换函数而已。能做到这一点其实是归功于VB的编译器的智能化而已,这并非是VB语言本身的长处或短处。

强类型语言和弱类型语言比较

强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误。

动态语言和静态语言

通常我们所说的动态语言、静态语言是指动态类型语言和静态类型语言。

动态类型语言:

动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如VBScript也多少属于动态类型语言。

静态类型语言:

静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。

对于动态语言与静态语言的区分,套用一句流行的话就是:Static typing when possible, dynamic typing when needed。

img

Java 基本数据类型

变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。

内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。

20201229172925

因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。

Java 的两大数据类型:

  • 内置数据类型

  • 引用数据类型

    20201231160127

内置数据类型

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte:

  • byte 数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 默认值是 0
  • byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short:

  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0
  • 例子:short s = 1000,short r = -20000。

int:

  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0
  • 例子:int a = 100000, int b = -200000。

long:

  • long 数据类型是 64 位、有符号的以二进制补码表示的整数;
  • 最小值是 -9,223,372,036,854,775,808(-2^63)
  • 最大值是 9,223,372,036,854,775,807(2^63 -1)
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是 0L
  • 例子: long a = 100000L,Long b = -200000L。
    "L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。

float:

  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double:

  • double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
  • 浮点数的默认类型为double类型;
  • double类型同样不能表示精确的值,如货币;
  • 默认值是 0.0d
  • 例子:double d1 = 123.4。

boolean:

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true 和 false;
  • 这种类型只作为一种标志来记录 true/false 情况;
  • 默认值是 false
  • 例子:boolean one = true。

char:

  • char类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(即为 0);
  • 最大值是 \uffff(即为65535);
  • char 数据类型可以储存任何字符;
  • 例子:char letter = 'A';

类型默认值

下表列出了 Java 各个类型的默认值:

数据类型 默认值
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char 'u0000'
String (or any object) null
boolean false
public class Test {
    static boolean bool;
    static byte by;
    static char ch;
    static double d;
    static float f;
    static int i;
    static long l;
    static short sh;
    static String str;
 
    public static void main(String[] args) {
        System.out.println("Bool :" + bool);
        System.out.println("Byte :" + by);
        System.out.println("Character:" + ch);
        System.out.println("Double :" + d);
        System.out.println("Float :" + f);
        System.out.println("Integer :" + i);
        System.out.println("Long :" + l);
        System.out.println("Short :" + sh);
        System.out.println("String :" + str);
    }
}

image-20201230124053970

对于数值类型的基本类型的取值范围,我们无需强制去记忆,因为它们的值都已经以常量的形式定义在对应的包装类中了。请看下面的例子:

//基本类型字节空间和取值范围测试
public class DataTypeSize {
    public static void main(String[] args){
        System.out.println("byte:"+Byte.SIZE/8+"字节("+Byte.MIN_VALUE+"~"+Byte.MAX_VALUE+")");
        System.out.println("short:"+Short.SIZE/8+"字节("+Short.MIN_VALUE+"~"+Short.MAX_VALUE+")");
        System.out.println("int:"+Integer.SIZE/8+"字节("+Integer.MIN_VALUE+"~"+Integer.MAX_VALUE+")");
        System.out.println("long:"+Long.SIZE/8+"字节("+Long.MIN_VALUE+"~"+Long.MAX_VALUE+")");
        System.out.println("float:"+Float.SIZE/8+"字节("+Float.MIN_VALUE+"~"+Float.MAX_VALUE+")");
        System.out.println("double:"+Double.SIZE/8+"字节("+Double.MIN_VALUE+"~"+Double.MAX_VALUE+")");
        System.out.println("char:"+Character.SIZE/8+"字节");
    }
}

编译以上代码输出结果如下所示:

image-20210106204354836

数据类型扩展

整数扩展

在JAVA里的进制中

  • 0b开头二进制
  • 0开头是八进制
  • 0x开头是十六进制点数扩展
public static void main(String[] args) {

        int i1 = 10;
        int i2 = 0b10;  //二进制10
        int i3 = 010;   //八进制10
        int i4 = 0x10;  //十六进制10    16进制: 0~9 A~F

        // 全部输出查看结果
        System.out.println(i1);  //10
        System.out.println(i2); //2
        System.out.println(i3); //8
        System.out.println(i4); //16
        

        System.out.println("二进制输出" + Integer.toBinaryString(i1));
        System.out.println("八进制输出" + Integer.toOctalString(i1));
        System.out.printf("八进制输出" + "%010o\n", i1);
        System.out.printf("十六进制输出" + "%010x\n", i1);
        System.out.println("十六进制输出" + Integer.toHexString(i1));
    }

20201230130700

浮点数扩展

  1. 浮点数是一个离散的数,表示的值不精确存在误差不适合进行数值比较

  2. 浮点数在计算机中是以一种类似于科学计数法的方式(IEEE 754)来存储和运算的(计算机组成原理)。

  3. 浮点数的计算是不精确的,它存在舍入误差和表示大小溢出的问题。浮点数强制转型成整数时,会舍弃掉小数,显示整数的最大值

  4. 浮点数的存储也是不精确的,它是采用二进制的方式存储在计算机中,超出精度后溢出的部分就不会被存储,所以会出现误差。

    public static void main(String[] args) {
        float f1=0.1F;
        double f2=0.1;
        System.out.println(f1==f2);//false
      
        float f3=888888888888888f;
        float f4=f3+1;
        System.out.println(f3==f4);//ture
    }
    

那么问题也来了,既然我们在程序中使用的浮点数来表示小数的时候,会出现误差,我们在表示银行业务的时候,应该用什么来表示账户余额才能使其不出现问题呢?
我们可以采用Java中提供的数学工具类BigDecimal类来对其进行表示。

注意:在Java中,最好完全避免使用浮点数来进行比较。

字符扩展

在Java中,我们通常使用''将字符包裹起来用于字符的表示,但实际上,计算机对字符的存储是一个二进制的整数,每个正整数都对应了一个字符,这一一的对应就是Unicode编码,Unicode编码就是一张表,一列表示为数字,一列表示为字符,正是通过这张表将其一一对应。

char ch1 = 'A';
char ch2 = 'a';

System.out.println(ch1);//A
System.out.println((int)ch1);//65

System.out.println(ch2);//a
System.out.println((int)ch2);//97

这里就不得不说到字符中的转义字符,我们在表示字符的时候,也可以表示成下面的这种形式,其范围在\u0000~\uFFFF

char ch3 = '\u0061';
System.out.println(ch3);//a

这里的\u就表示Unicode编码。转义字符也有很多,常见的有:

\n 回车
\r 换行
\'单引号
\"双引号
\t水平制表符
\b空格
\\ 反斜杠

布尔值扩展

boolean flag = true;
if (flag == true) {}
if (flag) {}

两个if判断所表示的意识完全一致,但是后者比前者更为简洁,者一般就是新手程序员喜欢写的,而后者一般为老程序员的习惯,我们写代码的时候,不仅是要完成业务逻辑的需要,我们写出的代码也应该简洁精炼,在这里就不得不提到Jquery的一句话,write less,do more,写的更少,做的更多。

类型转换

自动类型转换

数据类型自动转换:将取值范围小的类型自动转换为取值范围大的类型。例如:

一个 int 类型变量和一个 byte 类型变量进行相加,运算的结果是:变量的类型将是 int 类型。

同样道理,当一个 int 类型变量和一个 double 变量运算时, int 类型将会自动提升为 double 类型进行运算。

20201230135035

转换规则:
范围小的类型向范围大的类型提升:byte、short、char 运算时候直接提升为 int。
byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double。

强制类型转换

强制类型转换:将取值存储位数多的的类型强制转换成储存位数少的的类型 。

比较而言,自动转换是 Java 自动执行的,而强制转换需要我们自己手动执行。

20201230135524

将 1.5 赋值到 int 类型变量:产生编译失败,无法赋值。

要修改为:
int i = (int)1.5; //但是这样会导致 1.5 变成 1。
int d=(int)2.5; // double类型数据强制转成int类型,直接去掉小数点,保留整数。

   public static void main(String[] args) {
        /**
         *  int a=1.5; 编译失败,无法赋值
         */
        int i = (int)1.5;
        double d=2.5;
        //int类型和double类型运算,结果是double类型
        //int类型会提升为double类型
        double e = d + i;
        System.out.println(e);
    }

分析以下程序:

    public static void main(String[] args) {
        System.out.println(4/3);
        System.out.println(4/3.0);
        System.out.println(4.0/3);
        System.out.println((float)(4/3.0));

        System.out.println(4*3);
        System.out.println(4*3.0);
        System.out.println(4.0*3);

        System.out.println(4+3);
        System.out.println(4+3.0);
        System.out.println(4.0+3);
    }

image-20210106202127235

变量、常量、作用域

变量

变量,见名知义,就是可以变化的量。
因为Java是一种强类型的语言,每一个变量都必须声明它们的类型,它是程序中最基本的存储单元,其中包括变量名、变量类型和作用域。语法如下:
数据类型 变量名 [= 值] [{,变量名 [ = 值]}...];

//一行中声明多个同类型的变量 都未赋初始值
int a, b, c;
//一行中声明多个变量 都赋值
int d = 1, e = 2;
//一行中声明多个变量 有赋值 有未赋值
int f, g = 3;
//一行中声明一个变量
int age = 20;
//声明一个引用型变量
String name = "Ara_Hu";

我们在声明变量时,为了提高程序的可阅读性,我们最好不要在一行中声明多个变量,我们可以一行声明一个变量,在某些关键的变量之前加上注释,标明这个变量的用途,这样可以大大提高我们程序的可阅读性。

注意:

  • 每个变量都需要有类型,类型可以是基本类型,也可以是引用类型
  • 变量名必须是合法的标识符
  • 变量声明是一条完整的语句,必须以分号结尾
  • 变量在声明后必须要赋值后才能进行使用

常量

常量,可以理解为一种特殊的变量,它的值初始化之后就不能再进行修改,它是不会变动的值,常量名一般采用大写字符。

//声明一个常量
final double PI = 3.14;

我们声明常量的时候需要使用final关键字来对变量进行修饰,在修饰前它是一个变量,但是在通过final是修饰后,它就成为了一个常量。

**注意:常量的值,是不能进行修改的**

作用域

作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

变量又分为类变量实例变量局部变量,三种不同类型的变量就对应着不同的作用范围,就是它们存在着不同的作用域。

  • 类变量:在类中但是独立于方法之外的变量,用 static 修饰。
  • 实例变量:在类中但是独立于方法之外的变量,不过没有 static 修饰。
  • 局部变量:类的方法中的变量。

类变量在本类中任何地方都可直接使用,在其他类中用类名.变量名可使用;
实例变量不论在什么地方都需要先实例化本类,在通过实例化的本类的引用(new的该对象的变量名)来进行使用;
局部变量仅在声明此变量的那段代码中可用,一般都是在声明此变量到最接近声明此变量之后的}段中有效(比如下面这个图,变量i仅在两个红框之间有效);

20201230140522

类变量:

  • 在本类中可以直接使用,在其他类中可直接通过类名调用(ClassName.VariableName)
  • 类变量通常是一个常量,为了方便调用,一般在类变量前面添加public和final关键字,声明为公开的常量
  • 类变量在第一次被访问时创建,在程序结束时销毁

实例变量:

  • 实例变量在对象创建的时候创建,在对象被销毁的时候销毁
  • 实例变量可以通过变量名访问(就是new出来的对象的变量名)
  • 一般实例变量都会构建为private私有的,然后类中提供对应的getter和setter方法来进行访问和赋值

局部变量:

  • 仅在声明它的方法或者代码块中有效
  • 当包含局部变量的方法或者代码块执行时,变量创建,结束时销毁
  • 局部变量没有默认值,所以局部变量在被声明后,必须初始化才能使用
public class Demo {
    //类变量 在此类加载时就会初始化
    static String name = "Ara_Hu";
    //实例变量 在此类实例化时才会初始化
    int age = 20;
    public static void main(String[] args) {      
        //局部变量 在此方法运行时初始化
        int i = 0;   
    }
}

基本运算符

计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量。我们可以把运算符分成以下几组:

  • 算术运算符
  • 关系运算符
  • 位运算符
  • 逻辑运算符
  • 赋值运算符
  • 其他运算符

1. 算术运算符

算术运算符在数学表达式中的使用方式与在代数中使用的方式相同。下表列出了算术运算符的使用示例 -

假设整数类型变量A的值为:10,变量B的值为:20,则 -

运算符 描述 示例
+ 加法运算符,第一个操作数加上第二个数操作数 A + B结果为:30
- 减法运算符,从第一个操作数减去第二个操作数 A - B结果为:-10
* 两个操作数相乘 A * B结果为:200
/ 左操作数除以右操作数返回模值 B / A结果为:2
% 左操作数除以右操作数返回余数 B % A结果为:0
++ 将操作数的值增加1 A++,则A的值为:11
-- 将操作数的值减1 A--,则A的值为:9
public class Test {
  public static void main(String[] args) {
     int a = 10;
     int b = 20;
     int c = 25;
     int d = 25;
     System.out.println("a + b = " + (a + b) );
     System.out.println("a - b = " + (a - b) );
     System.out.println("a * b = " + (a * b) );
     System.out.println("b / a = " + (b / a) );
     System.out.println("b % a = " + (b % a) );
     System.out.println("c % a = " + (c % a) );
     System.out.println("a++   = " +  (a++) );
     System.out.println("a--   = " +  (a--) );
     // 查看  d++ 与 ++d 的不同
     System.out.println("d++   = " +  (d++) );
     System.out.println("++d   = " +  (++d) );
  }
}

注意:Java中两个整数相除,只会输出结果的整数部分,要输出较为准确的结果,把其中一个变为浮点数

两个整数之间的运算结果是整数

int和float运算的结果是float类型

    public static void main(String[] args) {
        System.out.println(4/3);
        System.out.println(4/3.0);
        System.out.println((float)(4/3.0));
        System.out.println((4/((float)3.0)));
    }

image-20210106205641673

自增自减运算符

1、自增(++)自减(--)运算符是一种特殊的算术运算符,在算术运算符中需要两个操作数来进行运算,而自增自减运算符是一个操作数。

public class selfAddMinus{
    public static void main(String[] args){
        int a = 3;//定义一个变量;
        int b = ++a;//自增运算
        int c = 3;
        int d = --c;//自减运算
        System.out.println("进行自增运算后的值等于"+b);
        System.out.println("进行自减运算后的值等于"+d);
    }
}

运行结果为:

进行自增运算后的值等于4
进行自减运算后的值等于2

解析:

  • int b = ++a; 拆分运算过程为: a=a+1=4; b=a=4, 最后结果为b=4,a=4
  • int d = --c; 拆分运算过程为: c=c-1=2; d=c=2, 最后结果为d=2,c=2

2、前缀自增自减法(++a,--a): 先进行自增或者自减运算,再进行表达式运算。

3、后缀自增自减法(a++,a--): 先进行表达式运算,再进行自增或者自减运算 实例:

public class selfAddMinus{
    public static void main(String[] args){
        int a = 5;//定义一个变量;
        int b = 5;
        int x = 2*++a;
        int y = 2*b++;
        System.out.println("自增运算符前缀运算后a="+a+",x="+x);
        System.out.println("自增运算符后缀运算后b="+b+",y="+y);
    }
}

运行结果为:

自增运算符前缀运算后a=6,x=12
自增运算符后缀运算后b=6,y=10

如何理解?

b=++a就是先执行a=a+1,在执行b=a;

b=*--a是先执行b=a;在执行a=a+1;

对于以下,有int x = 5, y = 6, z;
题目1:z = ++x + y++;
题目2:z = ++x + x++;
题目3:x = ++x + x++;
对于上面的三道题目,我们下面一一解答。使用的技巧就是:把原始计算式转化成多个、有先后计算顺序的、小的计算式,然后带入变量的值,进行求解。记住:同一优先级的运算符的计算顺序是从右往左。
Q1:z = ++x + y++;  可以转化为:
            x = x +1;
            z = x + y;
            y = y + 1;
带入x = 5, y = 6,可得x = 6; z = 12; y = 7; 

Q2:z = ++x + x++;  可以转化为:
            x = x +1;
            z = x + x;
            x = x + 1;
带入x = 5,可得x = 6; z = 6+6=12; x = 7;   故x=7,z=12;

Q3:x = ++x + x++;  可以转化为:
            x = x +1;
            x = x + x;
            x = x + 1;
带入x = 5,可得x = 5+1=6; x = 6+6=12; x = 12+1=13;   故x=13。

溢出
整数溢出

要特别注意,整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错,却会得到一个奇怪的结果:

public class Main {
    public static void main(String[] args) {
        int x = 2147483640;
        int y = 15;
        int sum = x + y;
        System.out.println(sum); // -2147483641
    }
}

要解释上述结果,我们把整数214748364015换成二进制做加法:

0111 1111 1111 1111 1111 1111 1111 1000
+ 0000 0000 0000 0000 0000 0000 0000 1111
-----------------------------------------
  1000 0000 0000 0000 0000 0000 0000 0111

由于最高位计算结果为1,因此,加法结果变成了一个负数。

要解决上面的问题,可以把int换成long类型,由于long可表示的整型范围更大,所以结果就不会溢出:

long x = 2147483640;
long y = 15;
long sum = x + y;
System.out.println(sum); // 2147483655
浮点数溢出

整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

  • NaN表示Not a Number
  • Infinity表示无穷大
  • -Infinity表示负无穷大

例如:

double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -Infinity

这三种特殊值在实际运算中很少碰到,我们只需要了解即可。

2. 关系运算符

Java语言支持以下关系运算符。假设变量A的值是10,变量B的值是20,则 -

运算符 描述 示例
== 等于运算符,检查两个操作数的值是否相等,如果相等,则条件变为真。 A==B结果为假。
!= 不等于运算符,检查两个操作数的值是否不相等,如果不相等,则条件变为真。 A!=B结果为真。
> 大于运算符,检查左操作数的值是否大于右操作数的值,如果大于,则条件变为真。 A>B结果为假。
< 小于运算符,检查左操作数的值是否小于右操作数的值,如果小于,则条件变为真。 A<B结果为真。
>= 大于或等于运算符,检查左操作数的值是否大于等于右操作数的值,如果大于或等于,则条件变为真。 A>=B结果为假。
<= 小于或等于运算符,检查左操作数的值是否小于或等于右操作数的值,如果小于或等于,则条件变为真。 A<=B结果为真。
public class Test {
 
  public static void main(String[] args) {
     int a = 10;
     int b = 20;
     System.out.println("a == b = " + (a == b) );
     System.out.println("a != b = " + (a != b) );
     System.out.println("a > b = " + (a > b) );
     System.out.println("a < b = " + (a < b) );
     System.out.println("b >= a = " + (b >= a) );
     System.out.println("b <= a = " + (b <= a) );
  }
}

a == b = false
a != b = true
a > b = false
a < b = true
b >= a = true
b <= a = false

3. 按位运算符

移位运算

在计算机中,整数总是以二进制的形式表示。例如,int类型的整数7使用4字节表示的二进制如下:

00000000 0000000 0000000 00000111

可以对整数进行移位运算。对整数7左移1位将得到整数14,左移两位将得到整数28

int n = 7;       // 00000000 00000000 00000000 00000111 = 7
int a = n << 1;  // 00000000 00000000 00000000 00001110 = 14
int b = n << 2;  // 00000000 00000000 00000000 00011100 = 28
int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192
int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912

左移29位时,由于最高位变成1,因此结果变成了负数。

类似的,对整数28进行右移,结果如下:

int n = 7;       // 00000000 00000000 00000000 00000111 = 7
int a = n >> 1;  // 00000000 00000000 00000000 00000011 = 3
int b = n >> 2;  // 00000000 00000000 00000000 00000001 = 1
int c = n >> 3;  // 00000000 00000000 00000000 00000000 = 0

如果对一个负数进行右移,最高位的1不动,结果仍然是一个负数:

int n = -536870912;
int a = n >> 1;  // 11110000 00000000 00000000 00000000 = -268435456
int b = n >> 2;  // 11111000 00000000 00000000 00000000 = -134217728
int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2
int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1

还有一种无符号的右移运算,使用>>>,它的特点是不管符号位,右移后高位总是补0,因此,对一个负数进行>>>右移,它会变成正数,原因是最高位的1变成了0

int n = -536870912;
int a = n >>> 1;  // 01110000 00000000 00000000 00000000 = 1879048192
int b = n >>> 2;  // 00111000 00000000 00000000 00000000 = 939524096
int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7
int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1

byteshort类型进行移位时,会首先转换为int再进行位移。

仔细观察可发现,左移实际上就是不断地×2,右移实际上就是不断地÷2。

位运算

位运算是按位进行与、或、非和异或的运算。

与运算的规则是,必须两个数同时为1,结果才为1

n = 0 & 0; // 0
n = 0 & 1; // 0
n = 1 & 0; // 0
n = 1 & 1; // 1

或运算的规则是,只要任意一个为1,结果就为1

n = 0 | 0; // 0
n = 0 | 1; // 1
n = 1 | 0; // 1
n = 1 | 1; // 1

非运算的规则是,01互换:

n = ~0; // 1
n = ~1; // 0

异或运算的规则是,如果两个数不同,结果为1,否则为0

n = 0 ^ 0; // 0
n = 0 ^ 1; // 1
n = 1 ^ 0; // 1
n = 1 ^ 1; // 0

对两个整数进行位运算,实际上就是按位对齐,然后依次对每一位进行运算。例如:

// 位运算 
public class Main {
    public static void main(String[] args) {
        int i = 167776589; // 00001010 00000000 00010001 01001101
        int n = 167776512; // 00001010 00000000 00010001 00000000
        System.out.println(i & n); // 167776512
    }
}

	a = 0011 1100
	b = 0000 1101
-----------------
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a  = 1100 0011

下面的表中列出了按位运算符,假设整数变量A=60,变量B=13,那么 -

运算符 描述 示例
& 二进制AND运算符,如果存在于两个操作数中,则它会将结果复制到结果中。 A & B的结果为:12,也就是:0000 1100
` ` 二进制OR运算符,如果存在于任一操作数中,则复制一位。
^ 二进制异或运算符,如果在一个操作数中设置但不在两个操作数中设置,则复制该位。 A ^ B的结果为:49,也就是:0011 0001
~ 二元一元补充运算符是一元的,具有“翻转”位的效果。 ~A的结果为:-61,也就是:1100 0011
<< 二进制左移运算符,左操作数值向左移动右操作数指定的位数。 A << 2的结果为:240,也就是:1111 0000
>> 二进制右移运算符,左操作数值向右移动右操作数指定的位数。 A >> 2的结果为:15,也就是:1111
>>> 右移零填充运算符。 左操作数值向右移动右操作数指定的位数,移位值用零填充。 A >>>2的结果为:15,也就是:0000 1111

4. 逻辑运算符

下表列出了逻辑运算符 -

假设布尔变量A的值为:true,变量B 的值为:false,则 -

运算符 描述 示例
&& 逻辑AND运算符。 如果两个操作数都不为零,则条件成立。 (A && B)结果为:false
` `
! 逻辑非运算符。用于反转其操作数的逻辑状态。 如果条件为真,则口逻辑NOT运算符将为false !(A && B)结果为:true
短路运算

布尔运算的一个重要特点是短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行,直接返回结果。

因为false && x的结果总是false,无论xtrue还是false,因此,与运算在确定第一个值为false后,不再继续计算,而是直接返回false

public class Main {
    public static void main(String[] args) {
        boolean b = 5 < 3;
        boolean result = b && (5 / 0 > 0);
        System.out.println(result);
    }
}

如果没有短路运算,&&后面的表达式会由于除数为0而报错,但实际上该语句并未报错,原因在于与运算是短路运算符,提前计算出了结果false

如果变量b的值为true,则表达式变为true && (5 / 0 > 0)。因为无法进行短路运算,该表达式必定会由于除数为0而报错,可以自行测试。

类似的,对于||运算,只要能确定第一个值为true,后续计算也不再进行,而是直接返回true

boolean result = true || (5 / 0 > 0); // true

5. 赋值运算符

以下是Java语言支持的赋值运算符 -

运算符 描述 示例
= 简单赋值运算符。 将右侧操作数的值分配给左侧操作数。 C = A + BA + B的值分配给C
+= 相加与赋值运算符。 它将右操作数相加到左操作数并将结果分配给左操作数。 C += A等于C = C + A
-= 减去与赋值运算符。 它从左操作数中减去右操作数,并将结果赋给左操作数。 C -= A等于C = C - A
*= 乘以与赋值运算符。 它将右操作数与左操作数相乘,并将结果赋给左操作数。 C *= A等于C = C * A
/= 除以与赋值运算符。 它将左操作数除以右操作数,并将结果赋给左操作数。 C /= A等于C = C / A
%= 模数与赋值运算符。 它使用两个操作数来计算获取模数,并将结果赋给左操作数。 C %= A等于C = C % A
<<= 左移与赋值运算符。 C <<= 2C = C << 2相同
>>= 右移与赋值运算符。 C >>= 2C = C >> 2相同
&= 按位与赋值运算符。 C &= 2C = C & 2相同
^= 按位异或和赋值运算符。 C ^= 2C = C ^ 2相同
` =` 按位包含或与赋值运算符。

6. 其它运算符

Java语言支持的其他运算符很少。

条件运算符(?😃

条件运算符也称为三元运算符。 此运算符由三个操作数组成,用于计算布尔表达式。 运算符的目标是确定应将哪个值赋给变量。 运算符写成 -

variable x = (expression) ? value if true : value if false

下面是一段示例代码:

public class Test {
   public static void main(String args[]) {
      int a, b;
      a = 10;
      b = (a == 1) ? 20: 30;
      System.out.println( "Value of b is : " +  b );

      b = (a == 10) ? 20: 30;
      System.out.println( "Value of b is : " + b );
   }
}

执行上面示例代码,得到以下结果 -

image-20201230202234065

6.2. instanceof运算符
此运算符仅用于对象引用变量。 运算符检查对象是否属于特定类型(类类型或接口类型)。 instanceof运算符写成 -

( Object reference variable ) instanceof  (class/interface type)

如果操作符左侧的变量引用的对象是右侧的类/接口类型,则结果为真。 以下是一个例子 -

public class Test {
   public static void main(String args[]) {
      String name = "haha";
      // 当 name 的类型是 String 时,则返回为:true
      boolean result = name instanceof String;
      System.out.println( result );
   }
}

执行上面示例代码,得到以下结果:

true

如果要比较的对象与右侧类型兼容,则此运算符仍将返回true。 以下是另一个例子 -

class Vehicle {}
public class Car extends Vehicle {
   public static void main(String args[]) {
      Vehicle a = new Car();
      boolean result =  a instanceof Car;
      System.out.println( result );
   }
}

执行上在示例代码,得到以下结果:

true

7.Java运算符优先级

在Java的计算表达式中,运算优先级从高到低依次是:

  • ()
  • ! ~ ++ --
  • * / %
  • + -
  • << >> >>>
  • &
  • |
  • += -= *= /=

记不住也没关系,只需要加括号就可以保证运算的优先级正确。

下表中具有最高优先级的运算符在的表的最上面,最低优先级的在表的底部。

类别 操作符 关联性
后缀 () [] . (点操作符) 左到右
一元 expr++ expr-- 从左到右
一元 ++expr --expr + - ~ ! 从右到左
乘性 * /% 左到右
加性 + - 左到右
移位 >> >>> << 左到右
关系 > >= < <= 左到右
相等 == != 左到右
按位与 左到右
按位异或 ^ 左到右
按位或 | 左到右
逻辑与 && 左到右
逻辑或 | | 左到右
条件 ?: 从右到左
赋值 = + = - = * = / =%= >> = << =&= ^ = | = 从右到左
逗号 左到右
posted @ 2021-01-06 21:02  一头浓发的程序员  阅读(43)  评论(0)    收藏  举报